toolcraft 0.0.103 → 0.0.105
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/composition.json +7 -2
- package/dist/cli.js +53 -0
- package/dist/composition.json +7 -2
- package/dist/index.d.ts +25 -3
- package/dist/index.js +18 -0
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.js +121 -1
- package/dist/sdk.d.ts +2 -1
- package/dist/sdk.js +56 -0
- package/dist/stream.d.ts +19 -0
- package/dist/stream.js +92 -0
- package/dist/testing/harness.d.ts +11 -0
- package/dist/testing/harness.js +70 -0
- package/dist/testing/index.d.ts +1 -1
- package/node_modules/tiny-stdio-mcp-server/LICENSE +21 -0
- package/node_modules/tiny-stdio-mcp-server/README.md +231 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +15 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.js +84 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +16 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +61 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/file-type.d.ts +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/file-type.js +93 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +28 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.js +110 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +15 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.js +72 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/index.d.ts +7 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/index.js +9 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/mime.d.ts +7 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/mime.js +52 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/remote.d.ts +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/remote.js +69 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +9 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.js +7 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +14 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +118 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +20 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.js +26 -0
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +35 -0
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +865 -0
- package/node_modules/tiny-stdio-mcp-server/dist/testing.d.ts +7 -0
- package/node_modules/tiny-stdio-mcp-server/dist/testing.js +20 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +247 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.js +22 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +56 -0
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +9 -5
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { assertBase64, fileTypeFromBuffer, parseContentType, safeRemoteLabel } from "./mime.js";
|
|
2
|
+
import { readRemoteBytes } from "./remote.js";
|
|
3
|
+
function isTextMimeType(mimeType) {
|
|
4
|
+
const normalizedMimeType = mimeType.toLowerCase();
|
|
5
|
+
return (normalizedMimeType.startsWith("text/") ||
|
|
6
|
+
normalizedMimeType === "application/json" ||
|
|
7
|
+
normalizedMimeType.endsWith("+json") ||
|
|
8
|
+
normalizedMimeType === "application/xml" ||
|
|
9
|
+
normalizedMimeType.endsWith("+xml") ||
|
|
10
|
+
normalizedMimeType === "application/javascript" ||
|
|
11
|
+
normalizedMimeType === "application/typescript");
|
|
12
|
+
}
|
|
13
|
+
export class File {
|
|
14
|
+
data;
|
|
15
|
+
mimeType;
|
|
16
|
+
isText;
|
|
17
|
+
name;
|
|
18
|
+
charset;
|
|
19
|
+
constructor(data, mimeType, isText, name, charset = "utf-8") {
|
|
20
|
+
this.data = data;
|
|
21
|
+
this.mimeType = mimeType;
|
|
22
|
+
this.isText = isText;
|
|
23
|
+
this.name = name;
|
|
24
|
+
this.charset = charset;
|
|
25
|
+
}
|
|
26
|
+
static async fromUrl(url, options) {
|
|
27
|
+
const response = await fetch(url);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch file from ${safeRemoteLabel(url)}: ${response.status} ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await readRemoteBytes(response, "file", url, options);
|
|
32
|
+
const detected = fileTypeFromBuffer(data);
|
|
33
|
+
let mimeType;
|
|
34
|
+
if (detected) {
|
|
35
|
+
mimeType = detected.mime;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const contentType = parseContentType(response.headers.get("content-type"));
|
|
39
|
+
if (contentType.mimeType) {
|
|
40
|
+
mimeType = contentType.mimeType;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error(`Unable to detect MIME type from ${safeRemoteLabel(url)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
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);
|
|
59
|
+
}
|
|
60
|
+
static fromBytes(data, mimeType) {
|
|
61
|
+
const isText = isTextMimeType(mimeType);
|
|
62
|
+
return new File(data, mimeType, isText);
|
|
63
|
+
}
|
|
64
|
+
static fromText(text, mimeType = "text/plain") {
|
|
65
|
+
return new File(text, mimeType, isTextMimeType(mimeType));
|
|
66
|
+
}
|
|
67
|
+
static fromBase64(base64, mimeType) {
|
|
68
|
+
assertBase64(base64);
|
|
69
|
+
const data = Buffer.from(base64, "base64");
|
|
70
|
+
const isText = isTextMimeType(mimeType);
|
|
71
|
+
return new File(new Uint8Array(data), mimeType, isText);
|
|
72
|
+
}
|
|
73
|
+
toContentBlock() {
|
|
74
|
+
const uri = this.name ? `file:///${this.name}` : "file:///data";
|
|
75
|
+
if (this.isText) {
|
|
76
|
+
let text;
|
|
77
|
+
if (typeof this.data === "string") {
|
|
78
|
+
text = this.data;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
text = new TextDecoder(this.charset).decode(this.data);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
type: "resource",
|
|
85
|
+
resource: {
|
|
86
|
+
uri,
|
|
87
|
+
mimeType: this.mimeType,
|
|
88
|
+
text,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
let blob;
|
|
94
|
+
if (typeof this.data === "string") {
|
|
95
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
type: "resource",
|
|
102
|
+
resource: {
|
|
103
|
+
uri,
|
|
104
|
+
mimeType: this.mimeType,
|
|
105
|
+
blob,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FromUrlOptions } from "./remote.js";
|
|
2
|
+
export interface ImageContent {
|
|
3
|
+
type: "image";
|
|
4
|
+
data: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class Image {
|
|
8
|
+
private readonly base64Data;
|
|
9
|
+
private readonly mimeType;
|
|
10
|
+
private constructor();
|
|
11
|
+
static fromUrl(url: string, options?: FromUrlOptions): Promise<Image>;
|
|
12
|
+
static fromBytes(data: Uint8Array, format?: string): Image;
|
|
13
|
+
static fromBase64(base64: string, mimeType: string): Image;
|
|
14
|
+
toContentBlock(): ImageContent;
|
|
15
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { assertBase64, fileTypeFromBuffer, parseContentType, safeRemoteLabel } from "./mime.js";
|
|
2
|
+
import { readRemoteBytes } from "./remote.js";
|
|
3
|
+
const SUPPORTED_IMAGE_MIMES = new Set([
|
|
4
|
+
"image/png",
|
|
5
|
+
"image/jpeg",
|
|
6
|
+
"image/gif",
|
|
7
|
+
"image/webp",
|
|
8
|
+
]);
|
|
9
|
+
export class Image {
|
|
10
|
+
base64Data;
|
|
11
|
+
mimeType;
|
|
12
|
+
constructor(base64Data, mimeType) {
|
|
13
|
+
this.base64Data = base64Data;
|
|
14
|
+
this.mimeType = mimeType;
|
|
15
|
+
}
|
|
16
|
+
static async fromUrl(url, options) {
|
|
17
|
+
const response = await fetch(url);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error(`Failed to fetch image from ${safeRemoteLabel(url)}: ${response.status} ${response.statusText}`);
|
|
20
|
+
}
|
|
21
|
+
const data = await readRemoteBytes(response, "image", url, options);
|
|
22
|
+
const detected = fileTypeFromBuffer(data);
|
|
23
|
+
let mimeType;
|
|
24
|
+
if (detected && SUPPORTED_IMAGE_MIMES.has(detected.mime)) {
|
|
25
|
+
mimeType = detected.mime;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const contentType = parseContentType(response.headers.get("content-type")).mimeType;
|
|
29
|
+
if (contentType && SUPPORTED_IMAGE_MIMES.has(contentType)) {
|
|
30
|
+
mimeType = contentType;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new Error(`Unable to detect image MIME type from ${safeRemoteLabel(url)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
37
|
+
return new Image(base64, mimeType);
|
|
38
|
+
}
|
|
39
|
+
static fromBytes(data, format) {
|
|
40
|
+
let mimeType;
|
|
41
|
+
if (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
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const detected = fileTypeFromBuffer(data);
|
|
49
|
+
if (!detected || !SUPPORTED_IMAGE_MIMES.has(detected.mime)) {
|
|
50
|
+
throw new Error("Unable to detect image MIME type from bytes");
|
|
51
|
+
}
|
|
52
|
+
mimeType = detected.mime;
|
|
53
|
+
}
|
|
54
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
55
|
+
return new Image(base64, mimeType);
|
|
56
|
+
}
|
|
57
|
+
static fromBase64(base64, mimeType) {
|
|
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);
|
|
64
|
+
}
|
|
65
|
+
toContentBlock() {
|
|
66
|
+
return {
|
|
67
|
+
type: "image",
|
|
68
|
+
data: this.base64Data,
|
|
69
|
+
mimeType: this.mimeType,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { fileTypeFromBuffer, type FileTypeResult } from "./mime.js";
|
|
2
|
+
export { DEFAULT_FROM_URL_MAX_BYTES } from "./remote.js";
|
|
3
|
+
export type { FromUrlOptions } from "./remote.js";
|
|
4
|
+
export { Image, type ImageContent } from "./image.js";
|
|
5
|
+
export { Audio, type AudioContent } from "./audio.js";
|
|
6
|
+
export { File, type EmbeddedResource, type TextResourceContents, type BlobResourceContents, } from "./file.js";
|
|
7
|
+
export { toContentBlocks, type ContentBlock, type TextContent, type ToolReturn, } from "./convert.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// MIME detection
|
|
2
|
+
export { fileTypeFromBuffer } from "./mime.js";
|
|
3
|
+
export { DEFAULT_FROM_URL_MAX_BYTES } from "./remote.js";
|
|
4
|
+
// Content helpers
|
|
5
|
+
export { Image } from "./image.js";
|
|
6
|
+
export { Audio } from "./audio.js";
|
|
7
|
+
export { File, } from "./file.js";
|
|
8
|
+
// Conversion utility
|
|
9
|
+
export { toContentBlocks, } from "./convert.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
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;
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { safeRemoteLabel } from "./mime.js";
|
|
2
|
+
export const DEFAULT_FROM_URL_MAX_BYTES = 5 * 1024 * 1024;
|
|
3
|
+
function resolveMaxBytes(options) {
|
|
4
|
+
const maxBytes = options?.maxBytes ?? DEFAULT_FROM_URL_MAX_BYTES;
|
|
5
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
6
|
+
throw new Error("maxBytes must be a positive integer");
|
|
7
|
+
}
|
|
8
|
+
return maxBytes;
|
|
9
|
+
}
|
|
10
|
+
function createSizeError(kind, url, maxBytes) {
|
|
11
|
+
return new Error(`Remote ${kind} from ${safeRemoteLabel(url)} exceeds maximum size of ${maxBytes} bytes`);
|
|
12
|
+
}
|
|
13
|
+
function readContentLength(response) {
|
|
14
|
+
const rawContentLength = response.headers.get("content-length");
|
|
15
|
+
if (typeof rawContentLength !== "string") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const contentLength = Number(rawContentLength.trim());
|
|
19
|
+
if (!Number.isSafeInteger(contentLength) || contentLength < 0) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return contentLength;
|
|
23
|
+
}
|
|
24
|
+
function combineChunks(chunks, totalBytes) {
|
|
25
|
+
const combined = new Uint8Array(totalBytes);
|
|
26
|
+
let offset = 0;
|
|
27
|
+
for (const chunk of chunks) {
|
|
28
|
+
combined.set(chunk, offset);
|
|
29
|
+
offset += chunk.byteLength;
|
|
30
|
+
}
|
|
31
|
+
return combined;
|
|
32
|
+
}
|
|
33
|
+
async function readStreamBytes(body, kind, url, maxBytes) {
|
|
34
|
+
const reader = body.getReader();
|
|
35
|
+
const chunks = [];
|
|
36
|
+
let totalBytes = 0;
|
|
37
|
+
try {
|
|
38
|
+
while (true) {
|
|
39
|
+
const { done, value } = await reader.read();
|
|
40
|
+
if (done) {
|
|
41
|
+
return combineChunks(chunks, totalBytes);
|
|
42
|
+
}
|
|
43
|
+
totalBytes += value.byteLength;
|
|
44
|
+
if (totalBytes > maxBytes) {
|
|
45
|
+
await reader.cancel().catch(() => undefined);
|
|
46
|
+
throw createSizeError(kind, url, maxBytes);
|
|
47
|
+
}
|
|
48
|
+
chunks.push(value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
reader.releaseLock();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function readRemoteBytes(response, kind, url, options) {
|
|
56
|
+
const maxBytes = resolveMaxBytes(options);
|
|
57
|
+
const contentLength = readContentLength(response);
|
|
58
|
+
if (contentLength !== undefined && contentLength > maxBytes) {
|
|
59
|
+
throw createSizeError(kind, url, maxBytes);
|
|
60
|
+
}
|
|
61
|
+
if (response.body) {
|
|
62
|
+
return readStreamBytes(response.body, kind, url, maxBytes);
|
|
63
|
+
}
|
|
64
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
65
|
+
if (arrayBuffer.byteLength > maxBytes) {
|
|
66
|
+
throw createSizeError(kind, url, maxBytes);
|
|
67
|
+
}
|
|
68
|
+
return new Uint8Array(arrayBuffer);
|
|
69
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createServer } from "./server.js";
|
|
2
|
+
export type { CustomMethodHandler, MessageHandler, MessageSession, MessageSessionContext, Server } from "./server.js";
|
|
3
|
+
export { defineSchema } from "./schema.js";
|
|
4
|
+
export type { TypedSchema } from "./schema.js";
|
|
5
|
+
export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, DEFAULT_FROM_URL_MAX_BYTES, } from "./content/index.js";
|
|
6
|
+
export type { ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, FromUrlOptions, } from "./content/index.js";
|
|
7
|
+
export type { ToolReturn } from "./content/index.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
|
+
export { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Server
|
|
2
|
+
export { createServer } from "./server.js";
|
|
3
|
+
// Schema
|
|
4
|
+
export { defineSchema } from "./schema.js";
|
|
5
|
+
// Content helpers
|
|
6
|
+
export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, DEFAULT_FROM_URL_MAX_BYTES, } from "./content/index.js";
|
|
7
|
+
export { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSONRPCRequest, JSONRPCError, JSONRPCNotification } from "./types.js";
|
|
2
|
+
export interface ParseResult {
|
|
3
|
+
success: true;
|
|
4
|
+
request: JSONRPCRequest | JSONRPCNotification;
|
|
5
|
+
isNotification: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ParseError {
|
|
8
|
+
success: false;
|
|
9
|
+
error: JSONRPCError;
|
|
10
|
+
id: string | number | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseMessage(line: string): ParseResult | ParseError;
|
|
13
|
+
export declare function formatSuccessResponse(id: string | number | null, result: unknown): string;
|
|
14
|
+
export declare function formatErrorResponse(id: string | number | null, error: JSONRPCError): string;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { JSON_RPC_ERROR_CODES } from "./types.js";
|
|
2
|
+
export function parseMessage(line) {
|
|
3
|
+
let parsed;
|
|
4
|
+
try {
|
|
5
|
+
parsed = JSON.parse(line);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return {
|
|
9
|
+
success: false,
|
|
10
|
+
error: {
|
|
11
|
+
code: JSON_RPC_ERROR_CODES.PARSE_ERROR,
|
|
12
|
+
message: "Parse error",
|
|
13
|
+
},
|
|
14
|
+
id: null,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (typeof parsed !== "object" ||
|
|
18
|
+
parsed === null ||
|
|
19
|
+
Array.isArray(parsed)) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
error: {
|
|
23
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
24
|
+
message: "Invalid Request",
|
|
25
|
+
},
|
|
26
|
+
id: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const obj = parsed;
|
|
30
|
+
const hasId = "id" in obj;
|
|
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
|
+
|| Array.isArray(obj.params))) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: {
|
|
45
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
46
|
+
message: "Invalid Request",
|
|
47
|
+
},
|
|
48
|
+
id,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (obj.jsonrpc !== "2.0") {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: {
|
|
55
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
56
|
+
message: "Invalid Request",
|
|
57
|
+
},
|
|
58
|
+
id,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (typeof obj.method !== "string") {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: {
|
|
65
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
66
|
+
message: "Invalid Request",
|
|
67
|
+
},
|
|
68
|
+
id,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (!hasId) {
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
isNotification: true,
|
|
75
|
+
request: {
|
|
76
|
+
jsonrpc: "2.0",
|
|
77
|
+
method: obj.method,
|
|
78
|
+
params: obj.params,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (obj.id !== null && id === null) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: {
|
|
86
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
87
|
+
message: "Invalid Request",
|
|
88
|
+
},
|
|
89
|
+
id: null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
isNotification: false,
|
|
95
|
+
request: {
|
|
96
|
+
jsonrpc: "2.0",
|
|
97
|
+
id,
|
|
98
|
+
method: obj.method,
|
|
99
|
+
params: obj.params,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function formatSuccessResponse(id, result) {
|
|
104
|
+
const response = {
|
|
105
|
+
jsonrpc: "2.0",
|
|
106
|
+
id,
|
|
107
|
+
result,
|
|
108
|
+
};
|
|
109
|
+
return JSON.stringify(response);
|
|
110
|
+
}
|
|
111
|
+
export function formatErrorResponse(id, error) {
|
|
112
|
+
const response = {
|
|
113
|
+
jsonrpc: "2.0",
|
|
114
|
+
id,
|
|
115
|
+
error,
|
|
116
|
+
};
|
|
117
|
+
return JSON.stringify(response);
|
|
118
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { JSONSchema } from "./types.js";
|
|
2
|
+
type SchemaPropertyType = "string" | "number" | "integer" | "boolean" | "object" | "array";
|
|
3
|
+
interface SchemaPropertyDef {
|
|
4
|
+
type: SchemaPropertyType;
|
|
5
|
+
description?: string;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
[keyword: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
type SchemaDefinition = Record<string, SchemaPropertyDef>;
|
|
10
|
+
type InferType<T extends SchemaPropertyType> = T extends "string" ? string : T extends "number" | "integer" ? number : T extends "boolean" ? boolean : T extends "object" ? Record<string, unknown> : T extends "array" ? unknown[] : never;
|
|
11
|
+
type InferSchema<T extends SchemaDefinition> = {
|
|
12
|
+
[K in keyof T as T[K]["optional"] extends true ? never : K]: InferType<T[K]["type"]>;
|
|
13
|
+
} & {
|
|
14
|
+
[K in keyof T as T[K]["optional"] extends true ? K : never]?: InferType<T[K]["type"]>;
|
|
15
|
+
};
|
|
16
|
+
export interface TypedSchema<T> extends JSONSchema {
|
|
17
|
+
__type?: T;
|
|
18
|
+
}
|
|
19
|
+
export declare function defineSchema<T extends SchemaDefinition>(definition: T): TypedSchema<InferSchema<T>>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function defineSchema(definition) {
|
|
2
|
+
const properties = {};
|
|
3
|
+
const required = [];
|
|
4
|
+
for (const [key, prop] of Object.entries(definition)) {
|
|
5
|
+
const jsonSchemaProperty = {};
|
|
6
|
+
for (const [propertyKey, propertyValue] of Object.entries(prop)) {
|
|
7
|
+
if (propertyKey !== "optional") {
|
|
8
|
+
jsonSchemaProperty[propertyKey] = propertyValue;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(properties, key, {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: jsonSchemaProperty,
|
|
16
|
+
});
|
|
17
|
+
if (!prop.optional) {
|
|
18
|
+
required.push(key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties,
|
|
24
|
+
required,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ServerOptions, ToolDefinition, ToolHandler, HandleResult, Prompt, PromptHandler, Resource, ResourceHandler, ResourceTemplate, Transport, SDKTransport, JSONRPCNotification } from "./types.js";
|
|
2
|
+
import type { TypedSchema } from "./schema.js";
|
|
3
|
+
export interface Server {
|
|
4
|
+
tool<TIn, TOut = never>(name: string, description: string, inputSchema: TypedSchema<TIn>, handler: ToolHandler<TIn, TOut>, outputSchema?: TypedSchema<TOut>): Server;
|
|
5
|
+
registerTool<TIn, TOut = never>(definition: Omit<ToolDefinition<TIn, TOut>, "handler">, handler: ToolHandler<TIn, TOut>): Server;
|
|
6
|
+
prompt(definition: Prompt, handler: PromptHandler): Server;
|
|
7
|
+
resource(definition: Resource, handler: ResourceHandler): Server;
|
|
8
|
+
resourceTemplate(definition: ResourceTemplate, handler: ResourceHandler): Server;
|
|
9
|
+
method(name: string, handler: CustomMethodHandler): Server;
|
|
10
|
+
onNotification(listener: (notification: JSONRPCNotification) => void): () => void;
|
|
11
|
+
removeTool(name: string): boolean;
|
|
12
|
+
removePrompt(name: string): boolean;
|
|
13
|
+
removeResource(uri: string): boolean;
|
|
14
|
+
removeResourceTemplate(uriTemplate: string): boolean;
|
|
15
|
+
notifyToolsChanged(): Promise<void>;
|
|
16
|
+
notifyPromptsChanged(): Promise<void>;
|
|
17
|
+
notifyResourcesChanged(): Promise<void>;
|
|
18
|
+
notifyResourceUpdated(uri: string): Promise<void>;
|
|
19
|
+
createMessageSession(listener?: (notification: JSONRPCNotification) => void | Promise<void>): MessageSession;
|
|
20
|
+
handleMessage(method: string, params?: Record<string, unknown>): Promise<HandleResult>;
|
|
21
|
+
listen(): Promise<void>;
|
|
22
|
+
connect(transport: Transport): Promise<void>;
|
|
23
|
+
connectSDK(transport: SDKTransport): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface MessageSessionContext {
|
|
26
|
+
readonly signal: AbortSignal;
|
|
27
|
+
notify(method: string, params?: Record<string, unknown>): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export type CustomMethodHandler = (params: Record<string, unknown> | undefined, session: MessageSessionContext) => unknown | Promise<unknown>;
|
|
30
|
+
export type MessageHandler = (method: string, params?: Record<string, unknown>) => Promise<HandleResult>;
|
|
31
|
+
export interface MessageSession {
|
|
32
|
+
handleMessage: MessageHandler;
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
|
35
|
+
export declare function createServer(options: ServerOptions): Server;
|