@nwire/mcp 0.9.1 → 0.10.0

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/README.md CHANGED
@@ -1,36 +1,65 @@
1
1
  # @nwire/mcp
2
2
 
3
- > Model Context Protocol server exposes the kernel's CommandRouter + read-only inspect tools + mutating write tools as MCP tools over stdio.
3
+ > Model Context Protocol — Nwire wires as MCP tools.
4
4
 
5
- Wraps the Nwire kernel as a Model Context Protocol server. AI clients
6
- (Claude Desktop, Cursor, IDE plugins) speak JSON-RPC 2.0 over stdio per
7
- the MCP spec; this server routes `tools/list` and `tools/call` to:
5
+ ```bash
6
+ pnpm add @nwire/mcp @nwire/app @nwire/endpoint @nwire/wires
7
+ ```
8
8
 
9
- 1. **Kernel CommandRouter** — same commands the CLI and Studio drive.
10
- 2. **Inspect tools** — read-only introspection over `/__nwire/*` (falls back to `.nwire/` cache).
11
- 3. **Write tools** — mutating actions that POST to `/__nwire/*` on a live wire.
9
+ ## As an adopter
12
10
 
13
- One transport, three surfaces (CLI, Studio, MCP).
11
+ The 0.10 adopter consumes wires whose `binding.$adapter === "mcp"` and
12
+ exposes them as MCP tools — same wire, same handler, served alongside
13
+ HTTP and queue under one endpoint:
14
14
 
15
- ```bash
16
- pnpm add @nwire/mcp
15
+ ```ts
16
+ import { createApp } from "@nwire/app";
17
+ import { endpoint } from "@nwire/endpoint";
18
+ import { tool } from "@nwire/wires/mcp";
19
+ import { httpKoa } from "@nwire/koa";
20
+ import { mcpAdapter } from "@nwire/mcp";
21
+ import { z } from "zod";
22
+
23
+ const app = createApp({ appName: "tasks" });
24
+
25
+ app.wire(
26
+ tool("create-task", {
27
+ description: "Create a task",
28
+ input: z.object({ title: z.string() }),
29
+ }),
30
+ async (input) => ({ id: createId(), title: input.title }),
31
+ );
32
+
33
+ const mcp = mcpAdapter();
34
+ await endpoint("tasks", { port: 3000 })
35
+ .use(httpKoa({ prefix: "/api" }))
36
+ .use(mcp)
37
+ .mount(app)
38
+ .run();
39
+
40
+ // In-process introspection — no stdio server required:
41
+ mcp.list(); // [{ name, description, inputSchema }]
42
+ await mcp.call("create-task", { title: "x" });
17
43
  ```
18
44
 
19
- ## Quick example
45
+ ## Stdio server (legacy serveMcp)
46
+
47
+ The original `serveMcp()` entry — JSON-RPC 2.0 over stdin/stdout, suitable
48
+ for direct Claude Desktop / Cursor / IDE plugin integration — stays
49
+ available:
20
50
 
21
51
  ```ts
22
52
  import { serveMcp } from "@nwire/mcp";
23
- import { createKernel } from "@nwire/kernel";
53
+ import { createKernel } from "./kernel";
24
54
 
25
55
  const kernel = createKernel();
26
56
  kernel.router.register("dev", devHandler);
27
- kernel.router.register("ls", listHandler);
28
57
 
29
58
  await serveMcp({ kernel, serverName: "my-app" });
30
- // Process now speaks MCP over stdin/stdout; stderr is for logs.
59
+ // Process speaks MCP over stdin/stdout; stderr is for logs.
31
60
  ```
32
61
 
33
- Then point Claude Desktop at the binary in `claude_desktop_config.json`:
62
+ Claude Desktop config:
34
63
 
35
64
  ```json
36
65
  { "mcpServers": { "my-app": { "command": "node", "args": ["./mcp.js"] } } }
@@ -38,52 +67,14 @@ Then point Claude Desktop at the binary in `claude_desktop_config.json`:
38
67
 
39
68
  ## Surface
40
69
 
41
- | Export | Role |
42
- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
43
- | `serveMcp({ kernel?, serverName?, serverVersion?, stdin?, stdout? })` | Boot the server on stdio; resolves when stdin closes. |
44
- | `JsonRpcRequest` / `JsonRpcResponse` / `JsonRpcNotification` | JSON-RPC types for callers writing alternative transports (WebSocket, etc.). |
45
-
46
- ### Built-in tools
47
-
48
- Beyond the kernel's CommandRouter, the server exposes these tools:
49
-
50
- **Inspect (read-only)** — proxy `/__nwire/*` on a running wire, fall back to `.nwire/*.json` on disk:
51
-
52
- | Tool | Reads |
53
- | ------------------ | -------------------------------- |
54
- | `manifest` | `.nwire/manifest.json` |
55
- | `list_actions` | `.nwire/actions.json` |
56
- | `list_events` | `.nwire/events.json` |
57
- | `list_hooks` | `.nwire/hooks.json` |
58
- | `list_plugins` | `.nwire/plugins.json` |
59
- | `recent_events` | Live `/__nwire/events/recent` |
60
- | `recent_telemetry` | Live `/__nwire/telemetry/recent` |
61
-
62
- **Write (mutating)** — POST to a live wire:
63
-
64
- | Tool | Posts to |
65
- | ----------------- | -------------------------------------------------------- |
66
- | `dispatch_action` | `/__nwire/dispatch` — fire any registered action. |
67
- | `run_command` | `/__nwire/commands/run` — invoke a `defineCommand`. |
68
- | `replay_trace` | `/__nwire/replay` — replay a recording against the wire. |
69
-
70
- Write tools gracefully gap-flag when the target endpoint isn't mounted
71
- (e.g., MCP running standalone with no wire up).
72
-
73
- ### Progress notifications
74
-
75
- For kernel router commands, the server forwards `command.log` and
76
- `command.progress` events as MCP `notifications/progress` so the AI
77
- client streams output as the command runs. Inspect + write tools are
78
- synchronous and don't notify.
70
+ - `mcpAdapter(config?)` — 0.10 adopter
71
+ - `serveMcp(options)` stdio JSON-RPC server (legacy)
72
+ - `inspectTools`, `findInspectTool` read-only introspection tools
73
+ - `writeTools`, `findWriteTool` mutating tools that drive `/__nwire/*`
79
74
 
80
75
  ## Related
81
76
 
82
- - `@nwire/kernel` — supplies the `CommandRouter` + `CommandHandle` lifecycle.
83
- - `@nwire/cli` — same kernel router exposed as a terminal CLI.
84
- - `@nwire/studio` — same `/__nwire/*` middleware that write/inspect tools target.
85
- - `@nwire/scan` — produces the `.nwire/*.json` files inspect tools fall back to.
86
-
87
- ## Status
88
-
89
- v0.x — `initialize`, `tools/list`, `tools/call` work end-to-end. Resources, prompts, and sampling are sketched but not implemented yet.
77
+ - [`@nwire/wires`](../core-wires) — `tool()` binding helper
78
+ - [`@nwire/endpoint`](../core-endpoint)adopter lifecycle host
79
+ - [`examples/multi-transport`](../../examples/multi-transport)MCP alongside
80
+ HTTP + queue
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  * `serveMcp` switch as TODOs but not yet implemented. The hot path
24
24
  * (AI client invokes an nwire command) works end-to-end.
25
25
  */
26
- import { type Kernel } from "@nwire/kernel";
26
+ import { type Kernel } from "./internal/index.js";
27
27
  interface JsonRpcRequest {
28
28
  readonly jsonrpc: "2.0";
29
29
  readonly id?: string | number | null;
@@ -79,4 +79,4 @@ export type { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification };
79
79
  export { inspectTools, findInspectTool, resetInspectDiscoveryCache } from "./inspect.js";
80
80
  export type { InspectToolDef } from "./inspect.js";
81
81
  export { writeTools, findWriteTool } from "./write-tools.js";
82
- //# sourceMappingURL=index.d.ts.map
82
+ export { mcpAdapter, type McpAdapter, type McpAdapterConfig, type McpToolDescriptor } from "./mcp-adapter.js";
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@
23
23
  * `serveMcp` switch as TODOs but not yet implemented. The hot path
24
24
  * (AI client invokes an nwire command) works end-to-end.
25
25
  */
26
- import { createKernel } from "@nwire/kernel";
26
+ import { createKernel } from "./internal/index.js";
27
27
  import { inspectTools, findInspectTool } from "./inspect.js";
28
28
  import { writeTools, findWriteTool } from "./write-tools.js";
29
29
  const ERR_PARSE = -32700;
@@ -72,6 +72,15 @@ export async function serveMcp(options = {}) {
72
72
  log("stdin closed — server exiting");
73
73
  }
74
74
  async function handle(req, ctx) {
75
+ // JSON-RPC 2.0: a request with no `id` is a notification. Per the spec
76
+ // we must not respond to it. The MCP lifecycle uses
77
+ // `notifications/initialized` (and other `notifications/*` methods)
78
+ // for client-driven signals — these are silently accepted, never
79
+ // answered. Strict MCP clients hang otherwise.
80
+ const isNotification = req.id === undefined || req.method.startsWith("notifications/");
81
+ if (isNotification) {
82
+ return;
83
+ }
75
84
  const id = req.id ?? null;
76
85
  try {
77
86
  switch (req.method) {
@@ -263,4 +272,5 @@ function readLines(stream, onLine) {
263
272
  }
264
273
  export { inspectTools, findInspectTool, resetInspectDiscoveryCache } from "./inspect.js";
265
274
  export { writeTools, findWriteTool } from "./write-tools.js";
266
- //# sourceMappingURL=index.js.map
275
+ // 0.10 adopter shape — consumed by endpoint().use(mcpAdapter()).mount(app).
276
+ export { mcpAdapter } from "./mcp-adapter.js";
package/dist/inspect.d.ts CHANGED
@@ -37,4 +37,3 @@ export interface InspectToolDef {
37
37
  }
38
38
  export declare const inspectTools: readonly InspectToolDef[];
39
39
  export declare function findInspectTool(name: string): InspectToolDef | undefined;
40
- //# sourceMappingURL=inspect.d.ts.map
package/dist/inspect.js CHANGED
@@ -291,4 +291,3 @@ export const inspectTools = [
291
291
  export function findInspectTool(name) {
292
292
  return inspectTools.find((t) => t.name === name);
293
293
  }
294
- //# sourceMappingURL=inspect.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * CommandRouter — one dispatch table for every named kernel command.
3
+ *
4
+ * The CLI registers handlers ("dev", "test", "build", ...). The CLI, Studio,
5
+ * and MCP all *invoke* them via `kernel.run(name, args)`. Every handler
6
+ * receives a CommandContext with helpers that publish through the same
7
+ * EventBus, so the live UI feels identical regardless of which surface
8
+ * fired the command.
9
+ */
10
+ import type { EventBus, KernelEvent } from "./event-bus.js";
11
+ export interface CommandContext {
12
+ readonly commandId: string;
13
+ readonly bus: EventBus;
14
+ readonly signal: AbortSignal;
15
+ log(line: string, stream?: "stdout" | "stderr"): void;
16
+ progress(message: string, pct?: number): void;
17
+ }
18
+ export type CommandHandler<TArgs = unknown, TResult = unknown> = (ctx: CommandContext, args: TArgs) => Promise<TResult>;
19
+ export interface CommandHandle<TResult = unknown> {
20
+ readonly commandId: string;
21
+ readonly name: string;
22
+ readonly promise: Promise<TResult>;
23
+ abort(reason?: string): void;
24
+ on(handler: (event: KernelEvent) => void): () => void;
25
+ }
26
+ /**
27
+ * Holds the named-handler table + creates handles when commands run.
28
+ * Pure routing — execution is the handler's job; this class only wires up
29
+ * the context, lifecycle events, and abort plumbing.
30
+ */
31
+ export declare class CommandRouter {
32
+ private readonly bus;
33
+ private readonly handlers;
34
+ constructor(bus: EventBus);
35
+ register<TArgs, TResult>(name: string, handler: CommandHandler<TArgs, TResult>): void;
36
+ has(name: string): boolean;
37
+ list(): string[];
38
+ run<TArgs = unknown, TResult = unknown>(name: string, args: TArgs): CommandHandle<TResult>;
39
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * CommandRouter — one dispatch table for every named kernel command.
3
+ *
4
+ * The CLI registers handlers ("dev", "test", "build", ...). The CLI, Studio,
5
+ * and MCP all *invoke* them via `kernel.run(name, args)`. Every handler
6
+ * receives a CommandContext with helpers that publish through the same
7
+ * EventBus, so the live UI feels identical regardless of which surface
8
+ * fired the command.
9
+ */
10
+ import { randomUUID } from "node:crypto";
11
+ /**
12
+ * Holds the named-handler table + creates handles when commands run.
13
+ * Pure routing — execution is the handler's job; this class only wires up
14
+ * the context, lifecycle events, and abort plumbing.
15
+ */
16
+ export class CommandRouter {
17
+ bus;
18
+ handlers = new Map();
19
+ constructor(bus) {
20
+ this.bus = bus;
21
+ }
22
+ register(name, handler) {
23
+ if (this.handlers.has(name)) {
24
+ throw new Error(`command "${name}" is already registered`);
25
+ }
26
+ this.handlers.set(name, handler);
27
+ }
28
+ has(name) {
29
+ return this.handlers.has(name);
30
+ }
31
+ list() {
32
+ return [...this.handlers.keys()].sort();
33
+ }
34
+ run(name, args) {
35
+ const handler = this.handlers.get(name);
36
+ if (!handler) {
37
+ throw new Error(`unknown command "${name}"`);
38
+ }
39
+ const commandId = randomUUID();
40
+ const controller = new AbortController();
41
+ const startedAt = Date.now();
42
+ const ctx = {
43
+ commandId,
44
+ bus: this.bus,
45
+ signal: controller.signal,
46
+ log: (line, stream = "stdout") => {
47
+ this.bus.emit({
48
+ kind: "command.log",
49
+ commandId,
50
+ stream,
51
+ line,
52
+ at: new Date().toISOString(),
53
+ });
54
+ },
55
+ progress: (message, pct) => {
56
+ this.bus.emit({
57
+ kind: "command.progress",
58
+ commandId,
59
+ message,
60
+ pct,
61
+ at: new Date().toISOString(),
62
+ });
63
+ },
64
+ };
65
+ this.bus.emit({
66
+ kind: "command.started",
67
+ commandId,
68
+ name,
69
+ args,
70
+ at: new Date(startedAt).toISOString(),
71
+ });
72
+ // Defer the handler so subscribers can attach via `handle.on(...)`
73
+ // before any `ctx.log` / `ctx.progress` fires. Without this, every
74
+ // sync log call inside the handler races the caller's subscription.
75
+ const promise = Promise.resolve()
76
+ .then(() => handler(ctx, args))
77
+ .then((result) => {
78
+ this.bus.emit({
79
+ kind: "command.done",
80
+ commandId,
81
+ result,
82
+ durationMs: Date.now() - startedAt,
83
+ at: new Date().toISOString(),
84
+ });
85
+ return result;
86
+ })
87
+ .catch((err) => {
88
+ const message = err instanceof Error ? err.message : String(err);
89
+ this.bus.emit({
90
+ kind: "command.error",
91
+ commandId,
92
+ message,
93
+ durationMs: Date.now() - startedAt,
94
+ at: new Date().toISOString(),
95
+ });
96
+ throw err;
97
+ });
98
+ return {
99
+ commandId,
100
+ name,
101
+ promise,
102
+ abort: (reason) => controller.abort(reason),
103
+ on: (handler) => {
104
+ return this.bus.onAny((event) => {
105
+ if ("commandId" in event && event.commandId === commandId) {
106
+ handler(event);
107
+ }
108
+ });
109
+ },
110
+ };
111
+ }
112
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * EventBus — typed pub/sub for kernel-level events.
3
+ *
4
+ * Every command and every supervised process emits events here. The CLI
5
+ * renders them in ink, Studio mirrors them over SSE, MCP forwards them to
6
+ * the model. One emitter, many subscribers — that's the whole point.
7
+ */
8
+ import type { LogLine } from "@nwire/supervisor";
9
+ export type KernelEvent = {
10
+ kind: "command.started";
11
+ commandId: string;
12
+ name: string;
13
+ args: unknown;
14
+ at: string;
15
+ } | {
16
+ kind: "command.log";
17
+ commandId: string;
18
+ stream: "stdout" | "stderr";
19
+ line: string;
20
+ at: string;
21
+ } | {
22
+ kind: "command.progress";
23
+ commandId: string;
24
+ message: string;
25
+ pct?: number;
26
+ at: string;
27
+ } | {
28
+ kind: "command.done";
29
+ commandId: string;
30
+ result: unknown;
31
+ durationMs: number;
32
+ at: string;
33
+ } | {
34
+ kind: "command.error";
35
+ commandId: string;
36
+ message: string;
37
+ durationMs: number;
38
+ at: string;
39
+ } | {
40
+ kind: "process.started";
41
+ processId: string;
42
+ topology: string;
43
+ port?: number;
44
+ at: string;
45
+ } | {
46
+ kind: "process.log";
47
+ processId: string;
48
+ line: LogLine;
49
+ } | {
50
+ kind: "process.exited";
51
+ processId: string;
52
+ code: number | null;
53
+ signal: NodeJS.Signals | null;
54
+ at: string;
55
+ };
56
+ export type KernelEventKind = KernelEvent["kind"];
57
+ type EventByKind<K extends KernelEventKind> = Extract<KernelEvent, {
58
+ kind: K;
59
+ }>;
60
+ /**
61
+ * Tiny in-process bus. EventEmitter under the hood; the typed wrappers
62
+ * are the only public surface so consumers can't subscribe to unknown
63
+ * event names.
64
+ */
65
+ export declare class EventBus {
66
+ private readonly emitter;
67
+ constructor();
68
+ emit(event: KernelEvent): void;
69
+ on<K extends KernelEventKind>(kind: K, handler: (event: EventByKind<K>) => void): () => void;
70
+ onAny(handler: (event: KernelEvent) => void): () => void;
71
+ }
72
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * EventBus — typed pub/sub for kernel-level events.
3
+ *
4
+ * Every command and every supervised process emits events here. The CLI
5
+ * renders them in ink, Studio mirrors them over SSE, MCP forwards them to
6
+ * the model. One emitter, many subscribers — that's the whole point.
7
+ */
8
+ import { EventEmitter } from "node:events";
9
+ const WILDCARD = "__any__";
10
+ /**
11
+ * Tiny in-process bus. EventEmitter under the hood; the typed wrappers
12
+ * are the only public surface so consumers can't subscribe to unknown
13
+ * event names.
14
+ */
15
+ export class EventBus {
16
+ emitter = new EventEmitter();
17
+ constructor() {
18
+ // Plenty of headroom — Studio + ink + MCP can each add several listeners.
19
+ this.emitter.setMaxListeners(100);
20
+ }
21
+ emit(event) {
22
+ this.emitter.emit(event.kind, event);
23
+ this.emitter.emit(WILDCARD, event);
24
+ }
25
+ on(kind, handler) {
26
+ const listener = handler;
27
+ this.emitter.on(kind, listener);
28
+ return () => this.emitter.off(kind, listener);
29
+ }
30
+ onAny(handler) {
31
+ this.emitter.on(WILDCARD, handler);
32
+ return () => this.emitter.off(WILDCARD, handler);
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal command-routing substrate for the MCP server.
3
+ *
4
+ * Was `@nwire/app` in 0.9.x — moved in-tree as the kernel package was
5
+ * repurposed for the runtime substrate. MCP keeps its own dispatch table
6
+ * + event bus until the MCP server itself becomes a wire adapter.
7
+ */
8
+ export { createKernel, type Kernel } from "./kernel.js";
9
+ export { CommandRouter, type CommandHandler, type CommandContext, type CommandHandle, } from "./command-router.js";
10
+ export { EventBus, type KernelEvent, type KernelEventKind } from "./event-bus.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal command-routing substrate for the MCP server.
3
+ *
4
+ * Was `@nwire/app` in 0.9.x — moved in-tree as the kernel package was
5
+ * repurposed for the runtime substrate. MCP keeps its own dispatch table
6
+ * + event bus until the MCP server itself becomes a wire adapter.
7
+ */
8
+ export { createKernel } from "./kernel.js";
9
+ export { CommandRouter, } from "./command-router.js";
10
+ export { EventBus } from "./event-bus.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Kernel — composes the three primitives into the one object the CLI,
3
+ * Studio, and MCP all hold.
4
+ *
5
+ * const kernel = createKernel();
6
+ * kernel.router.register("dev", devHandler);
7
+ * kernel.run("dev", { wire: "main" }); // delegates to router
8
+ * kernel.bus.on("command.log", ...); // anyone can listen
9
+ * kernel.supervisor.start(...); // long-lived processes
10
+ *
11
+ * That's the whole surface. New commands plug into the router; new live
12
+ * surfaces plug into the bus; new long-running processes plug into the
13
+ * supervisor.
14
+ */
15
+ import { RunnerSupervisor } from "@nwire/supervisor";
16
+ import { CommandRouter, type CommandHandle } from "./command-router.js";
17
+ import { EventBus } from "./event-bus.js";
18
+ export interface Kernel {
19
+ readonly bus: EventBus;
20
+ readonly router: CommandRouter;
21
+ readonly supervisor: RunnerSupervisor;
22
+ run<TArgs = unknown, TResult = unknown>(name: string, args: TArgs): CommandHandle<TResult>;
23
+ }
24
+ export declare function createKernel(): Kernel;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Kernel — composes the three primitives into the one object the CLI,
3
+ * Studio, and MCP all hold.
4
+ *
5
+ * const kernel = createKernel();
6
+ * kernel.router.register("dev", devHandler);
7
+ * kernel.run("dev", { wire: "main" }); // delegates to router
8
+ * kernel.bus.on("command.log", ...); // anyone can listen
9
+ * kernel.supervisor.start(...); // long-lived processes
10
+ *
11
+ * That's the whole surface. New commands plug into the router; new live
12
+ * surfaces plug into the bus; new long-running processes plug into the
13
+ * supervisor.
14
+ */
15
+ import { RunnerSupervisor } from "@nwire/supervisor";
16
+ import { CommandRouter } from "./command-router.js";
17
+ import { EventBus } from "./event-bus.js";
18
+ export function createKernel() {
19
+ const bus = new EventBus();
20
+ const router = new CommandRouter(bus);
21
+ const supervisor = new RunnerSupervisor();
22
+ // Re-publish supervisor events on the kernel bus so a single subscriber
23
+ // sees the whole picture without listening on two emitters.
24
+ supervisor.on("log", (processId, line) => {
25
+ bus.emit({ kind: "process.log", processId, line });
26
+ });
27
+ supervisor.on("status", (processId, status, proc) => {
28
+ if (status === "running") {
29
+ bus.emit({
30
+ kind: "process.started",
31
+ processId,
32
+ topology: proc.topology,
33
+ port: proc.port,
34
+ at: new Date().toISOString(),
35
+ });
36
+ }
37
+ else if (status === "exited" || status === "crashed") {
38
+ bus.emit({
39
+ kind: "process.exited",
40
+ processId,
41
+ code: proc.exitCode ?? null,
42
+ signal: (proc.signal ?? null),
43
+ at: new Date().toISOString(),
44
+ });
45
+ }
46
+ });
47
+ return {
48
+ bus,
49
+ router,
50
+ supervisor,
51
+ run: (name, args) => router.run(name, args),
52
+ };
53
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `@nwire/mcp` — Adapter shape for 0.10.
3
+ *
4
+ * import { mcpAdapter } from "@nwire/mcp";
5
+ *
6
+ * const mcp = mcpAdapter();
7
+ * await endpoint("svc", { port: 3000 })
8
+ * .use(httpKoa({ prefix: "/api" }))
9
+ * .use(mcp)
10
+ * .mount(app)
11
+ * .run();
12
+ *
13
+ * const tools = mcp.list(); // [{ name, description, input }]
14
+ * const result = await mcp.call("create-task", { title: "x" });
15
+ *
16
+ * Consumes wires whose `binding.$adapter === "mcp"` + `binding.kind === "tool"`.
17
+ * In-process introspection (`list`, `call`) makes the adapter testable without
18
+ * spinning up an MCP stdio server. The legacy `serveMcp(...)` stdio entry
19
+ * stays available via the package's main export.
20
+ */
21
+ import type { Adapter } from "@nwire/endpoint";
22
+ import { type Logger } from "@nwire/logger";
23
+ export interface McpAdapterConfig {
24
+ readonly logger?: Logger;
25
+ }
26
+ export interface McpToolDescriptor {
27
+ readonly name: string;
28
+ readonly description?: string;
29
+ readonly inputSchema?: unknown;
30
+ }
31
+ export interface McpAdapter extends Adapter {
32
+ list(): readonly McpToolDescriptor[];
33
+ call(toolName: string, input: unknown): Promise<unknown>;
34
+ }
35
+ export declare function mcpAdapter(config?: McpAdapterConfig): McpAdapter;