Skip to main content

Connect an AI app to DALP through the hosted MCP server

Connect Claude, ChatGPT or another MCP app to DALP at /api/mcp: read tools, and prepare tools that post a draft to sign or run a write under an agent's mandate.

The hosted MCP server lets a member connect an AI app that speaks MCP, the Model Context Protocol, to DALP. Claude and ChatGPT are examples. The server runs inside DALP at /api/mcp, so the app needs no CLI and no API key. The app reads what the member can read. By default it prepares changes as drafts that the member opens and signs. With execution set to mandate, DALP instead runs a mint, burn, pause, resume, block or unblock from one of the organisation's agent accounts, inside that agent's on-chain mandate and with no person signing, as Execution modes describes. The app never signs, and the member's key never signs for it.

This page covers the hosted server. The dalp --mcp server described in CLI for AI agents is a different server, started on a person's own machine, and it is unaffected by the hosted server. Choose the hosted server when the member uses a chat app and the organisation wants a per-member grant it can see and revoke. Choose dalp --mcp when an agent runs next to a shell with the API key from dalp login.

A member connects an app, and the app reads or prepares drafts the member signs.An organisation owner switches the MCP server on. The organisation's assistant must be on too, and so must the platform-wide assistant switch, which is on by default. The app finds the OAuth sign-in from the metadata at /api/mcp. The member signs in, picks the organisation and allows read and prepare. Each tool call runs as that member in that organisation. A read tool returns what the member can read. A prepare tool posts a draft to the member's Actions inbox. The member reviews the draft and signs it with wallet verification. The app never signs. A prepare call with execution set to mandate skips the draft: one of the organisation's agents runs the write under its on-chain mandate.A member connects an app, and the app reads or prepares drafts the member signs.An organisation owner switches the MCP server on. The organisation's assistant must be on too, and so must the platform-wide assistant switch, which is on by default. The app finds the OAuth sign-in from the metadata at /api/mcp. The member signs in, picks the organisation and allows read and prepare. Each tool call runs as that member in that organisation. A read tool returns what the member can read. A prepare tool posts a draft to the member's Actions inbox. The member reviews the draft and signs it with wallet verification. The app never signs. A prepare call with execution set to mandate skips the draft: one of the organisation's agents runs the write under its on-chain mandate.

An owner switches the MCP server on, the member signs the app in over OAuth, and each tool call then reads as that member or posts a draft the member signs.

Switch the server on

An organisation owner or a platform administrator switches the server on. It is off by default. The platform-wide assistant switch, which is on by default, must be on too. While the server is off, or while either assistant switch is off, every request to /api/mcp answers 403 with mcp_server_off. Sign-in and consent still complete, but the app gets no data.

  1. Open the Assistant page in organisation settings.
  2. Make sure Assistant in search is on. The MCP server needs the assistant, and with the assistant off the “Connected apps” list is hidden.
  3. Turn on MCP server.

The same two roles are the only ones who can switch it off, in the Console or through the Platform API. The Assistant page appears only in their navigation, and another member who opens its address cannot change the switch and sees “Only an owner can change this setting.”

When an owner turns the switch off again, every request to /api/mcp for that organisation answers 403 with mcp_server_off until an owner turns it back on.

Connect an app

Give the app the server URL. Replace <your DALP address> with the address your organisation uses for the Console:

<your DALP address>/api/mcp

The app finds the sign-in by itself. The server answers a request without a valid token with 401 and a WWW-Authenticate header that points to its protected-resource metadata. The metadata names DALP's OAuth issuer, and the app registers itself as an OAuth client without signing in, so no one creates a client by hand and DALP does not check who the app is. The registration requires PKCE, the OAuth Proof Key for Code Exchange extension that binds each sign-in to the app that started it. An access token lasts one hour and a refresh token lasts 30 days.

The metadata is served by the Platform API at /.well-known/oauth-protected-resource/api/mcp. The WWW-Authenticate header of the 401 carries the exact address, and an app that follows it reads this document:

{
  "resource": "https://dalp.northwind.example/api/mcp",
  "authorization_servers": ["https://dalp.northwind.example/api/auth"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["dalp:read", "dalp:prepare"],
  "resource_name": "DALP"
}

The Console forwards /.well-known/* to the API, as it does /api/* and /dalp/*, so the Console address serves this metadata. A gateway in front of the Console must pass /.well-known through as well.

The server is stateless and speaks streamable HTTP. An app sends JSON-RPC requests with POST, and a GET to /api/mcp answers 405 with “This MCP server is stateless: POST JSON-RPC requests to it.”

What the member does

  1. The app opens a sign-in page titled with the app's name, such as Sign in to connect Claude. This page offers no account creation. The member signs in, including two-factor verification, and returns to the app's request.
  2. The consent page, titled with the app's name, such as “Connect Claude to DALP”, shows the Organisation the grant binds to. A member of several organisations picks one with “Choose an organisation”, and the page reloads with the choice.
  3. Under The app will be able to, the page lists what the app asked for: “Read the DALP data you can see”, “Prepare actions for you to review in your Actions inbox”, or both. With “Prepare actions” it also lists “Have your organisation's agent principal, if it has one, run a mint, burn, pause, resume, block or unblock within its mandate, with no one signing”, which is execution set to mandate, as Execution modes describes. The page then states: “It cannot sign anything with your account.”
  4. The member selects Allow or “Deny”. A member with no organisation cannot select “Allow”, and the page says “You are not a member of an organisation.” A failed answer keeps the member on the page with “Your answer could not be sent. Try again.”

The name on both pages is the name the app gave itself, so allow only apps you trust. A request that lost its signed parameters shows “This connection request is incomplete” and offers no answer. When the server is off, an owner sees “The MCP server is off” with a link, Open assistant settings. A member sees no notice, because only an owner can change the switch.

The grant belongs to one member in one organisation. The app works in that organisation only, with that member's permissions.

Read tools

The server offers the same four tools as dalp --mcp. An app finds a tool with search_tools, reads its schema with get_tool_details, and runs a read with call_read_tool or a prepare tool with call_write_tool. The view tool show_holders is listed on its own.

The read tools are the CLI's get commands, the same set dalp --mcp offers through call_read_tool, described in MCP tools. Every read runs as the member who signed the app in, in the organisation the grant binds to. The app sees what the member sees and nothing else. A read returns the route's own response body, without the masking the Console assistant applies, so names, wallet addresses, balances and identity data in it reach the app and the model provider behind it. The organisation's assistant data settings do not limit what the hosted server returns.

The access token is read-only on the API. A write method sent with it answers 403 SESSION_POLICY_DENIED (DALP-0036). A route that only the Console may call refuses the token with the same code, whatever the method. A write goes through a prepare tool instead, as a draft for a person to confirm in DALP or, under a mandate, as a write one of the organisation's agents runs.

Prepare tools

Each prepare_* tool matches one Console control, such as the controls on an asset's page, and is named after it, for example prepare_asset_pause. A prepare tool takes the asset's contract address as assetId, the control's own fields and an optional execution.

search_tools and get_tool_details return each prepare tool's MCP annotations, the hints that tell an app what a tool can change. prepare_asset_mint, prepare_asset_burn, prepare_asset_pause, prepare_asset_unpause, prepare_asset_blocklist_block and prepare_asset_blocklist_unblock carry destructiveHint: true, because with execution set to mandate they run the write at once and no person signs. The hint stays the same when a call uses inbox. Every other prepare tool carries destructiveHint: false. It answers mandate with execution-unsupported, so it can only post a draft. No prepare tool is marked read-only or idempotent, because an inbox call takes no idempotency key. The app runs every prepare tool through call_write_tool, which is marked destructive whatever tool it runs.

A prepare tool checks the address and each field, reads the asset as the member, and refuses when the member does not hold the permission the control needs. When the checks pass, the platform posts a draft to the member's Actions inbox. Nothing is signed or submitted at that point.

The draft appears as a “Request from an app”. Its sheet says which app prepared it through the MCP server. Its checks record that, when the app prepared it, the member held the permission the change needs and the app filled in every required field. The member selects Review to open the item's sheet, then “Open draft”, and the existing control opens with the values filled in. The member confirms with wallet verification. Opening the draft closes the item as accepted, whether or not the member then signs.

The app learns the draft's itemId and openUrl from the tool result. The link /actions?agentItem=<itemId> opens the item's sheet in the Actions inbox, and only the member it belongs to can open it.

Refusals

A refused prepare call returns an error result, not a draft. The caller fixes the cause and calls again.

Reason
What the platform observed
What to do
scope-missing
The member did not allow the app to prepare writes.
Sign the app in again and allow Prepare actions for you to review.
invalid-fields
assetId, a field, agentId or idempotencyKey is missing or bad.
Fix the fields the message lists and call the tool again.
asset-unreadable
The member cannot read the asset.
Use an asset the member can see.
permission-missing
The member does not hold the permission the control needs on the asset.
Ask an administrator to grant the permission. Retrying does not help.
no-agent-principal
With mandate, agentId names no agent of the member's organisation.
Use an agent from the Agents page, or call the tool with inbox.
execution-unsupported
With mandate, the control has no call an agent can run.
Call the tool with inbox.
mandate-refused
The agent's mandate, its state or the organisation's halt refused it.
Read the reason in the message. Change the mandate or use inbox.
restriction-held
With mandate, the call asks to unblock a wallet, and an active wallet restriction on that wallet lists the asset.
Ask the organisation's owner to lift the restriction, as Lift a restriction describes. A lift that completes unfreezes the wallet on the tokens the restriction froze. If the wallet was frozen on this asset before the restriction, call the unblock again once the restriction is lifted. Neither a retry nor inbox helps while the restriction is active.
execution-unavailable
DALP could not read the agent, the asset's metadata or the restrictions of the wallet to unblock, or could not reach the agent execution service.
Retry the call.
store-unavailable
DALP could not save the draft.
Retry the call. This is a retryable platform error.

A call_write_tool call that names a tool the server does not list returns the error Unknown tool: <name>. List the tools again and call one of them.

Execution modes

execution takes inbox, the default, or mandate. With inbox the draft goes to a person to sign. With mandate, DALP runs the write from an agent's account. An agent is a participant the organisation set up to act through a key the platform holds, and its mandate is the set of calls, amounts and expiry the chain enforces on it. The member's key never signs.

A mandate call names two more fields. agentId is the id of any of the organisation's agents from the Agents page: DALP checks that the agent belongs to the organisation, not that the member set it up. idempotencyKey is 8 to 128 letters, digits, dots, colons, dashes or underscores that the app sends again unchanged on a retry. A retry from the same app and member, with the same agentId and key, never runs the write twice. Another app, another member, another agent or an app that registered again as a new client starts a new write.

DALP makes the same checks as for a draft first, including the member's own permission for the control. Mint, burn, pause, resume, block and unblock run under a mandate. Any other control answers execution-unsupported. Every mint or burn row needs its holder and a positive amount with no more decimals than the asset has: DALP refuses an incomplete row or an over-precise amount instead of dropping or rounding it.

A wallet restriction freezes a participant's wallet on the tokens it lists, and DALP releases those freezes only through its lift. While one is active and lists the asset, DALP refuses an unblock of that wallet with restriction-held. The Console's “Unblock” sheet and the Platform API refuse the same unblock, so a draft prepared with inbox is refused before the member signs it.

A mandate meters transfers and approvals only, so a mandate that sets a limit on an asset cannot allow a mint or burn on it. DALP refuses such a mint or burn with mandate-refused and the reason target-not-allowed. When the mandate sets no limit on the asset and allows the call, the mint or burn runs with no amount cap. The answer carries the agent's account, the queue request and a state of queued or landed. A refused mandate call submits nothing and prepares no draft.

Views in the app

In an app that renders MCP Apps, show_holders opens a table of an asset's holders and balances, as the member can read them. The tool takes assetId.

A prepared draft has no view in the app. The prepare result carries the draft's openUrl and tells the app to give that link to the member, who opens the item in the Actions inbox.

Limits

Tool calls share the assistant's limits. The per-member and per-organisation windows are the assistant's turn windows, multiplied by ten for tool calls, and they follow whether the organisation uses the shared assistant or its own model key. A call that spends the window is refused with 429 and a Retry-After header that gives the seconds until the window resets:

{
  "error": "rate_limited",
  "error_description": "Too many MCP tool calls. Try again shortly."
}

The error is retryable. The app waits for Retry-After and sends the call again. A batch that carries several tool calls spends one unit per call.

Other responses

Status
Meaning
What to do
401
The token is missing (invalid_request), or it is expired or revoked (invalid_token). The WWW-Authenticate header points to the metadata.
Sign the app in again.
403
mcp_server_off: the organisation's MCP server is off, the assistant is off for the organisation or the whole platform, or the token carries no organisation.
Ask an owner to switch the server on.
403
insufficient_scope: the app was not granted dalp:read. Every tool needs it.
Sign the app in again and allow it to read.
405
A GET to /api/mcp.
Send JSON-RPC with POST.
429
The window is spent.
Wait for Retry-After.
503
DALP could not serve the request. This is a retryable platform error.
Retry after a moment.

Audit

Every tool call writes one audit row before the call runs and settles it with the outcome. The row records the route mcp.<tool>, the auth source oauth, the member, the organisation, and the app's id and name next to the tool's arguments. A call made through call_read_tool or call_write_tool is recorded under the tool it runs. A prepare tool is recorded as a mutation, and any other tool as a sensitive read. DALP settles the rows of one request together: when the request fails, or any call in it fails or is refused, every row of that request is recorded as failed, including a call in the same batch that succeeded. A call refused for the limit window leaves no row, because it never ran. A mandate call also writes an agent-execution row with the auth source agent, the member as actor and the submitted calls. The rows are part of the audit trail.

Connected apps and revocation

The Connected apps list on the Assistant page shows each connected app with these columns: “App”, “Member”, “Access”, “Connected” and “Last used”. “Access” reads “Read”, “Prepare actions” or both. “Last used” reads “Never” until the app uses the grant. An empty list reads “No apps are connected.”

An organisation owner sees every member's apps. A platform administrator who is not an owner sees only the apps they connected themselves.

Select “Revoke” on a row, then Revoke access to confirm:

Who revokes
What the dialog says
What is removed
An owner
The app loses access for every member who connected it. Members can connect it again.
The app's grants for the whole organisation.
A member
The app loses your access. You can connect it again.
Only that member's grant.

A revoked app must be signed in again. A revoke that finds no matching grant answers as not found. The list and the revoke belong to the Console only. The published API reference, the SDK and the CLI do not offer them.

Next steps

  • CLI for AI agents covers the local dalp --mcp server, which the hosted server leaves unaffected, and how to scope an assistant session.

Was this page helpful?