@jini-ai/mcp 0.1.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/LICENSE +201 -0
- package/dist/agent-install/index.d.ts +9 -0
- package/dist/agent-install/index.d.ts.map +1 -0
- package/dist/agent-install/index.js +9 -0
- package/dist/agent-install/index.js.map +1 -0
- package/dist/agent-install/install.d.ts +97 -0
- package/dist/agent-install/install.d.ts.map +1 -0
- package/dist/agent-install/install.js +410 -0
- package/dist/agent-install/install.js.map +1 -0
- package/dist/bin/serve.d.ts +75 -0
- package/dist/bin/serve.d.ts.map +1 -0
- package/dist/bin/serve.js +79 -0
- package/dist/bin/serve.js.map +1 -0
- package/dist/client/client.d.ts +48 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +242 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/config.d.ts +157 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +475 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/install-info.d.ts +69 -0
- package/dist/core/install-info.d.ts.map +1 -0
- package/dist/core/install-info.js +79 -0
- package/dist/core/install-info.js.map +1 -0
- package/dist/core/oauth.d.ts +274 -0
- package/dist/core/oauth.d.ts.map +1 -0
- package/dist/core/oauth.js +667 -0
- package/dist/core/oauth.js.map +1 -0
- package/dist/core/secure-write.d.ts +17 -0
- package/dist/core/secure-write.d.ts.map +1 -0
- package/dist/core/secure-write.js +72 -0
- package/dist/core/secure-write.js.map +1 -0
- package/dist/core/tokens.d.ts +72 -0
- package/dist/core/tokens.d.ts.map +1 -0
- package/dist/core/tokens.js +197 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/server/daemon-client.d.ts +21 -0
- package/dist/server/daemon-client.d.ts.map +1 -0
- package/dist/server/daemon-client.js +176 -0
- package/dist/server/daemon-client.js.map +1 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +18 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/resource-protocol.d.ts +38 -0
- package/dist/server/resource-protocol.d.ts.map +1 -0
- package/dist/server/resource-protocol.js +68 -0
- package/dist/server/resource-protocol.js.map +1 -0
- package/dist/server/resources/active-resource.d.ts +12 -0
- package/dist/server/resources/active-resource.d.ts.map +1 -0
- package/dist/server/resources/active-resource.js +46 -0
- package/dist/server/resources/active-resource.js.map +1 -0
- package/dist/server/tool-protocol.d.ts +45 -0
- package/dist/server/tool-protocol.d.ts.map +1 -0
- package/dist/server/tool-protocol.js +74 -0
- package/dist/server/tool-protocol.js.map +1 -0
- package/dist/server/tool-server.d.ts +105 -0
- package/dist/server/tool-server.d.ts.map +1 -0
- package/dist/server/tool-server.js +96 -0
- package/dist/server/tool-server.js.map +1 -0
- package/dist/server/tools/delegated-tool.d.ts +15 -0
- package/dist/server/tools/delegated-tool.d.ts.map +1 -0
- package/dist/server/tools/delegated-tool.js +90 -0
- package/dist/server/tools/delegated-tool.js.map +1 -0
- package/dist/server/tools/run-tools.d.ts +14 -0
- package/dist/server/tools/run-tools.d.ts.map +1 -0
- package/dist/server/tools/run-tools.js +142 -0
- package/dist/server/tools/run-tools.js.map +1 -0
- package/dist/server/tools/tool-catalog-tools.d.ts +8 -0
- package/dist/server/tools/tool-catalog-tools.d.ts.map +1 -0
- package/dist/server/tools/tool-catalog-tools.js +66 -0
- package/dist/server/tools/tool-catalog-tools.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server/daemon-client
|
|
3
|
+
*
|
|
4
|
+
* Bounded-I/O JSON GET/POST against a trusted daemon base URL — the transport
|
|
5
|
+
* primitive every proxy tool `createMcpToolServer` hosts is built on
|
|
6
|
+
* (`../client/client.js`'s `createMcpIdleExitController` is the other half of
|
|
7
|
+
* the mechanism; this module is the network half).
|
|
8
|
+
*
|
|
9
|
+
* Deliberately NOT `@jini-ai/cli`'s `getJsonFromDaemon`/`postJsonToDaemon`
|
|
10
|
+
* (`packages/cli/src/http.ts`): those map a failure onto `process.exit`,
|
|
11
|
+
* which is the right contract for a one-shot CLI invocation but wrong here —
|
|
12
|
+
* a stdio MCP server is a long-lived process serving many tool calls, and one
|
|
13
|
+
* failed call must return an MCP `{isError:true}` result and keep serving the
|
|
14
|
+
* next call, not terminate the process. This module ports the same
|
|
15
|
+
* bounded-read / timeout / redaction posture `http.ts` established
|
|
16
|
+
* (CR-004/SEC-RB-009 — see `ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
|
|
17
|
+
* `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`) as
|
|
18
|
+
* a throw-based primitive instead: a failed request throws a plain,
|
|
19
|
+
* already-redacted `Error` whose message a tool handler's caller
|
|
20
|
+
* (`handleToolCall` in `./tool-protocol.js`) turns into that MCP error result.
|
|
21
|
+
*
|
|
22
|
+
* No SSRF hardening here (unlike `../core/oauth.ts`'s `safeOAuthFetch`): the
|
|
23
|
+
* target is a caller-resolved, typically-loopback daemon the user already
|
|
24
|
+
* trusts enough to run — not an attacker- or server-metadata-controlled
|
|
25
|
+
* remote URL the way a configured external MCP server's OAuth endpoints are.
|
|
26
|
+
* This mirrors `@jini-ai/cli/http.ts`'s own posture for the identical "fetch my
|
|
27
|
+
* own daemon" concern (no `assertSafePublicUrl` there either).
|
|
28
|
+
*/
|
|
29
|
+
import { sanitizeUntrustedText } from '@jini-ai/cli';
|
|
30
|
+
/** Request deadline. Generous enough for a slow tool call, short enough that a stalled daemon doesn't hang a stdio server turn forever. */
|
|
31
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
32
|
+
/** Cap on a daemon response body. Comfortably larger than any real status/run envelope, small enough to bound worst-case memory from a hostile/broken daemon. */
|
|
33
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
34
|
+
/** Thrown internally when a response exceeds its byte cap; always translated to a plain `Error` before it crosses this module's public functions. */
|
|
35
|
+
export class DaemonResponseTooLargeError extends Error {
|
|
36
|
+
limitBytes;
|
|
37
|
+
constructor(limitBytes) {
|
|
38
|
+
super(`daemon response exceeded the ${limitBytes}-byte limit`);
|
|
39
|
+
this.limitBytes = limitBytes;
|
|
40
|
+
this.name = 'DaemonResponseTooLargeError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseJsonLoose(text) {
|
|
44
|
+
if (text.length === 0)
|
|
45
|
+
return {};
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(text);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read `resp`'s body as JSON without ever buffering past `maxBytes`. Prefers streaming via
|
|
55
|
+
* `resp.body`'s reader (what a real `fetch()` `Response` provides), rejecting mid-stream once the
|
|
56
|
+
* cap is exceeded rather than buffering a huge response whole first; falls back to `resp.text()`
|
|
57
|
+
* (still byte-checked before parsing) for response-like test doubles that expose no reader. A body
|
|
58
|
+
* that isn't valid JSON resolves to `{}` rather than throwing — a malformed daemon response should
|
|
59
|
+
* surface as a normal non-2xx/shape mismatch to the caller, not an opaque parse exception.
|
|
60
|
+
*/
|
|
61
|
+
async function readJsonWithLimit(resp, maxBytes) {
|
|
62
|
+
const contentLength = resp.headers?.get?.('content-length');
|
|
63
|
+
if (contentLength !== null && contentLength !== undefined) {
|
|
64
|
+
const declared = Number(contentLength);
|
|
65
|
+
if (Number.isFinite(declared) && declared > maxBytes)
|
|
66
|
+
throw new DaemonResponseTooLargeError(maxBytes);
|
|
67
|
+
}
|
|
68
|
+
const body = resp.body;
|
|
69
|
+
if (body != null && typeof body.getReader === 'function') {
|
|
70
|
+
const reader = body.getReader();
|
|
71
|
+
const decoder = new TextDecoder();
|
|
72
|
+
let text = '';
|
|
73
|
+
let total = 0;
|
|
74
|
+
try {
|
|
75
|
+
for (;;) {
|
|
76
|
+
const { done, value } = await reader.read();
|
|
77
|
+
if (done)
|
|
78
|
+
break;
|
|
79
|
+
if (value !== undefined) {
|
|
80
|
+
total += value.byteLength;
|
|
81
|
+
if (total > maxBytes)
|
|
82
|
+
throw new DaemonResponseTooLargeError(maxBytes);
|
|
83
|
+
text += decoder.decode(value, { stream: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
text += decoder.decode();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
reader.releaseLock();
|
|
90
|
+
}
|
|
91
|
+
return parseJsonLoose(text);
|
|
92
|
+
}
|
|
93
|
+
const text = await resp.text();
|
|
94
|
+
if (Buffer.byteLength(text, 'utf8') > maxBytes)
|
|
95
|
+
throw new DaemonResponseTooLargeError(maxBytes);
|
|
96
|
+
return parseJsonLoose(text);
|
|
97
|
+
}
|
|
98
|
+
/** Formats a `fetch()` rejection (network-unreachable) into a message safe to surface as an MCP tool error. */
|
|
99
|
+
function formatConnectionFailure(err, baseUrl) {
|
|
100
|
+
const cause = err !== null && typeof err === 'object' ? err.cause : null;
|
|
101
|
+
const code = cause !== null && typeof cause === 'object' && typeof cause.code === 'string'
|
|
102
|
+
? cause.code
|
|
103
|
+
: null;
|
|
104
|
+
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND') {
|
|
105
|
+
return `cannot reach the daemon at ${baseUrl}. Is it running?`;
|
|
106
|
+
}
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
return sanitizeUntrustedText(message);
|
|
109
|
+
}
|
|
110
|
+
/** Formats a non-2xx daemon response into a message safe to surface as an MCP tool error. */
|
|
111
|
+
function formatDaemonHttpError(status, url, data) {
|
|
112
|
+
const envelope = data;
|
|
113
|
+
const code = typeof envelope.error?.code === 'string' ? envelope.error.code : undefined;
|
|
114
|
+
const rawMessage = typeof envelope.error?.message === 'string' ? envelope.error.message : undefined;
|
|
115
|
+
const detail = rawMessage !== undefined ? sanitizeUntrustedText(rawMessage) : `HTTP ${status}`;
|
|
116
|
+
return code !== undefined ? `daemon ${status} on ${url}: ${code}: ${detail}` : `daemon ${status} on ${url}: ${detail}`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Shared transport core behind {@link getDaemonJson} and {@link postDaemonJson}: fetch
|
|
120
|
+
* `<baseUrl><route>` with a bounded timeout and response-size cap, and turn a network failure or a
|
|
121
|
+
* non-2xx daemon response into a thrown, already-redacted `Error` — this function either resolves
|
|
122
|
+
* with the parsed body or throws. Never calls `process.exit`.
|
|
123
|
+
*/
|
|
124
|
+
async function requestDaemonJson(baseUrl, route, init, options = {}) {
|
|
125
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
126
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
127
|
+
const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
128
|
+
const url = `${baseUrl}${route}`;
|
|
129
|
+
const timeoutController = new AbortController();
|
|
130
|
+
const timeoutHandle = setTimeout(() => {
|
|
131
|
+
timeoutController.abort(new Error(`request to ${route} timed out after ${timeoutMs}ms`));
|
|
132
|
+
}, timeoutMs);
|
|
133
|
+
// A stdio server turn shouldn't be kept alive purely by this timer.
|
|
134
|
+
timeoutHandle.unref?.();
|
|
135
|
+
const signal = options.signal !== undefined ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
|
|
136
|
+
try {
|
|
137
|
+
let resp;
|
|
138
|
+
try {
|
|
139
|
+
resp = await fetchImpl(url, {
|
|
140
|
+
method: init.method,
|
|
141
|
+
headers: init.method === 'POST' ? { 'content-type': 'application/json', ...options.headers } : { ...options.headers },
|
|
142
|
+
...(init.method === 'POST' ? { body: JSON.stringify(init.body ?? {}) } : {}),
|
|
143
|
+
signal,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new Error(formatConnectionFailure(err, baseUrl));
|
|
148
|
+
}
|
|
149
|
+
let data;
|
|
150
|
+
try {
|
|
151
|
+
data = await readJsonWithLimit(resp, maxResponseBytes);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
if (err instanceof DaemonResponseTooLargeError) {
|
|
155
|
+
throw new Error(`response from ${url} exceeded the ${err.limitBytes}-byte limit`);
|
|
156
|
+
}
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
if (!resp.ok) {
|
|
160
|
+
throw new Error(formatDaemonHttpError(resp.status, url, data));
|
|
161
|
+
}
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
clearTimeout(timeoutHandle);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** `GET <baseUrl><route>` and return the parsed JSON response, throwing on network failure or a non-2xx status. */
|
|
169
|
+
export async function getDaemonJson(baseUrl, route, options) {
|
|
170
|
+
return requestDaemonJson(baseUrl, route, { method: 'GET' }, options);
|
|
171
|
+
}
|
|
172
|
+
/** `POST body` (JSON-serialized) to `<baseUrl><route>` and return the parsed JSON response, throwing on network failure or a non-2xx status. */
|
|
173
|
+
export async function postDaemonJson(baseUrl, route, body, options) {
|
|
174
|
+
return requestDaemonJson(baseUrl, route, { method: 'POST', body }, options);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=daemon-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-client.js","sourceRoot":"","sources":["../../src/server/daemon-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server
|
|
3
|
+
* Sub-barrel for the MCP tool-hosting mechanism: the pure tool protocol
|
|
4
|
+
* layer (`tool-protocol.ts`), the pure resource protocol layer
|
|
5
|
+
* (`resource-protocol.ts`), the daemon transport (`daemon-client.ts`), the
|
|
6
|
+
* `Server`/`StdioServerTransport` wiring (`tool-server.ts`), the concrete
|
|
7
|
+
* kernel-run tool defs (`tools/run-tools.ts`), the concrete kernel
|
|
8
|
+
* resource defs (`resources/active-resource.ts`), and the delegated-tool
|
|
9
|
+
* execution def (`tools/delegated-tool.ts`).
|
|
10
|
+
*/
|
|
11
|
+
export * from './tool-protocol.js';
|
|
12
|
+
export * from './resource-protocol.js';
|
|
13
|
+
export * from './daemon-client.js';
|
|
14
|
+
export * from './tool-server.js';
|
|
15
|
+
export * from './tools/run-tools.js';
|
|
16
|
+
export * from './tools/delegated-tool.js';
|
|
17
|
+
export * from './resources/active-resource.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server
|
|
3
|
+
* Sub-barrel for the MCP tool-hosting mechanism: the pure tool protocol
|
|
4
|
+
* layer (`tool-protocol.ts`), the pure resource protocol layer
|
|
5
|
+
* (`resource-protocol.ts`), the daemon transport (`daemon-client.ts`), the
|
|
6
|
+
* `Server`/`StdioServerTransport` wiring (`tool-server.ts`), the concrete
|
|
7
|
+
* kernel-run tool defs (`tools/run-tools.ts`), the concrete kernel
|
|
8
|
+
* resource defs (`resources/active-resource.ts`), and the delegated-tool
|
|
9
|
+
* execution def (`tools/delegated-tool.ts`).
|
|
10
|
+
*/
|
|
11
|
+
export * from './tool-protocol.js';
|
|
12
|
+
export * from './resource-protocol.js';
|
|
13
|
+
export * from './daemon-client.js';
|
|
14
|
+
export * from './tool-server.js';
|
|
15
|
+
export * from './tools/run-tools.js';
|
|
16
|
+
export * from './tools/delegated-tool.js';
|
|
17
|
+
export * from './resources/active-resource.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReadResourceResult, Resource } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { McpToolContext } from './tool-protocol.js';
|
|
3
|
+
/** What one resource's `read` returns before this module wraps it into the MCP wire shape. */
|
|
4
|
+
export interface McpResourceReadResult {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
/** Overrides the resource def's own `mimeType` for this particular read. Rarely needed — most resources have a fixed content type. */
|
|
7
|
+
readonly mimeType?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One read-only MCP resource a `createMcpToolServer` caller registers. `uri` must be unique within
|
|
11
|
+
* a given resource list (`buildResourceIndex` throws otherwise, mirroring `buildToolIndex`'s
|
|
12
|
+
* duplicate-name guard). Unlike a tool, a resource takes no caller-supplied arguments — it is
|
|
13
|
+
* addressed purely by its static `uri`.
|
|
14
|
+
*/
|
|
15
|
+
export interface McpResourceDef {
|
|
16
|
+
readonly uri: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly description?: string;
|
|
19
|
+
readonly mimeType?: string;
|
|
20
|
+
readonly read: (ctx: McpToolContext) => Promise<McpResourceReadResult> | McpResourceReadResult;
|
|
21
|
+
}
|
|
22
|
+
/** Projects a resource list into the `Resource[]` shape `resources/list` returns. */
|
|
23
|
+
export declare function resourcesToList(resources: readonly McpResourceDef[]): Resource[];
|
|
24
|
+
/** Builds a uri -> def lookup, throwing if two resources in `resources` share a uri (a caller-configuration bug, surfaced eagerly at server-construction time rather than letting the second registration shadow the first). */
|
|
25
|
+
export declare function buildResourceIndex(resources: readonly McpResourceDef[]): Map<string, McpResourceDef>;
|
|
26
|
+
/**
|
|
27
|
+
* `resources/read` dispatch: looks up `uri` in `resources` and runs its `read`. Unlike
|
|
28
|
+
* {@link import('./tool-protocol.js').handleToolCall}, a failure here *throws* rather than
|
|
29
|
+
* returning an `{isError:true}` result — MCP resources have no such protocol-level "soft failure"
|
|
30
|
+
* content shape; an unknown uri or a read failure is meant to surface as a JSON-RPC error response,
|
|
31
|
+
* matching both the MCP spec's `resources/read` contract and the OD origin's own
|
|
32
|
+
* `ReadResourceRequestSchema` handler (which likewise just threw a plain `Error` on an unrecognized
|
|
33
|
+
* uri). The thrown message is passed through {@link sanitizeUntrustedText} unconditionally before
|
|
34
|
+
* it leaves this function, the same conservative posture `handleToolCall` uses — a `read`
|
|
35
|
+
* implementation may re-throw text that traces back to an untrusted daemon response.
|
|
36
|
+
*/
|
|
37
|
+
export declare function handleResourceRead(uri: string, resources: ReadonlyMap<string, McpResourceDef>, ctx: McpToolContext): Promise<ReadResourceResult>;
|
|
38
|
+
//# sourceMappingURL=resource-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-protocol.d.ts","sourceRoot":"","sources":["../../src/server/resource-protocol.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sIAAsI;IACtI,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAAC;CAChG;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,QAAQ,EAAE,CAOhF;AAED,gOAAgO;AAChO,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CASpG;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,EAC9C,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAsB7B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server/resource-protocol
|
|
3
|
+
*
|
|
4
|
+
* The pure, SDK-connection-free half of the MCP *resource* surface —
|
|
5
|
+
* mirrors `./tool-protocol.js` for `resources/list` and `resources/read`.
|
|
6
|
+
* A resource is read-only, addressed by a static `uri` with no arguments,
|
|
7
|
+
* and returns text content; `./tool-server.js` is the thin layer that wires
|
|
8
|
+
* this to a real `Server`'s `ListResourcesRequestSchema`/
|
|
9
|
+
* `ReadResourceRequestSchema` handlers, exactly as `tool-protocol.js` is
|
|
10
|
+
* wired to `ListToolsRequestSchema`/`CallToolRequestSchema`.
|
|
11
|
+
*/
|
|
12
|
+
import { sanitizeUntrustedText } from '@jini-ai/cli';
|
|
13
|
+
/** Projects a resource list into the `Resource[]` shape `resources/list` returns. */
|
|
14
|
+
export function resourcesToList(resources) {
|
|
15
|
+
return resources.map((resource) => ({
|
|
16
|
+
uri: resource.uri,
|
|
17
|
+
name: resource.name,
|
|
18
|
+
...(resource.description !== undefined ? { description: resource.description } : {}),
|
|
19
|
+
...(resource.mimeType !== undefined ? { mimeType: resource.mimeType } : {}),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/** Builds a uri -> def lookup, throwing if two resources in `resources` share a uri (a caller-configuration bug, surfaced eagerly at server-construction time rather than letting the second registration shadow the first). */
|
|
23
|
+
export function buildResourceIndex(resources) {
|
|
24
|
+
const index = new Map();
|
|
25
|
+
for (const resource of resources) {
|
|
26
|
+
if (index.has(resource.uri)) {
|
|
27
|
+
throw new Error(`createMcpToolServer: duplicate resource uri "${resource.uri}"`);
|
|
28
|
+
}
|
|
29
|
+
index.set(resource.uri, resource);
|
|
30
|
+
}
|
|
31
|
+
return index;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `resources/read` dispatch: looks up `uri` in `resources` and runs its `read`. Unlike
|
|
35
|
+
* {@link import('./tool-protocol.js').handleToolCall}, a failure here *throws* rather than
|
|
36
|
+
* returning an `{isError:true}` result — MCP resources have no such protocol-level "soft failure"
|
|
37
|
+
* content shape; an unknown uri or a read failure is meant to surface as a JSON-RPC error response,
|
|
38
|
+
* matching both the MCP spec's `resources/read` contract and the OD origin's own
|
|
39
|
+
* `ReadResourceRequestSchema` handler (which likewise just threw a plain `Error` on an unrecognized
|
|
40
|
+
* uri). The thrown message is passed through {@link sanitizeUntrustedText} unconditionally before
|
|
41
|
+
* it leaves this function, the same conservative posture `handleToolCall` uses — a `read`
|
|
42
|
+
* implementation may re-throw text that traces back to an untrusted daemon response.
|
|
43
|
+
*/
|
|
44
|
+
export async function handleResourceRead(uri, resources, ctx) {
|
|
45
|
+
const resource = resources.get(uri);
|
|
46
|
+
if (resource === undefined) {
|
|
47
|
+
throw new Error(`unsupported resource URI: ${uri}`);
|
|
48
|
+
}
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await resource.read(ctx);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
throw new Error(sanitizeUntrustedText(message));
|
|
56
|
+
}
|
|
57
|
+
const mimeType = result.mimeType ?? resource.mimeType;
|
|
58
|
+
return {
|
|
59
|
+
contents: [
|
|
60
|
+
{
|
|
61
|
+
uri,
|
|
62
|
+
text: result.text,
|
|
63
|
+
...(mimeType !== undefined ? { mimeType } : {}),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=resource-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-protocol.js","sourceRoot":"","sources":["../../src/server/resource-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAyBrD,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,SAAoC;IAClE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,GAAG,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5E,CAAC,CAAC,CAAC;AACN,CAAC;AAED,gOAAgO;AAChO,MAAM,UAAU,kBAAkB,CAAC,SAAoC;IACrE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;IAChD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,gDAAgD,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC;QACnF,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAW,EACX,SAA8C,EAC9C,GAAmB;IAEnB,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,MAA6B,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACtD,OAAO;QACL,QAAQ,EAAE;YACR;gBACE,GAAG;gBACH,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChD;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpResourceDef } from '../resource-protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
|
|
4
|
+
* unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
|
|
5
|
+
* `active:false`; a resource is meant to be raw structured data a client attaches to context, not
|
|
6
|
+
* a model-facing tool result, matching the OD origin's own `od://focus/active` handler (which also
|
|
7
|
+
* returned the raw `/api/active` body with no special-casing).
|
|
8
|
+
*/
|
|
9
|
+
export declare const activeContextResource: McpResourceDef;
|
|
10
|
+
/** The full set of kernel resource defs this package ships, ready to pass as `createMcpToolServer`'s `resources` option. */
|
|
11
|
+
export declare const KERNEL_RESOURCES: readonly McpResourceDef[];
|
|
12
|
+
//# sourceMappingURL=active-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-resource.d.ts","sourceRoot":"","sources":["../../../src/server/resources/active-resource.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAO9D;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAUnC,CAAC;AAEF,4HAA4H;AAC5H,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAA4B,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server/resources/active-resource
|
|
3
|
+
*
|
|
4
|
+
* The one MCP *resource* this package ships: a read-only pointer to
|
|
5
|
+
* `GET /api/active` (`packages/http/src/active-context.ts`'s `getActiveRoute`)
|
|
6
|
+
* — the same underlying data `../tools/run-tools.js`'s `getActiveContextTool`
|
|
7
|
+
* already proxies as a *tool*. This is not redundant: OD's origin
|
|
8
|
+
* (`apps/daemon/src/mcp.ts`) exposed the identical `/api/active` payload
|
|
9
|
+
* both ways — as the `get_active_context` tool (already ported, see
|
|
10
|
+
* `../tools/run-tools.js`) AND as the `od://focus/active` resource (its
|
|
11
|
+
* `ListResourcesRequestSchema`/`ReadResourceRequestSchema` handlers) —
|
|
12
|
+
* because tools and resources serve different MCP client affordances: a
|
|
13
|
+
* tool is invoked by the model mid-conversation, while a resource can be
|
|
14
|
+
* listed and attached to context by the user/client without any tool call.
|
|
15
|
+
* This module is that second affordance for the same primitive. See
|
|
16
|
+
* `source-map.md`'s 2026-07-21 addition for why the rest of OD's resource
|
|
17
|
+
* surface (`od://skills/...`, `od://design-systems/...`) was NOT ported the
|
|
18
|
+
* same way (both require a Skill/DesignSystem noun this kernel doesn't have).
|
|
19
|
+
*
|
|
20
|
+
* Security posture matches every other tool/resource in this package: no
|
|
21
|
+
* separate authorization mechanism here — whatever `@jini-ai/http`'s
|
|
22
|
+
* same-origin guard / bearer-auth middleware already enforces on
|
|
23
|
+
* `GET /api/active` is the only gate a read of this resource passes
|
|
24
|
+
* through.
|
|
25
|
+
*/
|
|
26
|
+
import { getDaemonJson } from '../daemon-client.js';
|
|
27
|
+
/**
|
|
28
|
+
* `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
|
|
29
|
+
* unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
|
|
30
|
+
* `active:false`; a resource is meant to be raw structured data a client attaches to context, not
|
|
31
|
+
* a model-facing tool result, matching the OD origin's own `od://focus/active` handler (which also
|
|
32
|
+
* returned the raw `/api/active` body with no special-casing).
|
|
33
|
+
*/
|
|
34
|
+
export const activeContextResource = {
|
|
35
|
+
uri: 'jini://active',
|
|
36
|
+
name: 'Active context',
|
|
37
|
+
description: 'The resource (resourceRef) plus optional detail the caller last recorded as its current focus via POST /api/active — the same generic, product-neutral pointer the get_active_context tool proxies, exposed here as an attachable MCP resource instead of a tool call.',
|
|
38
|
+
mimeType: 'application/json',
|
|
39
|
+
read: async (ctx) => {
|
|
40
|
+
const data = await getDaemonJson(ctx.baseUrl, '/api/active', { fetchImpl: ctx.fetchImpl });
|
|
41
|
+
return { text: JSON.stringify(data, null, 2) };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/** The full set of kernel resource defs this package ships, ready to pass as `createMcpToolServer`'s `resources` option. */
|
|
45
|
+
export const KERNEL_RESOURCES = [activeContextResource];
|
|
46
|
+
//# sourceMappingURL=active-resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-resource.js","sourceRoot":"","sources":["../../../src/server/resources/active-resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAQpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAmB;IACnD,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,wQAAwQ;IAC1Q,QAAQ,EAAE,kBAAkB;IAC5B,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAClB,MAAM,IAAI,GAAG,MAAM,aAAa,CAAuB,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;QACjH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IACjD,CAAC;CACF,CAAC;AAEF,4HAA4H;AAC5H,MAAM,CAAC,MAAM,gBAAgB,GAA8B,CAAC,qBAAqB,CAAC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/** What every tool handler receives alongside its parsed arguments. */
|
|
3
|
+
export interface McpToolContext {
|
|
4
|
+
/** The resolved daemon HTTP base URL (no trailing slash), fixed for the lifetime of one server run. */
|
|
5
|
+
readonly baseUrl: string;
|
|
6
|
+
/** Defaults to the global `fetch`; threaded through so a host can inject its own (e.g. for tests). */
|
|
7
|
+
readonly fetchImpl: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One MCP tool a `createMcpToolServer` caller registers. `name` must be unique within a given
|
|
11
|
+
* tool list (`buildToolIndex` throws otherwise — a caller bug, not something to silently drop).
|
|
12
|
+
* `handler` returns a JSON-serializable payload on success or throws an `Error` (or any value —
|
|
13
|
+
* non-`Error` throws are stringified) on failure; both are converted to the matching MCP
|
|
14
|
+
* `CallToolResult` shape by {@link handleToolCall}, so individual tools never construct MCP
|
|
15
|
+
* protocol objects themselves.
|
|
16
|
+
*/
|
|
17
|
+
export interface McpToolDef<Args extends Record<string, unknown> = Record<string, unknown>> {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly inputSchema: Tool['inputSchema'];
|
|
21
|
+
readonly annotations?: Tool['annotations'];
|
|
22
|
+
readonly handler: (args: Args, ctx: McpToolContext) => Promise<unknown> | unknown;
|
|
23
|
+
}
|
|
24
|
+
/** Wraps a successful tool result as MCP `text` content, JSON-stringifying anything that isn't already a string. */
|
|
25
|
+
export declare function okResult(payload: unknown): CallToolResult;
|
|
26
|
+
/** Wraps a tool failure as an MCP `isError` result. */
|
|
27
|
+
export declare function errorResult(message: string): CallToolResult;
|
|
28
|
+
/** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
|
|
29
|
+
export declare function requireString(value: unknown, name: string): asserts value is string;
|
|
30
|
+
/** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
|
|
31
|
+
export declare function toolsToList(tools: readonly McpToolDef[]): Tool[];
|
|
32
|
+
/** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
|
|
33
|
+
export declare function buildToolIndex(tools: readonly McpToolDef[]): Map<string, McpToolDef>;
|
|
34
|
+
/**
|
|
35
|
+
* `tools/call` dispatch: looks up `name` in `tools`, runs its handler against `rawArgs`, and
|
|
36
|
+
* converts the outcome to a `CallToolResult` — an unknown tool name or a thrown error both
|
|
37
|
+
* produce an `{isError:true}` result rather than rejecting, matching MCP's convention that tool
|
|
38
|
+
* failures are protocol-level results, not JSON-RPC errors. A thrown error's message is passed
|
|
39
|
+
* through {@link sanitizeUntrustedText} before it reaches the result: `handler` may re-throw text
|
|
40
|
+
* that ultimately traces back to the daemon (untrusted network peer), and a caller-thrown
|
|
41
|
+
* validation error is cheap to sanitize unconditionally rather than trying to prove which case
|
|
42
|
+
* applies at each call site.
|
|
43
|
+
*/
|
|
44
|
+
export declare function handleToolCall(name: string, rawArgs: Record<string, unknown> | undefined, tools: ReadonlyMap<string, McpToolDef>, ctx: McpToolContext): Promise<CallToolResult>;
|
|
45
|
+
//# sourceMappingURL=tool-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-protocol.d.ts","sourceRoot":"","sources":["../../src/server/tool-protocol.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/E,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,uGAAuG;IACvG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sGAAsG;IACtG,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACnF;AAED,oHAAoH;AACpH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAGzD;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAE3D;AAED,mMAAmM;AACnM,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAInF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,EAAE,CAOhE;AAED,mOAAmO;AACnO,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CASpF;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EACtC,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,cAAc,CAAC,CAYzB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @jini-ai/mcp/server/tool-protocol
|
|
3
|
+
*
|
|
4
|
+
* The pure, SDK-connection-free half of the MCP tool-hosting mechanism: the
|
|
5
|
+
* `McpToolDef` contract a caller registers tools against, `tools/list`
|
|
6
|
+
* projection, and `tools/call` dispatch (look up a tool by name, run its
|
|
7
|
+
* handler, wrap the result). None of this touches `@modelcontextprotocol/sdk`'s
|
|
8
|
+
* `Server`/transport classes or the network — a tool `handler` is just
|
|
9
|
+
* `(args, ctx) => value | Promise<value>` that either returns a
|
|
10
|
+
* JSON-serializable payload (wrapped as a successful MCP result) or throws
|
|
11
|
+
* (wrapped as an `{isError:true}` MCP result). `./tool-server.js` is the thin
|
|
12
|
+
* layer that wires this to a real `Server` + `StdioServerTransport`.
|
|
13
|
+
*/
|
|
14
|
+
import { sanitizeUntrustedText } from '@jini-ai/cli';
|
|
15
|
+
/** Wraps a successful tool result as MCP `text` content, JSON-stringifying anything that isn't already a string. */
|
|
16
|
+
export function okResult(payload) {
|
|
17
|
+
const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
18
|
+
return { content: [{ type: 'text', text }] };
|
|
19
|
+
}
|
|
20
|
+
/** Wraps a tool failure as an MCP `isError` result. */
|
|
21
|
+
export function errorResult(message) {
|
|
22
|
+
return { isError: true, content: [{ type: 'text', text: message }] };
|
|
23
|
+
}
|
|
24
|
+
/** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
|
|
25
|
+
export function requireString(value, name) {
|
|
26
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
27
|
+
throw new Error(`${name} is required (string).`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
|
|
31
|
+
export function toolsToList(tools) {
|
|
32
|
+
return tools.map((tool) => ({
|
|
33
|
+
name: tool.name,
|
|
34
|
+
description: tool.description,
|
|
35
|
+
inputSchema: tool.inputSchema,
|
|
36
|
+
...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
|
|
40
|
+
export function buildToolIndex(tools) {
|
|
41
|
+
const index = new Map();
|
|
42
|
+
for (const tool of tools) {
|
|
43
|
+
if (index.has(tool.name)) {
|
|
44
|
+
throw new Error(`createMcpToolServer: duplicate tool name "${tool.name}"`);
|
|
45
|
+
}
|
|
46
|
+
index.set(tool.name, tool);
|
|
47
|
+
}
|
|
48
|
+
return index;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `tools/call` dispatch: looks up `name` in `tools`, runs its handler against `rawArgs`, and
|
|
52
|
+
* converts the outcome to a `CallToolResult` — an unknown tool name or a thrown error both
|
|
53
|
+
* produce an `{isError:true}` result rather than rejecting, matching MCP's convention that tool
|
|
54
|
+
* failures are protocol-level results, not JSON-RPC errors. A thrown error's message is passed
|
|
55
|
+
* through {@link sanitizeUntrustedText} before it reaches the result: `handler` may re-throw text
|
|
56
|
+
* that ultimately traces back to the daemon (untrusted network peer), and a caller-thrown
|
|
57
|
+
* validation error is cheap to sanitize unconditionally rather than trying to prove which case
|
|
58
|
+
* applies at each call site.
|
|
59
|
+
*/
|
|
60
|
+
export async function handleToolCall(name, rawArgs, tools, ctx) {
|
|
61
|
+
const tool = tools.get(name);
|
|
62
|
+
if (tool === undefined) {
|
|
63
|
+
return errorResult(`unknown tool: ${name}`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const result = await tool.handler(rawArgs ?? {}, ctx);
|
|
67
|
+
return okResult(result);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
return errorResult(sanitizeUntrustedText(message));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=tool-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-protocol.js","sourceRoot":"","sources":["../../src/server/tool-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA2BrD,oHAAoH;AACpH,MAAM,UAAU,QAAQ,CAAC,OAAgB;IACvC,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,mMAAmM;AACnM,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,IAAY;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,WAAW,CAAC,KAA4B;IACtD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC,CAAC,CAAC;AACN,CAAC;AAED,mOAAmO;AACnO,MAAM,UAAU,cAAc,CAAC,KAA4B;IACzD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,OAA4C,EAC5C,KAAsC,EACtC,GAAmB;IAEnB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,WAAW,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,WAAW,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|