MCP Servers
Model Context Protocol servers are surfaced in AHP as a McpServerCustomization — a customization that represents one running (or registered) MCP server within a session. AHP intentionally does not re-spec MCP. It exposes:
- Enough state for clients to render the server (name, icon, enabled flag, runtime status).
- Enough state for clients to drive authentication when the server demands it.
- An optional
mcp://side-channel the client can use to talk to the upstream server when it needs to render an MCP App.
Everything else — connection management, transport, the server's command/args/env, tool discovery, request fan-out — lives in the agent harness the host wraps. The agent host's job is to normalize whatever the harness exposes into AHP state.
Where MCP servers appear
MCP servers may appear in two positions in SessionState.customizations:
- As a child of a container customization — for example, an MCP server declared inside a
plugins.jsonmanifest or discovered in a directory. The container'suripoints at the manifest file; the child'srangenarrows it to the declaration's span. - As a bare top-level entry — the host MAY surface MCP servers directly when they are globally configured rather than bundled in a plugin or directory.
Clients only ever publish customizations through ClientPluginCustomization, so client-contributed MCP servers always arrive as children of a client plugin. Top-level McpServerCustomization entries are always host-originated.
state.customizations
?.flatMap(c => c.type === 'mcpServer' ? [c] : (c.children ?? []))
.filter(c => c.type === 'mcpServer')Shape
McpServerCustomization {
type: 'mcpServer'
id: string // session-unique handle
uri: URI // declaration source (file or marketplace URL)
name: string
icons?: Icon[]
range?: TextRange // span inside `uri` for inline declarations
enabled: boolean // user-toggleable (see Customizations guide)
state: McpServerState // discriminated union — see below
channel?: URI // optional mcp:// side-channel
mcpApp?: McpServerCustomizationApps
}enabled follows the same model as any other container — it's toggled with session/customizationToggled. Disabling a server signals the host to stop it; the host then transitions the runtime through stopped and removes it from the session (or leaves it as stopped until removal, host's choice).
Runtime status
state is a discriminated union on kind. It is the host's view of the server's lifecycle, separate from enabled (which is the user's intent).
| Kind | Meaning |
|---|---|
starting | Registered but not yet running. Tools/resources are not available. |
ready | Running and serving requests. Tools/resources surface through the usual channels. |
authRequired | Reachable but blocked on authentication. Carries ProtectedResourceMetadata for the client to act on. |
error | Unrecoverable failure. Carries an ErrorInfo. Use authRequired for auth-specific failures. |
stopped | Shut down. The host MAY remove the entry shortly after. |
High-frequency lifecycle transitions go through the narrow session/mcpServerStateChanged action, which upserts just state (and optionally channel) on an existing entry. Use session/customizationUpdated for anything else (name, icons, mcpApp).
Authentication
AHP reuses the existing authenticate command for MCP server authentication. The flow is driven entirely by state — there is no MCP-specific notification.
McpServerAuthRequiredState carries:
reason—required,expired, orinsufficientScope. Mirrors the MCP authorization spec failure modes.resource—ProtectedResourceMetadataper RFC 9728. Theresourcefield inside is the canonical MCP server URI (per RFC 8707) and what the client passes back asauthenticate({ resource }).requiredScopes— Scopes parsed fromWWW-Authenticate: Bearer scope="…". Authoritative for the next authorization request; clients MUST NOT assume any relationship toresource.scopes_supported.description— Human-readable hint, typically the OAutherror_description.
Mid-tool-call step-up auth
reason: 'insufficientScope' almost always surfaces during a tool call — the model invokes an MCP tool, the upstream server responds 403, and a turn that was happily streaming suddenly needs the user to grant more access. AHP couples this case through two signals so it can't be missed:
- The host SHOULD raise
SessionStatus.InputNeededon the session when it transitions an MCP server toauthRequiredbecause of an in-flight request. This makes the block visible at the session-summary level, exactly like a tool confirmation or input request. - Clients SHOULD watch the
state.kindof any MCP server backing a running tool call (viaToolCallContributor—{ kind: 'mcp', customizationId }). When that server flips toauthRequired, the client SHOULD present an explicit affordance tied to that tool call (e.g. an inline "grant additional access" button), rather than relying on the user to spot a status badge on the server's customization entry.
The same monitoring pattern also covers reason: 'expired' mid-turn — the difference is purely whether the user needs to re-authenticate or grant additional scopes.
Per-agent protected resources in AgentInfo.protectedResources cover agents themselves. MCP server resources are advertised here, on the customization, so a single agent can carry an arbitrary number of MCP servers each with their own authorization servers without bloating the root state.
TIP
The existing authenticate command requires resource to match one declared by an agent. Hosts that surface MCP server auth via McpServerAuthRequiredState either need to widen that rule or mirror MCP server resources into AgentInfo.protectedResources until the dedicated MCP actions land. This is a known gap and will be tightened when the MCP-specific action surface is specified.
Where MCP tools live
MCP tools follow the normal AHP tool-call flow:
- The agent harness inside the host discovers tools from each
readyMCP server, the host normalizes them into the agent's tool catalogue, and exposes invocations throughsession/toolCallStart/session/toolCallReady/session/toolCallComplete. - The originating MCP server is identified by
ToolCallContributoron the tool call:{ kind: 'mcp', customizationId: <McpServerCustomization.id> }. Clients can use this to render the originating server's name/icon next to the tool call.
There is no separate "MCP tool" state. From the client's perspective an MCP tool call is just a tool call with an MCP contributor.
MCP Apps
MCP Apps (SEP-1865) let an MCP server ship a UI resource — typically an HTML page — that a host renders for a specific tool call. The View runs inside a sandbox controlled by the AHP client and talks back via JSON-RPC over postMessage. AHP's role is to give the client everything it needs to render that View on behalf of the agent host — and nothing more.
This section describes the AHP-level plumbing only. For the View ↔ host protocol itself (the ui/* methods, hostCapabilities, hostContext, sandboxing rules), see the upstream MCP Apps spec.
Division of labour
- Agent harness: holds the underlying MCP connection to the server. How the harness exposes that to the agent host (proxy, callback, message bus, etc.) is harness-defined and outside AHP.
- Agent host: normalizes whatever the harness gives it into AHP state. Decides which tool calls instantiate an App. Forwards the constrained subset of MCP traffic the client sends over the
mcp://channel into the harness. - AHP client: declares
capabilities.mcpAppsoninitialize. Renders the View. Runs theui/initializehandshake. ProvideshostContext(theme, locale, dimensions, etc.) and the locally-decided parts ofhostCapabilities(openLinks,downloadFile,sandbox,experimental). Delivers tool input/result notifications to the View. Routestools/*,resources/*,logging/*, andsampling/*over themcp://channel. - View: an opaque HTML document the client treats as untrusted. AHP says nothing about it directly.
The client is the only party that talks to the View. AHP carries no ui/* traffic — that protocol lives between the client and the iframe.
Declaring support
A client opts in by setting InitializeParams.capabilities.mcpApps to {}. Hosts SHOULD only populate mcpApp (and expose the corresponding mcp:// channel) on customizations served to clients that declared the capability. Clients that omit it MUST treat App-bearing tool calls as ordinary MCP tool calls.
Discovering App support
Each McpServerCustomization MAY advertise App support via mcpApp:
McpServerCustomization {
// ...
channel?: URI
mcpApp?: {
capabilities: AhpMcpUiHostCapabilities
}
}mcpApp SHOULD be present whenever the server can host Apps. Its presence tells the client "if a tool call from this server points at a UI resource, you can render it as an App, and here is the slice of hostCapabilities I can satisfy on your behalf".
AhpMcpUiHostCapabilities is deliberately a subset of the upstream HostCapabilities. It only covers capabilities that depend on the host's relationship with the upstream MCP server:
| AHP capability | What the host promises | What the client passes to ui/initialize |
|---|---|---|
serverTools | tools/list and tools/call will be proxied via the mcp:// channel. listChanged controls whether notifications/tools/list_changed is forwarded. | hostCapabilities.serverTools (mirroring listChanged) |
serverResources | resources/list, resources/templates/list, and resources/read will be proxied. listChanged controls notification forwarding. | hostCapabilities.serverResources |
logging | notifications/message from the App will be forwarded to the server; logging/setLevel from the server will reach the App. | hostCapabilities.logging ({}) |
sampling | sampling/createMessage from the App will be handled inside the agent host (typically by the same harness that runs the agent's turns). sampling.tools controls SEP-1577 content acceptance. | hostCapabilities.sampling |
The other hostCapabilities fields — openLinks, downloadFile, sandbox, experimental — depend on the client's renderer (web vs. desktop, what permissions it can grant, what CSP it enforces) and are not part of AhpMcpUiHostCapabilities. The client fills them in itself before responding to ui/initialize.
Identifying an App tool call
A tool call that should render as an App carries two AHP-level signals:
contributor—{ kind: 'mcp', customizationId }points at the originatingMcpServerCustomization. Look upmcpAppandchannelon that customization._meta.ui— the AHP tool call's_metaMAY carry auifield mirroring MCP Apps'McpUiToolMetaverbatim (typically{ resourceUri?: string, visibility?: ('model' | 'app')[] }). The client SHOULD readresourceUrito know which UI resource backs the call. AHP does not retype this shape — clients consume it through_meta.
If _meta.ui.resourceUri is absent, the tool call is an ordinary MCP tool call and the client renders it normally.
End-to-end flow
The client is responsible for translating any locally-supported ui/* request into the right local affordance — e.g. ui/open-link opens a system link, ui/request-display-mode toggles the renderer's layout, ui/message becomes a steering message or queued message on the AHP session, ui/update-model-context becomes an attachment or Message._meta field on the next user message.
Next steps
mcp://channel — the side-channel clients use to talk to the upstream server.- Session Channel Reference — full type definitions for
McpServerCustomization,McpServerState, and friends.