ā server.json and client manifests
docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api
en/docs/api/mcp-bundle/discovery.md
new file +41
ā0
1
+
# server.json and client manifests
2
+
3
+
Every endpoint also serves two kinds of documents. Both are protected by the documentation token rule (JsonRpcBundle `protected_doc`), answer 404 the same way as the endpoint itself, and take the endpoint URL from the request: `<prefix>[/{ver}]`.
4
+
5
+
## server.json for the MCP Registry
6
+
7
+
`GET <prefix>/server.json` (or `<prefix>/v2/server.json`) is the document the [MCP Registry](https://registry.modelcontextprotocol.io) expects when the server is published there (schema `2025-12-11`). Clients do not read it to connect.
8
+
9
+
```json
10
+
{
11
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
12
+
"name": "io.github.your-org/your-app",
13
+
"title": "Your App MCP Server",
14
+
"description": "Exposes Your App RPC services as MCP tools.",
15
+
"version": "v1",
16
+
"websiteUrl": "https://api.example.com",
17
+
"icons": [{"src": "https://example.com/icon.png", "mimeType": "image/png", "sizes": ["128x128"]}],
18
+
"remotes": [{"type": "streamable-http", "url": "https://api.example.com/mcp"}]
19
+
}
20
+
```
21
+
22
+
* `name`, `title`, `description`, `icons` come from the group's `mcp_server_info` (falling back field by field to the bundle-wide setting, see [Configuration](/docs/api/mcp-bundle/config)); unset fields are left out of the document.
23
+
* `version` is the request's `{ver}`; `websiteUrl` is the request's scheme, host and port; `remotes[0].url` is the endpoint URL.
24
+
25
+
## Client manifests
26
+
27
+
`GET <prefix>/manifest/{aiName}.json` is a ready connection config. The token stays a placeholder: a manifest never contains a real token. The server key in the config is the group name.
28
+
29
+
| `aiName` | For | Format |
30
+
| --- | --- | --- |
31
+
| `claude` | Claude Code (`.mcp.json`) | `{"mcpServers": {"<group>": {"type": "http", "url": "ā¦", "headers": {"Ufo-RPC-Token": "${UFO_MCP_TOKEN}"}}}}` |
32
+
| `cursor` | Cursor (`mcp.json`) | `{"mcpServers": {"<group>": {"url": "ā¦", "headers": {"Ufo-RPC-Token": "${env:UFO_MCP_TOKEN}"}}}}` |
33
+
| `gemini` | Gemini CLI (`settings.json`) | `{"mcpServers": {"<group>": {"httpUrl": "ā¦", "headers": {"Ufo-RPC-Token": "<UFO_MCP_TOKEN>"}}}}` |
34
+
| `openai` | OpenAI Responses API | a `tools[]` item: `{"type": "mcp", "server_label": "<group>", "server_url": "ā¦", "headers": {ā¦}, "require_approval": "always"}` |
35
+
36
+
* Claude Code and Cursor substitute the `UFO_MCP_TOKEN` environment variable themselves.
37
+
* Gemini CLI does not substitute variables in headers, and the OpenAI API sends headers as is: replace `<UFO_MCP_TOKEN>` with the token by hand or in your code.
38
+
* `require_approval: "always"` for OpenAI is a conservative value so that the bundle never authorizes calls without confirmation; change it if needed.
39
+
* The header name is `ufo_json_rpc.security.token_name` (by default `Ufo-RPC-Token`).
40
+
41
+
An unknown `aiName` answers 404 `{"error": "Unknown MCP client manifest \"ā¦\"."}`. A manifest for another client: see [Extensions](/docs/api/mcp-bundle/developers/extensions).