🇫🇷 Version française
MCP Integration (Model Context Protocol)
Status: Experimental (
[Experimental("ORKEXP004")]on every MCP type — see Experimental APIs). Dual-era since PUB-07. Code:src/core/Orkeon.Infrastructure/MCP/.
The MCP integration works in both directions:
- Client —
McpClientspeaks JSON-RPC to an external MCP server;McpToolProviderconnects to any number of servers, lists their tools and registers each one in the OrkeonIToolRegistryas anMcpToolAdapter(IBaseTool), so agents use external MCP tools like native ones.DisconnectServerAsyncunregisters them. - Server —
McpServerexposes the tools of the OrkeonIToolRegistryto external MCP clients (tools/list/tools/call, withToolSchema→ JSON Schema conversion), over stdio (RunStdioAsync) or by processing individual requests (ProcessRequestAsync).
Version negotiation: two protocol lineages
McpProtocol declares what both sides speak:
- Modern, stateless lineage —
ModernVersion = "2026-07-28". No handshake: every request carries_meta(io.modelcontextprotocol/protocolVersion,clientInfo,clientCapabilities); capability discovery is theserver/discovermethod. - Legacy initialize lineage —
SupportedLegacyVersions = ["2025-11-25", "2025-06-18", "2024-11-05"]: classicinitializehandshake followed by the mandatorynotifications/initialized.2025-03-26is deliberately excluded: it is the only revision that mandates JSON-RPC batching, which this implementation does not speak.
Client (McpClient.ConnectAsync)
- Probe with
server/discover(declaring2026-07-28in_meta). - Success with
supportedVersions→ modern server; adopt the newest mutually supported revision. A success withoutsupportedVersionsis a legacy server answering an unknown method leniently → fall back toinitialize. - Error
-32022UnsupportedProtocolVersionError→ still a modern server; read its supported list from the error data, renegotiate, retryserver/discover. - Any other error → legacy server; run the
initializehandshake, which really negotiates (the server's chosen revision is accepted if the client supports it) and ends withnotifications/initialized.
On an established modern connection, every request carries per-request _meta; a
mid-flight -32022 triggers one renegotiation + retry with a mutually supported
revision (McpClient.SendAsync).
Server (McpServer) — dual-era on one endpoint
- Implements
server/discover(supported versions, capabilities,ttlMs/cacheScopefreshness hints, server identity in result_meta). - A request that declares an unsupported
_metaversion is rejected with-32022and the{ supported, requested }payload, whatever the method. - Legacy
initializeechoes the requested revision when supported, otherwise answers with the newest legacy revision.pingstays served for legacy sessions only (removed from the modern lineage). tools/listis returned in deterministic order (ordinal sort by name — a 2026-07-28 SHOULD, for stable client caches) with the modernresultType/ttlMs/cacheScopefields; legacy clients ignore these additive members.- Notifications (no
id, ornotifications/*) are never answered.
Transports
| Transport | Class | Notes |
|---|---|---|
| stdio | StdioMcpTransport |
Launches the server process (McpServerConfig.Command/Args); one JSON-RPC message per line; response correlation is id-agnostic (number or string ids round-trip). |
| HTTP | SseMcpTransport |
Streamable HTTP shape, JSON-response mode: one POST per message. Modern requests carry the MCP-Protocol-Version, Mcp-Method and Mcp-Name headers (derived from the message itself); an SSE-framed response body (text/event-stream) is unwrapped to its final data: payload. Server-initiated streams are not consumed. |
Activation
services.AddOrkeonMcp(configuration); // reads the "MCP" section
AddOrkeonMcp binds McpOptions (Enabled, EnableServer, Servers — a dictionary of
McpServerConfig: Transport stdio/sse, Command/Args or Url) and registers
McpToolProvider as a singleton; McpServer (+ McpServerOptions from MCP:Server:
Name, Version) is registered only when MCP:EnableServer = true. The
AddOrkeonInfrastructure(IConfiguration) overload calls AddOrkeonMcp itself.
The shared runner host honours the section on its own (STUDIO-21). orkeon run
— and every runner built on RunnerHost — calls AddOrkeonMcp when MCP:Servers
declares at least one server and MCP:Enabled is not false, then connects every
server in an explicit startup step (McpStartup) before the crew loads — and before
--validate judges the crew and --list-tools prints the manifest, so the three see the
same tool surface. A server that cannot be connected (a command that does not exist, an
endpoint that does not answer, a handshake still pending after 30 s) costs one error line
naming it, on the log and on stderr, and the run goes on: a crew naming one of that
server's tools then fails at load under StrictTools with the ordinary «unknown tool»
line. The tools are registered under their own names, without a server prefix; a name
the registry already holds is skipped. Orkeon Studio writes the section from its
«Settings › MCP» tab. The other shipped roots (orkeon-host, the REPL) still call the
parameterless overload: there, and in any embedding host, MCP stays a library surface
— the host calls AddOrkeonMcp(configuration) (or the config overload) itself.
There is no hosted service: an embedding host resolves McpToolProvider and calls
ConnectServerAsync(serverId, config) for each configured server (and
McpServer.RunStdioAsync() to serve), explicitly — which is exactly what the runner
host's startup step does.
Honest limitations
Aligned with Known limitations and the PUB-07 changelog entry:
- No
subscriptions/listen— server-push change notifications are not consumed. - No multi-round-trip requests (MRTR) — a modern
input_requiredinterim result is surfaced as an explicit tool error, never as partial data. - No MCP authorization (OAuth).
- HTTP = JSON-response mode only — modern headers sent, SSE bodies unwrapped, but no server-initiated streaming.
2025-03-26excluded (mandatory JSON-RPC batching).- Interop against reference servers (MCP Inspector) has not run yet — tracked in PUB-07's closure note.
Experimental status
Every MCP type carries [Experimental("ORKEXP004")]: referencing them from your code is a
compiler error until you suppress the diagnostic — that suppression is your opt-in
acknowledgement that the surface (notably the wire types) may still move. See
Experimental APIs.
See also: Opt-in subsystems · Experimental APIs · Known limitations · Back to index