@onlyne/mcp-core 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +173 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +550 -0
- package/dist/index.js.map +1 -0
- package/package.json +13 -8
- package/src/index.ts +0 -198
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Json, NodeId, ResourceDriver, ResourceRef } from '@onlyne/lib';
|
|
2
|
+
import { type ListResourcesResult, type ReadResourceResult, type ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
/** Shared settings of one configured MCP server. */
|
|
4
|
+
interface McpServerOptionsBase {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Per-request timeout for tool calls and resource reads; the default matches the library's driver wait. */
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
/**
|
|
9
|
+
* The local tool policy (RFC 0015 §4). Absent allows every tool the server lists; an empty
|
|
10
|
+
* list allows none. The policy is checked before any request leaves this node.
|
|
11
|
+
*/
|
|
12
|
+
allowedTools?: string[];
|
|
13
|
+
}
|
|
14
|
+
/** An HTTP streamable MCP server; `url` is the endpoint and `headers` carry its API key. */
|
|
15
|
+
export interface McpHttpServerOptions extends McpServerOptionsBase {
|
|
16
|
+
transport?: 'http';
|
|
17
|
+
url: string;
|
|
18
|
+
/** Extra headers per request: Hindsight puts its API key here when enabled. */
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A locally spawned MCP server. `command` and `args` are handed to spawn as-is; nothing is
|
|
23
|
+
* joined into a shell string, so a value can never become shell syntax.
|
|
24
|
+
*/
|
|
25
|
+
export interface McpStdioServerOptions extends McpServerOptionsBase {
|
|
26
|
+
transport: 'stdio';
|
|
27
|
+
command: string;
|
|
28
|
+
args?: string[];
|
|
29
|
+
env?: Record<string, string>;
|
|
30
|
+
cwd?: string;
|
|
31
|
+
}
|
|
32
|
+
/** One MCP server connection: exactly one of the HTTP and stdio transport contracts. */
|
|
33
|
+
export type McpServerOptions = McpHttpServerOptions | McpStdioServerOptions;
|
|
34
|
+
/** One entry of `tools/list`, kept as the bridge and the scheduler need it. */
|
|
35
|
+
export interface McpToolInfo {
|
|
36
|
+
name: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
annotations?: {
|
|
39
|
+
readOnlyHint?: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** One entry of `resources/list`, typed by the official SDK. */
|
|
43
|
+
export type McpResourceInfo = ListResourcesResult['resources'][number];
|
|
44
|
+
/** One page of `resources/list`; `nextCursor` is the server's own opaque cursor. */
|
|
45
|
+
export type McpResourcePage = Pick<ListResourcesResult, 'resources' | 'nextCursor'>;
|
|
46
|
+
/** The result of one `resources/read`: data only, never a message or a thread entry. */
|
|
47
|
+
export type McpResourceRead = ReadResourceResult;
|
|
48
|
+
/** The result of one tool call, classified for the operation's three-way outcome (RFC 0015 §3). */
|
|
49
|
+
export type McpOutcome = {
|
|
50
|
+
kind: 'result';
|
|
51
|
+
content?: Json;
|
|
52
|
+
structuredContent?: Json;
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'rejected';
|
|
55
|
+
text: string;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'unknown';
|
|
58
|
+
text: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* One MCP server connection on the official SDK: `Client` over either the streamable HTTP
|
|
62
|
+
* transport or a spawned stdio child. The SDK owns the session id, the handshake, SSE parsing,
|
|
63
|
+
* reconnect semantics and the stdio process lifecycle; this class is the façade the bridge and
|
|
64
|
+
* the gateway see. The local tool policy lives here, at the choke point every caller (resource
|
|
65
|
+
* operations, scheduled mounts, direct host calls) shares.
|
|
66
|
+
*/
|
|
67
|
+
export declare class McpConnection {
|
|
68
|
+
#private;
|
|
69
|
+
constructor(options: McpServerOptions);
|
|
70
|
+
get name(): string;
|
|
71
|
+
get transport(): 'http' | 'stdio';
|
|
72
|
+
/** The HTTP endpoint; empty for a stdio server, which has no URL. */
|
|
73
|
+
get url(): string;
|
|
74
|
+
get tools(): McpToolInfo[];
|
|
75
|
+
/** The last `resources/list` result; empty when the server declares no resources capability. */
|
|
76
|
+
get resources(): McpResourceInfo[];
|
|
77
|
+
/** What the server negotiated at `initialize`; undefined before the first successful connect. */
|
|
78
|
+
get capabilities(): ServerCapabilities | undefined;
|
|
79
|
+
get canListResources(): boolean;
|
|
80
|
+
get canSubscribe(): boolean;
|
|
81
|
+
/** The URIs this node currently holds a subscription for. */
|
|
82
|
+
get subscriptions(): ReadonlyArray<string>;
|
|
83
|
+
/** The local policy decision for one tool name. */
|
|
84
|
+
allows(tool: string): boolean;
|
|
85
|
+
/** Connects, reads the tool list and records the negotiated capabilities. */
|
|
86
|
+
start(): Promise<void>;
|
|
87
|
+
refreshTools(): Promise<McpToolInfo[]>;
|
|
88
|
+
/**
|
|
89
|
+
* The catalog entry for one operation. A name the current list already holds answers from the
|
|
90
|
+
* list; any other name refreshes `tools/list` once — connecting first when the session is down
|
|
91
|
+
* — so a catalog a previous run cached cannot turn a reachable tool into
|
|
92
|
+
* `unsupported_delivery`, and a name the live server does not list stays `unsupported_delivery`.
|
|
93
|
+
* The local policy runs before either, so a denied tool never reaches the network (RFC 0015 §4).
|
|
94
|
+
*/
|
|
95
|
+
resolveTool(name: string): Promise<McpToolInfo>;
|
|
96
|
+
call(name: string, arguments_: Json): Promise<McpOutcome>;
|
|
97
|
+
/** One page of `resources/list`; the cursor is the server's own value. */
|
|
98
|
+
listResources(cursor?: string): Promise<McpResourcePage>;
|
|
99
|
+
/** The complete catalog, walking `nextCursor` pages. */
|
|
100
|
+
refreshResources(): Promise<McpResourceInfo[]>;
|
|
101
|
+
/** A read-only `resources/read`; the contents are data and never become a message. */
|
|
102
|
+
readResource(uri: string): Promise<McpResourceRead>;
|
|
103
|
+
/** Subscribes this node to one resource's change notifications (RFC 0015 §4: a local action). */
|
|
104
|
+
subscribe(uri: string): Promise<void>;
|
|
105
|
+
/** Withdraws a subscription; a URI this node never subscribed costs no request. */
|
|
106
|
+
unsubscribe(uri: string): Promise<void>;
|
|
107
|
+
/** Withdraws every live subscription, leaving the session open. */
|
|
108
|
+
unsubscribeAll(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Aligns subscriptions with a catalog: subscribes newly listed resources and withdraws the
|
|
111
|
+
* ones that disappeared. A server without `resources.subscribe` simply holds no subscriptions.
|
|
112
|
+
*/
|
|
113
|
+
syncSubscriptions(resources?: ReadonlyArray<McpResourceInfo>): Promise<string[]>;
|
|
114
|
+
/** Observes `notifications/resources/updated`; returns the unsubscribe function. */
|
|
115
|
+
onResourceUpdated(listener: (uri: string) => void): () => void;
|
|
116
|
+
/** Observes `notifications/resources/list_changed` with the refreshed catalog. */
|
|
117
|
+
onResourceListChanged(listener: (resources: ReadonlyArray<McpResourceInfo>, error?: string) => void): () => void;
|
|
118
|
+
/** Reports a subscription that could not be restored after a reconnect (RFC 0015 §5). */
|
|
119
|
+
onSubscriptionLost(listener: (uri: string, reason: string) => void): () => void;
|
|
120
|
+
close(): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/** The resource clause one MCP server contributes: its id, operations and diagnostic data. */
|
|
123
|
+
export interface McpResourceDef {
|
|
124
|
+
resource_ref: ResourceRef;
|
|
125
|
+
operations: string[];
|
|
126
|
+
binding: {
|
|
127
|
+
handler: NodeId;
|
|
128
|
+
cursor: string;
|
|
129
|
+
};
|
|
130
|
+
log_readers: NodeId[];
|
|
131
|
+
data: Json;
|
|
132
|
+
}
|
|
133
|
+
/** Extra server facts a registration can carry; both are optional for a down server. */
|
|
134
|
+
export interface McpResourceOptions {
|
|
135
|
+
/** The `resources/list` catalog; absent when the server is unreachable or declares none. */
|
|
136
|
+
resources?: ReadonlyArray<McpResourceInfo>;
|
|
137
|
+
/** The capabilities negotiated at `initialize`, when the session is up. */
|
|
138
|
+
capabilities?: ServerCapabilities;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Builds the Onlyne resource a server maps to (RFC 0015 §2: one server, one tool set). The
|
|
142
|
+
* `data` field is the human- and machine-readable description: concrete tool names and resource
|
|
143
|
+
* URIs, the transport, the negotiated `features` (capabilities) and the local tool policy.
|
|
144
|
+
*/
|
|
145
|
+
export declare function resourceOf(owner: string, server: McpServerOptions, tools: ReadonlyArray<McpToolInfo>, controllers: string[], options?: McpResourceOptions): McpResourceDef;
|
|
146
|
+
/** Host callbacks for the resource primitives; all are observations, never business writes. */
|
|
147
|
+
export interface McpBridgeOptions {
|
|
148
|
+
onResourceUpdated?(server: string, uri: string): void;
|
|
149
|
+
onResourceListChanged?(server: string, resources: ReadonlyArray<McpResourceInfo>, error?: string): void;
|
|
150
|
+
onSubscriptionLost?(server: string, uri: string, reason: string): void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The bridge from resource operations to `tools/call`, plus the read-only MCP resource
|
|
154
|
+
* primitives. `validate` asks the connection for the operation's catalog entry: a name the live
|
|
155
|
+
* session does not list refreshes `tools/list` once, so a catalog a previous run cached stays
|
|
156
|
+
* callable while a name the server really dropped never reaches `tools/call` (RFC 0015 §2, §4).
|
|
157
|
+
*/
|
|
158
|
+
export interface McpBridge extends ResourceDriver {
|
|
159
|
+
listResources(server: string, cursor?: string): Promise<McpResourcePage>;
|
|
160
|
+
readResource(server: string, uri: string): Promise<Json>;
|
|
161
|
+
subscribe(server: string, uri: string): Promise<void>;
|
|
162
|
+
unsubscribe(server: string, uri: string): Promise<void>;
|
|
163
|
+
/** Subscribes every listed resource and withdraws vanished ones; returns the live set. */
|
|
164
|
+
syncSubscriptions(server: string): Promise<string[]>;
|
|
165
|
+
/** Withdraws every subscription this bridge made. */
|
|
166
|
+
close(): Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
export declare function createMcpBridge(servers: Array<{
|
|
169
|
+
server: McpServerOptions;
|
|
170
|
+
connection: McpConnection;
|
|
171
|
+
}>, options?: McpBridgeOptions): McpBridge;
|
|
172
|
+
export {};
|
|
173
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { OnlyneError } from '@onlyne/lib';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
5
|
+
import { ErrorCode, McpError, ResourceListChangedNotificationSchema, ResourceUpdatedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
|
+
/** A resources/list loop that never ends is a broken server, not a large catalog. */
|
|
8
|
+
const MAX_RESOURCE_PAGES = 1_000;
|
|
9
|
+
const wire = (value) => JSON.parse(JSON.stringify(value));
|
|
10
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
11
|
+
/**
|
|
12
|
+
* One MCP server connection on the official SDK: `Client` over either the streamable HTTP
|
|
13
|
+
* transport or a spawned stdio child. The SDK owns the session id, the handshake, SSE parsing,
|
|
14
|
+
* reconnect semantics and the stdio process lifecycle; this class is the façade the bridge and
|
|
15
|
+
* the gateway see. The local tool policy lives here, at the choke point every caller (resource
|
|
16
|
+
* operations, scheduled mounts, direct host calls) shares.
|
|
17
|
+
*/
|
|
18
|
+
export class McpConnection {
|
|
19
|
+
#client;
|
|
20
|
+
#connecting;
|
|
21
|
+
#closed = false;
|
|
22
|
+
#options;
|
|
23
|
+
#allowed;
|
|
24
|
+
#tools = [];
|
|
25
|
+
#resources = [];
|
|
26
|
+
#subscribed = new Set();
|
|
27
|
+
#resourceUpdatedListeners = new Set();
|
|
28
|
+
#resourceListChangedListeners = new Set();
|
|
29
|
+
#subscriptionLostListeners = new Set();
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#options = options;
|
|
32
|
+
this.#allowed = options.allowedTools === undefined ? undefined : new Set(options.allowedTools);
|
|
33
|
+
}
|
|
34
|
+
get name() { return this.#options.name; }
|
|
35
|
+
get transport() {
|
|
36
|
+
return this.#options.transport === 'stdio' ? 'stdio' : 'http';
|
|
37
|
+
}
|
|
38
|
+
/** The HTTP endpoint; empty for a stdio server, which has no URL. */
|
|
39
|
+
get url() { return this.#options.transport === 'stdio' ? '' : this.#options.url; }
|
|
40
|
+
get tools() { return this.#tools; }
|
|
41
|
+
/** The last `resources/list` result; empty when the server declares no resources capability. */
|
|
42
|
+
get resources() { return this.#resources; }
|
|
43
|
+
/** What the server negotiated at `initialize`; undefined before the first successful connect. */
|
|
44
|
+
get capabilities() {
|
|
45
|
+
return this.#client?.getServerCapabilities();
|
|
46
|
+
}
|
|
47
|
+
get canListResources() { return this.capabilities?.resources !== undefined; }
|
|
48
|
+
get canSubscribe() { return this.capabilities?.resources?.subscribe === true; }
|
|
49
|
+
/** The URIs this node currently holds a subscription for. */
|
|
50
|
+
get subscriptions() { return [...this.#subscribed]; }
|
|
51
|
+
/** The local policy decision for one tool name. */
|
|
52
|
+
allows(tool) {
|
|
53
|
+
return this.#allowed === undefined || this.#allowed.has(tool);
|
|
54
|
+
}
|
|
55
|
+
/** Connects, reads the tool list and records the negotiated capabilities. */
|
|
56
|
+
async start() {
|
|
57
|
+
this.#tools = await this.refreshTools();
|
|
58
|
+
}
|
|
59
|
+
async refreshTools() {
|
|
60
|
+
const client = await this.#ensureConnected();
|
|
61
|
+
const result = await client.listTools(undefined, this.#requestOptions());
|
|
62
|
+
this.#tools = result.tools.map(tool => ({
|
|
63
|
+
name: tool.name,
|
|
64
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
65
|
+
...(tool.annotations === undefined ? {} : { annotations: { readOnlyHint: tool.annotations.readOnlyHint } }),
|
|
66
|
+
}));
|
|
67
|
+
return this.#tools;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The catalog entry for one operation. A name the current list already holds answers from the
|
|
71
|
+
* list; any other name refreshes `tools/list` once — connecting first when the session is down
|
|
72
|
+
* — so a catalog a previous run cached cannot turn a reachable tool into
|
|
73
|
+
* `unsupported_delivery`, and a name the live server does not list stays `unsupported_delivery`.
|
|
74
|
+
* The local policy runs before either, so a denied tool never reaches the network (RFC 0015 §4).
|
|
75
|
+
*/
|
|
76
|
+
async resolveTool(name) {
|
|
77
|
+
if (!this.allows(name)) {
|
|
78
|
+
throw new OnlyneError('forbidden', `The local policy does not allow the tool ${name} on ${this.name}.`);
|
|
79
|
+
}
|
|
80
|
+
if (this.#closed) {
|
|
81
|
+
throw new OnlyneError('unavailable', `The MCP connection ${this.name} is closed.`);
|
|
82
|
+
}
|
|
83
|
+
const known = this.#tools.find(tool => tool.name === name);
|
|
84
|
+
if (known !== undefined)
|
|
85
|
+
return known;
|
|
86
|
+
try {
|
|
87
|
+
await this.refreshTools();
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof OnlyneError)
|
|
91
|
+
throw error;
|
|
92
|
+
throw new OnlyneError('unavailable', `The MCP server ${this.name} could not list its tools.`, { source: messageOf(error) });
|
|
93
|
+
}
|
|
94
|
+
const tool = this.#tools.find(candidate => candidate.name === name);
|
|
95
|
+
if (tool === undefined) {
|
|
96
|
+
throw new OnlyneError('unsupported_delivery', `The tool ${name} is not in ${this.name}'s tools/list.`);
|
|
97
|
+
}
|
|
98
|
+
return tool;
|
|
99
|
+
}
|
|
100
|
+
async call(name, arguments_) {
|
|
101
|
+
// The policy is checked before `#ensureConnected`, so a denied tool never reaches the network
|
|
102
|
+
// (and a scheduled mount cannot bypass the policy by calling the connection directly).
|
|
103
|
+
if (!this.allows(name)) {
|
|
104
|
+
throw new OnlyneError('forbidden', `The local policy does not allow the tool ${name} on ${this.name}.`);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const client = await this.#ensureConnected();
|
|
108
|
+
const result = await client.callTool({ name, arguments: arguments_ }, undefined, this.#requestOptions());
|
|
109
|
+
if (result.isError === true) {
|
|
110
|
+
return { kind: 'rejected', text: textOf(result.content) };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
kind: 'result',
|
|
114
|
+
...(result.content === undefined ? {} : { content: result.content }),
|
|
115
|
+
...(result.structuredContent === undefined ? {} : { structuredContent: result.structuredContent }),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof OnlyneError)
|
|
120
|
+
throw error;
|
|
121
|
+
// A transport failure clears the client handle so the next call attempts a fresh handshake.
|
|
122
|
+
this.#client = undefined;
|
|
123
|
+
return { kind: 'unknown', text: messageOf(error) };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** One page of `resources/list`; the cursor is the server's own value. */
|
|
127
|
+
async listResources(cursor) {
|
|
128
|
+
return this.#resourceRequest('list resources', async () => {
|
|
129
|
+
const client = await this.#ensureConnected();
|
|
130
|
+
this.#requireResourceCapability(client);
|
|
131
|
+
const result = await client.listResources(cursor === undefined ? undefined : { cursor }, this.#requestOptions());
|
|
132
|
+
return {
|
|
133
|
+
resources: result.resources,
|
|
134
|
+
...(result.nextCursor === undefined ? {} : { nextCursor: result.nextCursor }),
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** The complete catalog, walking `nextCursor` pages. */
|
|
139
|
+
async refreshResources() {
|
|
140
|
+
const resources = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
let cursor;
|
|
143
|
+
for (let page = 0; page < MAX_RESOURCE_PAGES; page++) {
|
|
144
|
+
const result = await this.listResources(cursor);
|
|
145
|
+
resources.push(...result.resources);
|
|
146
|
+
cursor = result.nextCursor;
|
|
147
|
+
if (cursor === undefined) {
|
|
148
|
+
this.#resources = resources;
|
|
149
|
+
return this.#resources;
|
|
150
|
+
}
|
|
151
|
+
if (seen.has(cursor)) {
|
|
152
|
+
throw new OnlyneError('unavailable', `The MCP server ${this.name} repeated a resources page cursor.`);
|
|
153
|
+
}
|
|
154
|
+
seen.add(cursor);
|
|
155
|
+
}
|
|
156
|
+
throw new OnlyneError('unavailable', `The MCP server ${this.name} returned more than ${MAX_RESOURCE_PAGES} resource pages.`);
|
|
157
|
+
}
|
|
158
|
+
/** A read-only `resources/read`; the contents are data and never become a message. */
|
|
159
|
+
async readResource(uri) {
|
|
160
|
+
this.#requireUri(uri);
|
|
161
|
+
return this.#resourceRequest(`read ${uri}`, async () => {
|
|
162
|
+
const client = await this.#ensureConnected();
|
|
163
|
+
this.#requireResourceCapability(client);
|
|
164
|
+
return client.readResource({ uri }, this.#requestOptions());
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Subscribes this node to one resource's change notifications (RFC 0015 §4: a local action). */
|
|
168
|
+
async subscribe(uri) {
|
|
169
|
+
this.#requireUri(uri);
|
|
170
|
+
await this.#resourceRequest(`subscribe to ${uri}`, async () => {
|
|
171
|
+
const client = await this.#ensureConnected();
|
|
172
|
+
this.#requireSubscribeCapability(client);
|
|
173
|
+
if (this.#subscribed.has(uri))
|
|
174
|
+
return;
|
|
175
|
+
await client.subscribeResource({ uri }, this.#requestOptions());
|
|
176
|
+
this.#subscribed.add(uri);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Withdraws a subscription; a URI this node never subscribed costs no request. */
|
|
180
|
+
async unsubscribe(uri) {
|
|
181
|
+
this.#requireUri(uri);
|
|
182
|
+
const client = this.#client;
|
|
183
|
+
this.#subscribed.delete(uri);
|
|
184
|
+
if (client === undefined)
|
|
185
|
+
return;
|
|
186
|
+
await this.#resourceRequest(`unsubscribe from ${uri}`, () => client.unsubscribeResource({ uri }, this.#requestOptions()));
|
|
187
|
+
}
|
|
188
|
+
/** Withdraws every live subscription, leaving the session open. */
|
|
189
|
+
async unsubscribeAll() {
|
|
190
|
+
await Promise.all([...this.#subscribed].map(uri => this.unsubscribe(uri).catch(() => undefined)));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Aligns subscriptions with a catalog: subscribes newly listed resources and withdraws the
|
|
194
|
+
* ones that disappeared. A server without `resources.subscribe` simply holds no subscriptions.
|
|
195
|
+
*/
|
|
196
|
+
async syncSubscriptions(resources) {
|
|
197
|
+
const client = await this.#resourceRequest('list resources', () => this.#ensureConnected());
|
|
198
|
+
if (client.getServerCapabilities()?.resources?.subscribe !== true)
|
|
199
|
+
return [];
|
|
200
|
+
const catalog = resources ?? await this.refreshResources();
|
|
201
|
+
const desired = new Set(catalog.map(resource => resource.uri));
|
|
202
|
+
for (const uri of [...this.#subscribed]) {
|
|
203
|
+
if (!desired.has(uri))
|
|
204
|
+
await this.unsubscribe(uri);
|
|
205
|
+
}
|
|
206
|
+
for (const uri of desired) {
|
|
207
|
+
if (!this.#subscribed.has(uri))
|
|
208
|
+
await this.subscribe(uri);
|
|
209
|
+
}
|
|
210
|
+
return [...this.#subscribed];
|
|
211
|
+
}
|
|
212
|
+
/** Observes `notifications/resources/updated`; returns the unsubscribe function. */
|
|
213
|
+
onResourceUpdated(listener) {
|
|
214
|
+
this.#resourceUpdatedListeners.add(listener);
|
|
215
|
+
return () => { this.#resourceUpdatedListeners.delete(listener); };
|
|
216
|
+
}
|
|
217
|
+
/** Observes `notifications/resources/list_changed` with the refreshed catalog. */
|
|
218
|
+
onResourceListChanged(listener) {
|
|
219
|
+
this.#resourceListChangedListeners.add(listener);
|
|
220
|
+
return () => { this.#resourceListChangedListeners.delete(listener); };
|
|
221
|
+
}
|
|
222
|
+
/** Reports a subscription that could not be restored after a reconnect (RFC 0015 §5). */
|
|
223
|
+
onSubscriptionLost(listener) {
|
|
224
|
+
this.#subscriptionLostListeners.add(listener);
|
|
225
|
+
return () => { this.#subscriptionLostListeners.delete(listener); };
|
|
226
|
+
}
|
|
227
|
+
async close() {
|
|
228
|
+
// RFC 0015 §4: closing the bridge withdraws its subscriptions before the session goes.
|
|
229
|
+
const client = this.#client;
|
|
230
|
+
await this.unsubscribeAll();
|
|
231
|
+
this.#closed = true;
|
|
232
|
+
this.#client = undefined;
|
|
233
|
+
this.#subscribed.clear();
|
|
234
|
+
if (client) {
|
|
235
|
+
await client.close().catch(() => undefined);
|
|
236
|
+
}
|
|
237
|
+
this.#resourceUpdatedListeners.clear();
|
|
238
|
+
this.#resourceListChangedListeners.clear();
|
|
239
|
+
this.#subscriptionLostListeners.clear();
|
|
240
|
+
}
|
|
241
|
+
/** Reconnecting re-runs initialize, as RFC 0015 §5 requires; the SDK throws when offline. */
|
|
242
|
+
async #ensureConnected() {
|
|
243
|
+
if (this.#closed) {
|
|
244
|
+
throw new OnlyneError('unavailable', `The MCP connection ${this.name} is closed.`);
|
|
245
|
+
}
|
|
246
|
+
if (this.#client !== undefined)
|
|
247
|
+
return this.#client;
|
|
248
|
+
// Concurrent first calls share one handshake: a second attempt would spawn a second child.
|
|
249
|
+
this.#connecting ??= this.#open();
|
|
250
|
+
const attempt = this.#connecting;
|
|
251
|
+
try {
|
|
252
|
+
return await attempt;
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
if (this.#connecting === attempt)
|
|
256
|
+
this.#connecting = undefined;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async #open() {
|
|
260
|
+
const client = new Client({ name: 'onlyne-mcp', version: '3.0.0' }, { capabilities: {} });
|
|
261
|
+
await client.connect(this.#transport(), this.#requestOptions());
|
|
262
|
+
if (this.#closed) {
|
|
263
|
+
// `close()` ran during the handshake and had no client to close, so this one must not leak.
|
|
264
|
+
await client.close().catch(() => undefined);
|
|
265
|
+
throw new OnlyneError('unavailable', `The MCP connection ${this.name} is closed.`);
|
|
266
|
+
}
|
|
267
|
+
this.#client = client;
|
|
268
|
+
this.#installNotificationHandlers(client);
|
|
269
|
+
await this.#restoreSubscriptions(client);
|
|
270
|
+
return client;
|
|
271
|
+
}
|
|
272
|
+
#transport() {
|
|
273
|
+
const options = this.#options;
|
|
274
|
+
if (options.transport === 'stdio') {
|
|
275
|
+
// The SDK spawns `command` with `args` directly (`shell: false`); no string is interpolated.
|
|
276
|
+
return new StdioClientTransport({
|
|
277
|
+
command: options.command,
|
|
278
|
+
...(options.args === undefined ? {} : { args: [...options.args] }),
|
|
279
|
+
...(options.env === undefined ? {} : { env: { ...options.env } }),
|
|
280
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return new StreamableHTTPClientTransport(new URL(options.url), {
|
|
284
|
+
requestInit: { headers: options.headers },
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
#installNotificationHandlers(client) {
|
|
288
|
+
client.setNotificationHandler(ResourceUpdatedNotificationSchema, notification => {
|
|
289
|
+
// The notification only reports the URI; the body is a later `resources/read` (RFC 0015 §2).
|
|
290
|
+
for (const listener of [...this.#resourceUpdatedListeners])
|
|
291
|
+
listener(notification.params.uri);
|
|
292
|
+
});
|
|
293
|
+
client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
|
|
294
|
+
void this.refreshResources().then(resources => {
|
|
295
|
+
for (const listener of [...this.#resourceListChangedListeners])
|
|
296
|
+
listener(resources);
|
|
297
|
+
}, error => {
|
|
298
|
+
for (const listener of [...this.#resourceListChangedListeners])
|
|
299
|
+
listener(this.#resources, messageOf(error));
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/** RFC 0015 §5: a reconnect must re-subscribe; a failed restore is reported, not faked. */
|
|
304
|
+
async #restoreSubscriptions(client) {
|
|
305
|
+
const pending = [...this.#subscribed];
|
|
306
|
+
if (pending.length === 0)
|
|
307
|
+
return;
|
|
308
|
+
if (client.getServerCapabilities()?.resources?.subscribe !== true) {
|
|
309
|
+
for (const uri of pending) {
|
|
310
|
+
this.#subscribed.delete(uri);
|
|
311
|
+
this.#emitSubscriptionLost(uri, 'The server no longer declares the resources.subscribe capability.');
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
for (const uri of pending) {
|
|
316
|
+
try {
|
|
317
|
+
await client.subscribeResource({ uri }, this.#requestOptions());
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
this.#subscribed.delete(uri);
|
|
321
|
+
this.#emitSubscriptionLost(uri, messageOf(error));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
#emitSubscriptionLost(uri, reason) {
|
|
326
|
+
for (const listener of [...this.#subscriptionLostListeners])
|
|
327
|
+
listener(uri, reason);
|
|
328
|
+
}
|
|
329
|
+
#requestOptions() {
|
|
330
|
+
return { resetTimeoutOnProgress: false, timeout: this.#options.timeoutMs ?? DEFAULT_TIMEOUT_MS };
|
|
331
|
+
}
|
|
332
|
+
#requireUri(uri) {
|
|
333
|
+
if (typeof uri !== 'string' || uri.length === 0) {
|
|
334
|
+
throw new OnlyneError('invalid_request', 'A resource URI is a non-empty string.');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
#requireResourceCapability(client) {
|
|
338
|
+
if (client.getServerCapabilities()?.resources === undefined) {
|
|
339
|
+
throw new OnlyneError('unsupported_delivery', `The MCP server ${this.name} declares no resources capability.`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
#requireSubscribeCapability(client) {
|
|
343
|
+
if (client.getServerCapabilities()?.resources?.subscribe !== true) {
|
|
344
|
+
throw new OnlyneError('unsupported_delivery', `The MCP server ${this.name} does not declare resources.subscribe.`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/** Maps SDK and transport failures for the resource primitives, never a bare string. */
|
|
348
|
+
async #resourceRequest(action, run) {
|
|
349
|
+
try {
|
|
350
|
+
return await run();
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
if (error instanceof OnlyneError)
|
|
354
|
+
throw error;
|
|
355
|
+
if (error instanceof McpError) {
|
|
356
|
+
if (error.code === ErrorCode.MethodNotFound) {
|
|
357
|
+
throw new OnlyneError('unsupported_delivery', `The MCP server ${this.name} does not support ${action}.`, { source: error.message });
|
|
358
|
+
}
|
|
359
|
+
if (error.code === ErrorCode.InvalidParams || error.code === ErrorCode.InvalidRequest) {
|
|
360
|
+
throw new OnlyneError('invalid_request', `The MCP server ${this.name} rejected ${action}.`, { source: error.message });
|
|
361
|
+
}
|
|
362
|
+
if (error.code === ErrorCode.ConnectionClosed)
|
|
363
|
+
this.#client = undefined;
|
|
364
|
+
throw new OnlyneError('unavailable', `The MCP server ${this.name} could not ${action}.`, { source: error.message, code: error.code });
|
|
365
|
+
}
|
|
366
|
+
this.#client = undefined;
|
|
367
|
+
throw new OnlyneError('unavailable', `The MCP transport failed while trying to ${action}.`, { source: messageOf(error) });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/** One line of a diagnostic description, bounded so a chatty server cannot bloat the data. */
|
|
372
|
+
function summaryOf(text, limit = 160) {
|
|
373
|
+
if (text === undefined)
|
|
374
|
+
return '';
|
|
375
|
+
const line = text.replace(/\s+/g, ' ').trim();
|
|
376
|
+
return line.length <= limit ? line : `${line.slice(0, limit - 1)}…`;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Builds the Onlyne resource a server maps to (RFC 0015 §2: one server, one tool set). The
|
|
380
|
+
* `data` field is the human- and machine-readable description: concrete tool names and resource
|
|
381
|
+
* URIs, the transport, the negotiated `features` (capabilities) and the local tool policy.
|
|
382
|
+
*/
|
|
383
|
+
export function resourceOf(owner, server, tools, controllers, options = {}) {
|
|
384
|
+
const resources = options.resources ?? [];
|
|
385
|
+
const capabilities = options.capabilities;
|
|
386
|
+
const location = server.transport === 'stdio'
|
|
387
|
+
? {
|
|
388
|
+
transport: 'stdio',
|
|
389
|
+
command: server.command,
|
|
390
|
+
...(server.args === undefined || server.args.length === 0 ? {} : { args: server.args }),
|
|
391
|
+
...(server.cwd === undefined ? {} : { cwd: server.cwd }),
|
|
392
|
+
}
|
|
393
|
+
: { transport: 'http', url: server.url };
|
|
394
|
+
const transport = location.transport;
|
|
395
|
+
const toolLine = tools.length === 0
|
|
396
|
+
? 'no tools'
|
|
397
|
+
: `tools ${tools.map(tool => {
|
|
398
|
+
const summary = summaryOf(tool.description);
|
|
399
|
+
return summary.length === 0 ? tool.name : `${tool.name} (${summary})`;
|
|
400
|
+
}).join(', ')}`;
|
|
401
|
+
const resourceLine = resources.length === 0
|
|
402
|
+
? 'no resources'
|
|
403
|
+
: `resources ${resources.map(resource => {
|
|
404
|
+
const summary = summaryOf(resource.description);
|
|
405
|
+
return summary.length === 0 ? resource.uri : `${resource.uri} (${summary})`;
|
|
406
|
+
}).join(', ')}`;
|
|
407
|
+
return {
|
|
408
|
+
resource_ref: { owner, id: server.name },
|
|
409
|
+
operations: tools.map(tool => tool.name),
|
|
410
|
+
binding: { handler: owner, cursor: '0' },
|
|
411
|
+
log_readers: controllers.length > 0 ? controllers : [owner],
|
|
412
|
+
data: wire({
|
|
413
|
+
kind: 'mcp_server',
|
|
414
|
+
...location,
|
|
415
|
+
...(server.allowedTools === undefined ? {} : { allowed_tools: server.allowedTools }),
|
|
416
|
+
tools: tools.map(tool => ({
|
|
417
|
+
name: tool.name,
|
|
418
|
+
...(typeof tool.description === 'string' ? { description: summaryOf(tool.description, 200) } : {}),
|
|
419
|
+
...(tool.annotations?.readOnlyHint === true ? { read_only: true } : {}),
|
|
420
|
+
})),
|
|
421
|
+
resources: resources.map(resource => ({
|
|
422
|
+
uri: resource.uri,
|
|
423
|
+
name: resource.name,
|
|
424
|
+
...(typeof resource.description === 'string' ? { description: summaryOf(resource.description, 200) } : {}),
|
|
425
|
+
...(typeof resource.mimeType === 'string' ? { mime_type: resource.mimeType } : {}),
|
|
426
|
+
})),
|
|
427
|
+
description: `MCP server ${server.name} over ${transport}: ${toolLine}; ${resourceLine}.`,
|
|
428
|
+
...(capabilities === undefined ? {} : {
|
|
429
|
+
features: {
|
|
430
|
+
tools: capabilities.tools !== undefined,
|
|
431
|
+
resources: capabilities.resources !== undefined,
|
|
432
|
+
resource_subscribe: capabilities.resources?.subscribe === true,
|
|
433
|
+
resource_list_changed: capabilities.resources?.listChanged === true,
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
...(controllers.length > 0 ? {} : { note: 'owner only' }),
|
|
437
|
+
}),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
export function createMcpBridge(servers, options = {}) {
|
|
441
|
+
const byServer = new Map(servers.map(entry => [entry.server.name, entry]));
|
|
442
|
+
const disposers = [];
|
|
443
|
+
let closed = false;
|
|
444
|
+
for (const entry of servers) {
|
|
445
|
+
const name = entry.server.name;
|
|
446
|
+
disposers.push(entry.connection.onResourceUpdated(uri => {
|
|
447
|
+
// A change notification is data: it never becomes an agent message or a thread entry.
|
|
448
|
+
options.onResourceUpdated?.(name, uri);
|
|
449
|
+
}));
|
|
450
|
+
disposers.push(entry.connection.onResourceListChanged((resources, error) => {
|
|
451
|
+
void entry.connection.syncSubscriptions(resources).then(() => { options.onResourceListChanged?.(name, resources, error); }, syncError => {
|
|
452
|
+
options.onResourceListChanged?.(name, resources, error ?? messageOf(syncError));
|
|
453
|
+
});
|
|
454
|
+
}));
|
|
455
|
+
disposers.push(entry.connection.onSubscriptionLost((uri, reason) => {
|
|
456
|
+
options.onSubscriptionLost?.(name, uri, reason);
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
function requireServer(name) {
|
|
460
|
+
if (closed)
|
|
461
|
+
throw new OnlyneError('unavailable', 'The MCP bridge is closed.');
|
|
462
|
+
const entry = byServer.get(name);
|
|
463
|
+
if (entry === undefined) {
|
|
464
|
+
throw new OnlyneError('invalid_request', `No MCP server serves the resource ${name}.`);
|
|
465
|
+
}
|
|
466
|
+
return entry;
|
|
467
|
+
}
|
|
468
|
+
async function requireTool(ref, operation) {
|
|
469
|
+
const entry = requireServer(ref.id);
|
|
470
|
+
// `resolveTool` checks the policy before any request (a denied tool is `forbidden`, not
|
|
471
|
+
// `unsupported`) and refreshes the catalog once when the name is not in the pinned list.
|
|
472
|
+
const tool = await entry.connection.resolveTool(operation);
|
|
473
|
+
return { entry, tool };
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
async validate(resource, operation, data) {
|
|
477
|
+
await requireTool(resource.resource_ref, operation);
|
|
478
|
+
// `arguments` for `tools/call` is an object when present (RFC 0015 §2).
|
|
479
|
+
if (data !== undefined && (data === null || typeof data !== 'object' || Array.isArray(data))) {
|
|
480
|
+
throw new OnlyneError('invalid_request', `The arguments for ${operation} are an object.`);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
async execute(resource, request) {
|
|
484
|
+
const { entry, tool } = await requireTool(resource.resource_ref, request.operation);
|
|
485
|
+
const arguments_ = (request.data ?? {});
|
|
486
|
+
const outcome = await entry.connection.call(request.operation, arguments_);
|
|
487
|
+
if (outcome.kind === 'unknown') {
|
|
488
|
+
// The transport never gave an answer: the call may or may not have run (RFC 0015 §3).
|
|
489
|
+
return { state: 'unknown', error: { code: 'unavailable', message: 'The MCP server gave no answer.', retryable: true, details: { source: outcome.text } } };
|
|
490
|
+
}
|
|
491
|
+
if (outcome.kind === 'rejected') {
|
|
492
|
+
// A read-only tool cannot have side effects, so its failure is a settled abort; a
|
|
493
|
+
// side-effecting tool's failure proves nothing about what it already did.
|
|
494
|
+
if (tool.annotations?.readOnlyHint === true) {
|
|
495
|
+
return { state: 'aborted', error: { code: 'unavailable', message: 'The tool reported failure.', retryable: false, details: { source: outcome.text } } };
|
|
496
|
+
}
|
|
497
|
+
return { state: 'unknown', error: { code: 'unavailable', message: 'The tool reported failure; the side effect is unconfirmed.', retryable: true, details: { source: outcome.text } } };
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
state: 'committed',
|
|
501
|
+
data: wire({
|
|
502
|
+
...(outcome.content === undefined ? {} : { content: outcome.content }),
|
|
503
|
+
...(outcome.structuredContent === undefined ? {} : { structuredContent: outcome.structuredContent }),
|
|
504
|
+
}),
|
|
505
|
+
};
|
|
506
|
+
},
|
|
507
|
+
async listResources(server, cursor) {
|
|
508
|
+
return requireServer(server).connection.listResources(cursor);
|
|
509
|
+
},
|
|
510
|
+
async readResource(server, uri) {
|
|
511
|
+
// Data only: no message admission, no thread entry, no business-state write.
|
|
512
|
+
return wire(await requireServer(server).connection.readResource(uri));
|
|
513
|
+
},
|
|
514
|
+
async subscribe(server, uri) {
|
|
515
|
+
await requireServer(server).connection.subscribe(uri);
|
|
516
|
+
},
|
|
517
|
+
async unsubscribe(server, uri) {
|
|
518
|
+
await requireServer(server).connection.unsubscribe(uri);
|
|
519
|
+
},
|
|
520
|
+
async syncSubscriptions(server) {
|
|
521
|
+
return requireServer(server).connection.syncSubscriptions();
|
|
522
|
+
},
|
|
523
|
+
async close() {
|
|
524
|
+
if (closed)
|
|
525
|
+
return;
|
|
526
|
+
closed = true;
|
|
527
|
+
await Promise.all(servers.map(entry => entry.connection.unsubscribeAll()));
|
|
528
|
+
for (const dispose of disposers)
|
|
529
|
+
dispose();
|
|
530
|
+
disposers.length = 0;
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/** An error result's content is text blocks; their `text` fields are the readable message. */
|
|
535
|
+
function textOf(content) {
|
|
536
|
+
if (typeof content === 'string')
|
|
537
|
+
return content;
|
|
538
|
+
if (Array.isArray(content)) {
|
|
539
|
+
return content
|
|
540
|
+
.map(block => {
|
|
541
|
+
if (typeof block !== 'object' || block === null || !('text' in block))
|
|
542
|
+
return '';
|
|
543
|
+
return typeof block.text === 'string' ? block.text : '';
|
|
544
|
+
})
|
|
545
|
+
.filter(text => text.length > 0)
|
|
546
|
+
.join('\n');
|
|
547
|
+
}
|
|
548
|
+
return JSON.stringify(content ?? '');
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlyne/mcp-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "MCP client session and resource driver that maps tools/list and tools/call onto Onlyne resource operations",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -17,16 +17,21 @@
|
|
|
17
17
|
"publishConfig": { "access": "public" },
|
|
18
18
|
"engines": { "bun": ">=1.4.3" },
|
|
19
19
|
"exports": {
|
|
20
|
-
".":
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
21
24
|
},
|
|
22
|
-
"files": ["
|
|
25
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
23
26
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"check": "tsc && tsc -p tsconfig.tests.json",
|
|
29
|
+
"test": "bun tests/verify.ts",
|
|
30
|
+
"prepack": "bun run build"
|
|
26
31
|
},
|
|
27
32
|
"dependencies": {
|
|
28
33
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
29
|
-
"@onlyne/lib": "^3.
|
|
34
|
+
"@onlyne/lib": "^3.2.0"
|
|
30
35
|
},
|
|
31
36
|
"devDependencies": {
|
|
32
37
|
"@types/bun": "^1.3.0",
|
package/src/index.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { Json, NodeId, Resource, ResourceDriver, ResourceRef } from '@onlyne/lib';
|
|
2
|
-
import { OnlyneError } from '@onlyne/lib';
|
|
3
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamablehttp.js';
|
|
5
|
-
|
|
6
|
-
export interface McpServerOptions {
|
|
7
|
-
name: string;
|
|
8
|
-
url: string;
|
|
9
|
-
/** Extra headers per request: Hindsight puts its API key here when enabled. */
|
|
10
|
-
headers?: Record<string, string>;
|
|
11
|
-
/** Per-request timeout for tool calls; the default matches the library's driver wait. */
|
|
12
|
-
timeoutMs?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** The result of one tool call, classified for the operation's three-way outcome (RFC 0015 §3). */
|
|
16
|
-
export type McpOutcome =
|
|
17
|
-
| { kind: 'result'; content?: Json; structuredContent?: Json }
|
|
18
|
-
| { kind: 'rejected'; text: string }
|
|
19
|
-
| { kind: 'unknown'; text: string };
|
|
20
|
-
|
|
21
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
22
|
-
const wire = (value: object): Json => JSON.parse(JSON.stringify(value)) as Json;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* One MCP server connection on the official SDK: `Client` over the streamable HTTP transport.
|
|
26
|
-
* The SDK owns the session id, the handshake, SSE parsing and the reconnect semantics; this
|
|
27
|
-
* class is only the façade the bridge and the gateway see.
|
|
28
|
-
*/
|
|
29
|
-
export class McpConnection {
|
|
30
|
-
#client: Client | undefined;
|
|
31
|
-
readonly #options: McpServerOptions;
|
|
32
|
-
#tools: Array<{ name: string; description?: string; annotations?: { readOnlyHint?: boolean } }> = [];
|
|
33
|
-
|
|
34
|
-
constructor(options: McpServerOptions) {
|
|
35
|
-
this.#options = options;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get url(): string { return this.#options.url; }
|
|
39
|
-
|
|
40
|
-
get tools(): Array<{ name: string; description?: string; annotations?: { readOnlyHint?: boolean } }> { return this.#tools; }
|
|
41
|
-
|
|
42
|
-
/** Connects and reads the tool list; the SDK handles initialize and the session handshake. */
|
|
43
|
-
async start(): Promise<void> {
|
|
44
|
-
this.#tools = await this.refreshTools();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async refreshTools(): Promise<Array<{ name: string; description?: string; annotations?: { readOnlyHint?: boolean } }>> {
|
|
48
|
-
const client = await this.#ensureConnected();
|
|
49
|
-
const result = await client.listTools();
|
|
50
|
-
this.#tools = result.tools.map(tool => ({
|
|
51
|
-
name: tool.name,
|
|
52
|
-
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
53
|
-
...(tool.annotations === undefined ? {} : { annotations: { readOnlyHint: tool.annotations.readOnlyHint } }),
|
|
54
|
-
}));
|
|
55
|
-
return this.#tools;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async call(name: string, arguments_: Json): Promise<McpOutcome> {
|
|
59
|
-
try {
|
|
60
|
-
const client = await this.#ensureConnected();
|
|
61
|
-
const result = await client.callTool({ name, arguments: arguments_ as Record<string, unknown> },
|
|
62
|
-
undefined, { resetTimeoutOnProgress: false, timeout: this.#options.timeoutMs ?? DEFAULT_TIMEOUT_MS });
|
|
63
|
-
if (result.isError === true) {
|
|
64
|
-
return { kind: 'rejected', text: textOf(result.content) };
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
kind: 'result',
|
|
68
|
-
...(result.content === undefined ? {} : { content: result.content as Json }),
|
|
69
|
-
...(result.structuredContent === undefined ? {} : { structuredContent: result.structuredContent as Json }),
|
|
70
|
-
};
|
|
71
|
-
} catch (error) {
|
|
72
|
-
// A transport failure clears the client handle so the next call attempts a fresh handshake.
|
|
73
|
-
this.#client = undefined;
|
|
74
|
-
return { kind: 'unknown', text: error instanceof Error ? error.message : String(error) };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async close(): Promise<void> {
|
|
79
|
-
const client = this.#client;
|
|
80
|
-
this.#client = undefined;
|
|
81
|
-
if (client) await client.close().catch(() => undefined);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Reconnecting re-runs initialize, as RFC 0015 §5 requires; the SDK throws when offline. */
|
|
85
|
-
async #ensureConnected(): Promise<Client> {
|
|
86
|
-
if (this.#client !== undefined) return this.#client;
|
|
87
|
-
const transport = new StreamableHTTPClientTransport(new URL(this.#options.url), {
|
|
88
|
-
requestInit: { headers: this.#options.headers },
|
|
89
|
-
});
|
|
90
|
-
const client = new Client({ name: 'onlyne-mcp', version: '3.0.0' }, { capabilities: {} });
|
|
91
|
-
await client.connect(transport);
|
|
92
|
-
this.#client = client;
|
|
93
|
-
return client;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** The resource clause one MCP server contributes: its id, operations and diagnostic data. */
|
|
98
|
-
export interface McpResourceDef {
|
|
99
|
-
resource_ref: ResourceRef;
|
|
100
|
-
operations: string[];
|
|
101
|
-
binding: { handler: NodeId; cursor: string };
|
|
102
|
-
log_readers: NodeId[];
|
|
103
|
-
data: Json;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Builds the Onlyne resource a server maps to (RFC 0015 §2: one server, one tool set). */
|
|
107
|
-
export function resourceOf(
|
|
108
|
-
owner: string,
|
|
109
|
-
server: McpServerOptions,
|
|
110
|
-
tools: ReadonlyArray<{ name: string; description?: string }>,
|
|
111
|
-
controllers: string[],
|
|
112
|
-
): McpResourceDef {
|
|
113
|
-
return {
|
|
114
|
-
resource_ref: { owner, id: server.name },
|
|
115
|
-
operations: tools.map(tool => tool.name),
|
|
116
|
-
binding: { handler: owner, cursor: '0' },
|
|
117
|
-
log_readers: controllers.length > 0 ? controllers : [owner],
|
|
118
|
-
data: wire({
|
|
119
|
-
kind: 'mcp_server',
|
|
120
|
-
url: server.url,
|
|
121
|
-
tools: tools.map(tool => ({
|
|
122
|
-
name: tool.name,
|
|
123
|
-
...(typeof tool.description === 'string' ? { description: tool.description.slice(0, 200) } : {}),
|
|
124
|
-
})),
|
|
125
|
-
...(controllers.length > 0 ? {} : { note: 'owner only' }),
|
|
126
|
-
}),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* The bridge from resource operations to `tools/call`. The tool list is pinned per server, so
|
|
132
|
-
* `validate` answers from the cache and a request for an unknown tool never leaves the node
|
|
133
|
-
* (RFC 0015 §2: `unsupported_delivery`, not sent).
|
|
134
|
-
*/
|
|
135
|
-
export function createMcpBridge(servers: Array<{ server: McpServerOptions; connection: McpConnection }>): ResourceDriver {
|
|
136
|
-
const byServer = new Map(servers.map(entry => [entry.server.name, entry]));
|
|
137
|
-
|
|
138
|
-
function requireTool(ref: ResourceRef, operation: string): { entry: { server: McpServerOptions; connection: McpConnection }; tool: { name: string; annotations?: { readOnlyHint?: boolean } } } {
|
|
139
|
-
const entry = byServer.get(ref.id);
|
|
140
|
-
if (entry === undefined) {
|
|
141
|
-
throw new OnlyneError('invalid_request', `No MCP server serves the resource ${ref.id}.`);
|
|
142
|
-
}
|
|
143
|
-
const tool = entry.connection.tools.find(candidate => candidate.name === operation);
|
|
144
|
-
if (tool === undefined) {
|
|
145
|
-
throw new OnlyneError('unsupported_delivery', `The tool ${operation} is not in ${ref.id}'s tools/list.`);
|
|
146
|
-
}
|
|
147
|
-
return { entry, tool };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
async validate(resource: Resource, operation: string, data: Json | undefined): Promise<void> {
|
|
152
|
-
requireTool(resource.resource_ref, operation);
|
|
153
|
-
// `arguments` for `tools/call` is an object when present (RFC 0015 §2).
|
|
154
|
-
if (data !== undefined && (data === null || typeof data !== 'object' || Array.isArray(data))) {
|
|
155
|
-
throw new OnlyneError('invalid_request', `The arguments for ${operation} are an object.`);
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
async execute(resource: Resource, request): Promise<Omit<import('@onlyne/lib').OperationResult, 'operation_id'>> {
|
|
160
|
-
const { entry, tool } = requireTool(resource.resource_ref, request.operation);
|
|
161
|
-
const arguments_ = (request.data ?? {}) as Json;
|
|
162
|
-
const outcome = await entry.connection.call(request.operation, arguments_);
|
|
163
|
-
if (outcome.kind === 'unknown') {
|
|
164
|
-
// The transport never gave an answer: the call may or may not have run (RFC 0015 §3).
|
|
165
|
-
return { state: 'unknown', error: { code: 'unavailable', message: 'The MCP server gave no answer.', retryable: true, details: { source: outcome.text } } };
|
|
166
|
-
}
|
|
167
|
-
if (outcome.kind === 'rejected') {
|
|
168
|
-
// A read-only tool cannot have side effects, so its failure is a settled abort; a
|
|
169
|
-
// side-effecting tool's failure proves nothing about what it already did.
|
|
170
|
-
if (tool.annotations?.readOnlyHint === true) {
|
|
171
|
-
return { state: 'aborted', error: { code: 'unavailable', message: 'The tool reported failure.', retryable: false, details: { source: outcome.text } } };
|
|
172
|
-
}
|
|
173
|
-
return { state: 'unknown', error: { code: 'unavailable', message: 'The tool reported failure; the side effect is unconfirmed.', retryable: true, details: { source: outcome.text } } };
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
state: 'committed',
|
|
177
|
-
data: wire({
|
|
178
|
-
...(outcome.content === undefined ? {} : { content: outcome.content }),
|
|
179
|
-
...(outcome.structuredContent === undefined ? {} : { structuredContent: outcome.structuredContent }),
|
|
180
|
-
}),
|
|
181
|
-
};
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** An error result's content is text blocks; their `text` fields are the readable message. */
|
|
187
|
-
function textOf(content: unknown): string {
|
|
188
|
-
if (typeof content === 'string') return content;
|
|
189
|
-
if (Array.isArray(content)) {
|
|
190
|
-
return content
|
|
191
|
-
.map(block => (typeof block === 'object' && block !== null && 'text' in block && typeof (block as { text?: unknown }).text === 'string'
|
|
192
|
-
? (block as { text: string }).text
|
|
193
|
-
: ''))
|
|
194
|
-
.filter(text => text.length > 0)
|
|
195
|
-
.join('\n');
|
|
196
|
-
}
|
|
197
|
-
return JSON.stringify(content ?? '');
|
|
198
|
-
}
|