tiny-stdio-mcp-server 0.1.2 → 0.1.4
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/content/audio.js +14 -6
- package/dist/content/convert.js +24 -5
- package/dist/content/file.d.ts +1 -0
- package/dist/content/file.js +33 -17
- package/dist/content/image.js +14 -6
- package/dist/content/mime.d.ts +6 -0
- package/dist/content/mime.js +51 -0
- package/dist/index.d.ts +2 -2
- package/dist/jsonrpc.js +20 -2
- package/dist/schema.js +9 -4
- package/dist/server.d.ts +17 -1
- package/dist/server.js +463 -31
- package/dist/types.d.ts +140 -11
- package/dist/types.js +7 -3
- package/package.json +7 -2
package/dist/content/audio.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fileTypeFromBuffer } from "./mime.js";
|
|
1
|
+
import { assertBase64, fileTypeFromBuffer, parseContentType, safeRemoteLabel } from "./mime.js";
|
|
2
2
|
const SUPPORTED_AUDIO_MIMES = new Set([
|
|
3
3
|
"audio/mpeg",
|
|
4
4
|
"audio/wav",
|
|
@@ -22,7 +22,7 @@ export class Audio {
|
|
|
22
22
|
static async fromUrl(url) {
|
|
23
23
|
const response = await fetch(url);
|
|
24
24
|
if (!response.ok) {
|
|
25
|
-
throw new Error(`Failed to fetch audio from ${url}: ${response.status} ${response.statusText}`);
|
|
25
|
+
throw new Error(`Failed to fetch audio from ${safeRemoteLabel(url)}: ${response.status} ${response.statusText}`);
|
|
26
26
|
}
|
|
27
27
|
const arrayBuffer = await response.arrayBuffer();
|
|
28
28
|
const data = new Uint8Array(arrayBuffer);
|
|
@@ -32,12 +32,12 @@ export class Audio {
|
|
|
32
32
|
mimeType = detected.mime;
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
const contentType = response.headers.get("content-type")
|
|
35
|
+
const contentType = parseContentType(response.headers.get("content-type")).mimeType;
|
|
36
36
|
if (contentType && SUPPORTED_AUDIO_MIMES.has(contentType)) {
|
|
37
37
|
mimeType = contentType;
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
|
-
throw new Error(`Unable to detect audio MIME type from ${url}`);
|
|
40
|
+
throw new Error(`Unable to detect audio MIME type from ${safeRemoteLabel(url)}`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
const base64 = Buffer.from(data).toString("base64");
|
|
@@ -47,11 +47,14 @@ export class Audio {
|
|
|
47
47
|
let mimeType;
|
|
48
48
|
if (format) {
|
|
49
49
|
if (format.includes("/")) {
|
|
50
|
-
mimeType = format;
|
|
50
|
+
mimeType = format.toLowerCase();
|
|
51
51
|
}
|
|
52
52
|
else {
|
|
53
53
|
mimeType = AUDIO_FORMAT_MAP[format.toLowerCase()] || `audio/${format}`;
|
|
54
54
|
}
|
|
55
|
+
if (!SUPPORTED_AUDIO_MIMES.has(mimeType)) {
|
|
56
|
+
throw new Error(`Unsupported audio MIME type: ${mimeType}`);
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
59
|
else {
|
|
57
60
|
const detected = fileTypeFromBuffer(data);
|
|
@@ -64,7 +67,12 @@ export class Audio {
|
|
|
64
67
|
return new Audio(base64, mimeType);
|
|
65
68
|
}
|
|
66
69
|
static fromBase64(base64, mimeType) {
|
|
67
|
-
|
|
70
|
+
assertBase64(base64);
|
|
71
|
+
const normalizedMimeType = mimeType.toLowerCase();
|
|
72
|
+
if (!SUPPORTED_AUDIO_MIMES.has(normalizedMimeType)) {
|
|
73
|
+
throw new Error(`Unsupported audio MIME type: ${normalizedMimeType}`);
|
|
74
|
+
}
|
|
75
|
+
return new Audio(base64, normalizedMimeType);
|
|
68
76
|
}
|
|
69
77
|
toContentBlock() {
|
|
70
78
|
return {
|
package/dist/content/convert.js
CHANGED
|
@@ -32,11 +32,30 @@ export function toContentBlocks(result) {
|
|
|
32
32
|
return [convertSingleValue(result)];
|
|
33
33
|
}
|
|
34
34
|
function isContentBlock(value) {
|
|
35
|
-
if (!("type"
|
|
35
|
+
if (!hasOwnProperty(value, "type") || typeof value.type !== "string") {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
value.
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (value.type === "text") {
|
|
39
|
+
return hasOwnProperty(value, "text") && typeof value.text === "string";
|
|
40
|
+
}
|
|
41
|
+
if (value.type === "image" || value.type === "audio") {
|
|
42
|
+
return hasOwnProperty(value, "data")
|
|
43
|
+
&& typeof value.data === "string"
|
|
44
|
+
&& hasOwnProperty(value, "mimeType")
|
|
45
|
+
&& typeof value.mimeType === "string";
|
|
46
|
+
}
|
|
47
|
+
if (value.type !== "resource" || !hasOwnProperty(value, "resource")) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const resource = value.resource;
|
|
51
|
+
return typeof resource === "object"
|
|
52
|
+
&& resource !== null
|
|
53
|
+
&& hasOwnProperty(resource, "uri")
|
|
54
|
+
&& typeof resource.uri === "string"
|
|
55
|
+
&& (!hasOwnProperty(resource, "mimeType") || typeof resource.mimeType === "string")
|
|
56
|
+
&& ((hasOwnProperty(resource, "text") && typeof resource.text === "string")
|
|
57
|
+
|| (hasOwnProperty(resource, "blob") && typeof resource.blob === "string"));
|
|
58
|
+
}
|
|
59
|
+
function hasOwnProperty(value, name) {
|
|
60
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
42
61
|
}
|
package/dist/content/file.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class File {
|
|
|
17
17
|
private readonly mimeType;
|
|
18
18
|
private readonly isText;
|
|
19
19
|
private readonly name?;
|
|
20
|
+
private readonly charset;
|
|
20
21
|
private constructor();
|
|
21
22
|
static fromUrl(url: string): Promise<File>;
|
|
22
23
|
static fromBytes(data: Uint8Array, mimeType: string): File;
|
package/dist/content/file.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import { fileTypeFromBuffer } from "./mime.js";
|
|
1
|
+
import { assertBase64, fileTypeFromBuffer, parseContentType, safeRemoteLabel } from "./mime.js";
|
|
2
2
|
function isTextMimeType(mimeType) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const normalizedMimeType = mimeType.toLowerCase();
|
|
4
|
+
return (normalizedMimeType.startsWith("text/") ||
|
|
5
|
+
normalizedMimeType === "application/json" ||
|
|
6
|
+
normalizedMimeType.endsWith("+json") ||
|
|
7
|
+
normalizedMimeType === "application/xml" ||
|
|
8
|
+
normalizedMimeType.endsWith("+xml") ||
|
|
9
|
+
normalizedMimeType === "application/javascript" ||
|
|
10
|
+
normalizedMimeType === "application/typescript");
|
|
8
11
|
}
|
|
9
12
|
export class File {
|
|
10
13
|
data;
|
|
11
14
|
mimeType;
|
|
12
15
|
isText;
|
|
13
16
|
name;
|
|
14
|
-
|
|
17
|
+
charset;
|
|
18
|
+
constructor(data, mimeType, isText, name, charset = "utf-8") {
|
|
15
19
|
this.data = data;
|
|
16
20
|
this.mimeType = mimeType;
|
|
17
21
|
this.isText = isText;
|
|
18
22
|
this.name = name;
|
|
23
|
+
this.charset = charset;
|
|
19
24
|
}
|
|
20
25
|
static async fromUrl(url) {
|
|
21
26
|
const response = await fetch(url);
|
|
22
27
|
if (!response.ok) {
|
|
23
|
-
throw new Error(`Failed to fetch file from ${url}: ${response.status} ${response.statusText}`);
|
|
28
|
+
throw new Error(`Failed to fetch file from ${safeRemoteLabel(url)}: ${response.status} ${response.statusText}`);
|
|
24
29
|
}
|
|
25
30
|
const arrayBuffer = await response.arrayBuffer();
|
|
26
31
|
const data = new Uint8Array(arrayBuffer);
|
|
@@ -30,26 +35,37 @@ export class File {
|
|
|
30
35
|
mimeType = detected.mime;
|
|
31
36
|
}
|
|
32
37
|
else {
|
|
33
|
-
const contentType = response.headers.get("content-type")
|
|
34
|
-
if (contentType) {
|
|
35
|
-
mimeType = contentType;
|
|
38
|
+
const contentType = parseContentType(response.headers.get("content-type"));
|
|
39
|
+
if (contentType.mimeType) {
|
|
40
|
+
mimeType = contentType.mimeType;
|
|
36
41
|
}
|
|
37
42
|
else {
|
|
38
|
-
throw new Error(`Unable to detect MIME type from ${url}`);
|
|
43
|
+
throw new Error(`Unable to detect MIME type from ${safeRemoteLabel(url)}`);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
46
|
+
const contentType = parseContentType(response.headers.get("content-type"));
|
|
47
|
+
const charset = contentType.charset ?? "utf-8";
|
|
48
|
+
let isText = isTextMimeType(mimeType);
|
|
49
|
+
if (isText) {
|
|
50
|
+
try {
|
|
51
|
+
new TextDecoder(charset);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
isText = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const name = new URL(url).pathname.split("/").pop() || "file";
|
|
58
|
+
return new File(data, mimeType, isText, name, charset);
|
|
44
59
|
}
|
|
45
60
|
static fromBytes(data, mimeType) {
|
|
46
61
|
const isText = isTextMimeType(mimeType);
|
|
47
62
|
return new File(data, mimeType, isText);
|
|
48
63
|
}
|
|
49
64
|
static fromText(text, mimeType = "text/plain") {
|
|
50
|
-
return new File(text, mimeType,
|
|
65
|
+
return new File(text, mimeType, isTextMimeType(mimeType));
|
|
51
66
|
}
|
|
52
67
|
static fromBase64(base64, mimeType) {
|
|
68
|
+
assertBase64(base64);
|
|
53
69
|
const data = Buffer.from(base64, "base64");
|
|
54
70
|
const isText = isTextMimeType(mimeType);
|
|
55
71
|
return new File(new Uint8Array(data), mimeType, isText);
|
|
@@ -62,7 +78,7 @@ export class File {
|
|
|
62
78
|
text = this.data;
|
|
63
79
|
}
|
|
64
80
|
else {
|
|
65
|
-
text = new TextDecoder(
|
|
81
|
+
text = new TextDecoder(this.charset).decode(this.data);
|
|
66
82
|
}
|
|
67
83
|
return {
|
|
68
84
|
type: "resource",
|
package/dist/content/image.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fileTypeFromBuffer } from "./mime.js";
|
|
1
|
+
import { assertBase64, fileTypeFromBuffer, parseContentType, safeRemoteLabel } from "./mime.js";
|
|
2
2
|
const SUPPORTED_IMAGE_MIMES = new Set([
|
|
3
3
|
"image/png",
|
|
4
4
|
"image/jpeg",
|
|
@@ -15,7 +15,7 @@ export class Image {
|
|
|
15
15
|
static async fromUrl(url) {
|
|
16
16
|
const response = await fetch(url);
|
|
17
17
|
if (!response.ok) {
|
|
18
|
-
throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
|
|
18
|
+
throw new Error(`Failed to fetch image from ${safeRemoteLabel(url)}: ${response.status} ${response.statusText}`);
|
|
19
19
|
}
|
|
20
20
|
const arrayBuffer = await response.arrayBuffer();
|
|
21
21
|
const data = new Uint8Array(arrayBuffer);
|
|
@@ -25,12 +25,12 @@ export class Image {
|
|
|
25
25
|
mimeType = detected.mime;
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
-
const contentType = response.headers.get("content-type")
|
|
28
|
+
const contentType = parseContentType(response.headers.get("content-type")).mimeType;
|
|
29
29
|
if (contentType && SUPPORTED_IMAGE_MIMES.has(contentType)) {
|
|
30
30
|
mimeType = contentType;
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
throw new Error(`Unable to detect image MIME type from ${url}`);
|
|
33
|
+
throw new Error(`Unable to detect image MIME type from ${safeRemoteLabel(url)}`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
const base64 = Buffer.from(data).toString("base64");
|
|
@@ -39,7 +39,10 @@ export class Image {
|
|
|
39
39
|
static fromBytes(data, format) {
|
|
40
40
|
let mimeType;
|
|
41
41
|
if (format) {
|
|
42
|
-
mimeType = format.includes("/") ? format : `image/${format}
|
|
42
|
+
mimeType = (format.includes("/") ? format : `image/${format}`).toLowerCase();
|
|
43
|
+
if (!SUPPORTED_IMAGE_MIMES.has(mimeType)) {
|
|
44
|
+
throw new Error(`Unsupported image MIME type: ${mimeType}`);
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
48
|
const detected = fileTypeFromBuffer(data);
|
|
@@ -52,7 +55,12 @@ export class Image {
|
|
|
52
55
|
return new Image(base64, mimeType);
|
|
53
56
|
}
|
|
54
57
|
static fromBase64(base64, mimeType) {
|
|
55
|
-
|
|
58
|
+
assertBase64(base64);
|
|
59
|
+
const normalizedMimeType = mimeType.toLowerCase();
|
|
60
|
+
if (!SUPPORTED_IMAGE_MIMES.has(normalizedMimeType)) {
|
|
61
|
+
throw new Error(`Unsupported image MIME type: ${normalizedMimeType}`);
|
|
62
|
+
}
|
|
63
|
+
return new Image(base64, normalizedMimeType);
|
|
56
64
|
}
|
|
57
65
|
toContentBlock() {
|
|
58
66
|
return {
|
package/dist/content/mime.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export { fileTypeFromBuffer, type FileTypeResult } from "./file-type.js";
|
|
2
|
+
export declare function parseContentType(value: string | null): {
|
|
3
|
+
mimeType?: string;
|
|
4
|
+
charset?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function assertBase64(value: string): void;
|
|
7
|
+
export declare function safeRemoteLabel(url: string): string;
|
package/dist/content/mime.js
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
1
|
export { fileTypeFromBuffer } from "./file-type.js";
|
|
2
|
+
export function parseContentType(value) {
|
|
3
|
+
if (!value) {
|
|
4
|
+
return {};
|
|
5
|
+
}
|
|
6
|
+
const [rawMimeType, ...parameters] = value.split(";");
|
|
7
|
+
const mimeType = rawMimeType?.trim().toLowerCase();
|
|
8
|
+
const charsetParameter = parameters.find((parameter) => parameter.trim().toLowerCase().startsWith("charset="));
|
|
9
|
+
const rawCharset = charsetParameter?.split("=", 2)[1]?.trim();
|
|
10
|
+
const charset = rawCharset?.startsWith('"') && rawCharset.endsWith('"')
|
|
11
|
+
? rawCharset.slice(1, -1)
|
|
12
|
+
: rawCharset;
|
|
13
|
+
return {
|
|
14
|
+
...(mimeType ? { mimeType } : {}),
|
|
15
|
+
...(charset ? { charset } : {})
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function assertBase64(value) {
|
|
19
|
+
if (value.length % 4 !== 0) {
|
|
20
|
+
throw new Error("Invalid base64 content");
|
|
21
|
+
}
|
|
22
|
+
let paddingStarted = false;
|
|
23
|
+
let paddingCount = 0;
|
|
24
|
+
for (const character of value) {
|
|
25
|
+
if (character === "=") {
|
|
26
|
+
paddingStarted = true;
|
|
27
|
+
paddingCount += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const code = character.charCodeAt(0);
|
|
31
|
+
const isValid = (code >= 65 && code <= 90) ||
|
|
32
|
+
(code >= 97 && code <= 122) ||
|
|
33
|
+
(code >= 48 && code <= 57) ||
|
|
34
|
+
character === "+" ||
|
|
35
|
+
character === "/";
|
|
36
|
+
if (!isValid || paddingStarted) {
|
|
37
|
+
throw new Error("Invalid base64 content");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (paddingCount > 2) {
|
|
41
|
+
throw new Error("Invalid base64 content");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function safeRemoteLabel(url) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = new URL(url);
|
|
47
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return "remote resource";
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { createServer } from "./server.js";
|
|
2
|
-
export type { Server } from "./server.js";
|
|
2
|
+
export type { MessageHandler, MessageSession, Server } from "./server.js";
|
|
3
3
|
export { defineSchema } from "./schema.js";
|
|
4
4
|
export type { TypedSchema } from "./schema.js";
|
|
5
5
|
export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, } from "./content/index.js";
|
|
6
6
|
export type { ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, } from "./content/index.js";
|
|
7
7
|
export type { ToolReturn } from "./content/index.js";
|
|
8
|
-
export type { ServerOptions, ToolHandler, ToolDefinition, Tool, CallToolResult, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, } from "./types.js";
|
|
8
|
+
export type { ServerOptions, ToolHandler, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, } from "./types.js";
|
|
9
9
|
export { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
package/dist/jsonrpc.js
CHANGED
|
@@ -28,7 +28,25 @@ export function parseMessage(line) {
|
|
|
28
28
|
}
|
|
29
29
|
const obj = parsed;
|
|
30
30
|
const hasId = "id" in obj;
|
|
31
|
-
const id = typeof obj.id === "string"
|
|
31
|
+
const id = typeof obj.id === "string"
|
|
32
|
+
? obj.id
|
|
33
|
+
: typeof obj.id === "number" && Number.isFinite(obj.id)
|
|
34
|
+
? obj.id
|
|
35
|
+
: obj.id === null
|
|
36
|
+
? null
|
|
37
|
+
: null;
|
|
38
|
+
if ("params" in obj
|
|
39
|
+
&& (typeof obj.params !== "object"
|
|
40
|
+
|| obj.params === null)) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: {
|
|
44
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
45
|
+
message: "Invalid Request",
|
|
46
|
+
},
|
|
47
|
+
id,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
32
50
|
if (obj.jsonrpc !== "2.0") {
|
|
33
51
|
return {
|
|
34
52
|
success: false,
|
|
@@ -60,7 +78,7 @@ export function parseMessage(line) {
|
|
|
60
78
|
},
|
|
61
79
|
};
|
|
62
80
|
}
|
|
63
|
-
if (id === null) {
|
|
81
|
+
if (obj.id !== null && id === null) {
|
|
64
82
|
return {
|
|
65
83
|
success: false,
|
|
66
84
|
error: {
|
package/dist/schema.js
CHANGED
|
@@ -2,10 +2,15 @@ export function defineSchema(definition) {
|
|
|
2
2
|
const properties = {};
|
|
3
3
|
const required = [];
|
|
4
4
|
for (const [key, prop] of Object.entries(definition)) {
|
|
5
|
-
properties
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Object.defineProperty(properties, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: {
|
|
10
|
+
type: prop.type,
|
|
11
|
+
...(prop.description !== undefined && { description: prop.description }),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
9
14
|
if (!prop.optional) {
|
|
10
15
|
required.push(key);
|
|
11
16
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
import type { ServerOptions, ToolHandler, HandleResult, Transport, SDKTransport, JSONRPCNotification } from "./types.js";
|
|
1
|
+
import type { ServerOptions, ToolDefinition, ToolHandler, HandleResult, Prompt, PromptHandler, Resource, ResourceHandler, ResourceTemplate, Transport, SDKTransport, JSONRPCNotification } from "./types.js";
|
|
2
2
|
import type { TypedSchema } from "./schema.js";
|
|
3
3
|
export interface Server {
|
|
4
4
|
tool<T>(name: string, description: string, inputSchema: TypedSchema<T>, handler: ToolHandler<T>): Server;
|
|
5
|
+
registerTool<T>(definition: Omit<ToolDefinition<T>, "handler">, handler: ToolHandler<T>): Server;
|
|
6
|
+
prompt(definition: Prompt, handler: PromptHandler): Server;
|
|
7
|
+
resource(definition: Resource, handler: ResourceHandler): Server;
|
|
8
|
+
resourceTemplate(definition: ResourceTemplate, handler: ResourceHandler): Server;
|
|
5
9
|
onNotification(listener: (notification: JSONRPCNotification) => void): () => void;
|
|
6
10
|
removeTool(name: string): boolean;
|
|
11
|
+
removePrompt(name: string): boolean;
|
|
12
|
+
removeResource(uri: string): boolean;
|
|
13
|
+
removeResourceTemplate(uriTemplate: string): boolean;
|
|
7
14
|
notifyToolsChanged(): Promise<void>;
|
|
15
|
+
notifyPromptsChanged(): Promise<void>;
|
|
16
|
+
notifyResourcesChanged(): Promise<void>;
|
|
17
|
+
notifyResourceUpdated(uri: string): Promise<void>;
|
|
18
|
+
createMessageSession(listener?: (notification: JSONRPCNotification) => void | Promise<void>): MessageSession;
|
|
8
19
|
handleMessage(method: string, params?: Record<string, unknown>): Promise<HandleResult>;
|
|
9
20
|
listen(): Promise<void>;
|
|
10
21
|
connect(transport: Transport): Promise<void>;
|
|
11
22
|
connectSDK(transport: SDKTransport): Promise<void>;
|
|
12
23
|
}
|
|
24
|
+
export type MessageHandler = (method: string, params?: Record<string, unknown>) => Promise<HandleResult>;
|
|
25
|
+
export interface MessageSession {
|
|
26
|
+
handleMessage: MessageHandler;
|
|
27
|
+
close(): void;
|
|
28
|
+
}
|
|
13
29
|
export declare function createServer(options: ServerOptions): Server;
|
package/dist/server.js
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
1
|
import * as readline from "readline";
|
|
2
|
+
import AjvModule from "ajv";
|
|
3
|
+
import uriTemplateParser from "uri-template";
|
|
4
|
+
import UriTemplate from "uri-template-lite";
|
|
2
5
|
import { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
3
6
|
import { parseMessage, formatSuccessResponse, formatErrorResponse, } from "./jsonrpc.js";
|
|
4
7
|
import { toContentBlocks } from "./content/convert.js";
|
|
5
8
|
const PROTOCOL_VERSION = "2025-11-25";
|
|
9
|
+
const SUPPORTED_PROTOCOL_VERSIONS = new Set([
|
|
10
|
+
"2025-03-26",
|
|
11
|
+
"2025-06-18",
|
|
12
|
+
PROTOCOL_VERSION,
|
|
13
|
+
]);
|
|
6
14
|
export function createServer(options) {
|
|
15
|
+
const Ajv = "default" in AjvModule ? AjvModule.default : AjvModule;
|
|
16
|
+
const jsonSchemaValidator = new Ajv({ strict: false });
|
|
17
|
+
const supportNotifications = options.supportNotifications !== false;
|
|
18
|
+
const supportResourceSubscriptions = options.supportResourceSubscriptions !== false;
|
|
7
19
|
const tools = new Map();
|
|
20
|
+
const prompts = new Map();
|
|
21
|
+
const resources = new Map();
|
|
22
|
+
const resourceTemplates = new Map();
|
|
8
23
|
const notificationListeners = new Set();
|
|
9
|
-
|
|
10
|
-
const
|
|
24
|
+
const connectionNotificationListeners = new Map();
|
|
25
|
+
const defaultLifecycle = {
|
|
26
|
+
initialized: false,
|
|
27
|
+
initializeAccepted: false,
|
|
28
|
+
notificationReady: false,
|
|
29
|
+
resourceSubscriptions: new Set(),
|
|
30
|
+
};
|
|
31
|
+
const messageLifecycles = new Set([defaultLifecycle]);
|
|
32
|
+
const handleMessageWithLifecycle = async (method, lifecycle, params) => {
|
|
11
33
|
// Allow ping and initialize before initialization
|
|
12
34
|
if (method === "ping") {
|
|
13
35
|
return { result: {} };
|
|
14
36
|
}
|
|
15
37
|
if (method === "initialize") {
|
|
16
|
-
|
|
38
|
+
// Re-initialize on the same connection is idempotent: real MCP clients
|
|
39
|
+
// (e.g. kimi-cli via fastmcp) re-send `initialize` on a persistent
|
|
40
|
+
// connection per tool call, and the official MCP SDK server re-responds
|
|
41
|
+
// with InitializeResult instead of erroring. Per-connection isolation is
|
|
42
|
+
// still enforced by the separate lifecycle object given to each connection.
|
|
43
|
+
lifecycle.initializeAccepted = true;
|
|
44
|
+
lifecycle.initialized = true;
|
|
45
|
+
lifecycle.notificationReady = false;
|
|
17
46
|
const requestedProtocol = typeof params?.protocolVersion === "string"
|
|
18
47
|
? params.protocolVersion
|
|
19
|
-
:
|
|
48
|
+
: undefined;
|
|
20
49
|
const result = {
|
|
21
|
-
protocolVersion: requestedProtocol
|
|
50
|
+
protocolVersion: requestedProtocol !== undefined && SUPPORTED_PROTOCOL_VERSIONS.has(requestedProtocol)
|
|
51
|
+
? requestedProtocol
|
|
52
|
+
: PROTOCOL_VERSION,
|
|
22
53
|
capabilities: {
|
|
23
54
|
tools: {
|
|
24
|
-
listChanged: true,
|
|
55
|
+
...(supportNotifications ? { listChanged: true } : {}),
|
|
56
|
+
},
|
|
57
|
+
prompts: {
|
|
58
|
+
...(supportNotifications ? { listChanged: true } : {}),
|
|
59
|
+
},
|
|
60
|
+
resources: {
|
|
61
|
+
...(supportNotifications ? { listChanged: true } : {}),
|
|
62
|
+
...(supportResourceSubscriptions ? { subscribe: true } : {}),
|
|
25
63
|
},
|
|
26
64
|
},
|
|
27
65
|
serverInfo: {
|
|
@@ -32,10 +70,19 @@ export function createServer(options) {
|
|
|
32
70
|
return { result };
|
|
33
71
|
}
|
|
34
72
|
if (method === "notifications/initialized") {
|
|
73
|
+
if (!lifecycle.initializeAccepted) {
|
|
74
|
+
return {
|
|
75
|
+
error: {
|
|
76
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
77
|
+
message: "Server not initialized",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
lifecycle.notificationReady = true;
|
|
35
82
|
return { result: undefined };
|
|
36
83
|
}
|
|
37
84
|
// All other methods require initialization
|
|
38
|
-
if (!initialized) {
|
|
85
|
+
if (!lifecycle.initialized) {
|
|
39
86
|
return {
|
|
40
87
|
error: {
|
|
41
88
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
@@ -46,17 +93,16 @@ export function createServer(options) {
|
|
|
46
93
|
if (method === "tools/list") {
|
|
47
94
|
const toolList = [];
|
|
48
95
|
for (const tool of tools.values()) {
|
|
96
|
+
const descriptor = { ...tool };
|
|
97
|
+
delete descriptor.handler;
|
|
49
98
|
toolList.push({
|
|
50
|
-
|
|
51
|
-
description: tool.description,
|
|
52
|
-
inputSchema: tool.inputSchema,
|
|
99
|
+
...descriptor,
|
|
53
100
|
});
|
|
54
101
|
}
|
|
55
102
|
return { result: { tools: toolList } };
|
|
56
103
|
}
|
|
57
104
|
if (method === "tools/call") {
|
|
58
105
|
const toolName = params?.name;
|
|
59
|
-
const toolArgs = params?.arguments || {};
|
|
60
106
|
if (!toolName) {
|
|
61
107
|
return {
|
|
62
108
|
error: {
|
|
@@ -74,11 +120,28 @@ export function createServer(options) {
|
|
|
74
120
|
},
|
|
75
121
|
};
|
|
76
122
|
}
|
|
123
|
+
const toolArgs = (params?.arguments ?? {});
|
|
124
|
+
if (options.validateToolArguments !== false && !jsonSchemaValidator.validate(tool.inputSchema, toolArgs)) {
|
|
125
|
+
return {
|
|
126
|
+
error: {
|
|
127
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
128
|
+
message: "Invalid tool arguments",
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
77
132
|
try {
|
|
78
133
|
const handlerResult = await tool.handler(toolArgs);
|
|
134
|
+
if (hasContentArray(handlerResult) && !isCallToolResult(handlerResult)) {
|
|
135
|
+
throw new Error("Invalid tool result");
|
|
136
|
+
}
|
|
79
137
|
const result = isCallToolResult(handlerResult)
|
|
80
138
|
? handlerResult
|
|
81
139
|
: { content: toContentBlocks(handlerResult) };
|
|
140
|
+
if (tool.outputSchema !== undefined
|
|
141
|
+
&& (result.structuredContent === undefined
|
|
142
|
+
|| !jsonSchemaValidator.validate(tool.outputSchema, result.structuredContent))) {
|
|
143
|
+
throw new Error("Invalid structured tool result");
|
|
144
|
+
}
|
|
82
145
|
return { result };
|
|
83
146
|
}
|
|
84
147
|
catch (err) {
|
|
@@ -98,6 +161,96 @@ export function createServer(options) {
|
|
|
98
161
|
return { result };
|
|
99
162
|
}
|
|
100
163
|
}
|
|
164
|
+
if (method === "prompts/list") {
|
|
165
|
+
return {
|
|
166
|
+
result: {
|
|
167
|
+
prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (method === "prompts/get") {
|
|
172
|
+
const promptName = typeof params?.name === "string" ? params.name : undefined;
|
|
173
|
+
if (promptName === undefined) {
|
|
174
|
+
return invalidParams("Prompt name required");
|
|
175
|
+
}
|
|
176
|
+
const prompt = prompts.get(promptName);
|
|
177
|
+
if (prompt === undefined) {
|
|
178
|
+
return invalidParams(`Prompt not found: ${promptName}`);
|
|
179
|
+
}
|
|
180
|
+
const args = toStringArguments(params?.arguments);
|
|
181
|
+
if (args === undefined || !hasRequiredPromptArguments(prompt, args)) {
|
|
182
|
+
return invalidParams("Invalid prompt arguments");
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const result = await prompt.handler(args);
|
|
186
|
+
if (!isGetPromptResult(result)) {
|
|
187
|
+
return internalError("Invalid prompt result");
|
|
188
|
+
}
|
|
189
|
+
return { result };
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return internalError(toErrorMessage(error));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (method === "resources/list") {
|
|
196
|
+
return {
|
|
197
|
+
result: {
|
|
198
|
+
resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (method === "resources/templates/list") {
|
|
203
|
+
return {
|
|
204
|
+
result: {
|
|
205
|
+
resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate),
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (method === "resources/read") {
|
|
210
|
+
const uri = typeof params?.uri === "string" ? params.uri : undefined;
|
|
211
|
+
if (uri === undefined || !isValidUri(uri)) {
|
|
212
|
+
return invalidParams("Resource URI required");
|
|
213
|
+
}
|
|
214
|
+
const resource = findReadableResource(uri, resources, resourceTemplates);
|
|
215
|
+
if (resource === undefined) {
|
|
216
|
+
return resourceNotFound(uri);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const result = await resource.handler(uri);
|
|
220
|
+
if (!isReadResourceResult(result)) {
|
|
221
|
+
return internalError("Invalid resource result");
|
|
222
|
+
}
|
|
223
|
+
return { result };
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
return internalError(toErrorMessage(error));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (method === "resources/subscribe" || method === "resources/unsubscribe") {
|
|
230
|
+
if (!supportResourceSubscriptions) {
|
|
231
|
+
return {
|
|
232
|
+
error: {
|
|
233
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
234
|
+
message: "Method not found",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const uri = typeof params?.uri === "string" ? params.uri : undefined;
|
|
239
|
+
if (uri === undefined || !isValidUri(uri)) {
|
|
240
|
+
return invalidParams("Resource URI required");
|
|
241
|
+
}
|
|
242
|
+
if (method === "resources/subscribe"
|
|
243
|
+
&& findReadableResource(uri, resources, resourceTemplates) === undefined) {
|
|
244
|
+
return resourceNotFound(uri);
|
|
245
|
+
}
|
|
246
|
+
if (method === "resources/subscribe") {
|
|
247
|
+
lifecycle.resourceSubscriptions.add(uri);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
lifecycle.resourceSubscriptions.delete(uri);
|
|
251
|
+
}
|
|
252
|
+
return { result: {} };
|
|
253
|
+
}
|
|
101
254
|
return {
|
|
102
255
|
error: {
|
|
103
256
|
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
@@ -105,14 +258,47 @@ export function createServer(options) {
|
|
|
105
258
|
},
|
|
106
259
|
};
|
|
107
260
|
};
|
|
108
|
-
const
|
|
261
|
+
const createMessageSession = (listener) => {
|
|
262
|
+
const lifecycle = {
|
|
263
|
+
initialized: false,
|
|
264
|
+
initializeAccepted: false,
|
|
265
|
+
notificationReady: false,
|
|
266
|
+
resourceSubscriptions: new Set(),
|
|
267
|
+
};
|
|
268
|
+
messageLifecycles.add(lifecycle);
|
|
269
|
+
if (listener !== undefined) {
|
|
270
|
+
connectionNotificationListeners.set(listener, lifecycle);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
handleMessage: (method, params) => handleMessageWithLifecycle(method, lifecycle, params),
|
|
274
|
+
close: () => {
|
|
275
|
+
if (listener !== undefined) {
|
|
276
|
+
connectionNotificationListeners.delete(listener);
|
|
277
|
+
}
|
|
278
|
+
messageLifecycles.delete(lifecycle);
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
const handleMessage = (method, params) => handleMessageWithLifecycle(method, defaultLifecycle, params);
|
|
283
|
+
const processLine = async (line, write, messageHandler) => {
|
|
109
284
|
const parsed = parseMessage(line);
|
|
110
285
|
if (!parsed.success) {
|
|
111
286
|
write(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
112
287
|
return;
|
|
113
288
|
}
|
|
114
289
|
const { request, isNotification } = parsed;
|
|
115
|
-
|
|
290
|
+
if (isNotification && request.method === "initialize") {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!isNotification && request.method === "notifications/initialized") {
|
|
294
|
+
const requestWithId = request;
|
|
295
|
+
write(formatErrorResponse(requestWithId.id, {
|
|
296
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
297
|
+
message: "Invalid Request",
|
|
298
|
+
}) + "\n");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const { result, error } = await messageHandler(request.method, request.params);
|
|
116
302
|
if (isNotification) {
|
|
117
303
|
return;
|
|
118
304
|
}
|
|
@@ -124,14 +310,20 @@ export function createServer(options) {
|
|
|
124
310
|
write(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
125
311
|
}
|
|
126
312
|
};
|
|
127
|
-
const broadcastNotification = (method) => {
|
|
313
|
+
const broadcastNotification = async (method, params, canSend = () => true) => {
|
|
128
314
|
const notification = {
|
|
129
315
|
jsonrpc: "2.0",
|
|
130
316
|
method,
|
|
317
|
+
...(params === undefined ? {} : { params }),
|
|
131
318
|
};
|
|
132
319
|
for (const listener of notificationListeners) {
|
|
133
320
|
listener(notification);
|
|
134
321
|
}
|
|
322
|
+
await Promise.all([...connectionNotificationListeners].map(async ([listener, lifecycle]) => {
|
|
323
|
+
if (lifecycle.notificationReady && canSend(lifecycle)) {
|
|
324
|
+
await listener(notification);
|
|
325
|
+
}
|
|
326
|
+
}));
|
|
135
327
|
};
|
|
136
328
|
const server = {
|
|
137
329
|
tool(name, description, inputSchema, handler) {
|
|
@@ -143,6 +335,30 @@ export function createServer(options) {
|
|
|
143
335
|
});
|
|
144
336
|
return server;
|
|
145
337
|
},
|
|
338
|
+
registerTool(definition, handler) {
|
|
339
|
+
tools.set(definition.name, {
|
|
340
|
+
...definition,
|
|
341
|
+
handler: handler,
|
|
342
|
+
});
|
|
343
|
+
return server;
|
|
344
|
+
},
|
|
345
|
+
prompt(definition, handler) {
|
|
346
|
+
prompts.set(definition.name, { ...definition, handler });
|
|
347
|
+
return server;
|
|
348
|
+
},
|
|
349
|
+
resource(definition, handler) {
|
|
350
|
+
if (!isValidUri(definition.uri)) {
|
|
351
|
+
throw new Error(`Invalid resource URI: ${definition.uri}`);
|
|
352
|
+
}
|
|
353
|
+
resources.set(definition.uri, { ...definition, handler });
|
|
354
|
+
return server;
|
|
355
|
+
},
|
|
356
|
+
resourceTemplate(definition, handler) {
|
|
357
|
+
uriTemplateParser.parse(definition.uriTemplate);
|
|
358
|
+
new UriTemplate(definition.uriTemplate);
|
|
359
|
+
resourceTemplates.set(definition.uriTemplate, { ...definition, handler });
|
|
360
|
+
return server;
|
|
361
|
+
},
|
|
146
362
|
onNotification(listener) {
|
|
147
363
|
notificationListeners.add(listener);
|
|
148
364
|
return () => {
|
|
@@ -152,11 +368,37 @@ export function createServer(options) {
|
|
|
152
368
|
removeTool(name) {
|
|
153
369
|
return tools.delete(name);
|
|
154
370
|
},
|
|
371
|
+
removePrompt(name) {
|
|
372
|
+
return prompts.delete(name);
|
|
373
|
+
},
|
|
374
|
+
removeResource(uri) {
|
|
375
|
+
return resources.delete(uri);
|
|
376
|
+
},
|
|
377
|
+
removeResourceTemplate(uriTemplate) {
|
|
378
|
+
return resourceTemplates.delete(uriTemplate);
|
|
379
|
+
},
|
|
155
380
|
async notifyToolsChanged() {
|
|
156
|
-
if (
|
|
157
|
-
broadcastNotification("notifications/tools/list_changed");
|
|
381
|
+
if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
382
|
+
await broadcastNotification("notifications/tools/list_changed");
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
async notifyPromptsChanged() {
|
|
386
|
+
if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
387
|
+
await broadcastNotification("notifications/prompts/list_changed");
|
|
158
388
|
}
|
|
159
389
|
},
|
|
390
|
+
async notifyResourcesChanged() {
|
|
391
|
+
if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
392
|
+
await broadcastNotification("notifications/resources/list_changed");
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
async notifyResourceUpdated(uri) {
|
|
396
|
+
if (!supportResourceSubscriptions) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
await broadcastNotification("notifications/resources/updated", { uri }, (lifecycle) => lifecycle.resourceSubscriptions.has(uri));
|
|
400
|
+
},
|
|
401
|
+
createMessageSession,
|
|
160
402
|
handleMessage,
|
|
161
403
|
async listen() {
|
|
162
404
|
return server.connect({
|
|
@@ -166,27 +408,40 @@ export function createServer(options) {
|
|
|
166
408
|
},
|
|
167
409
|
async connect(transport) {
|
|
168
410
|
return new Promise((resolve) => {
|
|
169
|
-
const
|
|
411
|
+
const lifecycle = { initialized: false, initializeAccepted: false, notificationReady: false, resourceSubscriptions: new Set() };
|
|
412
|
+
const messageHandler = (method, params) => handleMessageWithLifecycle(method, lifecycle, params);
|
|
413
|
+
messageLifecycles.add(lifecycle);
|
|
414
|
+
const listener = (notification) => {
|
|
170
415
|
transport.writable.write(`${JSON.stringify(notification)}\n`);
|
|
171
|
-
}
|
|
416
|
+
};
|
|
417
|
+
connectionNotificationListeners.set(listener, lifecycle);
|
|
172
418
|
const rl = readline.createInterface({
|
|
173
419
|
input: transport.readable,
|
|
174
420
|
crlfDelay: Infinity,
|
|
175
421
|
});
|
|
422
|
+
const pendingMessages = new Set();
|
|
176
423
|
rl.on("line", (line) => {
|
|
177
|
-
processLine(line, (data) => transport.writable.write(data));
|
|
424
|
+
const message = processLine(line, (data) => transport.writable.write(data), messageHandler);
|
|
425
|
+
pendingMessages.add(message);
|
|
426
|
+
void message.finally(() => {
|
|
427
|
+
pendingMessages.delete(message);
|
|
428
|
+
});
|
|
178
429
|
});
|
|
179
|
-
rl.on("close", () => {
|
|
180
|
-
|
|
430
|
+
rl.on("close", async () => {
|
|
431
|
+
await Promise.all([...pendingMessages]);
|
|
432
|
+
connectionNotificationListeners.delete(listener);
|
|
433
|
+
messageLifecycles.delete(lifecycle);
|
|
181
434
|
resolve();
|
|
182
435
|
});
|
|
183
436
|
});
|
|
184
437
|
},
|
|
185
438
|
async connectSDK(transport) {
|
|
186
|
-
return new Promise((resolve) => {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
439
|
+
return new Promise((resolve, reject) => {
|
|
440
|
+
const lifecycle = { initialized: false, initializeAccepted: false, notificationReady: false, resourceSubscriptions: new Set() };
|
|
441
|
+
const messageHandler = (method, params) => handleMessageWithLifecycle(method, lifecycle, params);
|
|
442
|
+
messageLifecycles.add(lifecycle);
|
|
443
|
+
const listener = (notification) => transport.send(notification);
|
|
444
|
+
connectionNotificationListeners.set(listener, lifecycle);
|
|
190
445
|
transport.onmessage = async (message) => {
|
|
191
446
|
// Ignore responses (we only handle requests/notifications)
|
|
192
447
|
if (!("method" in message)) {
|
|
@@ -194,11 +449,25 @@ export function createServer(options) {
|
|
|
194
449
|
}
|
|
195
450
|
// Handle notifications (no id) - don't respond
|
|
196
451
|
if (!("id" in message) || message.id === undefined) {
|
|
197
|
-
|
|
452
|
+
if (message.method === "initialize") {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
await messageHandler(message.method, message.params);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (message.method === "notifications/initialized") {
|
|
459
|
+
await transport.send({
|
|
460
|
+
jsonrpc: "2.0",
|
|
461
|
+
id: message.id,
|
|
462
|
+
error: {
|
|
463
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
464
|
+
message: "Invalid Request",
|
|
465
|
+
},
|
|
466
|
+
});
|
|
198
467
|
return;
|
|
199
468
|
}
|
|
200
469
|
const request = message;
|
|
201
|
-
const { result, error } = await
|
|
470
|
+
const { result, error } = await messageHandler(request.method, request.params);
|
|
202
471
|
if (error) {
|
|
203
472
|
const response = {
|
|
204
473
|
jsonrpc: "2.0",
|
|
@@ -217,18 +486,181 @@ export function createServer(options) {
|
|
|
217
486
|
}
|
|
218
487
|
};
|
|
219
488
|
transport.onclose = () => {
|
|
220
|
-
|
|
489
|
+
connectionNotificationListeners.delete(listener);
|
|
490
|
+
messageLifecycles.delete(lifecycle);
|
|
221
491
|
resolve();
|
|
222
492
|
};
|
|
223
|
-
transport.start()
|
|
493
|
+
void transport.start().catch((error) => {
|
|
494
|
+
connectionNotificationListeners.delete(listener);
|
|
495
|
+
messageLifecycles.delete(lifecycle);
|
|
496
|
+
reject(error);
|
|
497
|
+
});
|
|
224
498
|
});
|
|
225
499
|
},
|
|
226
500
|
};
|
|
227
501
|
return server;
|
|
228
502
|
}
|
|
503
|
+
function invalidParams(message) {
|
|
504
|
+
return {
|
|
505
|
+
error: {
|
|
506
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
507
|
+
message,
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function internalError(message) {
|
|
512
|
+
return {
|
|
513
|
+
error: {
|
|
514
|
+
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
515
|
+
message,
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function resourceNotFound(uri) {
|
|
520
|
+
return {
|
|
521
|
+
error: {
|
|
522
|
+
code: JSON_RPC_ERROR_CODES.RESOURCE_NOT_FOUND,
|
|
523
|
+
message: `Resource not found: ${uri}`,
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function toErrorMessage(error) {
|
|
528
|
+
return error instanceof Error ? error.message : String(error);
|
|
529
|
+
}
|
|
530
|
+
function isValidUri(uri) {
|
|
531
|
+
try {
|
|
532
|
+
new URL(uri);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function toStringArguments(value) {
|
|
540
|
+
if (value === undefined) {
|
|
541
|
+
return {};
|
|
542
|
+
}
|
|
543
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const args = {};
|
|
547
|
+
for (const [name, argument] of Object.entries(value)) {
|
|
548
|
+
if (typeof argument !== "string") {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
args[name] = argument;
|
|
552
|
+
}
|
|
553
|
+
return args;
|
|
554
|
+
}
|
|
555
|
+
function hasRequiredPromptArguments(prompt, args) {
|
|
556
|
+
return (prompt.arguments ?? []).every((argument) => argument.required !== true || args[argument.name] !== undefined);
|
|
557
|
+
}
|
|
558
|
+
function findReadableResource(uri, resources, resourceTemplates) {
|
|
559
|
+
const resource = resources.get(uri);
|
|
560
|
+
if (resource !== undefined) {
|
|
561
|
+
return resource;
|
|
562
|
+
}
|
|
563
|
+
return [...resourceTemplates.values()].find((template) => matchesUriTemplate(template.uriTemplate, uri));
|
|
564
|
+
}
|
|
565
|
+
function matchesUriTemplate(template, uri) {
|
|
566
|
+
try {
|
|
567
|
+
return new UriTemplate(template).match(uri) !== null;
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
229
573
|
function isCallToolResult(value) {
|
|
230
|
-
|
|
574
|
+
return hasContentArray(value) && value.content.every(isContentItem);
|
|
575
|
+
}
|
|
576
|
+
function isGetPromptResult(value) {
|
|
577
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "messages")) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
return Array.isArray(value.messages)
|
|
581
|
+
&& value.messages.every((message) => typeof message === "object"
|
|
582
|
+
&& message !== null
|
|
583
|
+
&& hasOwnProperty(message, "role")
|
|
584
|
+
&& (message.role === "user" || message.role === "assistant")
|
|
585
|
+
&& hasOwnProperty(message, "content")
|
|
586
|
+
&& isPromptContentItem(message.content));
|
|
587
|
+
}
|
|
588
|
+
function isReadResourceResult(value) {
|
|
589
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "contents")) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
return Array.isArray(value.contents)
|
|
593
|
+
&& value.contents.every((content) => typeof content === "object"
|
|
594
|
+
&& content !== null
|
|
595
|
+
&& hasOwnProperty(content, "uri")
|
|
596
|
+
&& typeof content.uri === "string"
|
|
597
|
+
&& isValidUri(content.uri)
|
|
598
|
+
&& ((hasOwnProperty(content, "text") && typeof content.text === "string")
|
|
599
|
+
|| (hasOwnProperty(content, "blob") && typeof content.blob === "string" && isBase64(content.blob))));
|
|
600
|
+
}
|
|
601
|
+
function hasContentArray(value) {
|
|
602
|
+
return typeof value === "object" && value !== null && hasOwnProperty(value, "content")
|
|
603
|
+
&& Array.isArray(value.content);
|
|
604
|
+
}
|
|
605
|
+
function isContentItem(value) {
|
|
606
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "type")) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
const block = value;
|
|
610
|
+
if (block.type === "text") {
|
|
611
|
+
return hasOwnProperty(block, "text") && typeof block.text === "string";
|
|
612
|
+
}
|
|
613
|
+
if (block.type === "image" || block.type === "audio") {
|
|
614
|
+
return hasOwnProperty(block, "data")
|
|
615
|
+
&& typeof block.data === "string"
|
|
616
|
+
&& isBase64(block.data)
|
|
617
|
+
&& hasOwnProperty(block, "mimeType")
|
|
618
|
+
&& typeof block.mimeType === "string";
|
|
619
|
+
}
|
|
620
|
+
if (block.type === "resource_link") {
|
|
621
|
+
return hasOwnProperty(block, "uri")
|
|
622
|
+
&& typeof block.uri === "string"
|
|
623
|
+
&& hasOwnProperty(block, "name")
|
|
624
|
+
&& typeof block.name === "string";
|
|
625
|
+
}
|
|
626
|
+
if (block.type !== "resource"
|
|
627
|
+
|| !hasOwnProperty(block, "resource")
|
|
628
|
+
|| typeof block.resource !== "object"
|
|
629
|
+
|| block.resource === null) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
const resource = block.resource;
|
|
633
|
+
return hasOwnProperty(resource, "uri")
|
|
634
|
+
&& typeof resource.uri === "string"
|
|
635
|
+
&& (!hasOwnProperty(resource, "mimeType") || typeof resource.mimeType === "string")
|
|
636
|
+
&& ((hasOwnProperty(resource, "text") && typeof resource.text === "string")
|
|
637
|
+
|| (hasOwnProperty(resource, "blob") && typeof resource.blob === "string" && isBase64(resource.blob)));
|
|
638
|
+
}
|
|
639
|
+
function isBase64(value) {
|
|
640
|
+
if (value.length === 0) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
if (value.length % 4 !== 0) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
647
|
+
const paddingStart = value.indexOf("=");
|
|
648
|
+
const encoded = paddingStart === -1 ? value : value.slice(0, paddingStart);
|
|
649
|
+
const padding = paddingStart === -1 ? "" : value.slice(paddingStart);
|
|
650
|
+
if (padding.length > 2 || [...padding].some((character) => character !== "=")) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
if ([...encoded].some((character) => !alphabet.includes(character))) {
|
|
231
654
|
return false;
|
|
232
655
|
}
|
|
233
|
-
return
|
|
656
|
+
return Buffer.from(value, "base64").toString("base64") === value;
|
|
657
|
+
}
|
|
658
|
+
function isPromptContentItem(value) {
|
|
659
|
+
if (!isContentItem(value)) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
return !(typeof value === "object" && value !== null && hasOwnProperty(value, "type") && value.type === "resource_link");
|
|
663
|
+
}
|
|
664
|
+
function hasOwnProperty(value, name) {
|
|
665
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
234
666
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface JSONRPCRequest {
|
|
2
2
|
jsonrpc: "2.0";
|
|
3
|
-
id: string | number;
|
|
3
|
+
id: string | number | null;
|
|
4
4
|
method: string;
|
|
5
5
|
params?: Record<string, unknown>;
|
|
6
6
|
}
|
|
@@ -15,13 +15,14 @@ export interface JSONRPCError {
|
|
|
15
15
|
message: string;
|
|
16
16
|
data?: unknown;
|
|
17
17
|
}
|
|
18
|
-
export declare const JSON_RPC_ERROR_CODES: {
|
|
18
|
+
export declare const JSON_RPC_ERROR_CODES: Readonly<{
|
|
19
19
|
readonly PARSE_ERROR: -32700;
|
|
20
20
|
readonly INVALID_REQUEST: -32600;
|
|
21
21
|
readonly METHOD_NOT_FOUND: -32601;
|
|
22
22
|
readonly INVALID_PARAMS: -32602;
|
|
23
23
|
readonly INTERNAL_ERROR: -32603;
|
|
24
|
-
|
|
24
|
+
readonly RESOURCE_NOT_FOUND: -32002;
|
|
25
|
+
}>;
|
|
25
26
|
export declare class ToolError extends Error {
|
|
26
27
|
readonly code: number;
|
|
27
28
|
constructor(code: number, message: string);
|
|
@@ -29,10 +30,19 @@ export declare class ToolError extends Error {
|
|
|
29
30
|
export interface ToolsCapability {
|
|
30
31
|
listChanged?: boolean;
|
|
31
32
|
}
|
|
33
|
+
export interface PromptsCapability {
|
|
34
|
+
listChanged?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ResourcesCapability {
|
|
37
|
+
subscribe?: boolean;
|
|
38
|
+
listChanged?: boolean;
|
|
39
|
+
}
|
|
32
40
|
export interface InitializeResult {
|
|
33
41
|
protocolVersion: string;
|
|
34
42
|
capabilities: {
|
|
35
43
|
tools?: ToolsCapability;
|
|
44
|
+
prompts?: PromptsCapability;
|
|
45
|
+
resources?: ResourcesCapability;
|
|
36
46
|
};
|
|
37
47
|
serverInfo: {
|
|
38
48
|
name: string;
|
|
@@ -41,13 +51,116 @@ export interface InitializeResult {
|
|
|
41
51
|
}
|
|
42
52
|
export interface Tool {
|
|
43
53
|
name: string;
|
|
44
|
-
|
|
54
|
+
title?: string;
|
|
55
|
+
description?: string;
|
|
45
56
|
inputSchema: JSONSchema;
|
|
57
|
+
outputSchema?: JSONSchema;
|
|
58
|
+
annotations?: ToolAnnotations;
|
|
59
|
+
execution?: ToolExecution;
|
|
60
|
+
icons?: Icon[];
|
|
61
|
+
_meta?: Record<string, unknown>;
|
|
46
62
|
}
|
|
47
63
|
export interface CallToolResult {
|
|
48
64
|
content: ContentItem[];
|
|
65
|
+
structuredContent?: Record<string, unknown>;
|
|
49
66
|
isError?: boolean;
|
|
50
67
|
}
|
|
68
|
+
export interface ToolAnnotations {
|
|
69
|
+
title?: string;
|
|
70
|
+
readOnlyHint?: boolean;
|
|
71
|
+
destructiveHint?: boolean;
|
|
72
|
+
idempotentHint?: boolean;
|
|
73
|
+
openWorldHint?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface ToolExecution {
|
|
76
|
+
taskSupport?: "optional" | "required" | "forbidden";
|
|
77
|
+
}
|
|
78
|
+
export interface Icon {
|
|
79
|
+
src: string;
|
|
80
|
+
mimeType?: string;
|
|
81
|
+
sizes?: string[];
|
|
82
|
+
theme?: "light" | "dark";
|
|
83
|
+
}
|
|
84
|
+
export interface ContentAnnotations {
|
|
85
|
+
audience?: Array<"user" | "assistant">;
|
|
86
|
+
priority?: number;
|
|
87
|
+
lastModified?: string;
|
|
88
|
+
}
|
|
89
|
+
export interface ResourceLink {
|
|
90
|
+
type: "resource_link";
|
|
91
|
+
uri: string;
|
|
92
|
+
name: string;
|
|
93
|
+
title?: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
mimeType?: string;
|
|
96
|
+
size?: number;
|
|
97
|
+
annotations?: ContentAnnotations;
|
|
98
|
+
}
|
|
99
|
+
export interface PromptArgument {
|
|
100
|
+
name: string;
|
|
101
|
+
description?: string;
|
|
102
|
+
required?: boolean;
|
|
103
|
+
}
|
|
104
|
+
export interface Prompt {
|
|
105
|
+
name: string;
|
|
106
|
+
title?: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
arguments?: PromptArgument[];
|
|
109
|
+
icons?: Icon[];
|
|
110
|
+
_meta?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
export interface PromptMessage {
|
|
113
|
+
role: "user" | "assistant";
|
|
114
|
+
content: PromptContentItem;
|
|
115
|
+
}
|
|
116
|
+
export interface GetPromptResult {
|
|
117
|
+
description?: string;
|
|
118
|
+
messages: PromptMessage[];
|
|
119
|
+
}
|
|
120
|
+
export type PromptHandler = (args: Record<string, string>) => Promise<GetPromptResult> | GetPromptResult;
|
|
121
|
+
export interface PromptDefinition extends Prompt {
|
|
122
|
+
handler: PromptHandler;
|
|
123
|
+
}
|
|
124
|
+
export interface Resource {
|
|
125
|
+
uri: string;
|
|
126
|
+
name: string;
|
|
127
|
+
title?: string;
|
|
128
|
+
description?: string;
|
|
129
|
+
mimeType?: string;
|
|
130
|
+
size?: number;
|
|
131
|
+
annotations?: ContentAnnotations;
|
|
132
|
+
icons?: Icon[];
|
|
133
|
+
_meta?: Record<string, unknown>;
|
|
134
|
+
}
|
|
135
|
+
export interface ResourceTemplate {
|
|
136
|
+
uriTemplate: string;
|
|
137
|
+
name: string;
|
|
138
|
+
title?: string;
|
|
139
|
+
description?: string;
|
|
140
|
+
mimeType?: string;
|
|
141
|
+
annotations?: ContentAnnotations;
|
|
142
|
+
icons?: Icon[];
|
|
143
|
+
_meta?: Record<string, unknown>;
|
|
144
|
+
}
|
|
145
|
+
export type ResourceContents = {
|
|
146
|
+
uri: string;
|
|
147
|
+
mimeType?: string;
|
|
148
|
+
text: string;
|
|
149
|
+
} | {
|
|
150
|
+
uri: string;
|
|
151
|
+
mimeType?: string;
|
|
152
|
+
blob: string;
|
|
153
|
+
};
|
|
154
|
+
export interface ReadResourceResult {
|
|
155
|
+
contents: ResourceContents[];
|
|
156
|
+
}
|
|
157
|
+
export type ResourceHandler = (uri: string) => Promise<ReadResourceResult> | ReadResourceResult;
|
|
158
|
+
export interface ResourceDefinition extends Resource {
|
|
159
|
+
handler: ResourceHandler;
|
|
160
|
+
}
|
|
161
|
+
export interface ResourceTemplateDefinition extends ResourceTemplate {
|
|
162
|
+
handler: ResourceHandler;
|
|
163
|
+
}
|
|
51
164
|
export interface HandleResult {
|
|
52
165
|
result?: unknown;
|
|
53
166
|
error?: {
|
|
@@ -55,48 +168,64 @@ export interface HandleResult {
|
|
|
55
168
|
message: string;
|
|
56
169
|
};
|
|
57
170
|
}
|
|
58
|
-
export type
|
|
171
|
+
export type PromptContentItem = {
|
|
59
172
|
type: "text";
|
|
60
173
|
text: string;
|
|
174
|
+
annotations?: ContentAnnotations;
|
|
61
175
|
} | {
|
|
62
176
|
type: "image";
|
|
63
177
|
data: string;
|
|
64
178
|
mimeType: string;
|
|
179
|
+
annotations?: ContentAnnotations;
|
|
65
180
|
} | {
|
|
66
181
|
type: "audio";
|
|
67
182
|
data: string;
|
|
68
183
|
mimeType: string;
|
|
184
|
+
annotations?: ContentAnnotations;
|
|
69
185
|
} | {
|
|
70
186
|
type: "resource";
|
|
187
|
+
annotations?: ContentAnnotations;
|
|
71
188
|
resource: {
|
|
72
189
|
uri: string;
|
|
73
|
-
mimeType
|
|
190
|
+
mimeType?: string;
|
|
74
191
|
text: string;
|
|
75
192
|
} | {
|
|
76
193
|
uri: string;
|
|
77
|
-
mimeType
|
|
194
|
+
mimeType?: string;
|
|
78
195
|
blob: string;
|
|
79
196
|
};
|
|
80
197
|
};
|
|
198
|
+
export type ContentItem = PromptContentItem | ResourceLink;
|
|
81
199
|
export interface JSONSchema {
|
|
82
200
|
type: "object";
|
|
83
|
-
properties
|
|
201
|
+
properties?: Record<string, JSONSchemaProperty>;
|
|
84
202
|
required?: string[];
|
|
203
|
+
[keyword: string]: unknown;
|
|
85
204
|
}
|
|
86
|
-
export interface JSONSchemaProperty {
|
|
87
|
-
type
|
|
205
|
+
export interface JSONSchemaProperty extends Record<string, unknown> {
|
|
206
|
+
type?: string | string[];
|
|
88
207
|
description?: string;
|
|
208
|
+
[keyword: string]: unknown;
|
|
89
209
|
}
|
|
90
210
|
export interface ServerOptions {
|
|
91
211
|
name: string;
|
|
92
212
|
version: string;
|
|
213
|
+
validateToolArguments?: boolean;
|
|
214
|
+
supportNotifications?: boolean;
|
|
215
|
+
supportResourceSubscriptions?: boolean;
|
|
93
216
|
}
|
|
94
217
|
import type { ToolReturn } from "./content/index.js";
|
|
95
218
|
export type ToolHandler<T = Record<string, unknown>> = (args: T) => Promise<ToolReturn | CallToolResult> | ToolReturn | CallToolResult;
|
|
96
219
|
export interface ToolDefinition<T = Record<string, unknown>> {
|
|
97
220
|
name: string;
|
|
98
|
-
|
|
221
|
+
title?: string;
|
|
222
|
+
description?: string;
|
|
99
223
|
inputSchema: JSONSchema;
|
|
224
|
+
outputSchema?: JSONSchema;
|
|
225
|
+
annotations?: ToolAnnotations;
|
|
226
|
+
execution?: ToolExecution;
|
|
227
|
+
icons?: Icon[];
|
|
228
|
+
_meta?: Record<string, unknown>;
|
|
100
229
|
handler: ToolHandler<T>;
|
|
101
230
|
}
|
|
102
231
|
export interface Transport {
|
package/dist/types.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
// JSON-RPC error codes
|
|
2
|
-
export const JSON_RPC_ERROR_CODES = {
|
|
2
|
+
export const JSON_RPC_ERROR_CODES = Object.freeze({
|
|
3
3
|
PARSE_ERROR: -32700,
|
|
4
4
|
INVALID_REQUEST: -32600,
|
|
5
5
|
METHOD_NOT_FOUND: -32601,
|
|
6
6
|
INVALID_PARAMS: -32602,
|
|
7
|
-
INTERNAL_ERROR: -32603
|
|
8
|
-
|
|
7
|
+
INTERNAL_ERROR: -32603,
|
|
8
|
+
RESOURCE_NOT_FOUND: -32002
|
|
9
|
+
});
|
|
9
10
|
export class ToolError extends Error {
|
|
10
11
|
code;
|
|
11
12
|
constructor(code, message) {
|
|
13
|
+
if (!Number.isFinite(code)) {
|
|
14
|
+
throw new Error("ToolError code must be a finite number");
|
|
15
|
+
}
|
|
12
16
|
super(message);
|
|
13
17
|
this.code = code;
|
|
14
18
|
this.name = "ToolError";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiny-stdio-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Minimal MCP server over stdio with typed tools and rich content helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "tsc",
|
|
23
|
+
"build": "node ../../scripts/guard-package-dist.mjs && tsc",
|
|
24
24
|
"prepublishOnly": "tsc"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
@@ -44,5 +44,10 @@
|
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"ajv": "^8.20.0",
|
|
50
|
+
"uri-template": "^2.0.0",
|
|
51
|
+
"uri-template-lite": "^23.4.0"
|
|
47
52
|
}
|
|
48
53
|
}
|