Runtime Protocols And MCP Support
Purpose
This functional area defines how AI clients communicate with the Runtime.
Main Capabilities
- REST-based tool listing
- REST-based direct invocation
- MCP JSON-RPC 2.0 support
- MCP Server-Sent Events (SSE) transport for clients that stream server messages
- MCP resource exposure for effective system prompts
- MCP prompt exposure for the same content as user-controlled slash commands
- MCP-compliant JSON Schema generation for tool input parameters, including scalar-items array types
- OAuth browser authorization routed through the Portal login experience for runtime clients that use Authorization Code + PKCE
- A2A JSON-RPC 2.0 support — synchronous and streaming message send, Agent Card discovery, task lifecycle, concentrator mode (registered remote agents), and push notifications — see A2A
Supported MCP Methods
initializepingtools/listtools/callresources/listresources/readprompts/listprompts/getnotifications/tools/list_changed— server-to-client push, not a client-initiated method; see Tool List Change Notifications below
MCP Resources vs MCP Prompts
Both surfaces expose the same toolset system prompt content but serve different consumption models:
resources (resources/list / resources/read) are application-controlled — clients attach the
content as system context automatically — while prompts (prompts/list / prompts/get) are
user-controlled — clients expose them as slash commands or menus. A toolset appears in both surfaces
as long as it has a non-null effective system prompt and at least one active authorized tool.
prompts/get returns a messages array suitable for slash-command use and follows the same audit
pattern as resources/read.
Transports
MCP clients differ in how they carry the same JSON-RPC messages. The Runtime implements the Streamable HTTP transport of the current specification (2025-11-25) and also keeps serving the deprecated HTTP+SSE transport of 2024-11-05, which the current specification explicitly retains as the backwards-compatibility path for older clients. Every shape routes through the same dispatcher, so the available tools, governance decisions, and audit records do not depend on the transport a client picks.
Streamable HTTP
One MCP endpoint — /runtime/{tenantId}/{clientId}/mcp — serves three methods.
POST carries a single JSON-RPC message and must list both application/json and
text/event-stream in its Accept header.
- A notification or a response is answered with HTTP 202 and no body.
- A request is answered either with a JSON body or, when the client accepts
text/event-stream, with an SSE stream. Within a session the stream opens with a priming event — an event id and an empty data field — so the client knows what to present asLast-Event-IDif the connection drops; the JSON-RPC response follows, and the stream then ends. - Input the server cannot accept — malformed JSON, an unsupported protocol version, a disallowed
origin — is answered with an HTTP error status whose body carries a JSON-RPC error with no
id.
GET opens the server-to-client stream. The client must list text/event-stream in Accept;
anything else is answered with HTTP 406. One standalone stream is served per session, so a second
concurrent one is answered with HTTP 409. When the SSE transport is switched off the GET is answered
with HTTP 405, as the specification prescribes for a server that offers no stream at this endpoint.
DELETE terminates the session named by Mcp-Session-Id and closes every stream open within it.
It answers HTTP 204 on success and HTTP 404 when the session is already gone.
Sessions
The initialize response carries a session id in the Mcp-Session-Id header. A client that received
one includes it on every later request. The Runtime does not by default require a session id, so
clients that do not track sessions keep working; setting RequireSession makes a request without the
header fail with HTTP 400. A session id the server does not recognise — never issued, terminated by
DELETE, or reclaimed after idling — is always answered with HTTP 404, which tells the client to
initialize afresh.
Resumption
Every frame on a session-bound stream carries an event id that identifies both the stream and the
position within it. A client that reconnects with Last-Event-ID is resumed on the stream that
issued that id and receives only the messages that followed it — never messages that belonged to a
different stream. Streams that answered a POST are retained briefly after their response so they can
still be resumed. When the client's last event has already aged out of the retained window, nothing
is replayed rather than replaying messages it has already processed.
Resumption works against the instance still holding the stream. Reconnecting elsewhere, or after that instance restarted, gives the client a fresh stream instead — the session itself is unaffected, so it does not have to initialize again.
Running more than one Runtime instance
A session is state; a stream is a live connection. They are stored differently, and that is what lets a client's requests be spread across instances:
- Session state — the id, its tenant, client application, owning caller, and expiry — lives in the distributed cache. Any instance can validate a session, refresh it, enforce the per-tenant cap, and terminate it.
- Streams stay on the instance whose socket is serving them, because a connection cannot be moved. When a message is posted to a different instance, that instance routes it to the one holding the stream, which writes it to the client.
So no sticky sessions are required at the ingress. Enable this by configuring the platform's distributed cache — the Runtime then uses it for both session state and message routing. Without it both are process-local, which is correct for a single instance and is what the Runtime logs a warning about at startup if it sees more than one would be needed.
If the instance holding a stream stops, that stream ends and the client reconnects — but its session survives in the shared store, so it resumes work rather than re-initializing.
Transport guards
- Origin. A request carrying an
Originheader is served only when the origin is the runtime's own or appears inRuntime:AllowedOrigins; otherwise it is refused with HTTP 403. This is what prevents a web page from driving the endpoint through DNS rebinding. - Protocol version. A request declaring an
MCP-Protocol-Versionthe Runtime does not implement is refused with HTTP 400. When the header is absent,2025-03-26is assumed, as the specification prescribes for backwards compatibility.
HTTP+SSE (deprecated transport)
For clients built against the 2024-11-05 transport, which expect a persistent stream established before any message is sent:
- The client opens
GET /runtime/{tenantId}/{clientId}/sse. The response is a long-livedtext/event-streamand carries the session id in theMcp-Session-Idheader. - The first frame is an
endpointevent whose data is the URL to post to, for example/runtime/tenant-a/client-a/messages?sessionId=…. - The client posts each JSON-RPC message to that URL. The POST returns HTTP 202; the response is
delivered as a
messageevent on the stream. Notifications produce no frame. - Closing the stream ends the session.
While a stream is idle the server writes a : keep-alive comment every 15 seconds so proxies do not
close the connection, and responses set X-Accel-Buffering: no and disable response buffering so
each frame reaches the wire immediately.
Session security
A session id is a random bearer capability that appears in a URL or header, so a request is accepted against a session only when its tenant, client application, and authenticated caller all match the identity that opened it. Anything else is rejected as an unknown session.
Tool List Change Notifications
The Runtime pushes notifications/tools/list_changed to a tenant's connected MCP clients whenever
that tenant's tool catalog changes — a mapping is published or unpublished, a toolset's assignment or
policy changes, or any other administrative change that invalidates the cached configuration snapshot.
The notification carries no params; it is a signal to refresh, not a diff. A client that receives it
should call tools/list again to pick up the current catalog.
This requires an open SSE stream to be delivered at all: the notification is sent only to sessions
that currently hold a live stream (a standalone GET, or a POST-answered stream still retained for
resumption). A client using bare POST request/response with no open stream, or one that has gone idle
or disconnected, simply never receives it — the Runtime does not queue it for later delivery, since by
the time such a client asks again it would call tools/list directly. This is the same best-effort
delivery model the rest of the SSE transport uses, and matches what the specification expects for a
notification of this kind.
Delivery follows the same session and stream mechanics described above: session state is resolved through the tenant's active session set, and the frame is handed to whichever Runtime instance currently holds each session's live stream — routed over the shared backplane when that instance is not the one that received the underlying configuration change. Sending this notification does not itself require the session's tenant, client application, or caller identity to change; it simply rides the same delivery path every other server-to-client message on the session uses.
Functionally, this closes the gap between "the Portal changed something" and "the connected AI client
knows to look again" — without it, a long-lived MCP session would keep working from a stale tool list
until it happened to reconnect or the client polled tools/list on its own schedule.
Configuration
Settings live under Runtime:Mcp:Sse:
| Setting | Default | Meaning |
|---|---|---|
Enabled |
true |
Serves the SSE surfaces. When false, GET .../mcp answers 405, the deprecated endpoints answer 404, and POST answers stay JSON. |
KeepAliveIntervalSeconds |
15 |
Cadence of keep-alive comments on an idle stream. |
SessionIdleTimeoutMinutes |
30 |
How long a session survives without activity. |
MaxSessionsPerTenant |
100 |
Concurrent session cap per tenant; excess stream requests get HTTP 429. |
MaxQueuedMessagesPerSession |
256 |
Delivery queue depth for a client that is not draining its stream. |
MaxReplayedMessagesPerStream |
128 |
How much each stream retains for Last-Event-ID resumption. 0 disables replay. |
PreferSseResponses |
true |
Whether a POST answers with SSE when the client accepts it. When false, POST answers are JSON, which the specification allows equally. |
RequireSession |
false |
Whether requests after initialization must carry Mcp-Session-Id. |
Session state and message routing come from the platform's distributed cache when it is configured, and are process-local otherwise. See Running more than one Runtime instance.
Correlation IDs On MCP Calls
A client may send an X-Correlation-Id header on an MCP request. The Runtime never adopts it as the
execution id it generates for the call, but records it as the external correlation id and echoes the
accepted value back on X-External-Correlation-Id. See Runtime Governance → External Correlation
IDs for acceptance rules, and Runtime Downstream
Invocation → Correlation Propagation for
how the toolset's configured strategy decides what, if anything, is forwarded to the downstream API.
MCP System Prompt Resources
The Runtime exposes toolset system prompts through MCP resources.
The resource payload is not just the raw toolset prompt anymore. It is the effective prompt computed for the requesting tenant and client application.
The effective prompt is composed from:
- the toolset default system prompt
- the client application's system prompt when the toolset assignment enables it
- an optional assignment-specific custom system prompt
Assignment custom prompts support two modes:
Append— append the custom prompt after the default chainOverride— replace the full default chain with the custom prompt
This means the same toolset can expose different prompt resources to different client applications within the same tenant.
MCP Schema Generation
The Runtime produces JSON Schema for each tool's input parameters, derived from the mapping's field rules.
The emitted MCP contract is based on ExposedFieldName, not on the raw downstream SourceFieldName. Structural markers from the source path, such as nested objects and [*] array positions, are preserved so the client sees the mapped business vocabulary without losing payload shape fidelity.
This schema generation is protocol-aware. Tools sourced from SOAP/WSDL contracts still expose standard MCP JSON Schema, while the Runtime keeps SOAP-specific invocation metadata behind the tool definition.
Array fields are handled as follows:
- Fields whose type is
arrayinclude anitemsnode that describes the element type (e.g."integer","string"). - Array leaf fields (e.g. a field path ending in
[*]) carry the items type from theArrayItemTypeproperty on the field rule. When no item type is specified, the schema defaults to"string". - Object-typed fields with
additionalPropertiespass that schema through unchanged.
The Runtime now supports three MCP schema discovery modes:
Full— the emitted schema includes descriptions and format hints for the exposed field contractDataStructureOnly— the emitted schema keeps only structural information needed by MCP binders: exposed field names, object nesting, array items,additionalProperties, and required flagsLazy— the emitted schema exposes only the_schema_discoveryplaceholder field and requires discovery before invocation
DataStructureOnly is implemented by stripping human-readable metadata from the generated schema tree while preserving the JSON Schema structure used by client-side parameter binders.
discover_tool_schema Meta-tool
When any visible tool uses Lazy or DataStructureOnly, the Runtime injects the discover_tool_schema MCP meta-tool into tools/list.
Functionally, this meta-tool:
- accepts a
tool_id - returns the authoritative full
inputSchema - returns the full
outputSchemawhen present - returns a schema version and schema hash so a caller can reason about schema freshness
Schema discovery activity is also represented in governance call monitoring as a distinct call type, separate from normal tool execution and resource reads.
MCP Invocation Semantics For Discovery
Tools whose inputSchema includes _schema_discovery are expected to be hydrated before first use unless the caller already retrieved and retained the full schema definition in the current conversation.
At invocation time the Runtime applies two important rules:
- if
_schema_discoveryis the only argument, the Runtime rejects the call and instructs the caller to usediscover_tool_schemafirst - if
_schema_discoveryappears alongside real arguments, the Runtime removes it before forwarding the request to downstream execution
For real invocations, the Runtime also translates the request payload from ExposedFieldName to SourceFieldName before building the downstream HTTP request, and translates the downstream response back from SourceFieldName to ExposedFieldName before returning the MCP result.
tools/list Response Logging
When request/response logging is enabled for at least one tool in the catalog, the tools/list response payload is captured as an audit dimension alongside the discovery call. This makes the exact tool contract visible to platform operators for audit and debugging.
Functional Value
This area makes the platform usable by both direct HTTP clients and MCP-capable AI systems.
For browser-capable clients, that usability now extends to a first-class Portal-hosted authorization journey that supports external identity providers, MFA, and an explicit operator confirmation step before runtime access is granted.
