berry-agent 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 行帧 JSON-RPC 编解码与关联表(03 §10.1 手写最小桥的协议腿——零新增依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 五方法面:initialize / notifications·initialized / tools/list / tools/call /
|
|
5
|
+
* ping——id 关联表桥级数十行即覆盖,不引 @modelcontextprotocol/sdk。
|
|
6
|
+
*
|
|
7
|
+
* 行帧卫生:server stdout 单行字节上限(缺省 8MiB)——超限按载体级失败收场:
|
|
8
|
+
* 本层停攒丢弃(静默巨行不进宿主堆)+ fatal 一次上报(桥侧收口 = 封读
|
|
9
|
+
* destroy + 结清 pending + 树杀 server)。
|
|
10
|
+
*
|
|
11
|
+
* 服务器→客户端方向(transport v1 = stdio-only):
|
|
12
|
+
* - 请求一律回 -32601(sampling/elicitation capability 拒答——本仓不承载
|
|
13
|
+
* 这些面),唯一例外 ping 照答(result null);
|
|
14
|
+
* - 通知忽略(tools/list_changed v1 不热刷——改配置走 /reload);
|
|
15
|
+
* - 宿主不主动 ping——crash 检测靠载体 close 事件(桥侧接线),本层只给
|
|
16
|
+
* failAll 结清面。
|
|
17
|
+
*/
|
|
18
|
+
import { BaseError } from '../contracts/index.js';
|
|
19
|
+
import { MCP_LINE_LIMIT_BYTES } from './types.js';
|
|
20
|
+
/** JSON-RPC 层错误(服务器 error 响应折形——业务面消费 code/message) */
|
|
21
|
+
export class JsonRpcError extends Error {
|
|
22
|
+
rpcCode;
|
|
23
|
+
rpcData;
|
|
24
|
+
constructor(error) {
|
|
25
|
+
super(`JSON-RPC ${error.code}: ${error.message}`);
|
|
26
|
+
this.name = 'JsonRpcError';
|
|
27
|
+
this.rpcCode = error.code;
|
|
28
|
+
this.rpcData = error.data;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 行劈解码器(字节面——攒段跨 chunk 拼行)。
|
|
33
|
+
*
|
|
34
|
+
* 防线两处:残段累积超帽(无换行的持续巨流)与完整单行超帽(一次到位的
|
|
35
|
+
* 巨行响应)——任一触发行帧 fatal:此后解码器恒死(不再攒、不再上报——
|
|
36
|
+
* 一次语义),fatal 后残余行不外发(pending 由连接层结清,行内容无意义)。
|
|
37
|
+
*/
|
|
38
|
+
export class LineDecoder {
|
|
39
|
+
limitBytes;
|
|
40
|
+
chunks = [];
|
|
41
|
+
size = 0;
|
|
42
|
+
fatal = false;
|
|
43
|
+
constructor(limitBytes = MCP_LINE_LIMIT_BYTES) {
|
|
44
|
+
this.limitBytes = limitBytes;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 喂入一段字节;返回完整行(UTF-8 解码、空行丢弃)。
|
|
48
|
+
* fatal = true 即载体级失败(本次起解码器死——后续 feed 恒空恒不报)。
|
|
49
|
+
*/
|
|
50
|
+
feed(chunk) {
|
|
51
|
+
if (this.fatal)
|
|
52
|
+
return { lines: [], fatal: false };
|
|
53
|
+
// 换行只可能在本次 chunk 内新现(此前 chunk 已全扫无换行)——快路径零拷贝
|
|
54
|
+
let idx = chunk.indexOf(0x0a);
|
|
55
|
+
if (idx < 0) {
|
|
56
|
+
this.chunks.push(chunk);
|
|
57
|
+
this.size += chunk.length;
|
|
58
|
+
if (this.size > this.limitBytes) {
|
|
59
|
+
this.fatal = true;
|
|
60
|
+
this.chunks = [];
|
|
61
|
+
this.size = 0;
|
|
62
|
+
return { lines: [], fatal: true };
|
|
63
|
+
}
|
|
64
|
+
return { lines: [], fatal: false };
|
|
65
|
+
}
|
|
66
|
+
// 劈完整行(首段与历史残段拼接;后续段即完整行;尾段成新残段)
|
|
67
|
+
const rawLines = [];
|
|
68
|
+
let start = 0;
|
|
69
|
+
let first = true;
|
|
70
|
+
while (idx >= 0) {
|
|
71
|
+
const seg = chunk.subarray(start, idx);
|
|
72
|
+
if (first && this.chunks.length > 0) {
|
|
73
|
+
rawLines.push(Buffer.concat([...this.chunks, seg]));
|
|
74
|
+
this.chunks = [];
|
|
75
|
+
this.size = 0;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
rawLines.push(seg);
|
|
79
|
+
}
|
|
80
|
+
first = false;
|
|
81
|
+
start = idx + 1;
|
|
82
|
+
idx = chunk.indexOf(0x0a, start);
|
|
83
|
+
}
|
|
84
|
+
const rest = chunk.subarray(start);
|
|
85
|
+
if (rest.length > 0) {
|
|
86
|
+
// subarray 视图持有母本——拷贝释放(防巨母本长活)
|
|
87
|
+
this.chunks = [Buffer.from(rest)];
|
|
88
|
+
this.size = rest.length;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.chunks = [];
|
|
92
|
+
this.size = 0;
|
|
93
|
+
}
|
|
94
|
+
// 单行超帽判定(先判后转——fatal 即全弃不发半批)
|
|
95
|
+
for (const raw of rawLines) {
|
|
96
|
+
if (raw.length > this.limitBytes) {
|
|
97
|
+
this.fatal = true;
|
|
98
|
+
this.chunks = [];
|
|
99
|
+
this.size = 0;
|
|
100
|
+
return { lines: [], fatal: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const lines = [];
|
|
104
|
+
for (const raw of rawLines) {
|
|
105
|
+
if (raw.length > 0)
|
|
106
|
+
lines.push(raw.toString('utf8'));
|
|
107
|
+
}
|
|
108
|
+
return { lines, fatal: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 行帧 JSON-RPC 连接(单服务器一座——请求关联表 + 服务器方向分派)。
|
|
113
|
+
*
|
|
114
|
+
* 生命周期语义:dead 旗(载体死/fatal)后 request 即拒(快失败不悬挂);
|
|
115
|
+
* failAll 是 pending 的总结清面(crash/close/超限三源共用)。
|
|
116
|
+
*/
|
|
117
|
+
export class JsonRpcConnection {
|
|
118
|
+
nextId = 1;
|
|
119
|
+
pending = new Map();
|
|
120
|
+
decoder;
|
|
121
|
+
options;
|
|
122
|
+
dead = false;
|
|
123
|
+
constructor(options) {
|
|
124
|
+
this.options = options;
|
|
125
|
+
this.decoder = new LineDecoder(options.lineLimitBytes);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 发请求(关联表登记 + 可选超时执法)。超时按拒绝结清——调用方分类语义
|
|
129
|
+
* (连接期超时 = MCP_CONNECT_FAILED;调用期超时 = 工具结果 error)。
|
|
130
|
+
*/
|
|
131
|
+
request(method, params, timeoutMs) {
|
|
132
|
+
if (this.dead) {
|
|
133
|
+
return Promise.reject(new BaseError('MCP_CONNECT_FAILED', '连接已死(载体级失败/crash)——不再发请求'));
|
|
134
|
+
}
|
|
135
|
+
const id = this.nextId++;
|
|
136
|
+
const frame = { jsonrpc: '2.0', id, method };
|
|
137
|
+
if (params !== undefined)
|
|
138
|
+
frame.params = params;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
let timer;
|
|
141
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
142
|
+
timer = setTimeout(() => {
|
|
143
|
+
this.pending.delete(id);
|
|
144
|
+
reject(new Error(`JSON-RPC 请求超时(${method},预算 ${timeoutMs}ms)`));
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
}
|
|
147
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
148
|
+
this.options.send(JSON.stringify(frame));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** 发通知(无 id 不等回复——notifications/initialized 唯一消费者) */
|
|
152
|
+
notify(method, params) {
|
|
153
|
+
if (this.dead)
|
|
154
|
+
return;
|
|
155
|
+
const frame = { jsonrpc: '2.0', method };
|
|
156
|
+
if (params !== undefined)
|
|
157
|
+
frame.params = params;
|
|
158
|
+
this.options.send(JSON.stringify(frame));
|
|
159
|
+
}
|
|
160
|
+
/** stdout 字节喂入(行劈 → 逐行分派;行帧 fatal 即载体级失败收口) */
|
|
161
|
+
feed(chunk) {
|
|
162
|
+
const { lines, fatal } = this.decoder.feed(chunk);
|
|
163
|
+
if (fatal) {
|
|
164
|
+
this.die(new BaseError('MCP_CONNECT_FAILED', `server stdout 单行超 ${this.options.lineLimitBytes ?? MCP_LINE_LIMIT_BYTES} 字节帽——载体级失败`), '行帧超帽');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
for (const line of lines)
|
|
168
|
+
this.dispatch(line);
|
|
169
|
+
}
|
|
170
|
+
/** 载体死结清(crash/close——pending 全拒;幂等) */
|
|
171
|
+
failAll(error) {
|
|
172
|
+
this.die(error, undefined);
|
|
173
|
+
}
|
|
174
|
+
/** 内部死面:dead 封口 + pending 总清(fatal 时另报 onFatal 一次) */
|
|
175
|
+
die(error, fatalReason) {
|
|
176
|
+
if (this.dead)
|
|
177
|
+
return;
|
|
178
|
+
this.dead = true;
|
|
179
|
+
for (const entry of this.pending.values()) {
|
|
180
|
+
if (entry.timer !== undefined)
|
|
181
|
+
clearTimeout(entry.timer);
|
|
182
|
+
entry.reject(error);
|
|
183
|
+
}
|
|
184
|
+
this.pending.clear();
|
|
185
|
+
if (fatalReason !== undefined)
|
|
186
|
+
this.options.onFatal?.(fatalReason);
|
|
187
|
+
}
|
|
188
|
+
/** 单行分派(响应结清 / 服务器方向请求应答 / 通知忽略) */
|
|
189
|
+
dispatch(line) {
|
|
190
|
+
let msg;
|
|
191
|
+
try {
|
|
192
|
+
msg = JSON.parse(line);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
this.options.warn?.(`server 行帧 JSON 坏形跳过:${line.slice(0, 120)}`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (typeof msg !== 'object' || msg === null)
|
|
199
|
+
return;
|
|
200
|
+
const m = msg;
|
|
201
|
+
if (typeof m.method === 'string') {
|
|
202
|
+
if (m.id !== undefined) {
|
|
203
|
+
// 服务器→客户端请求:ping 照答;其余一律 -32601(capability 拒答)
|
|
204
|
+
const response = m.method === 'ping'
|
|
205
|
+
? { jsonrpc: '2.0', id: m.id, result: null }
|
|
206
|
+
: {
|
|
207
|
+
jsonrpc: '2.0',
|
|
208
|
+
id: m.id,
|
|
209
|
+
error: { code: -32601, message: `transport v1 stdio-only:拒答服务器请求 ${m.method}` },
|
|
210
|
+
};
|
|
211
|
+
if (!this.dead)
|
|
212
|
+
this.options.send(JSON.stringify(response));
|
|
213
|
+
}
|
|
214
|
+
// 通知(含 tools/list_changed)——v1 忽略不热刷
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (typeof m.id === 'number') {
|
|
218
|
+
const entry = this.pending.get(m.id);
|
|
219
|
+
if (entry === undefined)
|
|
220
|
+
return; // 迟到/未知 id——超时已结清或非本连接帧
|
|
221
|
+
this.pending.delete(m.id);
|
|
222
|
+
if (entry.timer !== undefined)
|
|
223
|
+
clearTimeout(entry.timer);
|
|
224
|
+
if (m.error !== undefined)
|
|
225
|
+
entry.reject(new JsonRpcError(m.error));
|
|
226
|
+
else
|
|
227
|
+
entry.resolve(m.result);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 服务面(03 §10.1 连接语义的编排核心——装配后异步发现零阻塞)。
|
|
3
|
+
*
|
|
4
|
+
* apply(config):同步返回零等待——每服务器后台发现(fire-and-forget 必带
|
|
5
|
+
* .catch 失败收口:MCP_CONNECT_FAILED 降 warn + ui.notify,单服务器失败不阻
|
|
6
|
+
* 同行其余、不阻宿主启动);续段每步查 scope 活(/reload/卸行竞速护栏——
|
|
7
|
+
* 死即协议化关停且不计失败)。发现完成 → 桥入活表 → 工具面重铺(全局合计
|
|
8
|
+
* 定形态:≤20 原生注册 / >20 目录降级——每桥落地/撤销均重铺,阈值随活集
|
|
9
|
+
* 自然收敛)。
|
|
10
|
+
*
|
|
11
|
+
* crash:撤该服务器工具(重铺减源)+ ui.notify warn + 不自动重连(复位走
|
|
12
|
+
* /reload)。作用域回卷:ctx.effect LIFO——各桥 close()(stdin.end 协议化
|
|
13
|
+
* 告别 → 宽限 → killTree 树杀)异步起跑不阻塞回卷序。
|
|
14
|
+
*/
|
|
15
|
+
import { createLogger } from '../context/index.js';
|
|
16
|
+
import { connectMcpServer } from './bridge.js';
|
|
17
|
+
import { buildMcpToolDefs } from './tools.js';
|
|
18
|
+
import { filterServerTools } from './tools.js';
|
|
19
|
+
/** 组 MCP 服务 */
|
|
20
|
+
export function createMcpService(deps) {
|
|
21
|
+
const logger = deps.logger ?? createLogger('mcp');
|
|
22
|
+
const notify = deps.notify ?? (() => undefined);
|
|
23
|
+
/** 活桥表(server → { bridge, config }——config 供过滤与重铺) */
|
|
24
|
+
const bridges = new Map();
|
|
25
|
+
/** 当前注册面注销器(重铺先全撤) */
|
|
26
|
+
let surfaceDisposers = [];
|
|
27
|
+
const isAlive = () => !deps.scope.isDisposed;
|
|
28
|
+
/** 撤当前注册面(重铺/回卷共用——幂等) */
|
|
29
|
+
function disposeSurface() {
|
|
30
|
+
for (const dispose of surfaceDisposers) {
|
|
31
|
+
try {
|
|
32
|
+
dispose();
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
logger.warn(`MCP 工具注销失败:${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
surfaceDisposers = [];
|
|
39
|
+
}
|
|
40
|
+
/** 拼活源清单(enabled/disabled 过滤落此——复合名/目录面只见过滤后集) */
|
|
41
|
+
function liveSources() {
|
|
42
|
+
const sources = [];
|
|
43
|
+
for (const { bridge, config } of bridges.values()) {
|
|
44
|
+
sources.push({
|
|
45
|
+
server: bridge.server,
|
|
46
|
+
tools: filterServerTools(bridge.tools, config),
|
|
47
|
+
toolTimeoutMs: bridge.toolTimeoutMs,
|
|
48
|
+
call: (tool, args) => bridge.call(tool, args),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return sources;
|
|
52
|
+
}
|
|
53
|
+
/** 重铺工具面(全撤重建——形态随活集合计裁决;scope 死即不铺) */
|
|
54
|
+
function resurface() {
|
|
55
|
+
if (!isAlive())
|
|
56
|
+
return;
|
|
57
|
+
disposeSurface();
|
|
58
|
+
const { defs } = buildMcpToolDefs(liveSources(), (message) => logger.warn(message));
|
|
59
|
+
for (const def of defs) {
|
|
60
|
+
try {
|
|
61
|
+
surfaceDisposers.push(deps.registry.register(def));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// per-def 容错:撞名(TOOL_NAME_CONFLICT)/令牌桶(TOOL_CHANGE_RATE_LIMITED)
|
|
65
|
+
// 等注册面拒——单件降级 warn 不炸全行
|
|
66
|
+
logger.warn(`MCP 工具注册失败(${def.name}):${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** 单服务器后台发现(fire-and-forget 调用方必带 .catch) */
|
|
71
|
+
async function discover(server, config) {
|
|
72
|
+
const bridge = await connectMcpServer(server, config, {
|
|
73
|
+
spawn: deps.spawn,
|
|
74
|
+
logger,
|
|
75
|
+
closeGraceMs: deps.closeGraceMs,
|
|
76
|
+
clientVersion: deps.clientVersion,
|
|
77
|
+
});
|
|
78
|
+
// 续段 scope 活查(异步边界护栏——/reload/卸行竞速):死即协议化关停,
|
|
79
|
+
// 不入活表不计失败(安静退场非降级语义)
|
|
80
|
+
if (!isAlive()) {
|
|
81
|
+
await bridge.close();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
bridges.set(server, { bridge, config });
|
|
85
|
+
bridge.onDown((reason) => {
|
|
86
|
+
// 运行期 crash:撤工具 + ui.notify warn + 不自动重连(复位走 /reload)
|
|
87
|
+
if (bridges.get(server)?.bridge !== bridge)
|
|
88
|
+
return; // 旧桥迟到事件(重 apply 后)
|
|
89
|
+
bridges.delete(server);
|
|
90
|
+
notify(`MCP 服务器 ${server} 已断开(${reason})——其工具已撤,复位走 /reload`);
|
|
91
|
+
resurface();
|
|
92
|
+
});
|
|
93
|
+
resurface();
|
|
94
|
+
}
|
|
95
|
+
/** 撤全部桥(重复 apply / 回卷共用——LIFO 关停异步起跑) */
|
|
96
|
+
function shutdownAll() {
|
|
97
|
+
const entries = [...bridges.values()].reverse(); // LIFO——后连先关
|
|
98
|
+
bridges.clear();
|
|
99
|
+
disposeSurface();
|
|
100
|
+
for (const { bridge } of entries)
|
|
101
|
+
void bridge.close();
|
|
102
|
+
}
|
|
103
|
+
// 作用域回卷登记(LIFO 尾位——回卷即全撤:stdin.end → 宽限 → killTree)
|
|
104
|
+
deps.scope.effect(() => () => shutdownAll());
|
|
105
|
+
const service = {
|
|
106
|
+
apply(config) {
|
|
107
|
+
shutdownAll(); // config 整值替换语义——旧桥全撤再发现(§5.3)
|
|
108
|
+
for (const [server, serverConfig] of Object.entries(config)) {
|
|
109
|
+
void discover(server, serverConfig).catch((err) => {
|
|
110
|
+
// 失败收口必带(fire-and-forget 纪律):降 warn + ui.notify 不炸装配
|
|
111
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
112
|
+
logger.warn(`MCP 服务器连接失败(${server}):${detail}`);
|
|
113
|
+
notify(`MCP 服务器 ${server} 连接失败:${detail}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
liveServers() {
|
|
118
|
+
return [...bridges.entries()].map(([server, { bridge, config }]) => ({
|
|
119
|
+
server,
|
|
120
|
+
toolCount: filterServerTools(bridge.tools, config).length,
|
|
121
|
+
}));
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
return service;
|
|
125
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { MCP_NATIVE_TOOL_LIMIT } from './types.js';
|
|
2
|
+
/** 目录工具名(降级形态单件名——命名域顶层 'mcp') */
|
|
3
|
+
export const MCP_DIRECTORY_TOOL_NAME = 'mcp';
|
|
4
|
+
/** 组复合名(`<server>__<tool>`——服务器键无下划线,首 `__` 即分界) */
|
|
5
|
+
export function mcpToolKey(server, tool) {
|
|
6
|
+
return `${server}__${tool}`;
|
|
7
|
+
}
|
|
8
|
+
/** 劈复合名(首 `__` 分界;无分界/空段 = 非法键返 null) */
|
|
9
|
+
export function splitMcpToolKey(key) {
|
|
10
|
+
const idx = key.indexOf('__');
|
|
11
|
+
if (idx <= 0 || idx === key.length - 2)
|
|
12
|
+
return null;
|
|
13
|
+
return { server: key.slice(0, idx), tool: key.slice(idx + 2) };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* enabled/disabled 双表过滤(enabled 在场 = 白名单收窄;disabled 恒排除——
|
|
17
|
+
* 双表合用先白后黑)。过滤语义在服务器侧原名面(复合名只在暴露面)。
|
|
18
|
+
*/
|
|
19
|
+
export function filterServerTools(tools, config) {
|
|
20
|
+
let out = tools;
|
|
21
|
+
if (config.enabled_tools !== undefined) {
|
|
22
|
+
const allow = new Set(config.enabled_tools);
|
|
23
|
+
out = out.filter((t) => allow.has(t.name));
|
|
24
|
+
}
|
|
25
|
+
if (config.disabled_tools !== undefined) {
|
|
26
|
+
const deny = new Set(config.disabled_tools);
|
|
27
|
+
out = out.filter((t) => !deny.has(t.name));
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 建工具面(形态裁决 + 定义产出)。非 object 根 inputSchema 的工具跳过 +
|
|
33
|
+
* warn(直喂纪律下坏形不入注册面——注册表 TOOL_SCHEMA_INVALID 会炸全行,
|
|
34
|
+
* 诚实降级单件跳过)。
|
|
35
|
+
*/
|
|
36
|
+
export function buildMcpToolDefs(sources, warn = () => undefined) {
|
|
37
|
+
// 逐源过滤 + 复合名配对(server 键词法已保证无 `__`——无歧义)
|
|
38
|
+
const entries = [];
|
|
39
|
+
for (const source of sources) {
|
|
40
|
+
for (const tool of source.tools) {
|
|
41
|
+
entries.push({ source, tool });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (entries.length > MCP_NATIVE_TOOL_LIMIT) {
|
|
45
|
+
return { defs: [buildDirectoryDef(sources)], directory: true };
|
|
46
|
+
}
|
|
47
|
+
const defs = [];
|
|
48
|
+
for (const { source, tool } of entries) {
|
|
49
|
+
if (!isRootObjectSchema(tool.inputSchema)) {
|
|
50
|
+
warn(`MCP 工具 schema 根非 object 跳过:${mcpToolKey(source.server, tool.name)}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const originalName = tool.name;
|
|
54
|
+
defs.push({
|
|
55
|
+
name: mcpToolKey(source.server, originalName),
|
|
56
|
+
description: tool.description ?? `MCP 工具(服务器 ${source.server})`,
|
|
57
|
+
parameters: tool.inputSchema,
|
|
58
|
+
timeoutMs: source.toolTimeoutMs,
|
|
59
|
+
execute: (args) => source.call(originalName, args), // 落桥换回服务器侧原名
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { defs, directory: false };
|
|
63
|
+
}
|
|
64
|
+
/** 目录工具(降级形态——schema 恒定三动作) */
|
|
65
|
+
function buildDirectoryDef(sources) {
|
|
66
|
+
// 管道帽 = 活服务器逐台预算最大值(缺省服务器不被他台大预算连带抬升——
|
|
67
|
+
// 各台缺省一致时即缺省值;call 落桥后逐台执法不受此帽语义干扰)
|
|
68
|
+
const pipelineTimeoutMs = sources.reduce((max, s) => Math.max(max, s.toolTimeoutMs), 0);
|
|
69
|
+
return {
|
|
70
|
+
name: MCP_DIRECTORY_TOOL_NAME,
|
|
71
|
+
description: 'MCP 服务器工具目录(多服务器工具合计超 20 件的降级形态):action=search 列出 / describe 详述 / call 调用;tool 参数为寻址键 <server>__<tool>',
|
|
72
|
+
parameters: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
action: { type: 'string', enum: ['search', 'describe', 'call'], description: '目录动作' },
|
|
76
|
+
tool: { type: 'string', description: '目录寻址键 <server>__<tool>(describe/call 必填)' },
|
|
77
|
+
query: { type: 'string', description: 'search 的过滤词(子串匹配,可选)' },
|
|
78
|
+
arguments: { type: 'object', description: 'call 动作的工具参数(可选)' },
|
|
79
|
+
},
|
|
80
|
+
required: ['action'],
|
|
81
|
+
},
|
|
82
|
+
timeoutMs: pipelineTimeoutMs,
|
|
83
|
+
execute: async (args) => directoryExecute(sources, args),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** 目录三动作路由(search/describe/call——未知键诚实 isError) */
|
|
87
|
+
async function directoryExecute(sources, args) {
|
|
88
|
+
const action = args.action;
|
|
89
|
+
if (action === 'search') {
|
|
90
|
+
const query = typeof args.query === 'string' ? args.query.toLowerCase() : '';
|
|
91
|
+
const lines = [];
|
|
92
|
+
for (const source of sources) {
|
|
93
|
+
for (const tool of source.tools) {
|
|
94
|
+
const key = mcpToolKey(source.server, tool.name);
|
|
95
|
+
const hay = `${key} ${tool.description ?? ''}`.toLowerCase();
|
|
96
|
+
if (query === '' || hay.includes(query)) {
|
|
97
|
+
lines.push(`${key} — ${tool.description ?? '(无描述)'}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { content: [{ type: 'text', text: lines.length > 0 ? lines.join('\n') : '(无匹配 MCP 工具)' }] };
|
|
102
|
+
}
|
|
103
|
+
if (action === 'describe' || action === 'call') {
|
|
104
|
+
const key = typeof args.tool === 'string' ? args.tool : '';
|
|
105
|
+
const split = splitMcpToolKey(key);
|
|
106
|
+
const source = split !== null ? sources.find((s) => s.server === split.server) : undefined;
|
|
107
|
+
const tool = source !== undefined && split !== null ? source.tools.find((t) => t.name === split.tool) : undefined;
|
|
108
|
+
if (source === undefined || tool === undefined || split === null) {
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{ type: 'text', text: `未知 MCP 工具键:${key}(search 动作可列当前可用键——服务器 crash 后工具即撤)` },
|
|
112
|
+
],
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (action === 'describe') {
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: JSON.stringify({
|
|
122
|
+
server: source.server,
|
|
123
|
+
name: tool.name,
|
|
124
|
+
description: tool.description ?? null,
|
|
125
|
+
inputSchema: tool.inputSchema,
|
|
126
|
+
}, null, 2),
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const callArgs = typeof args.arguments === 'object' && args.arguments !== null && !Array.isArray(args.arguments)
|
|
132
|
+
? args.arguments
|
|
133
|
+
: {};
|
|
134
|
+
return source.call(tool.name, callArgs); // 落桥换回服务器侧原名——逐台执法
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: 'text', text: `未知目录动作:${JSON.stringify(action)}(search/describe/call 三选一)` }],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** schema 根 object 判(直喂注册面的前置校验——非 object 根注册表会拒) */
|
|
142
|
+
function isRootObjectSchema(schema) {
|
|
143
|
+
const t = schema.type;
|
|
144
|
+
return t === 'object';
|
|
145
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core:mcp 类型面(03 §10.1——stdio 行帧 JSON-RPC 手写最小桥;批 17a-2)。
|
|
3
|
+
*
|
|
4
|
+
* 词面独立律(02 §4.1 席 deps = contracts + context):本件与 exec/tools 零
|
|
5
|
+
* DAG 边——spawn 与工具注册全经**窄面注入**消费(issue 件 IssueWorktreeFace
|
|
6
|
+
* 先例同律):
|
|
7
|
+
* - `McpSpawnFace` 与 exec SpawnPipeline.spawnInteractive 结构兼容(组合根
|
|
8
|
+
* 直接传管道真身;兼容性互证测试在 compat.test.ts);
|
|
9
|
+
* - `McpRegisterToolsFace` 与 tools ToolRegistry.register 子面结构兼容
|
|
10
|
+
* (返回注销器 = 撤工具面);
|
|
11
|
+
* - `McpScopeFace` 与 context Scope 的 effect/isDisposed 子面结构兼容
|
|
12
|
+
* (ctx.effect 回卷 + 续段 scope 活查)。
|
|
13
|
+
*
|
|
14
|
+
* context 席边为真消费(createLogger——装载态 ui.notify 接线随装配批)。
|
|
15
|
+
*/
|
|
16
|
+
import { BaseError } from '../contracts/index.js';
|
|
17
|
+
/* ---------------- 常量(缺省值单源——03 §10.1 字段集落码定形注) ---------------- */
|
|
18
|
+
/** 服务器键词法(`[A-Za-z0-9-]+`——`__` 与空白禁入;复合名 `<server>__<tool>` 的无歧义分界前提) */
|
|
19
|
+
export const MCP_SERVER_NAME_RE = /^[A-Za-z0-9-]+$/;
|
|
20
|
+
/** initialize 握手预算缺省秒数(spawn+握手同罩——只罩连接建立,不罩单次调用) */
|
|
21
|
+
export const MCP_STARTUP_TIMEOUT_SEC_DEFAULT = 10;
|
|
22
|
+
/** 单次工具调用预算缺省秒数(tools/call 逐台执法) */
|
|
23
|
+
export const MCP_TOOL_TIMEOUT_SEC_DEFAULT = 60;
|
|
24
|
+
/** server stdout 单行字节上限(行帧卫生——超限按载体级失败收场,静默巨行不进宿主堆) */
|
|
25
|
+
export const MCP_LINE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
26
|
+
/** 注册面爆炸防线阈值(过滤后全局合计 >20 → 全部降级为单件 mcp 目录工具) */
|
|
27
|
+
export const MCP_NATIVE_TOOL_LIMIT = 20;
|
|
28
|
+
/** 协议化关停宽限缺省(stdin.end 告别 → 宽限等退 → killTree 树杀兜底) */
|
|
29
|
+
export const MCP_CLOSE_GRACE_MS = 3_000;
|
|
30
|
+
/** 客户端握手 protocolVersion(已知集最基底值——最广服务器支持面;服务器回显异值不拒) */
|
|
31
|
+
export const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
32
|
+
/**
|
|
33
|
+
* 归一 config.servers(坏形响亮拒 MCP_CONFIG_INVALID——/reload 时刻可见)。
|
|
34
|
+
* 非对象/null/undefined → 空配置(缺省 servers 空 = 行惰性无害零 spawn)。
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeMcpConfig(raw) {
|
|
37
|
+
if (raw === undefined || raw === null)
|
|
38
|
+
return {};
|
|
39
|
+
if (typeof raw !== 'object' || Array.isArray(raw)) {
|
|
40
|
+
throw new BaseError('MCP_CONFIG_INVALID', 'config.servers 须为对象(键 = 服务器名)');
|
|
41
|
+
}
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
44
|
+
out[name] = normalizeServerEntry(name, value);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** 单服务器条目归一(字段集逐一类型校验——词法/绝对路径/正数域) */
|
|
49
|
+
function normalizeServerEntry(name, value) {
|
|
50
|
+
if (!MCP_SERVER_NAME_RE.test(name)) {
|
|
51
|
+
throw new BaseError('MCP_CONFIG_INVALID', `服务器键词法违例:${JSON.stringify(name)}(须匹配 [A-Za-z0-9-]+——__ 与空白禁入)`);
|
|
52
|
+
}
|
|
53
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
54
|
+
throw new BaseError('MCP_CONFIG_INVALID', `servers.${name} 须为对象`);
|
|
55
|
+
}
|
|
56
|
+
const v = value;
|
|
57
|
+
const command = v.command;
|
|
58
|
+
if (typeof command !== 'string' || command === '' || !command.startsWith('/')) {
|
|
59
|
+
throw new BaseError('MCP_CONFIG_INVALID', `servers.${name}.command 须为绝对路径(v1 只收绝对路径):${JSON.stringify(command)}`);
|
|
60
|
+
}
|
|
61
|
+
const args = strArray(v.args, `servers.${name}.args`);
|
|
62
|
+
const env = strRecord(v.env, `servers.${name}.env`);
|
|
63
|
+
const enabled = strArray(v.enabled_tools, `servers.${name}.enabled_tools`);
|
|
64
|
+
const disabled = strArray(v.disabled_tools, `servers.${name}.disabled_tools`);
|
|
65
|
+
const startup = positiveNumber(v.startup_timeout_sec, `servers.${name}.startup_timeout_sec`);
|
|
66
|
+
const toolTimeout = positiveNumber(v.tool_timeout_sec, `servers.${name}.tool_timeout_sec`);
|
|
67
|
+
return {
|
|
68
|
+
command,
|
|
69
|
+
...(args !== undefined ? { args } : {}),
|
|
70
|
+
...(env !== undefined ? { env } : {}),
|
|
71
|
+
...(enabled !== undefined ? { enabled_tools: enabled } : {}),
|
|
72
|
+
...(disabled !== undefined ? { disabled_tools: disabled } : {}),
|
|
73
|
+
...(startup !== undefined ? { startup_timeout_sec: startup } : {}),
|
|
74
|
+
...(toolTimeout !== undefined ? { tool_timeout_sec: toolTimeout } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** 字符串数组字段校验(undefined 放行——可选字段缺席合法) */
|
|
78
|
+
function strArray(value, label) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (!Array.isArray(value) || value.some((e) => typeof e !== 'string')) {
|
|
82
|
+
throw new BaseError('MCP_CONFIG_INVALID', `${label} 须为字符串数组`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
/** 字符串记录字段校验(env 显式注入面) */
|
|
87
|
+
function strRecord(value, label) {
|
|
88
|
+
if (value === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
91
|
+
throw new BaseError('MCP_CONFIG_INVALID', `${label} 须为 string→string 对象`);
|
|
92
|
+
}
|
|
93
|
+
for (const [k, val] of Object.entries(value)) {
|
|
94
|
+
if (typeof val !== 'string') {
|
|
95
|
+
throw new BaseError('MCP_CONFIG_INVALID', `${label}.${k} 须为 string`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
/** 正数域字段校验(超时秒——0/负/非数拒) */
|
|
101
|
+
function positiveNumber(value, label) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
105
|
+
throw new BaseError('MCP_CONFIG_INVALID', `${label} 须为正数(秒)`);
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|