Client-portal MCP
A second MCP server that lets an agency's clients use their own AI assistant in their client space.
Agencies give their clients a portal. The client-portal MCP server lets a client (or a partner with a client space) connect their own assistant, in Claude, ChatGPT or any MCP client, to that portal:
https://mcp.usedashly.com/portalIt is a separate server from the agency server, with its own tools and its own sign-in. The assistant acts for one person, in their client space only. It never sees the agency's internal data, the agency's people, internal notes, deals or billing.
Connecting
The client signs in with their own Dashly client-space account, through OAuth. An agency
API key (ak_…) does not work here: it is refused like any unknown credential.
Add a custom connector (or MCP server) with the URL https://mcp.usedashly.com/portal.
Click Connect, sign in with the client-space account, and approve the client-portal consent screen. The connection is bound to the client space chosen there.
The flow is the same standard OAuth flow as the agency server (see Connect a client), with its own protected-resource metadata:
https://mcp.usedashly.com/.well-known/oauth-protected-resource/portal{
"resource": "https://mcp.usedashly.com/portal",
"authorization_servers": ["https://app.usedashly.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email", "offline_access"]
}Because the client asks for the /portal resource, the authorization server shows the
portal's consent rather than the agency's. A connection approved for a client space is never
given access to agency data, even when the person is also agency staff. Clients whose tool
takes a header can instead use a personal dpm_… token created in their client space
(Settings → AI assistants). The connection ends when the person loses access to the
client space.
The server speaks the same protocol as the agency server (stateless JSON-RPC 2.0 over HTTP,
same protocol versions) and supports initialize, ping, tools/list and tools/call.
It has no prompts or resources.
Tools
Ids are the public ids the tools themselves return. Every call goes through the same rules as the portal's own pages: a section the agency hides from the portal is hidden here too, and a person can only do what their portal permissions allow.
list_my_projects
The projects the agency runs for the client, with their status. Useful before opening a request when there are several projects.
Arguments: none.
list_my_requests
The client's requests and where they stand (received, in progress, awaiting their validation, done). By default only the ones not done.
| Argument | Type | Required | Description |
|---|---|---|---|
open_only | boolean | no | false to include finished requests. Defaults to true. |
project_id | string | no | Keep only one project's requests (an id from list_my_projects). |
get_request
One request in full: its description, its steps and the exchanges with the agency.
| Argument | Type | Required | Description |
|---|---|---|---|
request_id | string | yes | The request's id, as list_my_requests gives it. |
create_request
Sends a new request to the agency (a change, a problem, a question). It arrives unassigned in the agency's queue, marked as created by the client through an AI assistant, and the agency is notified as for any portal request. The assistant is told to restate the request and confirm it with the person before sending.
| Argument | Type | Required | Description |
|---|---|---|---|
title | string | yes | A short, clear title (2 to 160 characters). |
description | string | no | The request in detail: context, pages concerned, expected result (up to 4,000 characters). |
project_id | string | no | The project concerned (an id from list_my_projects). |
comment_request
Adds a message to one of the client's requests, visible to the agency.
| Argument | Type | Required | Description |
|---|---|---|---|
request_id | string | yes | The request's id. |
message | string | yes | The message, as the person wants it sent (up to 5,000 characters). |
list_my_milestones
The main steps of the client's projects, with their dates and progress.
Arguments: none.
list_my_documents
The reports, presentations and documents the agency shared with the client, each with a link to open it.
Arguments: none.
my_services_status
The current status of the services the client's site or business runs on (hosting, CMS, payments…), from their public status pages.
Arguments: none.
Arguments are validated strictly: an argument a tool does not know is refused rather than ignored. There are deliberately no tools to delete, change a status, assign, estimate, or touch deals and billing.
Limits
The agency controls its clients' access, by default for all clients and per client:
| Limit | Default |
|---|---|
| Access switch | On. The agency can turn it off for one client or for all. |
| Monthly allowance per client | 2,000 tokens |
| Monthly allowance per person | None by default |
| Calls per minute, per person | 20 |
| Calls per minute, per client | 60 |
| New requests per client space | 10 per hour |
A read costs 1 token and a write (create_request, comment_request) costs 6. Calls are also
counted against the agency's own API allowance, at the same price as an API call. The agency
is alerted when a client reaches 80% and 100% of its monthly allowance.
Refusals
A refused call comes back as a tool result with isError: true, a stable code and a message
written for the person (pass it on as is):
| Code | Meaning |
|---|---|
client_disabled | The agency turned off assistant access for this client space. |
rate_limited | Too many calls per minute; retry later. |
client_quota_exhausted | The client space's monthly allowance is used up. |
user_quota_exhausted | This person's monthly allowance is used up. |
agency_quota_exhausted | The agency's API allowance is used up. |
tickets_rate_limited | Too many new requests this hour. |
section_hidden | The agency does not show this part of the portal to the client. |
forbidden | The person's portal permissions do not allow it. |
setup_pending | The client space's onboarding is not finished yet. |
not_found | The request or project does not exist in this client space. |
invalid_input | An argument is invalid. |
server_error | Something went wrong on Dashly's side. |
An expired or revoked connection is answered with an OAuth challenge pointing at the portal's metadata, so the client offers to reconnect.