@fgv/ts-extras-mcp 5.1.0-47 → 5.1.0-49

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +19 -9
  3. package/.rush/temp/1ef6da5e025cb1b91fb9dacea403fcf68d03a476.tar.log +0 -90
  4. package/.rush/temp/chunked-rush-logs/ts-extras-mcp.build.chunks.jsonl +0 -9
  5. package/.rush/temp/operation/build/all.log +0 -9
  6. package/.rush/temp/operation/build/log-chunks.jsonl +0 -9
  7. package/.rush/temp/operation/build/state.json +0 -3
  8. package/.rush/temp/shrinkwrap-deps.json +0 -735
  9. package/config/api-extractor.json +0 -38
  10. package/config/jest.config.json +0 -13
  11. package/config/rig.json +0 -6
  12. package/dist/test/unit/endToEnd.test.js +0 -220
  13. package/dist/test/unit/endToEnd.test.js.map +0 -1
  14. package/dist/test/unit/index.test.js +0 -41
  15. package/dist/test/unit/index.test.js.map +0 -1
  16. package/dist/test/unit/mcp.test.js +0 -494
  17. package/dist/test/unit/mcp.test.js.map +0 -1
  18. package/dist/test/unit/sdk.test.js +0 -68
  19. package/dist/test/unit/sdk.test.js.map +0 -1
  20. package/eslint.config.js +0 -15
  21. package/etc/ts-extras-mcp.api.md +0 -114
  22. package/lib/test/unit/endToEnd.test.d.ts +0 -13
  23. package/lib/test/unit/endToEnd.test.d.ts.map +0 -1
  24. package/lib/test/unit/endToEnd.test.js +0 -222
  25. package/lib/test/unit/endToEnd.test.js.map +0 -1
  26. package/lib/test/unit/index.test.d.ts +0 -2
  27. package/lib/test/unit/index.test.d.ts.map +0 -1
  28. package/lib/test/unit/index.test.js +0 -76
  29. package/lib/test/unit/index.test.js.map +0 -1
  30. package/lib/test/unit/mcp.test.d.ts +0 -2
  31. package/lib/test/unit/mcp.test.d.ts.map +0 -1
  32. package/lib/test/unit/mcp.test.js +0 -529
  33. package/lib/test/unit/mcp.test.js.map +0 -1
  34. package/lib/test/unit/sdk.test.d.ts +0 -2
  35. package/lib/test/unit/sdk.test.d.ts.map +0 -1
  36. package/lib/test/unit/sdk.test.js +0 -70
  37. package/lib/test/unit/sdk.test.js.map +0 -1
  38. package/rush-logs/ts-extras-mcp.build.cache.log +0 -3
  39. package/rush-logs/ts-extras-mcp.build.log +0 -9
  40. package/src/index.ts +0 -50
  41. package/src/packlets/mcp/adapter.ts +0 -155
  42. package/src/packlets/mcp/index.ts +0 -34
  43. package/src/packlets/mcp/model.ts +0 -235
  44. package/src/packlets/mcp/operations.ts +0 -181
  45. package/src/packlets/mcp/sdk.ts +0 -154
  46. package/src/packlets/mcp/session.ts +0 -137
  47. package/src/packlets/mcp/transports.ts +0 -111
  48. package/src/test/unit/endToEnd.test.ts +0 -254
  49. package/src/test/unit/index.test.ts +0 -43
  50. package/src/test/unit/mcp.test.ts +0 -601
  51. package/src/test/unit/sdk.test.ts +0 -73
  52. package/temp/build/lint/_eslint-5eVG3S6w.json +0 -54
  53. package/temp/build/typescript/ts_8nwakTlr.json +0 -1
  54. package/temp/ts-extras-mcp.api.json +0 -1843
  55. package/temp/ts-extras-mcp.api.md +0 -114
  56. package/tsconfig.json +0 -12
@@ -1,181 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- /**
24
- * Tool discovery (`listMcpTools`, paginated) and invocation (`callMcpTool`, with `CallToolResult`
25
- * projection), converting the throwing SDK calls into `Result<T>`.
26
- * @packageDocumentation
27
- */
28
-
29
- import { Converters as UtilsConverters, type Result, captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
30
- import { Converters, type JsonObject } from '@fgv/ts-json-base';
31
-
32
- import {
33
- type IMcpSession,
34
- type IMcpToolAnnotations,
35
- type IMcpToolCallResult,
36
- type IMcpToolDescriptor
37
- } from './model';
38
- import { type ISdkContentBlock, type ISdkToolDescriptor } from './sdk';
39
- import { McpSession } from './session';
40
-
41
- /**
42
- * Validates/normalizes a raw (untrusted) MCP `Tool.annotations` blob into an
43
- * {@link IMcpToolAnnotations}, keeping only the five known fields that convert cleanly.
44
- *
45
- * @remarks
46
- * Each known field is converted independently with its own Converter, so a present-but-malformed
47
- * field is dropped (never failing the whole blob) and unknown keys are ignored. Returns
48
- * `undefined` when the blob is absent, not a JSON object, or normalizes to no usable known
49
- * fields — so a tool with no (usable) annotations leaves {@link IMcpToolDescriptor.annotations}
50
- * absent. The raw server value is never propagated verbatim (per the MCP untrusted-server
51
- * warning).
52
- */
53
- function _normalizeAnnotations(raw: unknown): IMcpToolAnnotations | undefined {
54
- const objResult = Converters.jsonObject.convert(raw);
55
- if (objResult.isFailure()) {
56
- return undefined;
57
- }
58
- const obj = objResult.value;
59
- const title = UtilsConverters.string.convert(obj.title).orDefault();
60
- const readOnlyHint = UtilsConverters.boolean.convert(obj.readOnlyHint).orDefault();
61
- const destructiveHint = UtilsConverters.boolean.convert(obj.destructiveHint).orDefault();
62
- const idempotentHint = UtilsConverters.boolean.convert(obj.idempotentHint).orDefault();
63
- const openWorldHint = UtilsConverters.boolean.convert(obj.openWorldHint).orDefault();
64
-
65
- const normalized: IMcpToolAnnotations = {
66
- ...(title !== undefined ? { title } : {}),
67
- ...(readOnlyHint !== undefined ? { readOnlyHint } : {}),
68
- ...(destructiveHint !== undefined ? { destructiveHint } : {}),
69
- ...(idempotentHint !== undefined ? { idempotentHint } : {}),
70
- ...(openWorldHint !== undefined ? { openWorldHint } : {})
71
- };
72
- return Object.keys(normalized).length > 0 ? normalized : undefined;
73
- }
74
-
75
- /**
76
- * Projects a single SDK tool descriptor into a public {@link IMcpToolDescriptor}. The
77
- * `inputSchema` field is validated as a `JsonValue`; an absent/non-JSON schema becomes `null`
78
- * (which {@link adaptMcpTools} then reports as un-adaptable rather than offering it to the model).
79
- * The raw `annotations` blob is validated/normalized (never propagated raw) per the MCP
80
- * untrusted-server warning.
81
- */
82
- function _toDescriptor(tool: ISdkToolDescriptor): IMcpToolDescriptor {
83
- const annotations = _normalizeAnnotations(tool.annotations);
84
- return {
85
- name: tool.name,
86
- description: tool.description,
87
- inputSchema: Converters.jsonValue.convert(tool.inputSchema).orDefault(null),
88
- ...(annotations !== undefined ? { annotations } : {})
89
- };
90
- }
91
-
92
- /**
93
- * Projects the SDK `CallToolResult` content blocks to a single string: `text` blocks are
94
- * concatenated; every other block type becomes a one-line `[<type> block]` summary.
95
- */
96
- function _projectContent(blocks: ReadonlyArray<ISdkContentBlock> | undefined): string {
97
- if (blocks === undefined || blocks.length === 0) {
98
- return '';
99
- }
100
- return blocks
101
- .map((block) =>
102
- block.type === 'text' && block.text !== undefined ? block.text : `[${block.type} block]`
103
- )
104
- .join('\n');
105
- }
106
-
107
- /**
108
- * Lists every tool a connected MCP server advertises, following the SDK's `nextCursor`
109
- * pagination until the full catalog is accumulated.
110
- *
111
- * @param session - A session from `connectMcpSession`.
112
- * @returns `Success` with the full tool catalog, or `Failure` on a foreign handle or a
113
- * transport/protocol error.
114
- * @public
115
- */
116
- export async function listMcpTools(session: IMcpSession): Promise<Result<ReadonlyArray<IMcpToolDescriptor>>> {
117
- const sessionResult = McpSession.fromHandle(session);
118
- if (sessionResult.isFailure()) {
119
- return fail(`listMcpTools: ${sessionResult.message}`);
120
- }
121
- const { client } = sessionResult.value;
122
-
123
- const all: IMcpToolDescriptor[] = [];
124
- let cursor: string | undefined;
125
-
126
- // Cursor-paginated loop: fetch each page, accumulate, advance until nextCursor is absent.
127
- do {
128
- const pageResult = await captureAsyncResult(() =>
129
- client.listTools(cursor !== undefined ? { cursor } : undefined)
130
- ).withErrorFormat((msg) => `listMcpTools: ${msg}`);
131
- if (pageResult.isFailure()) {
132
- return fail(pageResult.message);
133
- }
134
- for (const tool of pageResult.value.tools) {
135
- all.push(_toDescriptor(tool));
136
- }
137
- cursor = pageResult.value.nextCursor;
138
- } while (cursor !== undefined);
139
-
140
- return succeed(all);
141
- }
142
-
143
- /**
144
- * Calls a named tool on a connected MCP server.
145
- *
146
- * @remarks
147
- * The SDK `CallToolResult` is projected to {@link IMcpToolCallResult} (text-block concatenation;
148
- * non-text blocks summarized). A result flagged `isError: true` is mapped to `Result.fail` with
149
- * the projected content — it is never swallowed, so `executeClientToolTurn` routes it back to the
150
- * model as a provider-native error tool-result.
151
- *
152
- * @param session - A session from `connectMcpSession`.
153
- * @param name - The tool name to invoke.
154
- * @param args - The tool arguments (a JSON object).
155
- * @returns `Success` with the projected content, or `Failure` on tool error / transport error /
156
- * foreign handle.
157
- * @public
158
- */
159
- export async function callMcpTool(
160
- session: IMcpSession,
161
- name: string,
162
- args: JsonObject
163
- ): Promise<Result<IMcpToolCallResult>> {
164
- const sessionResult = McpSession.fromHandle(session);
165
- if (sessionResult.isFailure()) {
166
- return fail(`callMcpTool: ${sessionResult.message}`);
167
- }
168
- // The `withErrorFormat` wraps only transport/throw failures (it transforms the failure flowing
169
- // out of `captureAsyncResult`); the `isError` failure created in the later `onSuccess` is
170
- // downstream and stays clean, so the model-facing tool-error text is the server's verbatim
171
- // content rather than a doubly-prefixed message.
172
- return captureAsyncResult(() => sessionResult.value.client.callTool({ name, arguments: args }))
173
- .withErrorFormat((msg) => `callMcpTool '${name}': ${msg}`)
174
- .onSuccess((raw): Result<IMcpToolCallResult> => {
175
- const content = _projectContent(raw.content);
176
- if (raw.isError === true) {
177
- return fail(content.length > 0 ? content : `tool '${name}' reported an error`);
178
- }
179
- return succeed({ content });
180
- });
181
- }
@@ -1,154 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- /**
24
- * **The single `@modelcontextprotocol/sdk` import site for the entire package.**
25
- *
26
- * Every other module in `@fgv/ts-extras-mcp` depends only on the minimal local projection
27
- * exported here (`ISdkClient`, `ISdkTransport`, the three `make*` factories) — never on the SDK
28
- * directly. This keeps the announced v2 client-package rename a one-file change and lets unit
29
- * tests mock this module instead of the SDK internals (no live server required).
30
- *
31
- * The only `as unknown as` casts in the package live here, at the boundary where the real SDK
32
- * objects are narrowed to the local projection.
33
- *
34
- * @internal
35
- */
36
-
37
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
38
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
39
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
40
-
41
- /**
42
- * Opaque transport handle. The concrete value is an SDK `Transport`; consumers of this module
43
- * treat it as opaque and only ever hand it back to {@link makeClient}-produced clients.
44
- * @internal
45
- */
46
- export type ISdkTransport = object;
47
-
48
- /**
49
- * A single content block in an MCP `CallToolResult`. Only `text` blocks carry inline content;
50
- * all other block types are projected to a structural summary by the operations layer.
51
- * @internal
52
- */
53
- export interface ISdkContentBlock {
54
- readonly type: string;
55
- readonly text?: string;
56
- }
57
-
58
- /**
59
- * Projection of the SDK's `CallToolResult`.
60
- * @internal
61
- */
62
- export interface ISdkCallToolResult {
63
- readonly content?: ReadonlyArray<ISdkContentBlock>;
64
- readonly isError?: boolean;
65
- }
66
-
67
- /**
68
- * Projection of a single tool descriptor in the SDK's `ListToolsResult`.
69
- * @internal
70
- */
71
- export interface ISdkToolDescriptor {
72
- readonly name: string;
73
- readonly description?: string;
74
- readonly inputSchema?: unknown;
75
- /**
76
- * Raw MCP `Tool.annotations` (untrusted). Declared as `unknown` so the SDK field survives
77
- * narrowing; the operations layer validates/normalizes it (never propagates raw) per the MCP
78
- * spec's untrusted-server warning.
79
- */
80
- readonly annotations?: unknown;
81
- }
82
-
83
- /**
84
- * Projection of the SDK's `ListToolsResult` (one page).
85
- * @internal
86
- */
87
- export interface ISdkListToolsResult {
88
- readonly tools: ReadonlyArray<ISdkToolDescriptor>;
89
- readonly nextCursor?: string;
90
- }
91
-
92
- /**
93
- * Server identity returned by the SDK's `getServerVersion()` after the initialize handshake.
94
- * @internal
95
- */
96
- export interface ISdkImplementation {
97
- readonly name: string;
98
- readonly version: string;
99
- }
100
-
101
- /**
102
- * Minimal projection of the SDK `Client` surface the package depends on.
103
- * @internal
104
- */
105
- export interface ISdkClient {
106
- connect(transport: ISdkTransport): Promise<void>;
107
- getServerVersion(): ISdkImplementation | undefined;
108
- listTools(params?: { cursor?: string }): Promise<ISdkListToolsResult>;
109
- callTool(params: { name: string; arguments?: Record<string, unknown> }): Promise<ISdkCallToolResult>;
110
- close(): Promise<void>;
111
- }
112
-
113
- /**
114
- * Parameters for the stdio transport factory.
115
- * @internal
116
- */
117
- export interface ISdkStdioParams {
118
- readonly command: string;
119
- readonly args?: ReadonlyArray<string>;
120
- readonly env?: Record<string, string>;
121
- readonly cwd?: string;
122
- }
123
-
124
- /**
125
- * Constructs an MCP client. The `as unknown as` cast is the package's single SDK-type
126
- * bridge — the runtime object is a real SDK `Client`.
127
- * @internal
128
- */
129
- export function makeClient(name: string, version: string): ISdkClient {
130
- return new Client({ name, version }, { capabilities: {} }) as unknown as ISdkClient;
131
- }
132
-
133
- /**
134
- * Constructs a stdio transport that spawns the given command as a subprocess.
135
- * @internal
136
- */
137
- export function makeStdioTransport(params: ISdkStdioParams): ISdkTransport {
138
- return new StdioClientTransport({
139
- command: params.command,
140
- args: params.args ? [...params.args] : undefined,
141
- env: params.env,
142
- cwd: params.cwd
143
- }) as unknown as ISdkTransport;
144
- }
145
-
146
- /**
147
- * Constructs a Streamable-HTTP transport for the given URL, with optional static headers.
148
- * @internal
149
- */
150
- export function makeHttpTransport(url: URL, headers?: Record<string, string>): ISdkTransport {
151
- return new StreamableHTTPClientTransport(url, {
152
- requestInit: headers ? { headers } : undefined
153
- }) as unknown as ISdkTransport;
154
- }
@@ -1,137 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- /**
24
- * Session lifecycle — connect / close, converting the throwing SDK calls into `Result<T>`.
25
- * @packageDocumentation
26
- */
27
-
28
- import { type Result, captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
29
-
30
- import { type IConnectMcpSessionParams, type IMcpServerInfo, type IMcpSession } from './model';
31
- import { type ISdkClient, makeClient } from './sdk';
32
- import { McpTransport } from './transports';
33
-
34
- /** Default client name advertised during the initialize handshake. */
35
- const DEFAULT_CLIENT_NAME: string = '@fgv/ts-extras-mcp';
36
- /**
37
- * Default client version advertised during the initialize handshake.
38
- *
39
- * @remarks
40
- * Keep in sync with this package's `package.json` version on each release. It is informational
41
- * only — MCP servers use it for logging/telemetry, not for behavior — so a drift is low-impact,
42
- * but it should be bumped alongside the lockstep version. (Reading it from `package.json` at
43
- * runtime is avoided to keep the bundle free of a JSON import / `require` of the manifest.)
44
- */
45
- const DEFAULT_CLIENT_VERSION: string = '5.1.0';
46
-
47
- /**
48
- * Concrete session handle. Packlet-internal — carries the live SDK client that the operations
49
- * layer recovers via {@link McpSession.fromHandle}. Not exported from the package barrel.
50
- * @internal
51
- */
52
- export class McpSession implements IMcpSession {
53
- public readonly clientName: string;
54
- public readonly clientVersion: string;
55
- public readonly serverInfo: IMcpServerInfo | undefined;
56
- /** The live SDK client. */
57
- public readonly client: ISdkClient;
58
-
59
- public constructor(
60
- client: ISdkClient,
61
- clientName: string,
62
- clientVersion: string,
63
- serverInfo: IMcpServerInfo | undefined
64
- ) {
65
- this.client = client;
66
- this.clientName = clientName;
67
- this.clientVersion = clientVersion;
68
- this.serverInfo = serverInfo;
69
- }
70
-
71
- /**
72
- * Narrows a public {@link IMcpSession} handle back to the concrete {@link McpSession}. Fails
73
- * loudly when handed a foreign object that did not originate from {@link connectMcpSession}.
74
- */
75
- public static fromHandle(handle: IMcpSession): Result<McpSession> {
76
- if (handle instanceof McpSession) {
77
- return succeed(handle);
78
- }
79
- return fail('invalid MCP session: expected a handle from connectMcpSession');
80
- }
81
- }
82
-
83
- /**
84
- * Connects to an MCP server over the given transport and performs the initialize handshake.
85
- *
86
- * @param params - Transport plus optional client identity and logger.
87
- * @returns `Success` with an opaque {@link IMcpSession}, or `Failure` if the transport handle is
88
- * foreign or the connection/handshake fails.
89
- * @public
90
- */
91
- export async function connectMcpSession(params: IConnectMcpSessionParams): Promise<Result<IMcpSession>> {
92
- const { transport, clientName, clientVersion, logger } = params;
93
-
94
- const transportResult = McpTransport.fromHandle(transport);
95
- if (transportResult.isFailure()) {
96
- return fail(`connectMcpSession: ${transportResult.message}`);
97
- }
98
-
99
- const name = clientName ?? DEFAULT_CLIENT_NAME;
100
- const version = clientVersion ?? DEFAULT_CLIENT_VERSION;
101
- const client = makeClient(name, version);
102
-
103
- logger?.info(
104
- `mcp: connecting (client ${name}@${version}, transport ${transportResult.value.transportKind})`
105
- );
106
-
107
- return captureAsyncResult(() => client.connect(transportResult.value.sdkTransport))
108
- .onSuccess(() => {
109
- const raw = client.getServerVersion();
110
- const serverInfo: IMcpServerInfo | undefined =
111
- raw !== undefined ? { name: raw.name, version: raw.version } : undefined;
112
- logger?.info(
113
- serverInfo !== undefined
114
- ? `mcp: connected to server ${serverInfo.name}@${serverInfo.version}`
115
- : 'mcp: connected (server did not report identity)'
116
- );
117
- return succeed<IMcpSession>(new McpSession(client, name, version, serverInfo));
118
- })
119
- .withErrorFormat((msg) => `connectMcpSession: ${msg}`);
120
- }
121
-
122
- /**
123
- * Closes an MCP session, tearing down the transport (and any spawned subprocess).
124
- *
125
- * @param session - A session from {@link connectMcpSession}.
126
- * @returns `Success(true)`, or `Failure` if the handle is foreign or the close call throws.
127
- * @public
128
- */
129
- export async function closeMcpSession(session: IMcpSession): Promise<Result<true>> {
130
- const sessionResult = McpSession.fromHandle(session);
131
- if (sessionResult.isFailure()) {
132
- return fail(`closeMcpSession: ${sessionResult.message}`);
133
- }
134
- return captureAsyncResult(() => sessionResult.value.client.close())
135
- .onSuccess(() => succeed(true as const))
136
- .withErrorFormat((msg) => `closeMcpSession: ${msg}`);
137
- }
@@ -1,111 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- /**
24
- * Transport factories — convert the throwing SDK transport constructors into `Result<T>`.
25
- * @packageDocumentation
26
- */
27
-
28
- import { type Result, captureResult, fail, succeed } from '@fgv/ts-utils';
29
-
30
- import { type IMcpHttpTransportParams, type IMcpStdioTransportParams, type IMcpTransport } from './model';
31
- import { type ISdkTransport, makeHttpTransport, makeStdioTransport } from './sdk';
32
-
33
- /**
34
- * Concrete transport handle. Packlet-internal — carries the SDK transport that
35
- * {@link McpTransport.fromHandle} recovers inside {@link connectMcpSession}. Not exported from
36
- * the package barrel, so it does not appear in the public API surface.
37
- * @internal
38
- */
39
- export class McpTransport implements IMcpTransport {
40
- public readonly transportKind: 'stdio' | 'http';
41
- /** The wrapped SDK transport. */
42
- public readonly sdkTransport: ISdkTransport;
43
-
44
- public constructor(transportKind: 'stdio' | 'http', sdkTransport: ISdkTransport) {
45
- this.transportKind = transportKind;
46
- this.sdkTransport = sdkTransport;
47
- }
48
-
49
- /**
50
- * Narrows a public {@link IMcpTransport} handle back to the concrete {@link McpTransport}.
51
- * Fails loudly when handed a foreign object that did not originate from one of this
52
- * package's transport factories.
53
- */
54
- public static fromHandle(handle: IMcpTransport): Result<McpTransport> {
55
- if (handle instanceof McpTransport) {
56
- return succeed(handle);
57
- }
58
- return fail('invalid MCP transport: expected a handle from createStdioTransport / createHttpTransport');
59
- }
60
- }
61
-
62
- /**
63
- * Creates a stdio MCP transport that speaks MCP over the stdin/stdout of a spawned subprocess.
64
- *
65
- * @remarks
66
- * **Security — trust boundary.** The transport spawns `params.command` (with `params.args`) as a
67
- * child process. Never source the command or arguments from untrusted input; treat them with the
68
- * same care as any shell-out. See the package README's security note.
69
- *
70
- * @param params - The command, arguments, environment, and working directory.
71
- * @returns `Success` with an opaque transport handle, or `Failure` if the SDK constructor throws
72
- * (e.g. an empty command).
73
- * @public
74
- */
75
- export function createStdioTransport(params: IMcpStdioTransportParams): Result<IMcpTransport> {
76
- if (params.command.trim().length === 0) {
77
- return fail('createStdioTransport: command must be a non-empty string');
78
- }
79
- return captureResult(() =>
80
- makeStdioTransport({
81
- command: params.command,
82
- args: params.args,
83
- env: params.env,
84
- cwd: params.cwd
85
- })
86
- )
87
- .onSuccess((sdkTransport) => succeed<IMcpTransport>(new McpTransport('stdio', sdkTransport)))
88
- .withErrorFormat((msg) => `createStdioTransport: ${msg}`);
89
- }
90
-
91
- /**
92
- * Creates a Streamable-HTTP MCP transport for the given endpoint URL.
93
- *
94
- * @param params - The endpoint URL and optional static headers.
95
- * @returns `Success` with an opaque transport handle, or `Failure` if the URL is invalid or the
96
- * SDK constructor throws.
97
- * @public
98
- */
99
- export function createHttpTransport(params: IMcpHttpTransportParams): Result<IMcpTransport> {
100
- return captureResult(() => new URL(params.url))
101
- .withErrorFormat(() => `invalid url '${params.url}'`)
102
- .onSuccess((url): Result<IMcpTransport> => {
103
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
104
- return fail(`url must use http or https protocol (got '${url.protocol}')`);
105
- }
106
- return captureResult(() => makeHttpTransport(url, params.headers)).onSuccess((sdkTransport) =>
107
- succeed<IMcpTransport>(new McpTransport('http', sdkTransport))
108
- );
109
- })
110
- .withErrorFormat((msg) => `createHttpTransport: ${msg}`);
111
- }