@smthrs/mcp 0.0.0-stage → 1.0.0-rc.3

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -2
  4. package/dist/cjs/Diagnostics.d.ts +46 -0
  5. package/dist/cjs/Diagnostics.d.ts.map +1 -0
  6. package/dist/cjs/Diagnostics.js +29 -0
  7. package/dist/cjs/Diagnostics.js.map +7 -0
  8. package/dist/cjs/McpClient.d.ts +304 -0
  9. package/dist/cjs/McpClient.d.ts.map +1 -0
  10. package/dist/cjs/McpClient.js +622 -0
  11. package/dist/cjs/McpClient.js.map +7 -0
  12. package/dist/cjs/McpError.d.ts +44 -0
  13. package/dist/cjs/McpError.d.ts.map +1 -0
  14. package/dist/cjs/McpError.js +41 -0
  15. package/dist/cjs/McpError.js.map +7 -0
  16. package/dist/cjs/McpFlows.d.ts +112 -0
  17. package/dist/cjs/McpFlows.d.ts.map +1 -0
  18. package/dist/cjs/McpFlows.js +127 -0
  19. package/dist/cjs/McpFlows.js.map +7 -0
  20. package/dist/cjs/index.d.ts +39 -0
  21. package/dist/cjs/index.d.ts.map +1 -0
  22. package/dist/cjs/index.js +41 -0
  23. package/dist/cjs/index.js.map +7 -0
  24. package/dist/cjs/internal/DiagnosticReporter.d.ts +17 -0
  25. package/dist/cjs/internal/DiagnosticReporter.d.ts.map +1 -0
  26. package/dist/cjs/internal/DiagnosticReporter.js +56 -0
  27. package/dist/cjs/internal/DiagnosticReporter.js.map +7 -0
  28. package/dist/cjs/internal/HttpTransport.d.ts +67 -0
  29. package/dist/cjs/internal/HttpTransport.d.ts.map +1 -0
  30. package/dist/cjs/internal/HttpTransport.js +298 -0
  31. package/dist/cjs/internal/HttpTransport.js.map +7 -0
  32. package/dist/cjs/internal/JsonLimits.d.ts +29 -0
  33. package/dist/cjs/internal/JsonLimits.d.ts.map +1 -0
  34. package/dist/cjs/internal/JsonLimits.js +51 -0
  35. package/dist/cjs/internal/JsonLimits.js.map +7 -0
  36. package/dist/cjs/internal/Limits.d.ts +36 -0
  37. package/dist/cjs/internal/Limits.d.ts.map +1 -0
  38. package/dist/cjs/internal/Limits.js +34 -0
  39. package/dist/cjs/internal/Limits.js.map +7 -0
  40. package/dist/cjs/internal/Rpc.d.ts +141 -0
  41. package/dist/cjs/internal/Rpc.d.ts.map +1 -0
  42. package/dist/cjs/internal/Rpc.js +92 -0
  43. package/dist/cjs/internal/Rpc.js.map +7 -0
  44. package/dist/cjs/internal/StdioTransport.d.ts +78 -0
  45. package/dist/cjs/internal/StdioTransport.d.ts.map +1 -0
  46. package/dist/cjs/internal/StdioTransport.js +310 -0
  47. package/dist/cjs/internal/StdioTransport.js.map +7 -0
  48. package/dist/cjs/internal/Transport.d.ts +87 -0
  49. package/dist/cjs/internal/Transport.d.ts.map +1 -0
  50. package/dist/cjs/internal/Transport.js +116 -0
  51. package/dist/cjs/internal/Transport.js.map +7 -0
  52. package/dist/cjs/package.json +1 -0
  53. package/dist/esm/Diagnostics.d.ts +46 -0
  54. package/dist/esm/Diagnostics.d.ts.map +1 -0
  55. package/dist/esm/Diagnostics.js +26 -0
  56. package/dist/esm/Diagnostics.js.map +1 -0
  57. package/dist/esm/McpClient.d.ts +304 -0
  58. package/dist/esm/McpClient.d.ts.map +1 -0
  59. package/dist/esm/McpClient.js +671 -0
  60. package/dist/esm/McpClient.js.map +1 -0
  61. package/dist/esm/McpError.d.ts +44 -0
  62. package/dist/esm/McpError.d.ts.map +1 -0
  63. package/dist/esm/McpError.js +43 -0
  64. package/dist/esm/McpError.js.map +1 -0
  65. package/dist/esm/McpFlows.d.ts +112 -0
  66. package/dist/esm/McpFlows.d.ts.map +1 -0
  67. package/dist/esm/McpFlows.js +167 -0
  68. package/dist/esm/McpFlows.js.map +1 -0
  69. package/dist/esm/index.d.ts +39 -0
  70. package/dist/esm/index.d.ts.map +1 -0
  71. package/dist/esm/index.js +39 -0
  72. package/dist/esm/index.js.map +1 -0
  73. package/dist/esm/internal/DiagnosticReporter.d.ts +17 -0
  74. package/dist/esm/internal/DiagnosticReporter.d.ts.map +1 -0
  75. package/dist/esm/internal/DiagnosticReporter.js +44 -0
  76. package/dist/esm/internal/DiagnosticReporter.js.map +1 -0
  77. package/dist/esm/internal/HttpTransport.d.ts +67 -0
  78. package/dist/esm/internal/HttpTransport.d.ts.map +1 -0
  79. package/dist/esm/internal/HttpTransport.js +266 -0
  80. package/dist/esm/internal/HttpTransport.js.map +1 -0
  81. package/dist/esm/internal/JsonLimits.d.ts +29 -0
  82. package/dist/esm/internal/JsonLimits.d.ts.map +1 -0
  83. package/dist/esm/internal/JsonLimits.js +55 -0
  84. package/dist/esm/internal/JsonLimits.js.map +1 -0
  85. package/dist/esm/internal/Limits.d.ts +36 -0
  86. package/dist/esm/internal/Limits.d.ts.map +1 -0
  87. package/dist/esm/internal/Limits.js +41 -0
  88. package/dist/esm/internal/Limits.js.map +1 -0
  89. package/dist/esm/internal/Rpc.d.ts +141 -0
  90. package/dist/esm/internal/Rpc.d.ts.map +1 -0
  91. package/dist/esm/internal/Rpc.js +129 -0
  92. package/dist/esm/internal/Rpc.js.map +1 -0
  93. package/dist/esm/internal/StdioTransport.d.ts +78 -0
  94. package/dist/esm/internal/StdioTransport.d.ts.map +1 -0
  95. package/dist/esm/internal/StdioTransport.js +332 -0
  96. package/dist/esm/internal/StdioTransport.js.map +1 -0
  97. package/dist/esm/internal/Transport.d.ts +87 -0
  98. package/dist/esm/internal/Transport.d.ts.map +1 -0
  99. package/dist/esm/internal/Transport.js +146 -0
  100. package/dist/esm/internal/Transport.js.map +1 -0
  101. package/docs/README.md +139 -0
  102. package/docs/api.md +469 -0
  103. package/docs/concepts/the-session.md +135 -0
  104. package/docs/concepts/tools-as-flows.md +116 -0
  105. package/docs/guides/bound-an-untrusted-server.md +158 -0
  106. package/docs/guides/configure-servers-for-the-cli.md +167 -0
  107. package/docs/guides/connect-a-server.md +161 -0
  108. package/docs/guides/grant-authority-to-mcp-tools.md +130 -0
  109. package/docs/guides/handle-a-failed-tool-call.md +125 -0
  110. package/docs/guides/select-the-tools-a-run-sees.md +92 -0
  111. package/docs/guides/testing.md +132 -0
  112. package/docs/guides/validate-structured-output.md +103 -0
  113. package/docs/installation.md +117 -0
  114. package/docs/quickstart.md +200 -0
  115. package/docs/troubleshooting.md +316 -0
  116. package/package.json +157 -3
  117. package/src/Diagnostics.ts +47 -0
  118. package/src/McpClient.ts +985 -0
  119. package/src/McpError.ts +52 -0
  120. package/src/McpFlows.ts +210 -0
  121. package/src/index.ts +42 -0
  122. package/src/internal/DiagnosticReporter.ts +47 -0
  123. package/src/internal/HttpTransport.ts +400 -0
  124. package/src/internal/JsonLimits.ts +53 -0
  125. package/src/internal/Limits.ts +48 -0
  126. package/src/internal/Rpc.ts +219 -0
  127. package/src/internal/StdioTransport.ts +491 -0
  128. package/src/internal/Transport.ts +178 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Transport.js","sourceRoot":"","sources":["../../../src/internal/Transport.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The request/notify surface both MCP transports provide, and the failure\n * wording they share. {@link McpClient} speaks to this interface only; stdio\n * and Streamable HTTP differ in how a frame travels, not in what it means.\n *\n * @since 1.0.0-rc.1\n */\n\nimport { Effect, Stream } from \"effect\"\nimport { McpError } from \"../McpError.ts\"\nimport * as Limits from \"./Limits.ts\"\nimport type * as Rpc from \"./Rpc.ts\"\n\n/**\n * One live connection to an MCP server.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Transport {\n /** Sends a request and resolves with its `result`, or fails with the server's `error`. */\n readonly request: (method: string, params?: unknown, timeoutMs?: number) => Effect.Effect<unknown, McpError>\n /** Sends a notification, bounding delivery by the optional positive-integer deadline. */\n readonly notify: (method: string, params?: unknown, timeoutMs?: number) => Effect.Effect<void, McpError>\n}\n\n/**\n * Default request deadline.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const defaultRequestTimeoutMs = 120_000\n\n/**\n * Default maximum inbound JSON-RPC frame size (one MiB).\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const defaultMaxFrameBytes = 1024 * 1024\n\n/**\n * Default maximum outbound JSON-RPC frame size (one MiB).\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const defaultMaxOutboundFrameBytes = 1024 * 1024\n\n/**\n * The reason sent with a best-effort `notifications/cancelled`.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const cancellationReason = \"request no longer awaited\"\n\n/**\n * A `connection_closed` failure naming the server.\n *\n * @category errors\n * @since 1.0.0-rc.1\n */\nexport const closed = (server: string, reason: string): McpError =>\n new McpError({ code: \"connection_closed\", message: `MCP server \"${server}\" ${reason}`, server })\n\n/**\n * A `timeout` failure naming the server, method and deadline.\n *\n * @category errors\n * @since 1.0.0-rc.1\n */\nexport const timeout = (server: string, method: string, timeoutMs: number): McpError =>\n new McpError({\n code: \"timeout\",\n message: `MCP server \"${server}\" did not answer ${method} within ${timeoutMs}ms`,\n server\n })\n\n/**\n * The model-facing failure for a correlated JSON-RPC error reply. Remote text\n * is withheld; the caller reports it to Diagnostics.\n *\n * @category errors\n * @since 1.0.0-rc.1\n */\nexport const replyError = (\n server: string,\n method: string,\n reply: Extract<Rpc.Reply, { readonly _tag: \"Error\" }>\n): McpError => {\n // Servers do not standardize unknown-tool prose, so this heuristic stays\n // limited to the two MCP error codes and an explicit tool plus absence phrase.\n const remoteUnknownTool = (reply.code === -32_601 || reply.code === -32_602) &&\n /\\btool\\b/i.test(reply.message) &&\n /\\b(?:unknown|unrecognized|no such|not found)\\b/i.test(reply.message)\n return new McpError({\n code: method === \"tools/call\"\n ? remoteUnknownTool ? \"tool_not_found\" : \"tool_failed\"\n : \"protocol_error\",\n message: `MCP server \"${server}\" failed ${method} (${reply.code}); remote details withheld`,\n server\n })\n}\n\n/**\n * Splits a byte stream into lines in linear time, retaining one bounded\n * partial line. Lines end at LF, with a CR before the LF dropped; with\n * `crTerminates`, as server-sent events require, a lone CR also ends a line.\n * Blank lines are kept: server-sent events use them as delimiters.\n *\n * @category constructors\n * @since 1.0.0-rc.1\n */\nexport const lines = <E>(\n server: string,\n maxLineBytes: number,\n stream: Stream.Stream<Uint8Array, E>,\n options: { readonly crTerminates?: boolean } = {}\n): Stream.Stream<string, E | McpError> => {\n type PartialLine = { pieces: Array<Uint8Array>; bytes: number; skipLf: boolean }\n const crTerminates = options.crTerminates === true\n const decoder = new TextDecoder()\n const decode = (partial: PartialLine): string => {\n const joined = new Uint8Array(partial.bytes)\n let offset = 0\n for (const piece of partial.pieces) {\n joined.set(piece, offset)\n offset += piece.byteLength\n }\n const end = joined[partial.bytes - 1] === 0x0d ? partial.bytes - 1 : partial.bytes\n return decoder.decode(joined.subarray(0, end))\n }\n const terminator = (chunk: Uint8Array, from: number): number => {\n if (!crTerminates) return chunk.indexOf(0x0a, from)\n for (let index = from; index < chunk.byteLength; index += 1) {\n if (chunk[index] === 0x0a || chunk[index] === 0x0d) return index\n }\n return -1\n }\n const tooLong = () => Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxLineBytes} bytes`))\n return stream.pipe(\n Stream.mapAccumEffect(\n (): PartialLine => ({ pieces: [], bytes: 0, skipLf: false }),\n (partial, chunk) => {\n const complete: Array<string> = []\n const append = (piece: Uint8Array): boolean => {\n if (piece.byteLength === 0) return true\n const bytes = partial.bytes + piece.byteLength\n // A final CR may be the first half of CRLF. Allow that one byte\n // beyond the cap, but count it if more frame content follows.\n const contentBytes = bytes - (piece[piece.byteLength - 1] === 0x0d ? 1 : 0)\n if (contentBytes > maxLineBytes) return false\n partial.pieces.push(piece)\n partial.bytes = bytes\n return true\n }\n // The LF of a CRLF split across chunks ends nothing new.\n let start = partial.skipLf && chunk[0] === 0x0a ? 1 : 0\n partial.skipLf = false\n for (let end = terminator(chunk, start); end !== -1; end = terminator(chunk, start)) {\n if (!append(chunk.subarray(start, end))) return tooLong()\n complete.push(decode(partial))\n partial = { pieces: [], bytes: 0, skipLf: false }\n start = end + 1\n if (chunk[end] === 0x0d) {\n if (start === chunk.byteLength) partial.skipLf = true\n else if (chunk[start] === 0x0a) start += 1\n }\n }\n if (!append(chunk.subarray(start))) return tooLong()\n return Effect.succeed([partial, complete] as const)\n },\n { onHalt: (partial) => partial.bytes === 0 ? [] : [decode(partial)] }\n )\n )\n}\n"]}
package/docs/README.md ADDED
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: "@smthrs/mcp"
3
+ description: "A Model Context Protocol client for Node, and the adapter that projects a remote server's tools into a Smithers run as ordinary flows named mcp/<server>/<tool>."
4
+ ---
5
+
6
+ `@smthrs/mcp` connects to a Model Context Protocol (MCP) server over stdio or
7
+ Streamable HTTP and
8
+ projects the tools that server offers as flows a Smithers agent can call. It is
9
+ two halves: `McpClient`, a small JSON-RPC client covering the `initialize`
10
+ handshake, `tools/list`, and `tools/call`, and `McpFlows`, which turns a
11
+ connected session's catalog into one flow per tool.
12
+
13
+ ## What it solves
14
+
15
+ MCP is how a service publishes tools to an agent: a GitHub server, a database
16
+ server, an internal service somebody on your team wrote. Calling one of those
17
+ from an agent run could have been a second kind of capability, with its own
18
+ dispatch, its own permission rules, and its own error type. It is not. This
19
+ package makes a remote tool an ordinary flow:
20
+
21
+ - Each tool becomes a flow named `mcp/<server>/<tool>`, so two servers may offer
22
+ a tool of the same name without colliding.
23
+ - Each flow carries the server's own JSON Schema as its parameter document, so a
24
+ model reading the catalog sees the real argument shape rather than a
25
+ placeholder.
26
+ - A cell calls it with the two lines it already uses for a filesystem flow: find
27
+ the name in `ctx.flows`, invoke it with `ctx.call`.
28
+
29
+ The client half is deliberately small. Resources, prompts, sampling, and roots
30
+ are not implemented, because a projection needs a tool catalog and a way to call
31
+ one entry of it, and nothing else.
32
+
33
+ ## Install
34
+
35
+ `@smthrs/mcp` is not published to npm yet. Its source is on
36
+ [GitHub](https://github.com/smithersai/smithers).
37
+
38
+ It needs Node.js 26.4.0 or later. Opening a connection requires two services
39
+ from the caller's environment: Effect's `ChildProcessSpawner`, because an MCP
40
+ server is a subprocess, and a `Scope`, because closing the scope tears that
41
+ subprocess down. `@effect/platform-node` supplies the spawner on Node. For the
42
+ version requirements and the import forms, see
43
+ [Installation](./installation.md).
44
+
45
+ ## Connect a server and read its flows
46
+
47
+ `McpFlows.connected` spawns the server, completes the handshake, fetches the
48
+ tool catalog, and returns the projection in one step:
49
+
50
+ ```ts
51
+ import { NodeServices } from "@effect/platform-node"
52
+ import * as McpFlows from "@smthrs/mcp/McpFlows"
53
+ import { Effect } from "effect"
54
+
55
+ const program = Effect.scoped(Effect.gen(function*() {
56
+ const source = yield* McpFlows.connected({
57
+ server: "github",
58
+ // A reviewed server installed at an exact version with --ignore-scripts.
59
+ command: "/path/to/mcp-servers/node_modules/.bin/mcp-server-github",
60
+ args: [],
61
+ env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN },
62
+ include: ["create_issue", "get_issue", "list_issues"]
63
+ })
64
+ const bindings = yield* source.bindings()
65
+ return bindings.map((binding) => binding.descriptor.name)
66
+ }))
67
+
68
+ console.log(await Effect.runPromise(Effect.provide(program, NodeServices.layer)))
69
+ ```
70
+
71
+ ```text
72
+ [ 'mcp/github/create_issue', 'mcp/github/get_issue', 'mcp/github/list_issues' ]
73
+ ```
74
+
75
+ Three details in that program decide how the rest behaves:
76
+
77
+ - `Effect.scoped` owns the subprocess. The session lasts as long as the scope,
78
+ and every request outstanding when it closes fails with `connection_closed`
79
+ rather than hanging.
80
+ - `include` is an exact-name allowlist, checked against the catalog the server
81
+ actually sent, so a typo fails the connection instead of quietly handing the
82
+ model a smaller toolset. Omit it to project every tool.
83
+ - `env` is overlaid on a bootstrap allowlist (`PATH`, `HOME`, `USER`, `LANG`,
84
+ `LC_*`, `TERM`, `TMPDIR`, `SHELL`), not on the full host environment, so a
85
+ server receives only the credentials you declare. Install and pin the server
86
+ before giving it one, as
87
+ [Connect a server](./guides/connect-a-server.md) shows.
88
+
89
+ One step separates that source from a cell that can call it. Every projected
90
+ flow declares the widest authority the capability vocabulary can express,
91
+ because an MCP tool is opaque code this package does not control, and a host
92
+ narrows that declaration to the authority it actually grants the server. The
93
+ recipe is in
94
+ [Grant authority to MCP tools](./guides/grant-authority-to-mcp-tools.md).
95
+
96
+ ## How this relates to the smithers CLI
97
+
98
+ `@smthrs/mcp` is one of the packages behind [`@smthrs/cli`](/api/cli), the
99
+ `smthrs` command line that plans, runs, and inspects Smithers flows. The CLI
100
+ composes this package for you: `smthrs --mcp-config <path>` reads a JSON array
101
+ of server entries, connects every one of them when the executor starts, and adds
102
+ their tools to the run's flow catalog. Each entry in that file is structurally an
103
+ `McpClient.ConnectOptions` with optional `include`, `exclude`, and `namePrefix`
104
+ projection fields. The flag supports filtering and renaming tools per server. See
105
+ [Configure servers for the CLI](./guides/configure-servers-for-the-cli.md).
106
+
107
+ Import the package directly when you embed Smithers in a program of your own, or
108
+ when you need to customize the projected capability declaration. The
109
+ projected `FlowBinding.Source` is the same type the standard flows return, so it
110
+ composes with them in one array; that contract belongs to
111
+ [`@smthrs/harness`](/api/harness).
112
+
113
+ The CLI also hosts the mirror image of this package, under a name close enough
114
+ to confuse.
115
+ `smthrs --mcp-config` is a Smithers run calling somebody else's tools;
116
+ [`smthrs mcp`](/cli/mcp) runs Smithers itself as an MCP server, so an agent such
117
+ as Claude Code can drive a control plane. For that direction, see
118
+ [Wire the MCP server into an agent](/pkg/cli/guides/wire-the-mcp-server).
119
+
120
+ ## Where to go next
121
+
122
+ - [Installation](./installation.md): requirements, the services a connection
123
+ needs, and the import forms.
124
+ - [Quickstart](./quickstart.md): a real server in its own process, two tool
125
+ calls, and the flows they project.
126
+ - Concepts: [a remote tool as a flow](./concepts/tools-as-flows.md) and
127
+ [the life of a session](./concepts/the-session.md).
128
+ - Guides: [connect a server](./guides/connect-a-server.md),
129
+ [select the tools a run sees](./guides/select-the-tools-a-run-sees.md),
130
+ [grant authority to MCP tools](./guides/grant-authority-to-mcp-tools.md),
131
+ [handle a failed tool call](./guides/handle-a-failed-tool-call.md),
132
+ [validate structured output](./guides/validate-structured-output.md),
133
+ [bound an untrusted server](./guides/bound-an-untrusted-server.md),
134
+ [configure servers for the CLI](./guides/configure-servers-for-the-cli.md),
135
+ and [test against a server](./guides/testing.md).
136
+ - [API reference](./api.md): every export of `McpClient`, `McpError`, and
137
+ `McpFlows`.
138
+ - [Troubleshooting](./troubleshooting.md): each failure this package reports,
139
+ found by the message you saw.
package/docs/api.md ADDED
@@ -0,0 +1,469 @@
1
+ ---
2
+ title: "API reference"
3
+ description: "Every public export of @smthrs/mcp: sessions, limits, safe errors, private host diagnostics, and tool flow projections."
4
+ ---
5
+
6
+ `@smthrs/mcp` exports four modules from its root entry point, and each is also
7
+ importable from `@smthrs/mcp/<Module>`:
8
+
9
+ ```ts
10
+ import { Diagnostics, McpClient, McpError, McpFlows } from "@smthrs/mcp"
11
+ // or
12
+ import * as McpClient from "@smthrs/mcp/McpClient"
13
+ ```
14
+
15
+ `@smthrs/mcp/internal/*` and `@smthrs/mcp/*/index` are not public, so the
16
+ JSON-RPC codec and the stdio and HTTP transports are not importable.
17
+ `@smthrs/mcp/package.json` is exported.
18
+
19
+ `McpError` is a namespace under both import forms. The error class is
20
+ `McpError.McpError`.
21
+
22
+ For the flow-binding contract this package implements, see the
23
+ [`@smthrs/harness` reference](/api/harness). For the action vocabulary
24
+ `McpFlows.capabilities` is derived from, see the
25
+ [`@smthrs/capability` reference](/api/capability).
26
+
27
+ ## Example
28
+
29
+ ```ts
30
+ import * as McpFlows from "@smthrs/mcp/McpFlows"
31
+ import { Effect } from "effect"
32
+
33
+ const program = Effect.scoped(Effect.gen(function*() {
34
+ const source = yield* McpFlows.connected({
35
+ server: "github",
36
+ // Installed with --ignore-scripts at an exact version; see the Connect a server guide.
37
+ command: "/path/to/mcp-servers/node_modules/.bin/mcp-server-github",
38
+ args: []
39
+ })
40
+ return yield* source.bindings()
41
+ }))
42
+ ```
43
+
44
+ ## McpClient
45
+
46
+ A minimal MCP client covering the `initialize` handshake, `tools/list`, and
47
+ `tools/call` over stdio or Streamable HTTP. It is deliberately not a general MCP SDK: resources,
48
+ prompts, sampling, and roots are not wired up.
49
+
50
+ ### McpClient.connect
51
+
52
+ ```ts
53
+ const connect: <O extends ConnectOptions>(
54
+ options: O
55
+ ) => Effect.Effect<McpClient, McpError, Requirements<O> | Scope.Scope>
56
+ ```
57
+
58
+ Spawns the server (`command`) or opens a Streamable HTTP session (`url`),
59
+ completes the handshake, and fetches its tool catalog once, up front, following
60
+ `nextCursor` across pages.
61
+
62
+ Requires a `Scope`, plus `ChildProcessSpawner` for `command` or `HttpClient`
63
+ for `url` (`Requirements<O>`). The connection's lifetime is the scope's
64
+ lifetime: closing the scope tears the process down or ends the HTTP session,
65
+ and every stdio request pending at that moment fails with `connection_closed`.
66
+
67
+ Over HTTP, a destination the `HttpClient`'s egress policy denies and an
68
+ unreachable server fail with `connection_closed`; a non-2xx answer fails with
69
+ `protocol_error`; a `404` for an established session fails with
70
+ `connection_closed` and is not retried.
71
+
72
+ Fails with `spawn_failed` when the process will not start, `protocol_error` when
73
+ an option is invalid or negotiation fails, and `invalid_response` when the
74
+ catalog breaks one of the rules in
75
+ [Bound an untrusted server](./guides/bound-an-untrusted-server.md).
76
+
77
+ ### McpClient.McpClient
78
+
79
+ A live session.
80
+
81
+ ```ts
82
+ interface McpClient {
83
+ readonly server: string
84
+ readonly tools: ReadonlyArray<ToolDescription>
85
+ readonly callTool: (
86
+ name: string,
87
+ args: Record<string, unknown>
88
+ ) => Effect.Effect<ToolResult, McpError>
89
+ }
90
+ ```
91
+
92
+ | Member | Meaning |
93
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `server` | The name this session was connected under. It is the default flow-name prefix and appears in every error message. |
95
+ | `tools` | The catalog as fetched at connect time, in the order the server listed it. A snapshot: this client never re-polls it. |
96
+ | `callTool` | Calls one catalogued tool. An unknown name fails with `tool_not_found` before a JSON-RPC frame is written. Declared structured output is validated before it is returned. |
97
+
98
+ ### McpClient.ToolDescription
99
+
100
+ One remote tool as the server describes it.
101
+
102
+ | Field | Type | Meaning |
103
+ | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
104
+ | `name` | `string` | The tool's name. Never empty, `.`, or `..`; never containing `/` or an invisible or control character. |
105
+ | `description` | `string \| undefined` | The server's description, when it sent a string. |
106
+ | `inputSchema` | `Record<string, unknown>` | The tool's parameter shape, a JSON Schema document with `type: "object"`. |
107
+ | `outputSchema` | `Record<string, unknown> \| undefined` | The tool's structured result shape, when the server disclosed one. |
108
+
109
+ ### McpClient.ToolResult
110
+
111
+ The result of one `tools/call`.
112
+
113
+ | Field | Type | Meaning |
114
+ | ------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
115
+ | `content` | `ReadonlyArray<Record<string, unknown>>` | The tool's content blocks, passed through by shape. `[]` for a structured-only result. |
116
+ | `isError` | `boolean` | Whether the tool reported a problem. A successful call may carry `true`; this is not a failure. |
117
+ | `structuredContent` | `Record<string, unknown> \| undefined` | The tool's structured result, validated against its declared `outputSchema` when it declared one. |
118
+
119
+ ### McpClient.ConnectOptions
120
+
121
+ `StdioConnectOptions | HttpConnectOptions`. The stdio form:
122
+
123
+ | Field | Type | Meaning |
124
+ | ----------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
125
+ | `server` | `string` | The name this server is known by, for flow naming and error messages. Required. |
126
+ | `command` | `string` | The executable to spawn. Required. |
127
+ | `args` | `ReadonlyArray<string>` | Its arguments. Required. |
128
+ | `cwd` | `string \| undefined` | The child's working directory. |
129
+ | `env` | `Record<string, string \| undefined> \| undefined` | Values overlaid on the bootstrap child environment. |
130
+ | `handshakeTimeoutMs` | `number \| undefined` | Deadline for each `initialize` and `tools/list` request. Default 10000. |
131
+ | `requestTimeoutMs` | `number \| undefined` | Deadline for each later tool request. Default 120000. |
132
+ | `queueCapacity` | `number \| undefined` | Maximum outbound frames waiting to be written. Default 64. |
133
+ | `maxFrameBytes` | `number \| undefined` | Maximum UTF-8 bytes in one inbound JSON-RPC frame. Default 1048576. |
134
+ | `maxOutboundFrameBytes` | `number \| undefined` | Maximum UTF-8 bytes in one outbound JSON-RPC frame. Default 1048576. |
135
+ | `maxStderrBytes` | `number \| undefined` | Maximum diagnostic stderr bytes retained in memory and rendered after credential redaction. Default 2048. |
136
+ | `maxTools` | `number \| undefined` | Maximum tools accepted across every catalog page. Default 256. |
137
+ | `maxToolNameBytes` | `number \| undefined` | Maximum UTF-8 bytes in a tool name. Default 128. |
138
+ | `maxToolDocumentBytes` | `number \| undefined` | Maximum UTF-8 bytes of one tool's description plus its JSON-encoded `inputSchema`. Default 65536. |
139
+ | `maxCatalogPages` | `number \| undefined` | Maximum `tools/list` pages walked. Default 32. |
140
+
141
+ `HttpConnectOptions` has `server`, the catalog limits, `requestTimeoutMs`,
142
+ `maxFrameBytes`, and `maxOutboundFrameBytes` from the table above, plus:
143
+
144
+ | Field | Type | Meaning |
145
+ | -------------- | --------------------------- | ---------------------------------------------------------------------------------------------- |
146
+ | `url` | `string` | The server's MCP endpoint, an absolute `http:` or `https:` URL without credentials. Required. |
147
+ | `authProvider` | `AuthProvider \| undefined` | `{ token: Effect<Redacted<string>, McpError> }`, read once per HTTP message as a bearer token. |
148
+
149
+ Every numeric field must be a positive safe integer. Anything else fails with
150
+ `protocol_error` naming the option, before the process is spawned or a request
151
+ is sent.
152
+
153
+ The bootstrap child environment contains only `PATH`, `HOME`, `USER`, `LANG`,
154
+ `LC_*`, `TERM`, `TMPDIR`, and `SHELL`. Other ambient names are withheld;
155
+ entries in `env` are explicit declarations and are applied last.
156
+
157
+ ### McpClient.ConnectOptionsSchema
158
+
159
+ ```ts
160
+ const ConnectOptionsSchema = Schema.Struct({
161
+ server: Schema.NonEmptyString,
162
+ command: Schema.NonEmptyString,
163
+ args: Schema.Array(Schema.String),
164
+ cwd: Schema.optional(Schema.NonEmptyString),
165
+ env: Schema.optional(Schema.Record(Schema.String, Schema.String)),
166
+ // every limit, each a positive integer
167
+ handshakeTimeoutMs: Schema.optional(PositiveInteger)
168
+ // ...
169
+ })
170
+ ```
171
+
172
+ The authoritative decoder for a persisted stdio MCP server entry. It requires
173
+ non-empty `server` and `command` strings, string `args`, an optional
174
+ string-valued `env` record, and positive integers for every limit. Use it
175
+ wherever connection options arrive from a file or a database.
176
+
177
+ ### McpClient.HttpConnectOptionsSchema
178
+
179
+ ```ts
180
+ const HttpConnectOptionsSchema = Schema.Struct({
181
+ server: Schema.NonEmptyString,
182
+ url: Schema.String, // absolute http: or https:, no userinfo
183
+ bearerTokenEnv: Schema.optionalKey(Schema.String), // an environment variable name
184
+ // every limit HttpConnectOptions accepts, each a positive integer
185
+ requestTimeoutMs: Schema.optional(PositiveInteger)
186
+ // ...
187
+ })
188
+ ```
189
+
190
+ The authoritative decoder for a persisted Streamable HTTP server entry. It
191
+ requires a non-empty `server`, an absolute `http:` or `https:` `url` without
192
+ a user or password, and positive integers for every limit. The entry never
193
+ holds a credential: `bearerTokenEnv` names the environment variable the host
194
+ reads the bearer token from and supplies as `authProvider`.
195
+
196
+ ### McpClient.clientInfo
197
+
198
+ ```ts
199
+ const clientInfo: { readonly name: string; readonly version: string }
200
+ ```
201
+
202
+ The frozen identity disclosed to every server during initialization:
203
+ `name` is `"smithers"` and `version` is this package's version. Not
204
+ configurable.
205
+
206
+ ### McpClient.supportedProtocolVersions
207
+
208
+ ```ts
209
+ const supportedProtocolVersions: ReadonlyArray<string>
210
+ ```
211
+
212
+ The MCP revisions whose `tools/list` and `tools/call` shapes this client
213
+ decodes, frozen, always proposing the first entry:
214
+ `["2025-06-18", "2025-03-26", "2024-11-05"]`.
215
+
216
+ ### Limit defaults
217
+
218
+ Each option's default is exported as a constant, so a caller can read or
219
+ adjust one without restating a literal.
220
+
221
+ | Constant | Value | Option |
222
+ | ------------------------------ | ------- | ----------------------- |
223
+ | `defaultHandshakeTimeoutMs` | 10000 | `handshakeTimeoutMs` |
224
+ | `defaultRequestTimeoutMs` | 120000 | `requestTimeoutMs` |
225
+ | `defaultQueueCapacity` | 64 | `queueCapacity` |
226
+ | `defaultMaxFrameBytes` | 1048576 | `maxFrameBytes` |
227
+ | `defaultMaxOutboundFrameBytes` | 1048576 | `maxOutboundFrameBytes` |
228
+ | `defaultMaxStderrBytes` | 2048 | `maxStderrBytes` |
229
+ | `defaultMaxTools` | 256 | `maxTools` |
230
+ | `defaultMaxToolNameBytes` | 128 | `maxToolNameBytes` |
231
+ | `defaultMaxToolDocumentBytes` | 65536 | `maxToolDocumentBytes` |
232
+ | `defaultMaxCatalogPages` | 32 | `maxCatalogPages` |
233
+
234
+ `McpClient.maxJsonDepth` is a fixed safety limit of **128 nested containers**,
235
+ including the JSON-RPC envelope. Arrays and objects each count as one container;
236
+ scalar values do not. Both incoming messages and outgoing arguments obey it.
237
+ An inbound violation closes the connection with `protocol_error`; invalid
238
+ arguments fail before dispatch without closing an otherwise healthy session.
239
+ Incoming JSON numbers that overflow to infinity are also rejected.
240
+
241
+ Before copying arguments, the client accounts for their expanded JSON size
242
+ against `maxOutboundFrameBytes`. Reusing the same object under several properties
243
+ does not bypass that accounting. The transport additionally checks the exact
244
+ UTF-8 size of the full encoded frame. Neither bound limits a caller's own
245
+ already-allocated input object or time spent in caller-provided Proxy traps.
246
+
247
+ ## McpError
248
+
249
+ The single typed error returned by the client and the flow adapter. Ordinary
250
+ tool outcomes stay in the success channel; this error is reserved for failures
251
+ of the MCP session itself.
252
+
253
+ ### McpError.McpError
254
+
255
+ ```ts
256
+ class McpError extends Schema.TaggedError<McpError>()("flows/mcp/McpError", {
257
+ code: Code,
258
+ message: Schema.String,
259
+ server: Schema.optional(Schema.String)
260
+ }) {}
261
+ ```
262
+
263
+ | Field | Type | Meaning |
264
+ | --------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
265
+ | `code` | `Code` | The stable, model-facing failure code. |
266
+ | `message` | `string` | A fixed failure summary naming the configured server, with a remote numeric error code when available. No child stderr, remote error prose/data, or user-controlled property paths. |
267
+ | `server` | `string \| undefined` | The server the failure belongs to. |
268
+
269
+ The tag is `"flows/mcp/McpError"`, so `Effect.catchTag("flows/mcp/McpError", ...)`
270
+ matches it.
271
+
272
+ ### McpError.Code
273
+
274
+ ```ts
275
+ const Code = Schema.Literals([
276
+ "spawn_failed",
277
+ "connection_closed",
278
+ "timeout",
279
+ "protocol_error",
280
+ "tool_not_found",
281
+ "tool_failed",
282
+ "invalid_response"
283
+ ])
284
+
285
+ type Code = typeof Code.Type
286
+ ```
287
+
288
+ | Code | Meaning |
289
+ | ------------------- | ------------------------------------------------------------------------------------------------ |
290
+ | `spawn_failed` | The server process would not start. |
291
+ | `connection_closed` | The process exited or a pipe closed while a request was outstanding. |
292
+ | `timeout` | The server did not answer within the deadline for that method. |
293
+ | `protocol_error` | Negotiation failed, an envelope was malformed, or an option was invalid. |
294
+ | `tool_not_found` | The catalog lacks a tool, `include` names a missing tool, or the server rejects an unknown tool. |
295
+ | `tool_failed` | The server rejected a `tools/call` with a JSON-RPC error. |
296
+ | `invalid_response` | A well-formed reply carried a `tools/list` or `tools/call` payload this client rejects. |
297
+
298
+ For which JSON-RPC errors become which code, see
299
+ [Handle a failed tool call](./guides/handle-a-failed-tool-call.md).
300
+
301
+ ## Diagnostics
302
+
303
+ Optional host-only diagnostics, separate from the model-facing `McpError`.
304
+ Install `Diagnostics.layer(report)` around the effect that opens the connection.
305
+ The connection captures that observer once. Without it, private details are
306
+ discarded rather than logged.
307
+
308
+ ```ts
309
+ import { Diagnostics } from "@smthrs/mcp"
310
+
311
+ // A host-owned, bounded sink; this is not an agent or journal callback.
312
+ const privateDiagnostics = Diagnostics.layer((event) => {
313
+ retainForLocalInspection(event)
314
+ })
315
+ ```
316
+
317
+ `Diagnostics.Diagnostics` is the optional Context service. Its `report` callback
318
+ takes one `Diagnostics.Event`:
319
+
320
+ | Field | Meaning |
321
+ | ----------- | ------------------------------------------------------------------------------------------- |
322
+ | `server` | The host-configured server alias. Do not put credentials in aliases. |
323
+ | `source` | `spawn`, `stderr`, `transport`, `remote-error`, `invalid-response`, or `invalid-arguments`. |
324
+ | `detail` | `Redacted.Redacted<string>`, at most 16 KiB of UTF-8. May contain secrets. |
325
+ | `truncated` | Whether this event's private detail exceeded that 16 KiB bound. |
326
+
327
+ Ordinary JSON serialization and inspection hide `detail`. A trusted local host
328
+ can explicitly unwrap it with `Redacted.value`; it must control access and
329
+ retention and must never forward that value to agents, journals, traces, or
330
+ routine logs. The callback is synchronous: it must not block or retain an
331
+ unbounded event history. Callback and serialization exceptions are isolated
332
+ from the MCP connection. Diagnostic delivery is best effort, not an audit log.
333
+ On process or stdio closure, pending requests wait up to 250 ms for the stderr
334
+ reader to finish before receiving the terminal error. Request deadlines and
335
+ scope interruption can end that wait sooner. A pipe held open beyond that
336
+ budget contributes only the tail already read; it cannot hold shutdown open.
337
+ The separate `maxStderrBytes` limit applies before the observer sees a stderr
338
+ tail, so `truncated: false` does not imply the entire child output is present.
339
+
340
+ This protects session errors, not successful tool output. `content`,
341
+ `structuredContent`, and tool-reported `isError: true` results remain unchanged;
342
+ the host must choose which tool outputs it may expose.
343
+
344
+ ## McpFlows
345
+
346
+ Projects a connected session's tool catalog as an ordinary
347
+ `FlowBinding.Source`, one flow per tool.
348
+
349
+ ### McpFlows.connected
350
+
351
+ ```ts
352
+ const connected: (
353
+ options: McpClient.ConnectOptions & ProjectionOptions
354
+ ) => Effect.Effect<FlowBinding.Source, McpError, ChildProcessSpawner | Scope.Scope>
355
+ ```
356
+
357
+ Connects and projects in one step. This is the checked entry point: it validates
358
+ the projection options against the freshly fetched catalog, failing with
359
+ `tool_not_found` when `include` names a tool the server does not offer, and with
360
+ `protocol_error` when `namePrefix` is empty.
361
+
362
+ ### McpFlows.mcp
363
+
364
+ ```ts
365
+ const mcp: (
366
+ client: McpClient.McpClient,
367
+ options?: ProjectionOptions
368
+ ) => FlowBinding.Source
369
+ ```
370
+
371
+ Projects an already-connected session. Total: it applies exact filters to the
372
+ catalog it is given and validates nothing.
373
+
374
+ The client is a precondition rather than a parameter this constructor resolves,
375
+ because connecting is scoped and a `Source` is not. A host composes
376
+ `McpClient.connect` once, where it composes its other scoped services, and
377
+ passes the live client here.
378
+
379
+ ### McpFlows.ProjectionOptions
380
+
381
+ | Field | Type | Meaning |
382
+ | ------------ | ------------------------------------ | --------------------------------------------------------------- |
383
+ | `include` | `ReadonlyArray<string> \| undefined` | Exact tool names to project. Omitted or empty means every tool. |
384
+ | `exclude` | `ReadonlyArray<string> \| undefined` | Exact tool names to drop, applied after `include`. |
385
+ | `namePrefix` | `string \| undefined` | Replaces the default `mcp/<server>` flow-name prefix. |
386
+
387
+ ### McpFlows.Args
388
+
389
+ ```ts
390
+ const Args = Schema.Record(Schema.String, Schema.Unknown)
391
+ ```
392
+
393
+ The decoded input accepted by every MCP tool flow. It is permissive because the
394
+ remote server, not this adapter, owns argument validation. The registry still
395
+ discloses the real parameter shape: each binding carries the server's own
396
+ `inputSchema` as its input document.
397
+
398
+ ### McpFlows.Result
399
+
400
+ ```ts
401
+ const Result = Schema.Struct({
402
+ content: Schema.Array(Schema.Record(Schema.String, Schema.Unknown)),
403
+ isError: Schema.Boolean,
404
+ structuredContent: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
405
+ })
406
+ ```
407
+
408
+ The decoded output returned by every MCP tool flow. `structuredContent` is
409
+ absent, not null, when the tool sent none.
410
+
411
+ ### McpFlows.capabilities
412
+
413
+ ```ts
414
+ const capabilities: ReadonlyArray<string>
415
+ ```
416
+
417
+ The authority every MCP tool flow declares: one exact
418
+ `namespace:operation:resource` string per host action, at resource `**`, derived
419
+ from `Capability.Action.literals` and frozen.
420
+
421
+ ```ts
422
+ ;[
423
+ "fs:read:**",
424
+ "fs:write:**",
425
+ "net:get:**",
426
+ "net:post:**",
427
+ "net:private:**",
428
+ "model:call:**",
429
+ "memory:read:**",
430
+ "memory:write:**",
431
+ "proc:spawn:**",
432
+ "jj:status:**",
433
+ "jj:diff:**",
434
+ "jj:snapshot:**",
435
+ "jj:restore:**",
436
+ "jj:workspace-add:**",
437
+ "jj:workspace-forget:**",
438
+ "jj:root:**",
439
+ "jj:revert:**",
440
+ "jj:op-restore:**"
441
+ ]
442
+ ```
443
+
444
+ It is enumerated rather than written as a wildcard because the cell boundary
445
+ parses each declaration with `Capability.parse`, which requires exactly three
446
+ colon-separated components and treats anything else as unauthorized. Narrowing
447
+ this to what a host actually grants is the host's job; see
448
+ [Grant authority to MCP tools](./guides/grant-authority-to-mcp-tools.md).
449
+
450
+ ### McpFlows.effects
451
+
452
+ ```ts
453
+ const effects: Effects.Declaration // from @smthrs/core/Effects
454
+ ```
455
+
456
+ The conservative effect envelope every MCP tool flow declares:
457
+
458
+ ```ts
459
+ {
460
+ reads: ["**"],
461
+ writes: ["**"],
462
+ mode: "expected",
463
+ onConflict: "serialize",
464
+ tier: "irreversible"
465
+ }
466
+ ```
467
+
468
+ The irreversible tier is why an abandoned `tools/call` sends one
469
+ `notifications/cancelled` rather than being left in flight.