docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api
en/docs/api/mcp-bundle/developers/README.md
new file +71
ā0
1
+
# For developers
2
+
3
+
How MCP Bundle works inside: principles, components, the request path and what is cached. The bundle is an extension of [JsonRpcBundle](/docs/api/json-rpc-bundle/developers) and follows the core's general rules for extensions.
4
+
5
+
## Principles
6
+
7
+
1. **A thin adapter over JsonRpcBundle.** Like REST Bundle, this bundle adds a protocol, not a second server. Business logic stays in the RPC services (`IRpcService`); a tool call is a regular RPC request executed by the core.
8
+
2. **Reuse, don't duplicate.** The service map, API groups and versions, translations, language negotiation, token security, cache pools, route import and request execution come from the core. When the core lacks an extension point, it is added to the core: that is how `IApiRouteImport` in 10.4.1 and `RpcExecutor` in 10.4.2 appeared.
9
+
3. **The route is the source of truth.** The API group comes from the route import (`api_group`), the version from the `{ver}` segment. The configuration only tunes the group's server.
10
+
4. **Publication = API group membership.** Every method of the group becomes a tool; there is no allowlist and no separate MCP registration.
11
+
5. **The official SDK implements the protocol.** The handshake, sessions, the stateless 2026-07-28 protocol, argument validation against `inputSchema`, batching, the CORS and DNS rebinding middleware are `mcp/sdk` (^0.8.1). The bundle builds an SDK server per request and wires the RPC methods into it.
12
+
6. **Fail loudly.** A method whose name is not a valid MCP tool name breaks its group (HTTP 500 with an explanation in debug) instead of silently disappearing from the tool list.
13
+
14
+
## Components
15
+
16
+
| Component | Role |
17
+
| --- | --- |
18
+
| `Routing\McpRouteImport` | Registers `config/router.yaml` with the core route loader (`IApiRouteImport`): every import gets `<group>_ufo_mcp_*` names and the `_mcp_name_prefix` attribute |
19
+
| `Controller\McpController` | Three actions: the MCP endpoint, `server.json`, client manifests |
20
+
| `EventListener\McpRequestListener` | `kernel.controller` listener for `McpController` only: API group, availability (404), token (401/403) |
21
+
| `Server\McpHandler` | Language negotiation, transport, SDK server, `Content-Language` / `Vary` |
22
+
| `Server\Transport\StreamableHttpTransportFactory` | The SDK Streamable HTTP transport with CORS, DNS rebinding protection and the group's request body limit |
23
+
| `Server\McpServerFactory` | The SDK server for one request: identity, `instructions`, session store, one tool per method of the group and version |
24
+
| `Server\Session\RpcCacheSessionStore` | Session-based protocol sessions in the core cache pool `rpc.cache.pool` |
25
+
| `Tool\ToolSchemaFactory` | An MCP `Tool` from the core `Service` metadata: name check, texts, `inputSchema`, `outputSchema`, annotations; cached in the core `RpcMetadataCache` |
26
+
| `Tool\RpcToolHandler`, `Tool\RpcToolExecutor` | `tools/call` ā core `RpcExecutor` ā `CallToolResult` |
27
+
| `Server\RequestPrepare\Holders\RpcFromMcp` | The request carrier (`IRpcRequestCarrier`): an `RpcRequest` from the tool, arguments and version |
28
+
| `DocAdapters\Outputs\ServerJsonAdapter` | `server.json` for the MCP Registry |
29
+
| `ClientManifest\*` | Client manifests (Claude Code, Cursor, Gemini CLI, OpenAI), a tagged locator |
30
+
| `ConfigService\McpConfig`, `McpGroupConfig`, `McpServerInfoConfig` | Typed view of the `ufo_mcp` configuration; a group without an entry gets the bundle-wide settings |
31
+
| `Security\McpAccessPolicyInterface`, `Availability\McpAvailabilityCheckerInterface` | Extension points; see [Extensions](/docs/api/mcp-bundle/developers/extensions) |
32
+
33
+
## Request path
34
+
35
+
```
36
+
HTTP request ā route from config/router.yaml, imported once per API group
37
+
ā McpRequestListener (kernel.controller, McpController only)
38
+
ApiGroupContext ā route api_group / _mcp_name_prefix
39
+
404: group disabled (McpAvailabilityCheckerInterface) or {ver} not among the group's ServiceMap versions
40
+
OPTIONS (CORS preflight) passes without a token
41
+
core IRpcSecurity: isValidApiRequest() for the endpoint, isValidDocRequest() for server.json / manifests
42
+
401 (no token) / 403 (invalid token) as {"error": "..."}
43
+
ā McpController::mcpAction ā McpHandler::handle()
44
+
core RpcLocaleNegotiator ā request language
45
+
StreamableHttpTransportFactory ā PSR-7 request + SDK transport (CORS, DNS rebinding, max_body_bytes)
46
+
McpServerFactory::create($ver, $locale) ā SDK Server (tools of ServiceMap::getServices($ver) of the group)
47
+
SDK: initialize / ping / tools/list / tools/call / DELETE session ...
48
+
Content-Language, Vary: Accept-Language
49
+
ā tools/call ā RpcToolHandler ā RpcToolExecutor
50
+
RpcFromMcp(service, arguments, version) ā core RpcExecutor::execute()
51
+
(same pipeline as /rpc; an escaping exception goes through rpc.error, the response is always an RpcResponse)
52
+
core RpcRequestHandler::responseToArray() ā CallToolResult (text + structuredContent | isError)
53
+
ā kernel.terminate: core PostResponseQueue fires rpc.post_response for every executed call
54
+
```
55
+
56
+
More about the core stages: [Lifecycle](/docs/api/json-rpc-bundle/developers/lifecycle).
57
+
58
+
## What is built per request, what is cached
59
+
60
+
* The SDK server and the tool list are created for every HTTP request: the server keeps no state between requests, sessions live in the cache pool.
61
+
* Every `Tool` is cached in the core `RpcMetadataCache` (`cache.system`), keyed by group, version, method and language. The cache is reset on every container rebuild and disabled in debug, the same rule the core applies to its own documentation.
62
+
* A compiler pass is deliberately not used: the container does not track DTO classes, and building schemas needs application services.
63
+
* Sessions (session-based protocol only) live in `rpc.cache.pool` under the key `ufo_mcp_<group>_<session id>` with `session_ttl` per item.
64
+
65
+
## In this section
66
+
67
+
* [Extensions](/docs/api/mcp-bundle/developers/extensions): extension points of the bundle and the core.
68
+
* [Testing](/docs/api/mcp-bundle/developers/testing): test suites, fixtures, coverage, MCP Inspector.
69
+
* [Known issues](/docs/api/mcp-bundle/developers/known_issues/open): what does not work as expected and how to work around it.
70
+
71
+
Bugs and proposals go to the bundle repository's [issue tracker](https://github.com/UFO-Tech/mcp-bundle/issues).