@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.
- package/CHANGELOG.md +121 -0
- package/LICENSE +21 -0
- package/README.md +111 -2
- package/dist/cjs/Diagnostics.d.ts +46 -0
- package/dist/cjs/Diagnostics.d.ts.map +1 -0
- package/dist/cjs/Diagnostics.js +29 -0
- package/dist/cjs/Diagnostics.js.map +7 -0
- package/dist/cjs/McpClient.d.ts +304 -0
- package/dist/cjs/McpClient.d.ts.map +1 -0
- package/dist/cjs/McpClient.js +622 -0
- package/dist/cjs/McpClient.js.map +7 -0
- package/dist/cjs/McpError.d.ts +44 -0
- package/dist/cjs/McpError.d.ts.map +1 -0
- package/dist/cjs/McpError.js +41 -0
- package/dist/cjs/McpError.js.map +7 -0
- package/dist/cjs/McpFlows.d.ts +112 -0
- package/dist/cjs/McpFlows.d.ts.map +1 -0
- package/dist/cjs/McpFlows.js +127 -0
- package/dist/cjs/McpFlows.js.map +7 -0
- package/dist/cjs/index.d.ts +39 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +41 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/DiagnosticReporter.d.ts +17 -0
- package/dist/cjs/internal/DiagnosticReporter.d.ts.map +1 -0
- package/dist/cjs/internal/DiagnosticReporter.js +56 -0
- package/dist/cjs/internal/DiagnosticReporter.js.map +7 -0
- package/dist/cjs/internal/HttpTransport.d.ts +67 -0
- package/dist/cjs/internal/HttpTransport.d.ts.map +1 -0
- package/dist/cjs/internal/HttpTransport.js +298 -0
- package/dist/cjs/internal/HttpTransport.js.map +7 -0
- package/dist/cjs/internal/JsonLimits.d.ts +29 -0
- package/dist/cjs/internal/JsonLimits.d.ts.map +1 -0
- package/dist/cjs/internal/JsonLimits.js +51 -0
- package/dist/cjs/internal/JsonLimits.js.map +7 -0
- package/dist/cjs/internal/Limits.d.ts +36 -0
- package/dist/cjs/internal/Limits.d.ts.map +1 -0
- package/dist/cjs/internal/Limits.js +34 -0
- package/dist/cjs/internal/Limits.js.map +7 -0
- package/dist/cjs/internal/Rpc.d.ts +141 -0
- package/dist/cjs/internal/Rpc.d.ts.map +1 -0
- package/dist/cjs/internal/Rpc.js +92 -0
- package/dist/cjs/internal/Rpc.js.map +7 -0
- package/dist/cjs/internal/StdioTransport.d.ts +78 -0
- package/dist/cjs/internal/StdioTransport.d.ts.map +1 -0
- package/dist/cjs/internal/StdioTransport.js +310 -0
- package/dist/cjs/internal/StdioTransport.js.map +7 -0
- package/dist/cjs/internal/Transport.d.ts +87 -0
- package/dist/cjs/internal/Transport.d.ts.map +1 -0
- package/dist/cjs/internal/Transport.js +116 -0
- package/dist/cjs/internal/Transport.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Diagnostics.d.ts +46 -0
- package/dist/esm/Diagnostics.d.ts.map +1 -0
- package/dist/esm/Diagnostics.js +26 -0
- package/dist/esm/Diagnostics.js.map +1 -0
- package/dist/esm/McpClient.d.ts +304 -0
- package/dist/esm/McpClient.d.ts.map +1 -0
- package/dist/esm/McpClient.js +671 -0
- package/dist/esm/McpClient.js.map +1 -0
- package/dist/esm/McpError.d.ts +44 -0
- package/dist/esm/McpError.d.ts.map +1 -0
- package/dist/esm/McpError.js +43 -0
- package/dist/esm/McpError.js.map +1 -0
- package/dist/esm/McpFlows.d.ts +112 -0
- package/dist/esm/McpFlows.d.ts.map +1 -0
- package/dist/esm/McpFlows.js +167 -0
- package/dist/esm/McpFlows.js.map +1 -0
- package/dist/esm/index.d.ts +39 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/DiagnosticReporter.d.ts +17 -0
- package/dist/esm/internal/DiagnosticReporter.d.ts.map +1 -0
- package/dist/esm/internal/DiagnosticReporter.js +44 -0
- package/dist/esm/internal/DiagnosticReporter.js.map +1 -0
- package/dist/esm/internal/HttpTransport.d.ts +67 -0
- package/dist/esm/internal/HttpTransport.d.ts.map +1 -0
- package/dist/esm/internal/HttpTransport.js +266 -0
- package/dist/esm/internal/HttpTransport.js.map +1 -0
- package/dist/esm/internal/JsonLimits.d.ts +29 -0
- package/dist/esm/internal/JsonLimits.d.ts.map +1 -0
- package/dist/esm/internal/JsonLimits.js +55 -0
- package/dist/esm/internal/JsonLimits.js.map +1 -0
- package/dist/esm/internal/Limits.d.ts +36 -0
- package/dist/esm/internal/Limits.d.ts.map +1 -0
- package/dist/esm/internal/Limits.js +41 -0
- package/dist/esm/internal/Limits.js.map +1 -0
- package/dist/esm/internal/Rpc.d.ts +141 -0
- package/dist/esm/internal/Rpc.d.ts.map +1 -0
- package/dist/esm/internal/Rpc.js +129 -0
- package/dist/esm/internal/Rpc.js.map +1 -0
- package/dist/esm/internal/StdioTransport.d.ts +78 -0
- package/dist/esm/internal/StdioTransport.d.ts.map +1 -0
- package/dist/esm/internal/StdioTransport.js +332 -0
- package/dist/esm/internal/StdioTransport.js.map +1 -0
- package/dist/esm/internal/Transport.d.ts +87 -0
- package/dist/esm/internal/Transport.d.ts.map +1 -0
- package/dist/esm/internal/Transport.js +146 -0
- package/dist/esm/internal/Transport.js.map +1 -0
- package/docs/README.md +139 -0
- package/docs/api.md +469 -0
- package/docs/concepts/the-session.md +135 -0
- package/docs/concepts/tools-as-flows.md +116 -0
- package/docs/guides/bound-an-untrusted-server.md +158 -0
- package/docs/guides/configure-servers-for-the-cli.md +167 -0
- package/docs/guides/connect-a-server.md +161 -0
- package/docs/guides/grant-authority-to-mcp-tools.md +130 -0
- package/docs/guides/handle-a-failed-tool-call.md +125 -0
- package/docs/guides/select-the-tools-a-run-sees.md +92 -0
- package/docs/guides/testing.md +132 -0
- package/docs/guides/validate-structured-output.md +103 -0
- package/docs/installation.md +117 -0
- package/docs/quickstart.md +200 -0
- package/docs/troubleshooting.md +316 -0
- package/package.json +157 -3
- package/src/Diagnostics.ts +47 -0
- package/src/McpClient.ts +985 -0
- package/src/McpError.ts +52 -0
- package/src/McpFlows.ts +210 -0
- package/src/index.ts +42 -0
- package/src/internal/DiagnosticReporter.ts +47 -0
- package/src/internal/HttpTransport.ts +400 -0
- package/src/internal/JsonLimits.ts +53 -0
- package/src/internal/Limits.ts +48 -0
- package/src/internal/Rpc.ts +219 -0
- package/src/internal/StdioTransport.ts +491 -0
- 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.
|