Skip to content

Commit 8970711

Browse files
authored
feat(mcp): add managed MCP servers for agents and runtimes (#252)
- add agent mcpServers APIs, MCP catalog storage, and batch install by server name - wire MCP servers into Codex, OpenClaw, and PicoClaw runtime provisioning and reconciliation - add web UI, validation, docs, and tests for MCP server management
1 parent 40bb351 commit 8970711

90 files changed

Lines changed: 9863 additions & 1011 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cli/serve/serve.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"csgclaw/internal/im"
4242
"csgclaw/internal/llm"
4343
"csgclaw/internal/localstore"
44+
"csgclaw/internal/mcp"
4445
"csgclaw/internal/modelprovider"
4546
internalonboard "csgclaw/internal/onboard"
4647
"csgclaw/internal/participant"
@@ -611,6 +612,7 @@ func startServerWithConfigPath(ctx context.Context, run *command.Context, cfg co
611612
if err != nil {
612613
return err
613614
}
615+
mcpSvc := mcp.NewService()
614616
teamSvc, teamAdapters, err := NewTeamService(imSvc, feishuSvc, participantSvc)
615617
if err != nil {
616618
return err
@@ -637,6 +639,7 @@ func startServerWithConfigPath(ctx context.Context, run *command.Context, cfg co
637639
ListenAddr: cfg.Server.ListenAddr,
638640
Service: svc,
639641
Hub: hubSvc,
642+
MCP: mcpSvc,
640643
Participant: participantSvc,
641644
IM: imSvc,
642645
IMBus: imBus,

cli/serve/serve_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,9 @@ func TestServeForegroundPassesContextToServer(t *testing.T) {
791791
if opts.Upgrade == nil {
792792
return fmt.Errorf("Upgrade = nil, want configured manager")
793793
}
794+
if opts.MCP == nil {
795+
return fmt.Errorf("MCP = nil, want configured service")
796+
}
794797
if opts.OnReady == nil {
795798
return fmt.Errorf("OnReady is nil")
796799
}

docs/api.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ Examples:
216216
"created_at": "2026-05-16T08:00:00Z",
217217
"profile": "api.gpt-5.4",
218218
"runtime_options": {},
219+
"mcpServers": {
220+
"workspace-filesystem": {
221+
"command": "npx",
222+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
223+
}
224+
},
219225
"agent_profile": {
220226
"provider": "api",
221227
"base_url": "https://api.example.com/v1",
@@ -234,6 +240,8 @@ Notes:
234240

235241
- The real `api_key` is never returned in `agent_profile`
236242
- `runtime_options` is sanitized before being exposed by the API
243+
- `mcpServers` is a direct server-name-to-server-config map. Generic Agent
244+
responses redact secret values in server `env` and `headers` maps.
237245
- `profile` is the normalized selector generated by the server, for example `api.gpt-5.4`
238246
- `detection_results` is used to present default profile detection results
239247

@@ -262,6 +270,7 @@ Request fields:
262270
- `created_at`
263271
- `profile`
264272
- `runtime_options`
273+
- `mcpServers`
265274
- `agent_profile`
266275

267276
Example request body:
@@ -290,6 +299,39 @@ Additional notes:
290299
- `field_mask` limits which fields are overwritten during replacement
291300
- `agent_profile.api_key` is write-only and will be redacted in reads
292301

302+
OpenClaw, PicoClaw, and Codex CLI agents configure MCP servers through the
303+
top-level `mcpServers` field. Its value is the direct server-name-to-server-
304+
config map used by every supported runtime:
305+
306+
```json
307+
{
308+
"name": "alice",
309+
"runtime_kind": "openclaw_sandbox",
310+
"mcpServers": {
311+
"workspace-filesystem": {
312+
"command": "npx",
313+
"args": [
314+
"-y",
315+
"@modelcontextprotocol/server-filesystem",
316+
"/home/node/.openclaw/workspace"
317+
]
318+
}
319+
},
320+
"profile": "api.gpt-5.4"
321+
}
322+
```
323+
324+
Runtime adapters map this shared input as follows:
325+
326+
- OpenClaw: `mcpServers` -> `openclaw.json` `mcp.servers`
327+
- PicoClaw: `mcpServers` -> PicoClaw config `tools.mcp.servers`
328+
- Codex CLI: `mcpServers` -> managed `[mcp_servers."<name>"]` blocks in the isolated Codex home `config.toml`
329+
330+
MCP commands run inside the target runtime environment, so filesystem server
331+
directory arguments must be paths visible to that runtime. MCP-shaped values
332+
under `runtime_options` (including `mcp` and `mcpServers`) are rejected; use
333+
the top-level `mcpServers` field instead.
334+
293335
### `GET /api/v1/agents/{id}`
294336

295337
Gets a single agent.
@@ -306,6 +348,7 @@ Supported fields:
306348
- `description`
307349
- `image`
308350
- `runtime_options`
351+
- `mcpServers`
309352
- `agent_profile`
310353

311354
Example request body:
@@ -322,9 +365,73 @@ Example request body:
322365
Notes:
323366

324367
- Omitted fields are left unchanged
368+
- `runtime_options` uses whole-object replacement when submitted
369+
- `mcpServers` uses whole-map replacement when submitted; send `null` to clear
370+
the CSGClaw-managed server set
371+
- Updating MCP servers on OpenClaw, PicoClaw, or Codex CLI agents may recreate
372+
that agent runtime so the native configuration takes effect
325373
- If `agent_profile.api_key` is sent empty, the server keeps the existing key
326374
- If `agent_profile.env` changes, `env_restart_required` may become `true` in the response
327375

376+
### Agent MCP server endpoints
377+
378+
#### `GET /api/v1/agents/{id}/mcp-servers`
379+
380+
Returns the agent's editable MCP server configuration. Unlike generic Agent
381+
responses, this configuration-page endpoint returns raw values so a user can
382+
review and edit existing tokens. `desired` and `actual` are each direct
383+
server-name-to-server-config maps when non-null; neither is nested under
384+
another MCP field. `desired` preserves the distinction between `null` (no
385+
managed set) and `{}` (an explicitly managed empty set). If the native runtime
386+
configuration cannot be read, the endpoint still returns `desired`, sets
387+
`actual` to `null`, and includes an optional `actual_error` message.
388+
389+
```json
390+
{
391+
"agent_id": "u-alice",
392+
"runtime_kind": "openclaw_sandbox",
393+
"desired": {
394+
"context7": {
395+
"command": "uvx",
396+
"args": ["context7-mcp"],
397+
"env": { "CONTEXT7_API_KEY": "secret" }
398+
}
399+
},
400+
"actual": {
401+
"context7": {
402+
"command": "uvx",
403+
"args": ["context7-mcp"],
404+
"env": { "CONTEXT7_API_KEY": "secret" }
405+
}
406+
}
407+
}
408+
```
409+
410+
#### `PUT /api/v1/agents/{id}/mcp-servers`
411+
412+
Replaces the agent's `mcpServers` direct map. Sending `null` clears the managed
413+
set. The response has the same raw `desired`/`actual` shape as the `GET`
414+
endpoint.
415+
416+
#### `POST /api/v1/agents/{id}/mcp-servers:batchAdd`
417+
418+
Accepts `{ "names": ["..."] }` and merges the named server definitions from
419+
the MCP catalog into the agent's `mcpServers` map. The response has the same raw
420+
`desired`/`actual` shape as the `GET` endpoint.
421+
422+
### MCP server catalog
423+
424+
The reusable MCP server catalog remains at `/api/v1/mcp-servers`:
425+
426+
- `GET /api/v1/mcp-servers` lists catalog servers
427+
- `POST /api/v1/mcp-servers` creates a catalog server
428+
- `PUT /api/v1/mcp-servers/{name}` replaces one catalog server
429+
- `DELETE /api/v1/mcp-servers/{name}` deletes one catalog server
430+
431+
Catalog list and mutation responses use `mcpServers` as the direct server map.
432+
Creating or replacing one catalog entry uses `{ "name": "...", "config": { ... } }`;
433+
`config` is that entry's server configuration.
434+
328435
### `DELETE /api/v1/agents/{id}`
329436

330437
Deletes an agent.

docs/api.zh.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
213213
"created_at": "2026-05-16T08:00:00Z",
214214
"profile": "api.gpt-5.4",
215215
"runtime_options": {},
216+
"mcpServers": {
217+
"workspace-filesystem": {
218+
"command": "npx",
219+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
220+
}
221+
},
216222
"agent_profile": {
217223
"provider": "api",
218224
"base_url": "https://api.example.com/v1",
@@ -231,6 +237,8 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
231237

232238
- `agent_profile` 中不会返回真实 `api_key`
233239
- `runtime_options` 会经过 API 侧脱敏处理
240+
- `mcpServers` 是 server 名称到 server 配置的直接映射;通用 Agent 响应会
241+
脱敏各 server 的 `env``headers` 中的密钥值
234242
- `profile` 是服务端归一化后的选择器,例如 `api.gpt-5.4`
235243
- `detection_results` 用于展示默认 profile 探测结果
236244

@@ -259,6 +267,7 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
259267
- `created_at`
260268
- `profile`
261269
- `runtime_options`
270+
- `mcpServers`
262271
- `agent_profile`
263272

264273
请求体示例:
@@ -287,6 +296,34 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
287296
- `field_mask` 用于替换时只覆盖指定字段
288297
- `agent_profile.api_key` 只在写入时使用,读取时会被脱敏
289298

299+
OpenClaw、PicoClaw 和 Codex CLI agent 通过顶层 `mcpServers` 字段配置 MCP server。该字段的值是所有已支持 runtime 共用的、从 server 名称到 server 配置的直接映射:
300+
301+
```json
302+
{
303+
"name": "alice",
304+
"runtime_kind": "openclaw_sandbox",
305+
"mcpServers": {
306+
"workspace-filesystem": {
307+
"command": "npx",
308+
"args": [
309+
"-y",
310+
"@modelcontextprotocol/server-filesystem",
311+
"/home/node/.openclaw/workspace"
312+
]
313+
}
314+
},
315+
"profile": "api.gpt-5.4"
316+
}
317+
```
318+
319+
各 runtime adapter 的映射关系:
320+
321+
- OpenClaw:`mcpServers` -> `openclaw.json``mcp.servers`
322+
- PicoClaw:`mcpServers` -> PicoClaw 配置的 `tools.mcp.servers`
323+
- Codex CLI:`mcpServers` -> 隔离 Codex home `config.toml` 中由 CSGClaw 管理的 `[mcp_servers."<name>"]`
324+
325+
注意:MCP command 在目标 runtime 环境内执行,filesystem server 的目录参数也必须是该 runtime 可见路径。`runtime_options` 中的 MCP 形态值(包括 `mcp``mcpServers`)会被拒绝;请使用顶层 `mcpServers` 字段。
326+
290327
### `GET /api/v1/agents/{id}`
291328

292329
获取单个 agent。
@@ -303,6 +340,7 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
303340
- `description`
304341
- `image`
305342
- `runtime_options`
343+
- `mcpServers`
306344
- `agent_profile`
307345

308346
请求体示例:
@@ -319,9 +357,60 @@ Participant 是 channel-scoped identity,用于房间、消息、mention、通
319357
说明:
320358

321359
- 省略的字段不会修改
360+
- `runtime_options` 一旦提交就是整体替换
361+
- `mcpServers` 一旦提交就是整个映射替换;传 `null` 可清除 CSGClaw 托管的 server 集合
362+
- OpenClaw、PicoClaw 或 Codex CLI agent 的 MCP server 变更可能触发该 agent runtime recreate,使原生配置生效
322363
- `agent_profile.api_key` 如果传空,服务端会保留原有密钥
323364
- 如果 `agent_profile.env` 发生变化,响应中的 `env_restart_required` 可能为 `true`
324365

366+
### Agent MCP server 接口
367+
368+
#### `GET /api/v1/agents/{id}/mcp-servers`
369+
370+
返回 Agent 可编辑的 MCP server 配置。与通用 Agent 响应不同,这个配置页面接口会返回原始值,以便用户查看和编辑已配置的 token。非空的 `desired``actual` 都是从 server 名称到 server 配置的直接映射,不再嵌套在其他 MCP 字段中。`desired` 会保留 `null`(未托管集合)与 `{}`(显式托管空集合)的区别;如果无法读取 runtime 原生配置,接口仍会返回 `desired`,将 `actual` 设为 `null`,并在可选 `actual_error` 中给出失败原因。
371+
372+
```json
373+
{
374+
"agent_id": "u-alice",
375+
"runtime_kind": "openclaw_sandbox",
376+
"desired": {
377+
"context7": {
378+
"command": "uvx",
379+
"args": ["context7-mcp"],
380+
"env": { "CONTEXT7_API_KEY": "secret" }
381+
}
382+
},
383+
"actual": {
384+
"context7": {
385+
"command": "uvx",
386+
"args": ["context7-mcp"],
387+
"env": { "CONTEXT7_API_KEY": "secret" }
388+
}
389+
}
390+
}
391+
```
392+
393+
#### `PUT /api/v1/agents/{id}/mcp-servers`
394+
395+
替换该 Agent 的 `mcpServers` 直接映射。传入 `null` 会清除托管集合。响应与 `GET` 一样,返回未脱敏的 `desired`/`actual`
396+
397+
#### `POST /api/v1/agents/{id}/mcp-servers:batchAdd`
398+
399+
接收 `{ "names": ["..."] }`,将 MCP catalog 中同名 server 的定义合并到该 Agent 的 `mcpServers` 映射。响应与 `GET` 一样,返回未脱敏的 `desired`/`actual`
400+
401+
### MCP server catalog
402+
403+
可复用的 MCP server catalog 保持在 `/api/v1/mcp-servers`
404+
405+
- `GET /api/v1/mcp-servers`:列出 catalog server
406+
- `POST /api/v1/mcp-servers`:创建 catalog server
407+
- `PUT /api/v1/mcp-servers/{name}`:替换一个 catalog server
408+
- `DELETE /api/v1/mcp-servers/{name}`:删除一个 catalog server
409+
410+
catalog 的列表和变更响应以 `mcpServers` 作为直接 server 映射。创建或替换单个
411+
catalog 条目时使用 `{ "name": "...", "config": { ... } }`,其中 `config`
412+
就是该条目的 server 配置。
413+
325414
### `DELETE /api/v1/agents/{id}`
326415

327416
删除 agent。

0 commit comments

Comments
 (0)