openai: shared MCP layer + openclaw-tools bridge [Adolf P5]

- adolf-llm/server.js now loads SHARED_MCP_SERVERS from the mounted
  /shared-mcp.json instead of a hardcoded stub, so adding a shared MCP
  server is a one-file change. Verified end-to-end: a real chat-completions
  turn writes a session .mcp.json containing both cognee and openclaw-tools
  entries (kimi itself still needs `kimi login` in adolf-llm-home, unrelated
  to this change).
- Documented the Gate-1 transport reconciliation: decompiled the installed
  @moonshot-ai/kimi-code package to confirm its .mcp.json schema keys remote
  servers on `transport` ("stdio"/"http"/"sse", inferred as "http" from a
  bare `url`, never "sse"), while OpenClaw's own canonical mcp.servers schema
  uses different literals ("streamable-http"/"sse") for the same field name
  and treats `type` as a CLI-native alias it normalizes itself. `type: "http"`
  is the one shape both consumers tolerate, so shared-mcp.json keeps it.
- New openai/openclaw-tools/ service: a stateless MCP-over-Streamable-HTTP
  bridge (Node, @modelcontextprotocol/sdk) exposing message_send, cron_create,
  cron_list, nodes_invoke, and browser_invoke, each proxying to the OpenClaw
  gateway's POST /tools/invoke. Verified initialize + tools/list handshake and
  a tools/call against the not-yet-running `adolf` gateway returns a clean
  isError content instead of breaking the MCP connection. Documented that
  cron/nodes are hard-denied on that HTTP surface by default until P6 adds
  them to gateway.tools.allow; message/browser are not similarly restricted.
- Wired openclaw-tools into docker-compose.yml (openai network, :8020) and
  added its shared-mcp.json entry alongside cognee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 15:49:23 +00:00
parent 1e66d3dcb5
commit 9ab6b7dfed
6 changed files with 327 additions and 16 deletions

View File

@@ -19,21 +19,45 @@ fs.mkdirSync(STATE_DIR, { recursive: true });
// ---------------------------------------------------------------------------
// Shared MCP layer (Gate 1). Kimi Code CLI has NO `--mcp-config-file` flag and
// no `kimi mcp` subcommand; it auto-discovers a project-root `.mcp.json`
// (Claude-Code-compatible schema) by walking up from its cwd. So we drop a
// `.mcp.json` into each session's working directory before spawning kimi.
// no `kimi mcp` subcommand; it auto-discovers a project-root `.mcp.json` by
// walking up from its cwd to the nearest `.git` (falling back to cwd itself
// when none is found). So we drop a `.mcp.json` into each session's working
// directory before spawning kimi.
//
// STUB: the server list is empty for now. cognee-mcp (memory_search/add/cognify,
// P4) and the openclaw-tools bridge (P5) do not exist yet. Wiring them later is
// a one-line change here — add entries to SHARED_MCP_SERVERS and every future
// session dir picks them up automatically. Schema per server, e.g.:
// cognee: { command, args, env } (stdio)
// openclaw-tools:{ type: "sse"|"http", url, headers } (remote)
// TODO(P4/P5): populate SHARED_MCP_SERVERS from cognee-mcp + openclaw-tools.
const SHARED_MCP_SERVERS = {
// TODO(P4): "cognee": { command: "...", args: [...], env: {...} },
// TODO(P5): "openclaw-tools": { type: "sse", url: "http://openclaw:.../mcp" },
};
// Single source of truth: `/shared-mcp.json` (mounted read-only from the repo
// root's `shared-mcp.json`, the same file P6 wires into OpenClaw's own
// `mcp.servers` registry). Adding a server is then a one-file change — no
// server list is hardcoded here anymore.
//
// Gate-1 transport finding (P5, verified by decompiling the installed
// @moonshot-ai/kimi-code package, packages/agent-core/src/config/schema.ts's
// McpServerConfigSchema): Kimi's own field name for remote MCP servers is
// `transport` (literal "stdio" | "http" | "sse"), not `type`. When `transport`
// is omitted, Kimi's config preprocessor infers it from shape: `command` ->
// "stdio", `url` -> "http" (never "sse" — sse requires an explicit
// `transport: "sse"`). It does NOT recognize a `type` key at all; unknown keys
// are silently stripped by the (non-strict) zod schema.
// OpenClaw's own canonical `mcp.servers` schema (docs/gateway/
// configuration-reference.md) uses different literals for the same
// transport: `transport: "streamable-http"` or `"sse"`, with `type: "http"`
// documented as a *CLI-native alias* that `openclaw mcp set` / `openclaw
// doctor --fix` normalize into canonical `transport: "streamable-http"`.
// So the two consumers disagree on the literal value for HTTP streaming
// ("http" vs "streamable-http") under the same field name `transport` --
// writing `transport` explicitly in shared-mcp.json would satisfy at most one
// side. `type: "http"` is the one shape both sides tolerate today: Kimi
// ignores the unrecognized `type` key and correctly infers transport "http"
// from the `url` field alone; OpenClaw recognizes `type` as its documented
// alias and normalizes it on its own terms (P6 concern, not touched here).
// Hence shared-mcp.json intentionally keeps `"type": "http"` for both cognee
// and openclaw-tools rather than switching to `transport`.
let SHARED_MCP_SERVERS = {};
try {
const raw = fs.readFileSync('/shared-mcp.json', 'utf8');
SHARED_MCP_SERVERS = JSON.parse(raw).mcpServers || {};
} catch (err) {
console.error(`shared-mcp.json not loaded (${err.message}); sessions will get no shared MCP servers`);
}
function writeMcpConfig(dir) {
const cfg = { mcpServers: SHARED_MCP_SERVERS };