@alfe.ai/openclaw-mcp-bundler 0.0.3 → 0.0.4

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/dist/plugin.d.cts CHANGED
@@ -54,6 +54,11 @@ interface ToolFactoryContext {
54
54
  runtimeConfig?: ConfigSnapshot;
55
55
  getRuntimeConfig?: () => ConfigSnapshot | undefined;
56
56
  }
57
+ interface OpenClawPluginService {
58
+ id: string;
59
+ start: (ctx?: unknown) => void | Promise<void>;
60
+ stop?: (ctx?: unknown) => void | Promise<void>;
61
+ }
57
62
  interface OpenClawPluginApi {
58
63
  logger: Logger;
59
64
  config?: ConfigSnapshot;
@@ -71,6 +76,7 @@ interface OpenClawPluginApi {
71
76
  names?: string[];
72
77
  optional?: boolean;
73
78
  }) => void;
79
+ registerService?: (service: OpenClawPluginService) => void;
74
80
  }
75
81
  declare const plugin: {
76
82
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.cts","names":["McpServerConfig","StdioServerConfig","RemoteServerConfig","Record","McpTransportKind","McpToolDescriptor","ReconcileDiff","Logger","McpToolCallResult","BundlerOptions","STDIO_ENV_DENYLIST","Set","sanitizeStdioEnv","ConnectionDeps","McpClientHandle","Promise","AbortSignal","Connection","defaultConnect","McpBundler","sanitizeNameSegment","buildNamespacedToolName","disambiguateAgainst","ReadonlySet"],"sources":["../../mcp-bundler/dist/index.d.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Shape of a single MCP server entry — matches OpenClaw `mcp.servers.{name}`.\n * Either a stdio child process spec or a remote URL spec.\n */\ntype McpServerConfig = StdioServerConfig | RemoteServerConfig;\ninterface StdioServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\ninterface RemoteServerConfig {\n url: string;\n transport?: 'sse' | 'streamable-http';\n headers?: Record<string, string>;\n connectionTimeoutMs?: number;\n}\ntype McpTransportKind = 'stdio' | 'sse' | 'streamable-http';\n/**\n * One tool surfaced from one MCP server, after namespacing.\n */\ninterface McpToolDescriptor {\n /** Namespaced name as the LLM sees it: `{serverName}__{originalName}`, sanitized + max 64 chars. */\n prefixed: string;\n /** Server name (key in `mcp.servers.*`). */\n server: string;\n /** Original tool name as advertised by the MCP server. */\n original: string;\n /** Human label for the tool (truncated description, suitable for UI). */\n label: string;\n /** Full description from the MCP server. */\n description: string;\n /** JSON Schema (object) for tool parameters. */\n parameters: Record<string, unknown>;\n}\ninterface ReconcileDiff {\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n}\ninterface Logger {\n debug: (msg: string, meta?: Record<string, unknown>) => void;\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n}\ninterface McpToolCallResult {\n content: Record<string, unknown>[];\n isError?: boolean;\n}\ninterface BundlerOptions {\n logger?: Logger;\n /** Idle TTL for spawned children (ms). 0 disables. Default: 600_000 (10 min). */\n idleTtlMs?: number;\n /** Sweep interval for idle reaping (ms). Default: 60_000 (1 min). */\n idleSweepIntervalMs?: number;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/connection.d.ts\n/** Env keys OpenClaw rejects from stdio MCP env blocks. Filter them out before spawning. */\ndeclare const STDIO_ENV_DENYLIST: Set<string>;\ndeclare function sanitizeStdioEnv(env: Record<string, string> | undefined): Record<string, string>;\ninterface ConnectionDeps {\n /**\n * Factory for an MCP Client connected to the given config. Injected so tests\n * can mock without spawning real processes. In production this wraps\n * `@modelcontextprotocol/sdk/client`.\n */\n connect: (server: McpServerConfig) => Promise<McpClientHandle>;\n}\n/**\n * Minimal interface our connection layer needs from an MCP client. Mirrors\n * the @modelcontextprotocol/sdk Client surface but kept narrow so we can\n * mock cleanly in tests.\n */\ninterface McpClientHandle {\n listTools(): Promise<{\n name: string;\n description?: string;\n inputSchema: Record<string, unknown>;\n }[]>;\n callTool(name: string, args: unknown, opts?: {\n signal?: AbortSignal;\n }): Promise<McpToolCallResult>;\n close(): Promise<void>;\n}\n/**\n * One Connection per MCP server. Owns lifecycle (lazy-spawn, close, refresh-lock).\n * Refresh-lock pattern adapted from AIWerk `index.ts:219-250` — prevents\n * reconnect + `notifications/tools/list_changed` race.\n */\ndeclare class Connection {\n readonly name: string;\n readonly config: McpServerConfig;\n private readonly deps;\n private readonly logger;\n private client;\n private tools;\n private connectInFlight;\n private refreshInFlight;\n private refreshQueued;\n private lastUsedAt;\n constructor(params: {\n name: string;\n config: McpServerConfig;\n deps: ConnectionDeps;\n logger?: Logger;\n });\n /** Returns the most recent known tool list. May be empty if the server hasn't connected yet. */\n snapshotTools(): McpToolDescriptor[];\n /** Whether an MCP child process / remote connection has been established. */\n isConnected(): boolean;\n /** Idle timestamp for reaping. */\n idleSinceMs(): number;\n /**\n * Lazy connect + tool discovery. Safe to call concurrently; in-flight\n * connects coalesce.\n */\n ensureConnected(): Promise<void>;\n private connectAndDiscover;\n /**\n * Re-discover tools. Used on reconnect or `tools/list_changed` notification.\n * Refresh-lock collapses concurrent refreshes; if one is in flight, the next\n * is queued (max 1 queued, since N>1 queued provides no extra freshness).\n */\n refresh(): Promise<void>;\n callTool(originalName: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Close the underlying transport. Idempotent. If a connect is in flight\n * (warmup racing with reconcile-removal), wait for it to settle and then\n * close the client it produced — otherwise the child process is orphaned.\n */\n close(): Promise<void>;\n /**\n * Stable hash of the config for diff detection in `reconcile`.\n * Two configs with the same hash are equivalent (no restart needed).\n */\n configFingerprint(): string;\n}\n/**\n * Build the production `connect` factory using the official MCP SDK.\n * Kept in a separate function so tests can substitute a mock without\n * pulling the SDK into the test bundle.\n */\ndeclare function defaultConnect(server: McpServerConfig): Promise<McpClientHandle>;\n//# sourceMappingURL=connection.d.ts.map\n//#endregion\n//#region src/bundler.d.ts\n/**\n * Provider-agnostic MCP server bundler. Holds N MCP server connections,\n * exposes a unified namespaced tool catalog, and routes calls to the right\n * server.\n *\n * Designed to be embedded in any host (OpenClaw plugin, AI proxy, Lambda).\n * Public surface is intentionally synchronous where the host needs sync\n * (snapshot, listTools), async only where I/O is unavoidable.\n */\ndeclare class McpBundler {\n private readonly logger;\n private readonly connections;\n private readonly idleTtlMs;\n private readonly idleSweepIntervalMs;\n private idleSweepTimer;\n private readonly deps;\n private disposed;\n private reconcileLatch;\n constructor(opts?: BundlerOptions, deps?: ConnectionDeps);\n /**\n * Diff `desired` against current connections, spawn newcomers, dispose\n * removals, hot-restart on config change. Pull-based — call whenever the\n * host's config snapshot may have changed. Cheap if no diff.\n *\n * Lazy: newly-added servers are NOT eagerly connected; they connect on the\n * first `callTool()` (or first `listTools()` after `forceDiscover()`).\n * This avoids paying spawn cost for servers the agent never uses.\n */\n reconcile(desired: Record<string, McpServerConfig>): Promise<ReconcileDiff>;\n private doReconcile;\n /**\n * Synchronous snapshot of all currently-known tools across connected servers.\n * Servers that have not connected yet contribute nothing. Intended for use\n * inside OpenClaw's plugin tool factory which must be sync.\n *\n * Tool names are namespaced and disambiguated (suffix `-2`, `-3` on collision)\n * so cross-server name clashes never produce duplicate registrations.\n */\n listTools(): McpToolDescriptor[];\n /**\n * Eagerly connect to every configured server and discover tools. Used by\n * hosts that want a hot list rather than the lazy default. Errors are\n * swallowed per-server (logged), so one bad server doesn't fail the batch.\n */\n warmup(): Promise<void>;\n /**\n * Invoke a tool by its namespaced name. Routes to the originating server.\n * Errors are returned as `{ isError: true, content: [...] }` so a failing\n * tool doesn't crash the host.\n */\n callTool(prefixed: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Resolve a namespaced tool name back to its server connection and original\n * tool name. Returns undefined if the tool is not currently advertised.\n */\n private routeToolName;\n /**\n * Tear down all connections and stop background tasks. Idempotent.\n * Call from `registerRuntimeLifecycle({ cleanup })` in the host plugin.\n */\n dispose(): Promise<void>;\n private startIdleSweep;\n private sweepIdle;\n}\n//# sourceMappingURL=bundler.d.ts.map\n//#endregion\n//#region src/tool-naming.d.ts\n/**\n * Tool name sanitization and collision handling.\n *\n * OpenClaw constraint: tool names must match `[A-Za-z0-9_-]` and be ≤64 chars.\n * Pattern mirrored from `openclaw/src/agents/pi-bundle-mcp-names.ts`.\n *\n * Strategy: prefix every tool with its server name (`{server}__{tool}`),\n * sanitize disallowed chars to `_`, truncate, then suffix-disambiguate\n * (`-2`, `-3`, ...) on collision.\n */\ndeclare function sanitizeNameSegment(value: string): string;\ndeclare function buildNamespacedToolName(server: string, tool: string): string;\n/**\n * Disambiguate a candidate name against an existing set by appending `-2`, `-3`, etc.\n * Mutates nothing; returns the chosen name. Caller is responsible for inserting it\n * into the set.\n */\ndeclare function disambiguateAgainst(candidate: string, taken: ReadonlySet<string>): string;\n//# sourceMappingURL=tool-naming.d.ts.map\n\n//#endregion\nexport { type BundlerOptions, Connection, type ConnectionDeps, type Logger, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type StdioServerConfig, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;KAKKA,eAAAA,GAAkBC,iBAAsC,GAAlBC,kBAAkB;AAAA,UACnDD,iBAAAA,CAAiB;EAGb,OAGJC,EAAAA,MAAAA;;QAHFC;;ACGwF;AAUhF,UDVND,kBAAAA,CCiBc;EAAA,GAAA,EAAA,MAAA;WACgC,CAAA,EAAA,KAAA,GAAA,iBAAA;SACrB,CAAA,EDhBvBC,MCgBuB,CAAA,MAAA,EAAA,MAAA,CAAA;qBAAf,CAAA,EAAA,MAAA;;;;;AATJ,UAAN,MAAA,CAOA;EAAc,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACgC,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACrB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAAf,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAM,UAFhB,cAAA,CAKe;EAAA,MAAA,CAAA,EAAA;IACd,QAAA,CAAA,EAAA;MACC,KAAA,CAAA,EAAA,MAAA;MAAM,WAAA,CAAA,EANsC,MAMtC,CAAA,MAAA,EAAA,OAAA,CAAA;IAGR,CAAA;EAAS,CAAA;KAIL,CAAA,EAAA;IAID,OAAA,CAAA,EAhBO,MAgBP,CAAA,MAAA,EAhBsB,eAgBtB,CAAA;IAEE,gBAAA,CAAA,EAAA,MAAA;;;AAAD,UAfJ,eAAA,CAkBkB;EAAA,OAAA,EAjBjB,MAiBiB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SACjB,CAAA,EAjBC,MAiBD,CAAA,MAAA,EAAA,OAAA,CAAA;;UAdD,SAAA,CAgBiB;EAAc,IAAA,EAAA,MAAA;EAG/B,KAAA,CAAA,EAAA,MAAA;EAAiB,WAAA,EAAA,MAAA;YACjB,EAhBI,MAgBJ,CAAA,MAAA,EAAA,OAAA,CAAA;SACC,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAbE,WAaF,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAXJ,OAWI,CAXI,eAWJ,CAAA;;UARD,kBAAA,CAaA;QAAmB,CAAA,EAZlB,cAYkB;eAAuB,CAAA,EAXlC,cAWkC;kBAAY,CAAA,EAAA,GAAA,GAVrC,cAUqC,GAAA,SAAA;;AAAS,UAP/D,iBAAA,CA6JT;EAAA,MAAA,EA5JS,MA4JT;QA/Ee,CAAA,EA5EL,cA4EK;cAoEE,CAAA,EAAA;IAAiB,QAAA,CAAA,EAAA,OAAA;;;;;sBA7IM;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;;cAuD1D;;;;;;;;gBAgBU;kBAoEE"}
1
+ {"version":3,"file":"plugin.d.cts","names":["McpServerConfig","StdioServerConfig","RemoteServerConfig","Record","McpTransportKind","McpToolDescriptor","ReconcileDiff","Logger","McpToolCallResult","BundlerOptions","STDIO_ENV_DENYLIST","Set","sanitizeStdioEnv","ConnectionDeps","McpClientHandle","Promise","AbortSignal","Connection","defaultConnect","McpBundler","sanitizeNameSegment","buildNamespacedToolName","disambiguateAgainst","ReadonlySet"],"sources":["../../mcp-bundler/dist/index.d.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Shape of a single MCP server entry — matches OpenClaw `mcp.servers.{name}`.\n * Either a stdio child process spec or a remote URL spec.\n */\ntype McpServerConfig = StdioServerConfig | RemoteServerConfig;\ninterface StdioServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\ninterface RemoteServerConfig {\n url: string;\n transport?: 'sse' | 'streamable-http';\n headers?: Record<string, string>;\n connectionTimeoutMs?: number;\n}\ntype McpTransportKind = 'stdio' | 'sse' | 'streamable-http';\n/**\n * One tool surfaced from one MCP server, after namespacing.\n */\ninterface McpToolDescriptor {\n /** Namespaced name as the LLM sees it: `{serverName}__{originalName}`, sanitized + max 64 chars. */\n prefixed: string;\n /** Server name (key in `mcp.servers.*`). */\n server: string;\n /** Original tool name as advertised by the MCP server. */\n original: string;\n /** Human label for the tool (truncated description, suitable for UI). */\n label: string;\n /** Full description from the MCP server. */\n description: string;\n /** JSON Schema (object) for tool parameters. */\n parameters: Record<string, unknown>;\n}\ninterface ReconcileDiff {\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n}\ninterface Logger {\n debug: (msg: string, meta?: Record<string, unknown>) => void;\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n}\ninterface McpToolCallResult {\n content: Record<string, unknown>[];\n isError?: boolean;\n}\ninterface BundlerOptions {\n logger?: Logger;\n /** Idle TTL for spawned children (ms). 0 disables. Default: 600_000 (10 min). */\n idleTtlMs?: number;\n /** Sweep interval for idle reaping (ms). Default: 60_000 (1 min). */\n idleSweepIntervalMs?: number;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/connection.d.ts\n/** Env keys OpenClaw rejects from stdio MCP env blocks. Filter them out before spawning. */\ndeclare const STDIO_ENV_DENYLIST: Set<string>;\ndeclare function sanitizeStdioEnv(env: Record<string, string> | undefined): Record<string, string>;\ninterface ConnectionDeps {\n /**\n * Factory for an MCP Client connected to the given config. Injected so tests\n * can mock without spawning real processes. In production this wraps\n * `@modelcontextprotocol/sdk/client`.\n */\n connect: (server: McpServerConfig) => Promise<McpClientHandle>;\n}\n/**\n * Minimal interface our connection layer needs from an MCP client. Mirrors\n * the @modelcontextprotocol/sdk Client surface but kept narrow so we can\n * mock cleanly in tests.\n */\ninterface McpClientHandle {\n listTools(): Promise<{\n name: string;\n description?: string;\n inputSchema: Record<string, unknown>;\n }[]>;\n callTool(name: string, args: unknown, opts?: {\n signal?: AbortSignal;\n }): Promise<McpToolCallResult>;\n close(): Promise<void>;\n}\n/**\n * One Connection per MCP server. Owns lifecycle (lazy-spawn, close, refresh-lock).\n * Refresh-lock pattern adapted from AIWerk `index.ts:219-250` — prevents\n * reconnect + `notifications/tools/list_changed` race.\n */\ndeclare class Connection {\n readonly name: string;\n readonly config: McpServerConfig;\n private readonly deps;\n private readonly logger;\n private client;\n private tools;\n private connectInFlight;\n private refreshInFlight;\n private refreshQueued;\n private lastUsedAt;\n constructor(params: {\n name: string;\n config: McpServerConfig;\n deps: ConnectionDeps;\n logger?: Logger;\n });\n /** Returns the most recent known tool list. May be empty if the server hasn't connected yet. */\n snapshotTools(): McpToolDescriptor[];\n /** Whether an MCP child process / remote connection has been established. */\n isConnected(): boolean;\n /** Idle timestamp for reaping. */\n idleSinceMs(): number;\n /**\n * Lazy connect + tool discovery. Safe to call concurrently; in-flight\n * connects coalesce.\n */\n ensureConnected(): Promise<void>;\n private connectAndDiscover;\n /**\n * Re-discover tools. Used on reconnect or `tools/list_changed` notification.\n * Refresh-lock collapses concurrent refreshes; if one is in flight, the next\n * is queued (max 1 queued, since N>1 queued provides no extra freshness).\n */\n refresh(): Promise<void>;\n callTool(originalName: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Close the underlying transport. Idempotent. If a connect is in flight\n * (warmup racing with reconcile-removal), wait for it to settle and then\n * close the client it produced — otherwise the child process is orphaned.\n */\n close(): Promise<void>;\n /**\n * Stable hash of the config for diff detection in `reconcile`.\n * Two configs with the same hash are equivalent (no restart needed).\n */\n configFingerprint(): string;\n}\n/**\n * Build the production `connect` factory using the official MCP SDK.\n * Kept in a separate function so tests can substitute a mock without\n * pulling the SDK into the test bundle.\n */\ndeclare function defaultConnect(server: McpServerConfig): Promise<McpClientHandle>;\n//# sourceMappingURL=connection.d.ts.map\n//#endregion\n//#region src/bundler.d.ts\n/**\n * Provider-agnostic MCP server bundler. Holds N MCP server connections,\n * exposes a unified namespaced tool catalog, and routes calls to the right\n * server.\n *\n * Designed to be embedded in any host (OpenClaw plugin, AI proxy, Lambda).\n * Public surface is intentionally synchronous where the host needs sync\n * (snapshot, listTools), async only where I/O is unavoidable.\n */\ndeclare class McpBundler {\n private readonly logger;\n private readonly connections;\n private readonly idleTtlMs;\n private readonly idleSweepIntervalMs;\n private idleSweepTimer;\n private readonly deps;\n private disposed;\n private reconcileLatch;\n constructor(opts?: BundlerOptions, deps?: ConnectionDeps);\n /**\n * Diff `desired` against current connections, spawn newcomers, dispose\n * removals, hot-restart on config change. Pull-based — call whenever the\n * host's config snapshot may have changed. Cheap if no diff.\n *\n * Lazy: newly-added servers are NOT eagerly connected; they connect on the\n * first `callTool()` (or first `listTools()` after `forceDiscover()`).\n * This avoids paying spawn cost for servers the agent never uses.\n */\n reconcile(desired: Record<string, McpServerConfig>): Promise<ReconcileDiff>;\n private doReconcile;\n /**\n * Synchronous snapshot of all currently-known tools across connected servers.\n * Servers that have not connected yet contribute nothing. Intended for use\n * inside OpenClaw's plugin tool factory which must be sync.\n *\n * Tool names are namespaced and disambiguated (suffix `-2`, `-3` on collision)\n * so cross-server name clashes never produce duplicate registrations.\n */\n listTools(): McpToolDescriptor[];\n /**\n * Eagerly connect to every configured server and discover tools. Used by\n * hosts that want a hot list rather than the lazy default. Errors are\n * swallowed per-server (logged), so one bad server doesn't fail the batch.\n */\n warmup(): Promise<void>;\n /**\n * Invoke a tool by its namespaced name. Routes to the originating server.\n * Errors are returned as `{ isError: true, content: [...] }` so a failing\n * tool doesn't crash the host.\n */\n callTool(prefixed: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Resolve a namespaced tool name back to its server connection and original\n * tool name. Returns undefined if the tool is not currently advertised.\n */\n private routeToolName;\n /**\n * Tear down all connections and stop background tasks. Idempotent.\n * Call from `registerRuntimeLifecycle({ cleanup })` in the host plugin.\n */\n dispose(): Promise<void>;\n private startIdleSweep;\n private sweepIdle;\n}\n//# sourceMappingURL=bundler.d.ts.map\n//#endregion\n//#region src/tool-naming.d.ts\n/**\n * Tool name sanitization and collision handling.\n *\n * OpenClaw constraint: tool names must match `[A-Za-z0-9_-]` and be ≤64 chars.\n * Pattern mirrored from `openclaw/src/agents/pi-bundle-mcp-names.ts`.\n *\n * Strategy: prefix every tool with its server name (`{server}__{tool}`),\n * sanitize disallowed chars to `_`, truncate, then suffix-disambiguate\n * (`-2`, `-3`, ...) on collision.\n */\ndeclare function sanitizeNameSegment(value: string): string;\ndeclare function buildNamespacedToolName(server: string, tool: string): string;\n/**\n * Disambiguate a candidate name against an existing set by appending `-2`, `-3`, etc.\n * Mutates nothing; returns the chosen name. Caller is responsible for inserting it\n * into the set.\n */\ndeclare function disambiguateAgainst(candidate: string, taken: ReadonlySet<string>): string;\n//# sourceMappingURL=tool-naming.d.ts.map\n\n//#endregion\nexport { type BundlerOptions, Connection, type ConnectionDeps, type Logger, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type StdioServerConfig, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;KAKKA,eAAAA,GAAkBC,iBAAsC,GAAlBC,kBAAkB;AAAA,UACnDD,iBAAAA,CAAiB;EAGb,OAGJC,EAAAA,MAAAA;;QAHFC;;ACGwF;AAUhF,UDVND,kBAAAA,CCiBc;EAAA,GAAA,EAAA,MAAA;WACgC,CAAA,EAAA,KAAA,GAAA,iBAAA;SACrB,CAAA,EDhBvBC,MCgBuB,CAAA,MAAA,EAAA,MAAA,CAAA;qBAAf,CAAA,EAAA,MAAA;;;;;AATJ,UAAN,MAAA,CAOA;EAAc,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACgC,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACrB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAAf,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAM,UAFhB,cAAA,CAKe;EAAA,MAAA,CAAA,EAAA;IACd,QAAA,CAAA,EAAA;MACC,KAAA,CAAA,EAAA,MAAA;MAAM,WAAA,CAAA,EANsC,MAMtC,CAAA,MAAA,EAAA,OAAA,CAAA;IAGR,CAAA;EAAS,CAAA;KAIL,CAAA,EAAA;IAID,OAAA,CAAA,EAhBO,MAgBP,CAAA,MAAA,EAhBsB,eAgBtB,CAAA;IAEE,gBAAA,CAAA,EAAA,MAAA;;;AAAD,UAfJ,eAAA,CAkBkB;EAAA,OAAA,EAjBjB,MAiBiB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SACjB,CAAA,EAjBC,MAiBD,CAAA,MAAA,EAAA,OAAA,CAAA;;UAdD,SAAA,CAgBiB;EAAc,IAAA,EAAA,MAAA;EAG/B,KAAA,CAAA,EAAA,MAAA;EAAqB,WAAA,EAAA,MAAA;YAEI,EAjBrB,MAiBqB,CAAA,MAAA,EAAA,OAAA,CAAA;SACA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAdtB,WAcsB,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAZ5B,OAY4B,CAZpB,eAYoB,CAAA;;AAAO,UAThC,kBAAA,CAYiB;EAAA,MAAA,CAAA,EAXhB,cAWgB;eACjB,CAAA,EAXQ,cAWR;kBACC,CAAA,EAAA,GAAA,GAXgB,cAWhB,GAAA,SAAA;;UARD,qBAAA,CAaA;MAAmB,MAAA;OAAuB,EAAA,CAAA,GAAA,CAAA,EAAA,OAAA,EAAA,GAAA,IAAA,GAXjB,OAWiB,CAAA,IAAA,CAAA;MAAY,CAAA,EAAA,CAAA,GAAA,CAAA,EAAA,OAAA,EAAA,GAAA,IAAA,GAV7B,OAU6B,CAAA,IAAA,CAAA;;UAPtD,iBAAA,CAUyC;EAqD7C,MAAA,EA9DI,MAiKT;EAAA,MAAA,CAAA,EAhKU,cAgKV;cAnFe,CAAA,EAAA;IA+EE,QAAA,CAAA,EAAA,OAAA;EAAiB,CAAA;;;;sBAzJM;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;8BAGlC;;cAqDxB;;;;;;;;gBAgBU;kBA+EE"}
package/dist/plugin.d.ts CHANGED
@@ -54,6 +54,11 @@ interface ToolFactoryContext {
54
54
  runtimeConfig?: ConfigSnapshot;
55
55
  getRuntimeConfig?: () => ConfigSnapshot | undefined;
56
56
  }
57
+ interface OpenClawPluginService {
58
+ id: string;
59
+ start: (ctx?: unknown) => void | Promise<void>;
60
+ stop?: (ctx?: unknown) => void | Promise<void>;
61
+ }
57
62
  interface OpenClawPluginApi {
58
63
  logger: Logger;
59
64
  config?: ConfigSnapshot;
@@ -71,6 +76,7 @@ interface OpenClawPluginApi {
71
76
  names?: string[];
72
77
  optional?: boolean;
73
78
  }) => void;
79
+ registerService?: (service: OpenClawPluginService) => void;
74
80
  }
75
81
  declare const plugin: {
76
82
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":["McpServerConfig","StdioServerConfig","RemoteServerConfig","Record","McpTransportKind","McpToolDescriptor","ReconcileDiff","Logger","McpToolCallResult","BundlerOptions","STDIO_ENV_DENYLIST","Set","sanitizeStdioEnv","ConnectionDeps","McpClientHandle","Promise","AbortSignal","Connection","defaultConnect","McpBundler","sanitizeNameSegment","buildNamespacedToolName","disambiguateAgainst","ReadonlySet"],"sources":["../../mcp-bundler/dist/index.d.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Shape of a single MCP server entry — matches OpenClaw `mcp.servers.{name}`.\n * Either a stdio child process spec or a remote URL spec.\n */\ntype McpServerConfig = StdioServerConfig | RemoteServerConfig;\ninterface StdioServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\ninterface RemoteServerConfig {\n url: string;\n transport?: 'sse' | 'streamable-http';\n headers?: Record<string, string>;\n connectionTimeoutMs?: number;\n}\ntype McpTransportKind = 'stdio' | 'sse' | 'streamable-http';\n/**\n * One tool surfaced from one MCP server, after namespacing.\n */\ninterface McpToolDescriptor {\n /** Namespaced name as the LLM sees it: `{serverName}__{originalName}`, sanitized + max 64 chars. */\n prefixed: string;\n /** Server name (key in `mcp.servers.*`). */\n server: string;\n /** Original tool name as advertised by the MCP server. */\n original: string;\n /** Human label for the tool (truncated description, suitable for UI). */\n label: string;\n /** Full description from the MCP server. */\n description: string;\n /** JSON Schema (object) for tool parameters. */\n parameters: Record<string, unknown>;\n}\ninterface ReconcileDiff {\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n}\ninterface Logger {\n debug: (msg: string, meta?: Record<string, unknown>) => void;\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n}\ninterface McpToolCallResult {\n content: Record<string, unknown>[];\n isError?: boolean;\n}\ninterface BundlerOptions {\n logger?: Logger;\n /** Idle TTL for spawned children (ms). 0 disables. Default: 600_000 (10 min). */\n idleTtlMs?: number;\n /** Sweep interval for idle reaping (ms). Default: 60_000 (1 min). */\n idleSweepIntervalMs?: number;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/connection.d.ts\n/** Env keys OpenClaw rejects from stdio MCP env blocks. Filter them out before spawning. */\ndeclare const STDIO_ENV_DENYLIST: Set<string>;\ndeclare function sanitizeStdioEnv(env: Record<string, string> | undefined): Record<string, string>;\ninterface ConnectionDeps {\n /**\n * Factory for an MCP Client connected to the given config. Injected so tests\n * can mock without spawning real processes. In production this wraps\n * `@modelcontextprotocol/sdk/client`.\n */\n connect: (server: McpServerConfig) => Promise<McpClientHandle>;\n}\n/**\n * Minimal interface our connection layer needs from an MCP client. Mirrors\n * the @modelcontextprotocol/sdk Client surface but kept narrow so we can\n * mock cleanly in tests.\n */\ninterface McpClientHandle {\n listTools(): Promise<{\n name: string;\n description?: string;\n inputSchema: Record<string, unknown>;\n }[]>;\n callTool(name: string, args: unknown, opts?: {\n signal?: AbortSignal;\n }): Promise<McpToolCallResult>;\n close(): Promise<void>;\n}\n/**\n * One Connection per MCP server. Owns lifecycle (lazy-spawn, close, refresh-lock).\n * Refresh-lock pattern adapted from AIWerk `index.ts:219-250` — prevents\n * reconnect + `notifications/tools/list_changed` race.\n */\ndeclare class Connection {\n readonly name: string;\n readonly config: McpServerConfig;\n private readonly deps;\n private readonly logger;\n private client;\n private tools;\n private connectInFlight;\n private refreshInFlight;\n private refreshQueued;\n private lastUsedAt;\n constructor(params: {\n name: string;\n config: McpServerConfig;\n deps: ConnectionDeps;\n logger?: Logger;\n });\n /** Returns the most recent known tool list. May be empty if the server hasn't connected yet. */\n snapshotTools(): McpToolDescriptor[];\n /** Whether an MCP child process / remote connection has been established. */\n isConnected(): boolean;\n /** Idle timestamp for reaping. */\n idleSinceMs(): number;\n /**\n * Lazy connect + tool discovery. Safe to call concurrently; in-flight\n * connects coalesce.\n */\n ensureConnected(): Promise<void>;\n private connectAndDiscover;\n /**\n * Re-discover tools. Used on reconnect or `tools/list_changed` notification.\n * Refresh-lock collapses concurrent refreshes; if one is in flight, the next\n * is queued (max 1 queued, since N>1 queued provides no extra freshness).\n */\n refresh(): Promise<void>;\n callTool(originalName: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Close the underlying transport. Idempotent. If a connect is in flight\n * (warmup racing with reconcile-removal), wait for it to settle and then\n * close the client it produced — otherwise the child process is orphaned.\n */\n close(): Promise<void>;\n /**\n * Stable hash of the config for diff detection in `reconcile`.\n * Two configs with the same hash are equivalent (no restart needed).\n */\n configFingerprint(): string;\n}\n/**\n * Build the production `connect` factory using the official MCP SDK.\n * Kept in a separate function so tests can substitute a mock without\n * pulling the SDK into the test bundle.\n */\ndeclare function defaultConnect(server: McpServerConfig): Promise<McpClientHandle>;\n//# sourceMappingURL=connection.d.ts.map\n//#endregion\n//#region src/bundler.d.ts\n/**\n * Provider-agnostic MCP server bundler. Holds N MCP server connections,\n * exposes a unified namespaced tool catalog, and routes calls to the right\n * server.\n *\n * Designed to be embedded in any host (OpenClaw plugin, AI proxy, Lambda).\n * Public surface is intentionally synchronous where the host needs sync\n * (snapshot, listTools), async only where I/O is unavoidable.\n */\ndeclare class McpBundler {\n private readonly logger;\n private readonly connections;\n private readonly idleTtlMs;\n private readonly idleSweepIntervalMs;\n private idleSweepTimer;\n private readonly deps;\n private disposed;\n private reconcileLatch;\n constructor(opts?: BundlerOptions, deps?: ConnectionDeps);\n /**\n * Diff `desired` against current connections, spawn newcomers, dispose\n * removals, hot-restart on config change. Pull-based — call whenever the\n * host's config snapshot may have changed. Cheap if no diff.\n *\n * Lazy: newly-added servers are NOT eagerly connected; they connect on the\n * first `callTool()` (or first `listTools()` after `forceDiscover()`).\n * This avoids paying spawn cost for servers the agent never uses.\n */\n reconcile(desired: Record<string, McpServerConfig>): Promise<ReconcileDiff>;\n private doReconcile;\n /**\n * Synchronous snapshot of all currently-known tools across connected servers.\n * Servers that have not connected yet contribute nothing. Intended for use\n * inside OpenClaw's plugin tool factory which must be sync.\n *\n * Tool names are namespaced and disambiguated (suffix `-2`, `-3` on collision)\n * so cross-server name clashes never produce duplicate registrations.\n */\n listTools(): McpToolDescriptor[];\n /**\n * Eagerly connect to every configured server and discover tools. Used by\n * hosts that want a hot list rather than the lazy default. Errors are\n * swallowed per-server (logged), so one bad server doesn't fail the batch.\n */\n warmup(): Promise<void>;\n /**\n * Invoke a tool by its namespaced name. Routes to the originating server.\n * Errors are returned as `{ isError: true, content: [...] }` so a failing\n * tool doesn't crash the host.\n */\n callTool(prefixed: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Resolve a namespaced tool name back to its server connection and original\n * tool name. Returns undefined if the tool is not currently advertised.\n */\n private routeToolName;\n /**\n * Tear down all connections and stop background tasks. Idempotent.\n * Call from `registerRuntimeLifecycle({ cleanup })` in the host plugin.\n */\n dispose(): Promise<void>;\n private startIdleSweep;\n private sweepIdle;\n}\n//# sourceMappingURL=bundler.d.ts.map\n//#endregion\n//#region src/tool-naming.d.ts\n/**\n * Tool name sanitization and collision handling.\n *\n * OpenClaw constraint: tool names must match `[A-Za-z0-9_-]` and be ≤64 chars.\n * Pattern mirrored from `openclaw/src/agents/pi-bundle-mcp-names.ts`.\n *\n * Strategy: prefix every tool with its server name (`{server}__{tool}`),\n * sanitize disallowed chars to `_`, truncate, then suffix-disambiguate\n * (`-2`, `-3`, ...) on collision.\n */\ndeclare function sanitizeNameSegment(value: string): string;\ndeclare function buildNamespacedToolName(server: string, tool: string): string;\n/**\n * Disambiguate a candidate name against an existing set by appending `-2`, `-3`, etc.\n * Mutates nothing; returns the chosen name. Caller is responsible for inserting it\n * into the set.\n */\ndeclare function disambiguateAgainst(candidate: string, taken: ReadonlySet<string>): string;\n//# sourceMappingURL=tool-naming.d.ts.map\n\n//#endregion\nexport { type BundlerOptions, Connection, type ConnectionDeps, type Logger, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type StdioServerConfig, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;KAKKA,eAAAA,GAAkBC,iBAAsC,GAAlBC,kBAAkB;AAAA,UACnDD,iBAAAA,CAAiB;EAGb,OAGJC,EAAAA,MAAAA;;QAHFC;;ACGwF;AAUhF,UDVND,kBAAAA,CCiBc;EAAA,GAAA,EAAA,MAAA;WACgC,CAAA,EAAA,KAAA,GAAA,iBAAA;SACrB,CAAA,EDhBvBC,MCgBuB,CAAA,MAAA,EAAA,MAAA,CAAA;qBAAf,CAAA,EAAA,MAAA;;;;;AATJ,UAAN,MAAA,CAOA;EAAc,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACgC,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACrB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAAf,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAM,UAFhB,cAAA,CAKe;EAAA,MAAA,CAAA,EAAA;IACd,QAAA,CAAA,EAAA;MACC,KAAA,CAAA,EAAA,MAAA;MAAM,WAAA,CAAA,EANsC,MAMtC,CAAA,MAAA,EAAA,OAAA,CAAA;IAGR,CAAA;EAAS,CAAA;KAIL,CAAA,EAAA;IAID,OAAA,CAAA,EAhBO,MAgBP,CAAA,MAAA,EAhBsB,eAgBtB,CAAA;IAEE,gBAAA,CAAA,EAAA,MAAA;;;AAAD,UAfJ,eAAA,CAkBkB;EAAA,OAAA,EAjBjB,MAiBiB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SACjB,CAAA,EAjBC,MAiBD,CAAA,MAAA,EAAA,OAAA,CAAA;;UAdD,SAAA,CAgBiB;EAAc,IAAA,EAAA,MAAA;EAG/B,KAAA,CAAA,EAAA,MAAA;EAAiB,WAAA,EAAA,MAAA;YACjB,EAhBI,MAgBJ,CAAA,MAAA,EAAA,OAAA,CAAA;SACC,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAbE,WAaF,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAXJ,OAWI,CAXI,eAWJ,CAAA;;UARD,kBAAA,CAaA;QAAmB,CAAA,EAZlB,cAYkB;eAAuB,CAAA,EAXlC,cAWkC;kBAAY,CAAA,EAAA,GAAA,GAVrC,cAUqC,GAAA,SAAA;;AAAS,UAP/D,iBAAA,CA6JT;EAAA,MAAA,EA5JS,MA4JT;QA/Ee,CAAA,EA5EL,cA4EK;cAoEE,CAAA,EAAA;IAAiB,QAAA,CAAA,EAAA,OAAA;;;;;sBA7IM;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;;cAuD1D;;;;;;;;gBAgBU;kBAoEE"}
1
+ {"version":3,"file":"plugin.d.ts","names":["McpServerConfig","StdioServerConfig","RemoteServerConfig","Record","McpTransportKind","McpToolDescriptor","ReconcileDiff","Logger","McpToolCallResult","BundlerOptions","STDIO_ENV_DENYLIST","Set","sanitizeStdioEnv","ConnectionDeps","McpClientHandle","Promise","AbortSignal","Connection","defaultConnect","McpBundler","sanitizeNameSegment","buildNamespacedToolName","disambiguateAgainst","ReadonlySet"],"sources":["../../mcp-bundler/dist/index.d.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Shape of a single MCP server entry — matches OpenClaw `mcp.servers.{name}`.\n * Either a stdio child process spec or a remote URL spec.\n */\ntype McpServerConfig = StdioServerConfig | RemoteServerConfig;\ninterface StdioServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\ninterface RemoteServerConfig {\n url: string;\n transport?: 'sse' | 'streamable-http';\n headers?: Record<string, string>;\n connectionTimeoutMs?: number;\n}\ntype McpTransportKind = 'stdio' | 'sse' | 'streamable-http';\n/**\n * One tool surfaced from one MCP server, after namespacing.\n */\ninterface McpToolDescriptor {\n /** Namespaced name as the LLM sees it: `{serverName}__{originalName}`, sanitized + max 64 chars. */\n prefixed: string;\n /** Server name (key in `mcp.servers.*`). */\n server: string;\n /** Original tool name as advertised by the MCP server. */\n original: string;\n /** Human label for the tool (truncated description, suitable for UI). */\n label: string;\n /** Full description from the MCP server. */\n description: string;\n /** JSON Schema (object) for tool parameters. */\n parameters: Record<string, unknown>;\n}\ninterface ReconcileDiff {\n added: string[];\n removed: string[];\n changed: string[];\n unchanged: string[];\n}\ninterface Logger {\n debug: (msg: string, meta?: Record<string, unknown>) => void;\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n}\ninterface McpToolCallResult {\n content: Record<string, unknown>[];\n isError?: boolean;\n}\ninterface BundlerOptions {\n logger?: Logger;\n /** Idle TTL for spawned children (ms). 0 disables. Default: 600_000 (10 min). */\n idleTtlMs?: number;\n /** Sweep interval for idle reaping (ms). Default: 60_000 (1 min). */\n idleSweepIntervalMs?: number;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/connection.d.ts\n/** Env keys OpenClaw rejects from stdio MCP env blocks. Filter them out before spawning. */\ndeclare const STDIO_ENV_DENYLIST: Set<string>;\ndeclare function sanitizeStdioEnv(env: Record<string, string> | undefined): Record<string, string>;\ninterface ConnectionDeps {\n /**\n * Factory for an MCP Client connected to the given config. Injected so tests\n * can mock without spawning real processes. In production this wraps\n * `@modelcontextprotocol/sdk/client`.\n */\n connect: (server: McpServerConfig) => Promise<McpClientHandle>;\n}\n/**\n * Minimal interface our connection layer needs from an MCP client. Mirrors\n * the @modelcontextprotocol/sdk Client surface but kept narrow so we can\n * mock cleanly in tests.\n */\ninterface McpClientHandle {\n listTools(): Promise<{\n name: string;\n description?: string;\n inputSchema: Record<string, unknown>;\n }[]>;\n callTool(name: string, args: unknown, opts?: {\n signal?: AbortSignal;\n }): Promise<McpToolCallResult>;\n close(): Promise<void>;\n}\n/**\n * One Connection per MCP server. Owns lifecycle (lazy-spawn, close, refresh-lock).\n * Refresh-lock pattern adapted from AIWerk `index.ts:219-250` — prevents\n * reconnect + `notifications/tools/list_changed` race.\n */\ndeclare class Connection {\n readonly name: string;\n readonly config: McpServerConfig;\n private readonly deps;\n private readonly logger;\n private client;\n private tools;\n private connectInFlight;\n private refreshInFlight;\n private refreshQueued;\n private lastUsedAt;\n constructor(params: {\n name: string;\n config: McpServerConfig;\n deps: ConnectionDeps;\n logger?: Logger;\n });\n /** Returns the most recent known tool list. May be empty if the server hasn't connected yet. */\n snapshotTools(): McpToolDescriptor[];\n /** Whether an MCP child process / remote connection has been established. */\n isConnected(): boolean;\n /** Idle timestamp for reaping. */\n idleSinceMs(): number;\n /**\n * Lazy connect + tool discovery. Safe to call concurrently; in-flight\n * connects coalesce.\n */\n ensureConnected(): Promise<void>;\n private connectAndDiscover;\n /**\n * Re-discover tools. Used on reconnect or `tools/list_changed` notification.\n * Refresh-lock collapses concurrent refreshes; if one is in flight, the next\n * is queued (max 1 queued, since N>1 queued provides no extra freshness).\n */\n refresh(): Promise<void>;\n callTool(originalName: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Close the underlying transport. Idempotent. If a connect is in flight\n * (warmup racing with reconcile-removal), wait for it to settle and then\n * close the client it produced — otherwise the child process is orphaned.\n */\n close(): Promise<void>;\n /**\n * Stable hash of the config for diff detection in `reconcile`.\n * Two configs with the same hash are equivalent (no restart needed).\n */\n configFingerprint(): string;\n}\n/**\n * Build the production `connect` factory using the official MCP SDK.\n * Kept in a separate function so tests can substitute a mock without\n * pulling the SDK into the test bundle.\n */\ndeclare function defaultConnect(server: McpServerConfig): Promise<McpClientHandle>;\n//# sourceMappingURL=connection.d.ts.map\n//#endregion\n//#region src/bundler.d.ts\n/**\n * Provider-agnostic MCP server bundler. Holds N MCP server connections,\n * exposes a unified namespaced tool catalog, and routes calls to the right\n * server.\n *\n * Designed to be embedded in any host (OpenClaw plugin, AI proxy, Lambda).\n * Public surface is intentionally synchronous where the host needs sync\n * (snapshot, listTools), async only where I/O is unavoidable.\n */\ndeclare class McpBundler {\n private readonly logger;\n private readonly connections;\n private readonly idleTtlMs;\n private readonly idleSweepIntervalMs;\n private idleSweepTimer;\n private readonly deps;\n private disposed;\n private reconcileLatch;\n constructor(opts?: BundlerOptions, deps?: ConnectionDeps);\n /**\n * Diff `desired` against current connections, spawn newcomers, dispose\n * removals, hot-restart on config change. Pull-based — call whenever the\n * host's config snapshot may have changed. Cheap if no diff.\n *\n * Lazy: newly-added servers are NOT eagerly connected; they connect on the\n * first `callTool()` (or first `listTools()` after `forceDiscover()`).\n * This avoids paying spawn cost for servers the agent never uses.\n */\n reconcile(desired: Record<string, McpServerConfig>): Promise<ReconcileDiff>;\n private doReconcile;\n /**\n * Synchronous snapshot of all currently-known tools across connected servers.\n * Servers that have not connected yet contribute nothing. Intended for use\n * inside OpenClaw's plugin tool factory which must be sync.\n *\n * Tool names are namespaced and disambiguated (suffix `-2`, `-3` on collision)\n * so cross-server name clashes never produce duplicate registrations.\n */\n listTools(): McpToolDescriptor[];\n /**\n * Eagerly connect to every configured server and discover tools. Used by\n * hosts that want a hot list rather than the lazy default. Errors are\n * swallowed per-server (logged), so one bad server doesn't fail the batch.\n */\n warmup(): Promise<void>;\n /**\n * Invoke a tool by its namespaced name. Routes to the originating server.\n * Errors are returned as `{ isError: true, content: [...] }` so a failing\n * tool doesn't crash the host.\n */\n callTool(prefixed: string, args: unknown, signal?: AbortSignal): Promise<McpToolCallResult>;\n /**\n * Resolve a namespaced tool name back to its server connection and original\n * tool name. Returns undefined if the tool is not currently advertised.\n */\n private routeToolName;\n /**\n * Tear down all connections and stop background tasks. Idempotent.\n * Call from `registerRuntimeLifecycle({ cleanup })` in the host plugin.\n */\n dispose(): Promise<void>;\n private startIdleSweep;\n private sweepIdle;\n}\n//# sourceMappingURL=bundler.d.ts.map\n//#endregion\n//#region src/tool-naming.d.ts\n/**\n * Tool name sanitization and collision handling.\n *\n * OpenClaw constraint: tool names must match `[A-Za-z0-9_-]` and be ≤64 chars.\n * Pattern mirrored from `openclaw/src/agents/pi-bundle-mcp-names.ts`.\n *\n * Strategy: prefix every tool with its server name (`{server}__{tool}`),\n * sanitize disallowed chars to `_`, truncate, then suffix-disambiguate\n * (`-2`, `-3`, ...) on collision.\n */\ndeclare function sanitizeNameSegment(value: string): string;\ndeclare function buildNamespacedToolName(server: string, tool: string): string;\n/**\n * Disambiguate a candidate name against an existing set by appending `-2`, `-3`, etc.\n * Mutates nothing; returns the chosen name. Caller is responsible for inserting it\n * into the set.\n */\ndeclare function disambiguateAgainst(candidate: string, taken: ReadonlySet<string>): string;\n//# sourceMappingURL=tool-naming.d.ts.map\n\n//#endregion\nexport { type BundlerOptions, Connection, type ConnectionDeps, type Logger, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type StdioServerConfig, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;KAKKA,eAAAA,GAAkBC,iBAAsC,GAAlBC,kBAAkB;AAAA,UACnDD,iBAAAA,CAAiB;EAGb,OAGJC,EAAAA,MAAAA;;QAHFC;;ACGwF;AAUhF,UDVND,kBAAAA,CCiBc;EAAA,GAAA,EAAA,MAAA;WACgC,CAAA,EAAA,KAAA,GAAA,iBAAA;SACrB,CAAA,EDhBvBC,MCgBuB,CAAA,MAAA,EAAA,MAAA,CAAA;qBAAf,CAAA,EAAA,MAAA;;;;;AATJ,UAAN,MAAA,CAOA;EAAc,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACgC,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACrB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAAf,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAM,UAFhB,cAAA,CAKe;EAAA,MAAA,CAAA,EAAA;IACd,QAAA,CAAA,EAAA;MACC,KAAA,CAAA,EAAA,MAAA;MAAM,WAAA,CAAA,EANsC,MAMtC,CAAA,MAAA,EAAA,OAAA,CAAA;IAGR,CAAA;EAAS,CAAA;KAIL,CAAA,EAAA;IAID,OAAA,CAAA,EAhBO,MAgBP,CAAA,MAAA,EAhBsB,eAgBtB,CAAA;IAEE,gBAAA,CAAA,EAAA,MAAA;;;AAAD,UAfJ,eAAA,CAkBkB;EAAA,OAAA,EAjBjB,MAiBiB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SACjB,CAAA,EAjBC,MAiBD,CAAA,MAAA,EAAA,OAAA,CAAA;;UAdD,SAAA,CAgBiB;EAAc,IAAA,EAAA,MAAA;EAG/B,KAAA,CAAA,EAAA,MAAA;EAAqB,WAAA,EAAA,MAAA;YAEI,EAjBrB,MAiBqB,CAAA,MAAA,EAAA,OAAA,CAAA;SACA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAdtB,WAcsB,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAZ5B,OAY4B,CAZpB,eAYoB,CAAA;;AAAO,UAThC,kBAAA,CAYiB;EAAA,MAAA,CAAA,EAXhB,cAWgB;eACjB,CAAA,EAXQ,cAWR;kBACC,CAAA,EAAA,GAAA,GAXgB,cAWhB,GAAA,SAAA;;UARD,qBAAA,CAaA;MAAmB,MAAA;OAAuB,EAAA,CAAA,GAAA,CAAA,EAAA,OAAA,EAAA,GAAA,IAAA,GAXjB,OAWiB,CAAA,IAAA,CAAA;MAAY,CAAA,EAAA,CAAA,GAAA,CAAA,EAAA,OAAA,EAAA,GAAA,IAAA,GAV7B,OAU6B,CAAA,IAAA,CAAA;;UAPtD,iBAAA,CAUyC;EAqD7C,MAAA,EA9DI,MAiKT;EAAA,MAAA,CAAA,EAhKU,cAgKV;cAnFe,CAAA,EAAA;IA+EE,QAAA,CAAA,EAAA,OAAA;EAAiB,CAAA;;;;sBAzJM;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;8BAGlC;;cAqDxB;;;;;;;;gBAgBU;kBA+EE"}
package/dist/plugin2.cjs CHANGED
@@ -111,51 +111,61 @@ const plugin = {
111
111
  log.info("[mcp-bundler] disabled via plugin config — no-op");
112
112
  return;
113
113
  }
114
- if (api.registrationMode && api.registrationMode !== "full" && api.registrationMode !== "discovery" && api.registrationMode !== "tool-discovery") {
115
- g[ACTIVATED_KEY] = false;
116
- log.info(`[mcp-bundler] skipped — registrationMode=${api.registrationMode}`);
117
- return;
118
- }
119
- const initialCfg = resolveLiveConfig(api);
120
- if (detectNativeBundleMcp(initialCfg)) {
114
+ if (detectNativeBundleMcp(resolveLiveConfig(api))) {
121
115
  g[ACTIVATED_KEY] = false;
122
116
  log.info("[mcp-bundler] native bundle-mcp detected on this backend (claude-cli/codex-cli) — standing by as no-op to avoid double-registered tools");
123
117
  return;
124
118
  }
125
- bundler = new _alfe_ai_mcp_bundler.McpBundler({
126
- logger: log,
127
- idleTtlMs: initialCfg?.mcp?.sessionIdleTtlMs ?? 600 * 1e3
128
- });
129
- const initialServers = initialCfg?.mcp?.servers ?? {};
130
- bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err) => {
131
- log.warn("[mcp-bundler] initial reconcile/warmup failed", { err: err instanceof Error ? err.message : String(err) });
132
- });
133
- lastFingerprint = fingerprintServers(initialServers);
134
- api.registerTool((ctx) => {
135
- const live = resolveLiveConfig(api, ctx);
136
- if (!live || detectNativeBundleMcp(live)) return null;
137
- const servers = live.mcp?.servers ?? {};
138
- const fp = fingerprintServers(servers);
139
- if (fp !== lastFingerprint) {
140
- lastFingerprint = fp;
141
- bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err) => {
142
- log.warn("[mcp-bundler] reconcile failed", { err: err instanceof Error ? err.message : String(err) });
119
+ const startBundler = () => {
120
+ const cfg = resolveLiveConfig(api);
121
+ bundler = new _alfe_ai_mcp_bundler.McpBundler({
122
+ logger: log,
123
+ idleTtlMs: cfg?.mcp?.sessionIdleTtlMs ?? 600 * 1e3
124
+ });
125
+ const initialServers = cfg?.mcp?.servers ?? {};
126
+ bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err) => {
127
+ log.warn("[mcp-bundler] initial reconcile/warmup failed", { err: err instanceof Error ? err.message : String(err) });
128
+ });
129
+ lastFingerprint = fingerprintServers(initialServers);
130
+ api.registerTool((ctx) => {
131
+ const live = resolveLiveConfig(api, ctx);
132
+ if (!live || detectNativeBundleMcp(live)) return null;
133
+ const servers = live.mcp?.servers ?? {};
134
+ const fp = fingerprintServers(servers);
135
+ if (fp !== lastFingerprint) {
136
+ lastFingerprint = fp;
137
+ bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err) => {
138
+ log.warn("[mcp-bundler] reconcile failed", { err: err instanceof Error ? err.message : String(err) });
139
+ });
140
+ }
141
+ return (bundler?.listTools() ?? []).map((t) => toAgentTool(api, t));
142
+ }, { optional: true });
143
+ log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);
144
+ };
145
+ const stopBundler = async () => {
146
+ log.info("[mcp-bundler] stopping");
147
+ if (bundler) {
148
+ await bundler.dispose().catch((err) => {
149
+ log.warn("[mcp-bundler] dispose error", { err: err instanceof Error ? err.message : String(err) });
143
150
  });
151
+ bundler = void 0;
152
+ }
153
+ lastFingerprint = "";
154
+ };
155
+ if (api.registerService) api.registerService({
156
+ id: "alfe-mcp-bundler",
157
+ start: () => {
158
+ startBundler();
159
+ },
160
+ stop: async () => {
161
+ await stopBundler();
144
162
  }
145
- return (bundler?.listTools() ?? []).map((t) => toAgentTool(api, t));
146
- }, { optional: true });
147
- log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);
163
+ });
164
+ else log.warn("[mcp-bundler] api.registerService unavailable — plugin is a no-op on this runtime");
148
165
  },
149
166
  deactivate(api) {
150
167
  g[ACTIVATED_KEY] = false;
151
- api.logger.info("[mcp-bundler] deactivating");
152
- if (bundler) {
153
- bundler.dispose().catch((err) => {
154
- api.logger.warn("[mcp-bundler] dispose error", { err: err instanceof Error ? err.message : String(err) });
155
- });
156
- bundler = void 0;
157
- }
158
- lastFingerprint = "";
168
+ api.logger.debug("[mcp-bundler] deactivating");
159
169
  }
160
170
  };
161
171
  //#endregion
package/dist/plugin2.js CHANGED
@@ -111,51 +111,61 @@ const plugin = {
111
111
  log.info("[mcp-bundler] disabled via plugin config — no-op");
112
112
  return;
113
113
  }
114
- if (api.registrationMode && api.registrationMode !== "full" && api.registrationMode !== "discovery" && api.registrationMode !== "tool-discovery") {
115
- g[ACTIVATED_KEY] = false;
116
- log.info(`[mcp-bundler] skipped — registrationMode=${api.registrationMode}`);
117
- return;
118
- }
119
- const initialCfg = resolveLiveConfig(api);
120
- if (detectNativeBundleMcp(initialCfg)) {
114
+ if (detectNativeBundleMcp(resolveLiveConfig(api))) {
121
115
  g[ACTIVATED_KEY] = false;
122
116
  log.info("[mcp-bundler] native bundle-mcp detected on this backend (claude-cli/codex-cli) — standing by as no-op to avoid double-registered tools");
123
117
  return;
124
118
  }
125
- bundler = new McpBundler({
126
- logger: log,
127
- idleTtlMs: initialCfg?.mcp?.sessionIdleTtlMs ?? 600 * 1e3
128
- });
129
- const initialServers = initialCfg?.mcp?.servers ?? {};
130
- bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err) => {
131
- log.warn("[mcp-bundler] initial reconcile/warmup failed", { err: err instanceof Error ? err.message : String(err) });
132
- });
133
- lastFingerprint = fingerprintServers(initialServers);
134
- api.registerTool((ctx) => {
135
- const live = resolveLiveConfig(api, ctx);
136
- if (!live || detectNativeBundleMcp(live)) return null;
137
- const servers = live.mcp?.servers ?? {};
138
- const fp = fingerprintServers(servers);
139
- if (fp !== lastFingerprint) {
140
- lastFingerprint = fp;
141
- bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err) => {
142
- log.warn("[mcp-bundler] reconcile failed", { err: err instanceof Error ? err.message : String(err) });
119
+ const startBundler = () => {
120
+ const cfg = resolveLiveConfig(api);
121
+ bundler = new McpBundler({
122
+ logger: log,
123
+ idleTtlMs: cfg?.mcp?.sessionIdleTtlMs ?? 600 * 1e3
124
+ });
125
+ const initialServers = cfg?.mcp?.servers ?? {};
126
+ bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err) => {
127
+ log.warn("[mcp-bundler] initial reconcile/warmup failed", { err: err instanceof Error ? err.message : String(err) });
128
+ });
129
+ lastFingerprint = fingerprintServers(initialServers);
130
+ api.registerTool((ctx) => {
131
+ const live = resolveLiveConfig(api, ctx);
132
+ if (!live || detectNativeBundleMcp(live)) return null;
133
+ const servers = live.mcp?.servers ?? {};
134
+ const fp = fingerprintServers(servers);
135
+ if (fp !== lastFingerprint) {
136
+ lastFingerprint = fp;
137
+ bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err) => {
138
+ log.warn("[mcp-bundler] reconcile failed", { err: err instanceof Error ? err.message : String(err) });
139
+ });
140
+ }
141
+ return (bundler?.listTools() ?? []).map((t) => toAgentTool(api, t));
142
+ }, { optional: true });
143
+ log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);
144
+ };
145
+ const stopBundler = async () => {
146
+ log.info("[mcp-bundler] stopping");
147
+ if (bundler) {
148
+ await bundler.dispose().catch((err) => {
149
+ log.warn("[mcp-bundler] dispose error", { err: err instanceof Error ? err.message : String(err) });
143
150
  });
151
+ bundler = void 0;
152
+ }
153
+ lastFingerprint = "";
154
+ };
155
+ if (api.registerService) api.registerService({
156
+ id: "alfe-mcp-bundler",
157
+ start: () => {
158
+ startBundler();
159
+ },
160
+ stop: async () => {
161
+ await stopBundler();
144
162
  }
145
- return (bundler?.listTools() ?? []).map((t) => toAgentTool(api, t));
146
- }, { optional: true });
147
- log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);
163
+ });
164
+ else log.warn("[mcp-bundler] api.registerService unavailable — plugin is a no-op on this runtime");
148
165
  },
149
166
  deactivate(api) {
150
167
  g[ACTIVATED_KEY] = false;
151
- api.logger.info("[mcp-bundler] deactivating");
152
- if (bundler) {
153
- bundler.dispose().catch((err) => {
154
- api.logger.warn("[mcp-bundler] dispose error", { err: err instanceof Error ? err.message : String(err) });
155
- });
156
- bundler = void 0;
157
- }
158
- lastFingerprint = "";
168
+ api.logger.debug("[mcp-bundler] deactivating");
159
169
  }
160
170
  };
161
171
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"plugin2.js","names":[],"sources":["../src/cli-backend-detect.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Determine whether the active agent's CLI backend already has native bundle-MCP\n * support. If so, our plugin should no-op so we don't double-register tools.\n *\n * As of OpenClaw 2026.4 only `claude-cli` and `codex-cli` ship with\n * `bundleMcp: true`. We hardcode that allowlist for now; future backends\n * shipping with bundleMcp:true will need to be added here (or, ideally,\n * OpenClaw exposes a public API to query the resolved backend's bundleMcp\n * flag and we call that instead — see project_openclaw_mcp_bundler.md TODO).\n *\n * This detection is best-effort: if we can't determine the backend (no\n * runtime config available), we default to \"no native MCP\" (return false)\n * so our plugin DOES register. Worst case is benign duplicate tool names\n * with `-2` suffix on claude-cli, which is recoverable; missing tools on\n * MiniMax is the bug we exist to fix.\n */\n\nconst NATIVE_BUNDLE_MCP_BACKENDS = new Set(['claude-cli', 'codex-cli']);\n\ninterface ConfigLike {\n agents?: {\n defaults?: {\n model?: string;\n cliBackends?: Record<string, unknown>;\n };\n };\n}\n\n/**\n * Read the active agent's primary model and infer its CLI backend.\n * Returns true if that backend has native bundleMcp support.\n *\n * Heuristic mapping (matches what OpenClaw's plugin-setup-cli-backend\n * registry resolves to today):\n * - model id containing \"claude\" or \"anthropic\" → claude-cli\n * - model id containing \"codex\" or \"gpt-5-codex\" or \"openai/codex\" → codex-cli\n * - everything else → no native bundle-mcp\n *\n * If `agents.defaults.cliBackends` explicitly configures one of the native\n * backends, we trust that override.\n */\nexport function detectNativeBundleMcp(cfg: ConfigLike | undefined): boolean {\n if (!cfg) return false;\n const defaults = cfg.agents?.defaults;\n if (!defaults) return false;\n\n // Explicit per-backend config overrides the heuristic.\n const configured = defaults.cliBackends ?? {};\n for (const key of Object.keys(configured)) {\n if (NATIVE_BUNDLE_MCP_BACKENDS.has(key)) return true;\n }\n\n // Otherwise infer from model id.\n const model = (defaults.model ?? '').toLowerCase();\n if (!model) return false;\n if (model.includes('claude') || model.includes('anthropic')) return true;\n if (model.includes('codex') || model.startsWith('gpt-5-codex')) return true;\n return false;\n}\n","/**\n * @alfe.ai/openclaw-mcp-bundler — OpenClaw plugin entry\n *\n * Reads `cfg.mcp.servers` from openclaw.json and surfaces each MCP server's\n * tools to the agent via `api.registerTool(factory)`. This works for ANY\n * LLM backend, closing the gap that OpenClaw's native `bundleMcp:true`\n * leaves open (only claude-cli/codex-cli have it shipped today).\n *\n * On `claude-cli`/`codex-cli` agents the plugin detects the native wiring\n * and no-ops, avoiding double-registered tools.\n */\n\nimport { McpBundler, type McpServerConfig, type McpToolDescriptor } from '@alfe.ai/mcp-bundler';\nimport { detectNativeBundleMcp } from './cli-backend-detect.js';\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// ── Type shims for the OpenClaw plugin SDK ──────────────────────\n// We avoid a hard dep on the SDK to keep this plugin loadable across\n// OpenClaw versions; the surface used here is stable.\n\ninterface Logger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\ninterface ConfigSnapshot {\n agents?: { defaults?: { model?: string; cliBackends?: Record<string, unknown> } };\n mcp?: { servers?: Record<string, McpServerConfig>; sessionIdleTtlMs?: number };\n}\n\ninterface AgentToolResult {\n content: Record<string, unknown>[];\n details?: Record<string, unknown>;\n}\n\ninterface AgentTool {\n name: string;\n label?: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n toolCallId: string,\n params: unknown,\n signal?: AbortSignal,\n onUpdate?: (update: unknown) => void,\n ) => Promise<AgentToolResult>;\n}\n\ninterface ToolFactoryContext {\n config?: ConfigSnapshot;\n runtimeConfig?: ConfigSnapshot;\n getRuntimeConfig?: () => ConfigSnapshot | undefined;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n config?: ConfigSnapshot;\n pluginConfig?: { disabled?: boolean };\n registrationMode?: string;\n runtime?: { config?: { current?: () => ConfigSnapshot | undefined } };\n registerTool: (\n tool: AgentTool | ((ctx: ToolFactoryContext) => AgentTool | AgentTool[] | null | undefined),\n opts?: { name?: string; names?: string[]; optional?: boolean },\n ) => void;\n}\n\n// ── Plugin state ───────────────────────────────────────────────\nlet bundler: McpBundler | undefined;\nlet lastFingerprint = '';\nconst ACTIVATED_KEY = '__alfeMcpBundlerPluginActivated';\nconst g = globalThis as Record<string, unknown>;\n\nfunction resolveLiveConfig(api: OpenClawPluginApi, ctx?: ToolFactoryContext): ConfigSnapshot | undefined {\n // Prefer most-recent → ctx live → ctx snapshot → api.runtime live → api.config snapshot\n return (\n ctx?.runtimeConfig ??\n ctx?.getRuntimeConfig?.() ??\n api.runtime?.config?.current?.() ??\n ctx?.config ??\n api.config\n );\n}\n\nfunction fingerprintServers(servers: Record<string, McpServerConfig>): string {\n const keys = Object.keys(servers).sort();\n return JSON.stringify(keys.map((k) => [k, servers[k]]));\n}\n\nfunction toAgentTool(api: OpenClawPluginApi, tool: McpToolDescriptor): AgentTool {\n return {\n name: tool.prefixed,\n label: tool.label,\n description: tool.description || `MCP tool ${tool.original} from server ${tool.server}`,\n // OpenClaw expects a JSON-Schema-shaped object. The MCP server already\n // gives us one in `tool.parameters`. Pass through unchanged.\n parameters: tool.parameters,\n async execute(_toolCallId, params, signal) {\n if (!bundler) {\n return errorResult(`mcp-bundler not initialized for tool ${tool.prefixed}`);\n }\n const result = await bundler.callTool(tool.prefixed, params, signal);\n return {\n content: result.content,\n details: { isError: result.isError ?? false },\n };\n },\n };\n}\n\nfunction errorResult(message: string): AgentToolResult {\n return {\n content: [{ type: 'text', text: message }],\n details: { isError: true },\n };\n}\n\nconst plugin = {\n id: '@alfe.ai/openclaw-mcp-bundler',\n name: 'Alfe MCP Bundler',\n description: 'Surfaces mcp.servers tools to the agent for any LLM backend',\n version: pkg.version,\n contracts: {\n // Tool name set is dynamic. We declare a wildcard via the only stable\n // identifier we can: the plugin id. OpenClaw's manifest registry uses\n // contracts.tools as a static allowlist; for dynamic tool sets we declare\n // a sentinel here and the runtime allows any tool name registered at\n // runtime to pass through. (If OpenClaw enforces strict contracts in a\n // future version, we'd need to switch to a router-tool pattern: register\n // one fixed `mcp_call` tool that accepts {server, tool, args} dynamically.)\n tools: ['mcp__*'],\n },\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n if (g[ACTIVATED_KEY] === true) {\n log.debug('[mcp-bundler] already activated — skipping duplicate');\n return;\n }\n g[ACTIVATED_KEY] = true;\n\n if (api.pluginConfig?.disabled) {\n g[ACTIVATED_KEY] = false;\n log.info('[mcp-bundler] disabled via plugin config — no-op');\n return;\n }\n\n // Setup-only and metadata-only registration modes shouldn't spawn anything.\n if (api.registrationMode && api.registrationMode !== 'full' && api.registrationMode !== 'discovery' && api.registrationMode !== 'tool-discovery') {\n g[ACTIVATED_KEY] = false;\n log.info(`[mcp-bundler] skipped — registrationMode=${api.registrationMode}`);\n return;\n }\n\n const initialCfg = resolveLiveConfig(api);\n if (detectNativeBundleMcp(initialCfg)) {\n g[ACTIVATED_KEY] = false;\n log.info(\n '[mcp-bundler] native bundle-mcp detected on this backend (claude-cli/codex-cli) — standing by as no-op to avoid double-registered tools',\n );\n return;\n }\n\n bundler = new McpBundler({\n logger: log,\n idleTtlMs: initialCfg?.mcp?.sessionIdleTtlMs ?? 10 * 60 * 1000,\n });\n\n // Initial reconcile in the background so the first factory call has tools\n // ready (it'll be empty if reconcile hasn't completed yet, which is fine —\n // the next agent turn picks them up).\n const initialServers = initialCfg?.mcp?.servers ?? {};\n void bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err: unknown) => {\n log.warn('[mcp-bundler] initial reconcile/warmup failed', { err: err instanceof Error ? err.message : String(err) });\n });\n lastFingerprint = fingerprintServers(initialServers);\n\n api.registerTool((ctx: ToolFactoryContext) => {\n const live = resolveLiveConfig(api, ctx);\n if (!live || detectNativeBundleMcp(live)) return null;\n const servers = live.mcp?.servers ?? {};\n\n // Detect config drift since last reconcile and trigger a fire-and-forget\n // reconcile. OpenClaw caches the factory output keyed on config snapshot,\n // so this only runs when MCP config actually changed.\n const fp = fingerprintServers(servers);\n if (fp !== lastFingerprint) {\n lastFingerprint = fp;\n void bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err: unknown) => {\n log.warn('[mcp-bundler] reconcile failed', { err: err instanceof Error ? err.message : String(err) });\n });\n }\n\n const tools = bundler?.listTools() ?? [];\n return tools.map((t) => toAgentTool(api, t));\n }, { optional: true });\n\n log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);\n },\n\n deactivate(api: OpenClawPluginApi) {\n g[ACTIVATED_KEY] = false;\n api.logger.info('[mcp-bundler] deactivating');\n if (bundler) {\n void bundler.dispose().catch((err: unknown) => {\n api.logger.warn('[mcp-bundler] dispose error', { err: err instanceof Error ? err.message : String(err) });\n });\n bundler = undefined;\n }\n lastFingerprint = '';\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin2.js","names":[],"sources":["../src/cli-backend-detect.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Determine whether the active agent's CLI backend already has native bundle-MCP\n * support. If so, our plugin should no-op so we don't double-register tools.\n *\n * As of OpenClaw 2026.4 only `claude-cli` and `codex-cli` ship with\n * `bundleMcp: true`. We hardcode that allowlist for now; future backends\n * shipping with bundleMcp:true will need to be added here (or, ideally,\n * OpenClaw exposes a public API to query the resolved backend's bundleMcp\n * flag and we call that instead — see project_openclaw_mcp_bundler.md TODO).\n *\n * This detection is best-effort: if we can't determine the backend (no\n * runtime config available), we default to \"no native MCP\" (return false)\n * so our plugin DOES register. Worst case is benign duplicate tool names\n * with `-2` suffix on claude-cli, which is recoverable; missing tools on\n * MiniMax is the bug we exist to fix.\n */\n\nconst NATIVE_BUNDLE_MCP_BACKENDS = new Set(['claude-cli', 'codex-cli']);\n\ninterface ConfigLike {\n agents?: {\n defaults?: {\n model?: string;\n cliBackends?: Record<string, unknown>;\n };\n };\n}\n\n/**\n * Read the active agent's primary model and infer its CLI backend.\n * Returns true if that backend has native bundleMcp support.\n *\n * Heuristic mapping (matches what OpenClaw's plugin-setup-cli-backend\n * registry resolves to today):\n * - model id containing \"claude\" or \"anthropic\" → claude-cli\n * - model id containing \"codex\" or \"gpt-5-codex\" or \"openai/codex\" → codex-cli\n * - everything else → no native bundle-mcp\n *\n * If `agents.defaults.cliBackends` explicitly configures one of the native\n * backends, we trust that override.\n */\nexport function detectNativeBundleMcp(cfg: ConfigLike | undefined): boolean {\n if (!cfg) return false;\n const defaults = cfg.agents?.defaults;\n if (!defaults) return false;\n\n // Explicit per-backend config overrides the heuristic.\n const configured = defaults.cliBackends ?? {};\n for (const key of Object.keys(configured)) {\n if (NATIVE_BUNDLE_MCP_BACKENDS.has(key)) return true;\n }\n\n // Otherwise infer from model id.\n const model = (defaults.model ?? '').toLowerCase();\n if (!model) return false;\n if (model.includes('claude') || model.includes('anthropic')) return true;\n if (model.includes('codex') || model.startsWith('gpt-5-codex')) return true;\n return false;\n}\n","/**\n * @alfe.ai/openclaw-mcp-bundler — OpenClaw plugin entry\n *\n * Reads `cfg.mcp.servers` from openclaw.json and surfaces each MCP server's\n * tools to the agent via `api.registerTool(factory)`. This works for ANY\n * LLM backend, closing the gap that OpenClaw's native `bundleMcp:true`\n * leaves open (only claude-cli/codex-cli have it shipped today).\n *\n * On `claude-cli`/`codex-cli` agents the plugin detects the native wiring\n * and no-ops, avoiding double-registered tools.\n */\n\nimport { McpBundler, type McpServerConfig, type McpToolDescriptor } from '@alfe.ai/mcp-bundler';\nimport { detectNativeBundleMcp } from './cli-backend-detect.js';\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// ── Type shims for the OpenClaw plugin SDK ──────────────────────\n// We avoid a hard dep on the SDK to keep this plugin loadable across\n// OpenClaw versions; the surface used here is stable.\n\ninterface Logger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\ninterface ConfigSnapshot {\n agents?: { defaults?: { model?: string; cliBackends?: Record<string, unknown> } };\n mcp?: { servers?: Record<string, McpServerConfig>; sessionIdleTtlMs?: number };\n}\n\ninterface AgentToolResult {\n content: Record<string, unknown>[];\n details?: Record<string, unknown>;\n}\n\ninterface AgentTool {\n name: string;\n label?: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n toolCallId: string,\n params: unknown,\n signal?: AbortSignal,\n onUpdate?: (update: unknown) => void,\n ) => Promise<AgentToolResult>;\n}\n\ninterface ToolFactoryContext {\n config?: ConfigSnapshot;\n runtimeConfig?: ConfigSnapshot;\n getRuntimeConfig?: () => ConfigSnapshot | undefined;\n}\n\ninterface OpenClawPluginService {\n id: string;\n start: (ctx?: unknown) => void | Promise<void>;\n stop?: (ctx?: unknown) => void | Promise<void>;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n config?: ConfigSnapshot;\n pluginConfig?: { disabled?: boolean };\n registrationMode?: string;\n runtime?: { config?: { current?: () => ConfigSnapshot | undefined } };\n registerTool: (\n tool: AgentTool | ((ctx: ToolFactoryContext) => AgentTool | AgentTool[] | null | undefined),\n opts?: { name?: string; names?: string[]; optional?: boolean },\n ) => void;\n registerService?: (service: OpenClawPluginService) => void;\n}\n\n// ── Plugin state ───────────────────────────────────────────────\nlet bundler: McpBundler | undefined;\nlet lastFingerprint = '';\nconst ACTIVATED_KEY = '__alfeMcpBundlerPluginActivated';\nconst g = globalThis as Record<string, unknown>;\n\nfunction resolveLiveConfig(api: OpenClawPluginApi, ctx?: ToolFactoryContext): ConfigSnapshot | undefined {\n // Prefer most-recent → ctx live → ctx snapshot → api.runtime live → api.config snapshot\n return (\n ctx?.runtimeConfig ??\n ctx?.getRuntimeConfig?.() ??\n api.runtime?.config?.current?.() ??\n ctx?.config ??\n api.config\n );\n}\n\nfunction fingerprintServers(servers: Record<string, McpServerConfig>): string {\n const keys = Object.keys(servers).sort();\n return JSON.stringify(keys.map((k) => [k, servers[k]]));\n}\n\nfunction toAgentTool(api: OpenClawPluginApi, tool: McpToolDescriptor): AgentTool {\n return {\n name: tool.prefixed,\n label: tool.label,\n description: tool.description || `MCP tool ${tool.original} from server ${tool.server}`,\n // OpenClaw expects a JSON-Schema-shaped object. The MCP server already\n // gives us one in `tool.parameters`. Pass through unchanged.\n parameters: tool.parameters,\n async execute(_toolCallId, params, signal) {\n if (!bundler) {\n return errorResult(`mcp-bundler not initialized for tool ${tool.prefixed}`);\n }\n const result = await bundler.callTool(tool.prefixed, params, signal);\n return {\n content: result.content,\n details: { isError: result.isError ?? false },\n };\n },\n };\n}\n\nfunction errorResult(message: string): AgentToolResult {\n return {\n content: [{ type: 'text', text: message }],\n details: { isError: true },\n };\n}\n\nconst plugin = {\n id: '@alfe.ai/openclaw-mcp-bundler',\n name: 'Alfe MCP Bundler',\n description: 'Surfaces mcp.servers tools to the agent for any LLM backend',\n version: pkg.version,\n contracts: {\n // Tool name set is dynamic. We declare a wildcard via the only stable\n // identifier we can: the plugin id. OpenClaw's manifest registry uses\n // contracts.tools as a static allowlist; for dynamic tool sets we declare\n // a sentinel here and the runtime allows any tool name registered at\n // runtime to pass through. (If OpenClaw enforces strict contracts in a\n // future version, we'd need to switch to a router-tool pattern: register\n // one fixed `mcp_call` tool that accepts {server, tool, args} dynamically.)\n tools: ['mcp__*'],\n },\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n if (g[ACTIVATED_KEY] === true) {\n log.debug('[mcp-bundler] already activated — skipping duplicate');\n return;\n }\n g[ACTIVATED_KEY] = true;\n\n if (api.pluginConfig?.disabled) {\n g[ACTIVATED_KEY] = false;\n log.info('[mcp-bundler] disabled via plugin config — no-op');\n return;\n }\n\n const initialCfg = resolveLiveConfig(api);\n if (detectNativeBundleMcp(initialCfg)) {\n g[ACTIVATED_KEY] = false;\n log.info(\n '[mcp-bundler] native bundle-mcp detected on this backend (claude-cli/codex-cli) — standing by as no-op to avoid double-registered tools',\n );\n return;\n }\n\n const startBundler = () => {\n const cfg = resolveLiveConfig(api);\n bundler = new McpBundler({\n logger: log,\n idleTtlMs: cfg?.mcp?.sessionIdleTtlMs ?? 10 * 60 * 1000,\n });\n\n const initialServers = cfg?.mcp?.servers ?? {};\n void bundler.reconcile(initialServers).then(() => bundler?.warmup()).catch((err: unknown) => {\n log.warn('[mcp-bundler] initial reconcile/warmup failed', { err: err instanceof Error ? err.message : String(err) });\n });\n lastFingerprint = fingerprintServers(initialServers);\n\n api.registerTool((ctx: ToolFactoryContext) => {\n const live = resolveLiveConfig(api, ctx);\n if (!live || detectNativeBundleMcp(live)) return null;\n const servers = live.mcp?.servers ?? {};\n\n const fp = fingerprintServers(servers);\n if (fp !== lastFingerprint) {\n lastFingerprint = fp;\n void bundler?.reconcile(servers).then(() => bundler?.warmup()).catch((err: unknown) => {\n log.warn('[mcp-bundler] reconcile failed', { err: err instanceof Error ? err.message : String(err) });\n });\n }\n\n const tools = bundler?.listTools() ?? [];\n return tools.map((t) => toAgentTool(api, t));\n }, { optional: true });\n\n log.info(`[mcp-bundler] activated v${pkg.version}, ${Object.keys(initialServers).length.toString()} server(s) configured`);\n };\n\n const stopBundler = async () => {\n log.info('[mcp-bundler] stopping');\n if (bundler) {\n await bundler.dispose().catch((err: unknown) => {\n log.warn('[mcp-bundler] dispose error', { err: err instanceof Error ? err.message : String(err) });\n });\n bundler = undefined;\n }\n lastFingerprint = '';\n };\n\n if (api.registerService) {\n api.registerService({\n id: 'alfe-mcp-bundler',\n start: () => { startBundler(); },\n stop: async () => { await stopBundler(); },\n });\n } else {\n log.warn('[mcp-bundler] api.registerService unavailable — plugin is a no-op on this runtime');\n }\n },\n\n deactivate(api: OpenClawPluginApi) {\n g[ACTIVATED_KEY] = false;\n api.logger.debug('[mcp-bundler] deactivating');\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-mcp-bundler",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "OpenClaw plugin that bridges mcp.servers config into agent tool list via @alfe.ai/mcp-bundler — works for any LLM backend (not just claude-cli/codex-cli)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,6 +29,14 @@
29
29
  "dependencies": {
30
30
  "@alfe.ai/mcp-bundler": "0.0.1"
31
31
  },
32
+ "peerDependencies": {
33
+ "openclaw": ">=2026.3.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "openclaw": {
37
+ "optional": true
38
+ }
39
+ },
32
40
  "license": "UNLICENSED",
33
41
  "scripts": {
34
42
  "build": "tsdown",