tiny-stdio-mcp-server 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/dist/content/audio.d.ts +14 -0
- package/dist/content/audio.js +76 -0
- package/dist/content/convert.d.ts +10 -0
- package/dist/content/convert.js +25 -0
- package/dist/content/file-type.d.ts +11 -0
- package/dist/content/file-type.js +93 -0
- package/dist/content/file.d.ts +26 -0
- package/dist/content/file.js +94 -0
- package/dist/content/image.d.ts +14 -0
- package/dist/content/image.js +64 -0
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +8 -0
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/dist/jsonrpc.d.ts +14 -0
- package/dist/jsonrpc.js +99 -0
- package/dist/schema.d.ts +19 -0
- package/dist/schema.js +18 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +213 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +20 -0
- package/dist/types.d.ts +111 -0
- package/dist/types.js +8 -0
- package/package.json +39 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AudioContent {
|
|
2
|
+
type: "audio";
|
|
3
|
+
data: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class Audio {
|
|
7
|
+
private readonly base64Data;
|
|
8
|
+
private readonly mimeType;
|
|
9
|
+
private constructor();
|
|
10
|
+
static fromUrl(url: string): Promise<Audio>;
|
|
11
|
+
static fromBytes(data: Uint8Array, format?: string): Audio;
|
|
12
|
+
static fromBase64(base64: string, mimeType: string): Audio;
|
|
13
|
+
toContentBlock(): AudioContent;
|
|
14
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { fileTypeFromBuffer } from "./mime.js";
|
|
2
|
+
const SUPPORTED_AUDIO_MIMES = new Set([
|
|
3
|
+
"audio/mpeg",
|
|
4
|
+
"audio/wav",
|
|
5
|
+
"audio/ogg",
|
|
6
|
+
"audio/mp4",
|
|
7
|
+
]);
|
|
8
|
+
const AUDIO_FORMAT_MAP = {
|
|
9
|
+
mp3: "audio/mpeg",
|
|
10
|
+
wav: "audio/wav",
|
|
11
|
+
ogg: "audio/ogg",
|
|
12
|
+
m4a: "audio/mp4",
|
|
13
|
+
mpeg: "audio/mpeg",
|
|
14
|
+
};
|
|
15
|
+
export class Audio {
|
|
16
|
+
base64Data;
|
|
17
|
+
mimeType;
|
|
18
|
+
constructor(base64Data, mimeType) {
|
|
19
|
+
this.base64Data = base64Data;
|
|
20
|
+
this.mimeType = mimeType;
|
|
21
|
+
}
|
|
22
|
+
static async fromUrl(url) {
|
|
23
|
+
const response = await fetch(url);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch audio from ${url}: ${response.status} ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
28
|
+
const data = new Uint8Array(arrayBuffer);
|
|
29
|
+
const detected = fileTypeFromBuffer(data);
|
|
30
|
+
let mimeType;
|
|
31
|
+
if (detected && SUPPORTED_AUDIO_MIMES.has(detected.mime)) {
|
|
32
|
+
mimeType = detected.mime;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
36
|
+
if (contentType && SUPPORTED_AUDIO_MIMES.has(contentType)) {
|
|
37
|
+
mimeType = contentType;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error(`Unable to detect audio MIME type from ${url}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
44
|
+
return new Audio(base64, mimeType);
|
|
45
|
+
}
|
|
46
|
+
static fromBytes(data, format) {
|
|
47
|
+
let mimeType;
|
|
48
|
+
if (format) {
|
|
49
|
+
if (format.includes("/")) {
|
|
50
|
+
mimeType = format;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
mimeType = AUDIO_FORMAT_MAP[format.toLowerCase()] || `audio/${format}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const detected = fileTypeFromBuffer(data);
|
|
58
|
+
if (!detected || !SUPPORTED_AUDIO_MIMES.has(detected.mime)) {
|
|
59
|
+
throw new Error("Unable to detect audio MIME type from bytes");
|
|
60
|
+
}
|
|
61
|
+
mimeType = detected.mime;
|
|
62
|
+
}
|
|
63
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
64
|
+
return new Audio(base64, mimeType);
|
|
65
|
+
}
|
|
66
|
+
static fromBase64(base64, mimeType) {
|
|
67
|
+
return new Audio(base64, mimeType);
|
|
68
|
+
}
|
|
69
|
+
toContentBlock() {
|
|
70
|
+
return {
|
|
71
|
+
type: "audio",
|
|
72
|
+
data: this.base64Data,
|
|
73
|
+
mimeType: this.mimeType,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Image, type ImageContent } from "./image.js";
|
|
2
|
+
import { Audio, type AudioContent } from "./audio.js";
|
|
3
|
+
import { File, type EmbeddedResource } from "./file.js";
|
|
4
|
+
export interface TextContent {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
export type ContentBlock = TextContent | ImageContent | AudioContent | EmbeddedResource;
|
|
9
|
+
export type ToolReturn = string | Image | Audio | File | ContentBlock | Array<string | Image | Audio | File | ContentBlock>;
|
|
10
|
+
export declare function toContentBlocks(result: ToolReturn): ContentBlock[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Image } from "./image.js";
|
|
2
|
+
import { Audio } from "./audio.js";
|
|
3
|
+
import { File } from "./file.js";
|
|
4
|
+
function convertSingleValue(value) {
|
|
5
|
+
if (typeof value === "string") {
|
|
6
|
+
return { type: "text", text: value };
|
|
7
|
+
}
|
|
8
|
+
if (value instanceof Image) {
|
|
9
|
+
return value.toContentBlock();
|
|
10
|
+
}
|
|
11
|
+
if (value instanceof Audio) {
|
|
12
|
+
return value.toContentBlock();
|
|
13
|
+
}
|
|
14
|
+
if (value instanceof File) {
|
|
15
|
+
return value.toContentBlock();
|
|
16
|
+
}
|
|
17
|
+
// Already a ContentBlock
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
export function toContentBlocks(result) {
|
|
21
|
+
if (Array.isArray(result)) {
|
|
22
|
+
return result.flatMap((item) => toContentBlocks(item));
|
|
23
|
+
}
|
|
24
|
+
return [convertSingleValue(result)];
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal magic bytes detection for common media types.
|
|
3
|
+
* This can be replaced with `file-type` package (https://npm.im/file-type)
|
|
4
|
+
* if more comprehensive detection is needed. The API is designed to be
|
|
5
|
+
* compatible: fileTypeFromBuffer(data) returns { mime: string, ext: string } | undefined
|
|
6
|
+
*/
|
|
7
|
+
export interface FileTypeResult {
|
|
8
|
+
mime: string;
|
|
9
|
+
ext: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function fileTypeFromBuffer(data: Uint8Array): FileTypeResult | undefined;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal magic bytes detection for common media types.
|
|
3
|
+
* This can be replaced with `file-type` package (https://npm.im/file-type)
|
|
4
|
+
* if more comprehensive detection is needed. The API is designed to be
|
|
5
|
+
* compatible: fileTypeFromBuffer(data) returns { mime: string, ext: string } | undefined
|
|
6
|
+
*/
|
|
7
|
+
export function fileTypeFromBuffer(data) {
|
|
8
|
+
if (data.length < 12) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
// PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
12
|
+
if (data[0] === 0x89 &&
|
|
13
|
+
data[1] === 0x50 &&
|
|
14
|
+
data[2] === 0x4e &&
|
|
15
|
+
data[3] === 0x47 &&
|
|
16
|
+
data[4] === 0x0d &&
|
|
17
|
+
data[5] === 0x0a &&
|
|
18
|
+
data[6] === 0x1a &&
|
|
19
|
+
data[7] === 0x0a) {
|
|
20
|
+
return { mime: "image/png", ext: "png" };
|
|
21
|
+
}
|
|
22
|
+
// JPEG: FF D8 FF
|
|
23
|
+
if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
|
|
24
|
+
return { mime: "image/jpeg", ext: "jpg" };
|
|
25
|
+
}
|
|
26
|
+
// GIF: 47 49 46 38 (GIF8)
|
|
27
|
+
if (data[0] === 0x47 &&
|
|
28
|
+
data[1] === 0x49 &&
|
|
29
|
+
data[2] === 0x46 &&
|
|
30
|
+
data[3] === 0x38) {
|
|
31
|
+
return { mime: "image/gif", ext: "gif" };
|
|
32
|
+
}
|
|
33
|
+
// WEBP: 52 49 46 46 ... 57 45 42 50 (RIFF...WEBP)
|
|
34
|
+
if (data[0] === 0x52 &&
|
|
35
|
+
data[1] === 0x49 &&
|
|
36
|
+
data[2] === 0x46 &&
|
|
37
|
+
data[3] === 0x46 &&
|
|
38
|
+
data[8] === 0x57 &&
|
|
39
|
+
data[9] === 0x45 &&
|
|
40
|
+
data[10] === 0x42 &&
|
|
41
|
+
data[11] === 0x50) {
|
|
42
|
+
return { mime: "image/webp", ext: "webp" };
|
|
43
|
+
}
|
|
44
|
+
// MP3: FF FB or FF FA (MPEG audio) or 49 44 33 (ID3 tag)
|
|
45
|
+
if ((data[0] === 0xff && (data[1] === 0xfb || data[1] === 0xfa)) ||
|
|
46
|
+
(data[0] === 0x49 && data[1] === 0x44 && data[2] === 0x33)) {
|
|
47
|
+
return { mime: "audio/mpeg", ext: "mp3" };
|
|
48
|
+
}
|
|
49
|
+
// WAV: 52 49 46 46 ... 57 41 56 45 (RIFF...WAVE)
|
|
50
|
+
if (data[0] === 0x52 &&
|
|
51
|
+
data[1] === 0x49 &&
|
|
52
|
+
data[2] === 0x46 &&
|
|
53
|
+
data[3] === 0x46 &&
|
|
54
|
+
data[8] === 0x57 &&
|
|
55
|
+
data[9] === 0x41 &&
|
|
56
|
+
data[10] === 0x56 &&
|
|
57
|
+
data[11] === 0x45) {
|
|
58
|
+
return { mime: "audio/wav", ext: "wav" };
|
|
59
|
+
}
|
|
60
|
+
// OGG: 4F 67 67 53 (OggS)
|
|
61
|
+
if (data[0] === 0x4f &&
|
|
62
|
+
data[1] === 0x67 &&
|
|
63
|
+
data[2] === 0x67 &&
|
|
64
|
+
data[3] === 0x53) {
|
|
65
|
+
return { mime: "audio/ogg", ext: "ogg" };
|
|
66
|
+
}
|
|
67
|
+
// M4A: MP4 container with audio - check for M4A brand at offset 8
|
|
68
|
+
// ftyp followed by M4A brand
|
|
69
|
+
if (data[4] === 0x66 &&
|
|
70
|
+
data[5] === 0x74 &&
|
|
71
|
+
data[6] === 0x79 &&
|
|
72
|
+
data[7] === 0x70 &&
|
|
73
|
+
data[8] === 0x4d &&
|
|
74
|
+
data[9] === 0x34 &&
|
|
75
|
+
data[10] === 0x41) {
|
|
76
|
+
return { mime: "audio/mp4", ext: "m4a" };
|
|
77
|
+
}
|
|
78
|
+
// MP4: ... 66 74 79 70 (ftyp box, offset 4)
|
|
79
|
+
if (data[4] === 0x66 &&
|
|
80
|
+
data[5] === 0x74 &&
|
|
81
|
+
data[6] === 0x79 &&
|
|
82
|
+
data[7] === 0x70) {
|
|
83
|
+
return { mime: "video/mp4", ext: "mp4" };
|
|
84
|
+
}
|
|
85
|
+
// WEBM: 1A 45 DF A3 (EBML header)
|
|
86
|
+
if (data[0] === 0x1a &&
|
|
87
|
+
data[1] === 0x45 &&
|
|
88
|
+
data[2] === 0xdf &&
|
|
89
|
+
data[3] === 0xa3) {
|
|
90
|
+
return { mime: "video/webm", ext: "webm" };
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface TextResourceContents {
|
|
2
|
+
uri: string;
|
|
3
|
+
mimeType: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface BlobResourceContents {
|
|
7
|
+
uri: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
blob: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EmbeddedResource {
|
|
12
|
+
type: "resource";
|
|
13
|
+
resource: TextResourceContents | BlobResourceContents;
|
|
14
|
+
}
|
|
15
|
+
export declare class File {
|
|
16
|
+
private readonly data;
|
|
17
|
+
private readonly mimeType;
|
|
18
|
+
private readonly isText;
|
|
19
|
+
private readonly name?;
|
|
20
|
+
private constructor();
|
|
21
|
+
static fromUrl(url: string): Promise<File>;
|
|
22
|
+
static fromBytes(data: Uint8Array, mimeType: string): File;
|
|
23
|
+
static fromText(text: string, mimeType?: string): File;
|
|
24
|
+
static fromBase64(base64: string, mimeType: string): File;
|
|
25
|
+
toContentBlock(): EmbeddedResource;
|
|
26
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { fileTypeFromBuffer } from "./mime.js";
|
|
2
|
+
function isTextMimeType(mimeType) {
|
|
3
|
+
return (mimeType.startsWith("text/") ||
|
|
4
|
+
mimeType === "application/json" ||
|
|
5
|
+
mimeType === "application/xml" ||
|
|
6
|
+
mimeType === "application/javascript" ||
|
|
7
|
+
mimeType === "application/typescript");
|
|
8
|
+
}
|
|
9
|
+
export class File {
|
|
10
|
+
data;
|
|
11
|
+
mimeType;
|
|
12
|
+
isText;
|
|
13
|
+
name;
|
|
14
|
+
constructor(data, mimeType, isText, name) {
|
|
15
|
+
this.data = data;
|
|
16
|
+
this.mimeType = mimeType;
|
|
17
|
+
this.isText = isText;
|
|
18
|
+
this.name = name;
|
|
19
|
+
}
|
|
20
|
+
static async fromUrl(url) {
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Failed to fetch file from ${url}: ${response.status} ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
26
|
+
const data = new Uint8Array(arrayBuffer);
|
|
27
|
+
const detected = fileTypeFromBuffer(data);
|
|
28
|
+
let mimeType;
|
|
29
|
+
if (detected) {
|
|
30
|
+
mimeType = detected.mime;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
34
|
+
if (contentType) {
|
|
35
|
+
mimeType = contentType;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Unable to detect MIME type from ${url}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const isText = isTextMimeType(mimeType);
|
|
42
|
+
const name = url.split("/").pop() || "file";
|
|
43
|
+
return new File(data, mimeType, isText, name);
|
|
44
|
+
}
|
|
45
|
+
static fromBytes(data, mimeType) {
|
|
46
|
+
const isText = isTextMimeType(mimeType);
|
|
47
|
+
return new File(data, mimeType, isText);
|
|
48
|
+
}
|
|
49
|
+
static fromText(text, mimeType = "text/plain") {
|
|
50
|
+
return new File(text, mimeType, true);
|
|
51
|
+
}
|
|
52
|
+
static fromBase64(base64, mimeType) {
|
|
53
|
+
const data = Buffer.from(base64, "base64");
|
|
54
|
+
const isText = isTextMimeType(mimeType);
|
|
55
|
+
return new File(new Uint8Array(data), mimeType, isText);
|
|
56
|
+
}
|
|
57
|
+
toContentBlock() {
|
|
58
|
+
const uri = this.name ? `file:///${this.name}` : "file:///data";
|
|
59
|
+
if (this.isText) {
|
|
60
|
+
let text;
|
|
61
|
+
if (typeof this.data === "string") {
|
|
62
|
+
text = this.data;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
text = new TextDecoder("utf-8").decode(this.data);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
type: "resource",
|
|
69
|
+
resource: {
|
|
70
|
+
uri,
|
|
71
|
+
mimeType: this.mimeType,
|
|
72
|
+
text,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
let blob;
|
|
78
|
+
if (typeof this.data === "string") {
|
|
79
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
type: "resource",
|
|
86
|
+
resource: {
|
|
87
|
+
uri,
|
|
88
|
+
mimeType: this.mimeType,
|
|
89
|
+
blob,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ImageContent {
|
|
2
|
+
type: "image";
|
|
3
|
+
data: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class Image {
|
|
7
|
+
private readonly base64Data;
|
|
8
|
+
private readonly mimeType;
|
|
9
|
+
private constructor();
|
|
10
|
+
static fromUrl(url: string): Promise<Image>;
|
|
11
|
+
static fromBytes(data: Uint8Array, format?: string): Image;
|
|
12
|
+
static fromBase64(base64: string, mimeType: string): Image;
|
|
13
|
+
toContentBlock(): ImageContent;
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fileTypeFromBuffer } from "./mime.js";
|
|
2
|
+
const SUPPORTED_IMAGE_MIMES = new Set([
|
|
3
|
+
"image/png",
|
|
4
|
+
"image/jpeg",
|
|
5
|
+
"image/gif",
|
|
6
|
+
"image/webp",
|
|
7
|
+
]);
|
|
8
|
+
export class Image {
|
|
9
|
+
base64Data;
|
|
10
|
+
mimeType;
|
|
11
|
+
constructor(base64Data, mimeType) {
|
|
12
|
+
this.base64Data = base64Data;
|
|
13
|
+
this.mimeType = mimeType;
|
|
14
|
+
}
|
|
15
|
+
static async fromUrl(url) {
|
|
16
|
+
const response = await fetch(url);
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
|
|
19
|
+
}
|
|
20
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
21
|
+
const data = new Uint8Array(arrayBuffer);
|
|
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 = response.headers.get("content-type")?.split(";")[0];
|
|
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 ${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}`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const detected = fileTypeFromBuffer(data);
|
|
46
|
+
if (!detected || !SUPPORTED_IMAGE_MIMES.has(detected.mime)) {
|
|
47
|
+
throw new Error("Unable to detect image MIME type from bytes");
|
|
48
|
+
}
|
|
49
|
+
mimeType = detected.mime;
|
|
50
|
+
}
|
|
51
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
52
|
+
return new Image(base64, mimeType);
|
|
53
|
+
}
|
|
54
|
+
static fromBase64(base64, mimeType) {
|
|
55
|
+
return new Image(base64, mimeType);
|
|
56
|
+
}
|
|
57
|
+
toContentBlock() {
|
|
58
|
+
return {
|
|
59
|
+
type: "image",
|
|
60
|
+
data: this.base64Data,
|
|
61
|
+
mimeType: this.mimeType,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { fileTypeFromBuffer, type FileTypeResult } from "./mime.js";
|
|
2
|
+
export { Image, type ImageContent } from "./image.js";
|
|
3
|
+
export { Audio, type AudioContent } from "./audio.js";
|
|
4
|
+
export { File, type EmbeddedResource, type TextResourceContents, type BlobResourceContents, } from "./file.js";
|
|
5
|
+
export { toContentBlocks, type ContentBlock, type TextContent, type ToolReturn, } from "./convert.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// MIME detection
|
|
2
|
+
export { fileTypeFromBuffer } from "./mime.js";
|
|
3
|
+
// Content helpers
|
|
4
|
+
export { Image } from "./image.js";
|
|
5
|
+
export { Audio } from "./audio.js";
|
|
6
|
+
export { File, } from "./file.js";
|
|
7
|
+
// Conversion utility
|
|
8
|
+
export { toContentBlocks, } from "./convert.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fileTypeFromBuffer, type FileTypeResult } from "./file-type.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fileTypeFromBuffer } from "./file-type.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createServer } from "./server.js";
|
|
2
|
+
export type { Server } from "./server.js";
|
|
3
|
+
export { defineSchema } from "./schema.js";
|
|
4
|
+
export type { TypedSchema } from "./schema.js";
|
|
5
|
+
export { createTestPair } from "./testing.js";
|
|
6
|
+
export type { TestPair } from "./testing.js";
|
|
7
|
+
export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, } from "./content/index.js";
|
|
8
|
+
export type { ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, } from "./content/index.js";
|
|
9
|
+
export type { ToolReturn } from "./content/index.js";
|
|
10
|
+
export type { ServerOptions, ToolHandler, ToolDefinition, Tool, CallToolResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, } from "./types.js";
|
|
11
|
+
export { JSON_RPC_ERROR_CODES } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Server
|
|
2
|
+
export { createServer } from "./server.js";
|
|
3
|
+
// Schema
|
|
4
|
+
export { defineSchema } from "./schema.js";
|
|
5
|
+
// Testing utilities
|
|
6
|
+
export { createTestPair } from "./testing.js";
|
|
7
|
+
// Content helpers
|
|
8
|
+
export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, } from "./content/index.js";
|
|
9
|
+
export { JSON_RPC_ERROR_CODES } 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;
|
package/dist/jsonrpc.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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" || typeof obj.id === "number" ? obj.id : null;
|
|
32
|
+
if (obj.jsonrpc !== "2.0") {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: {
|
|
36
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
37
|
+
message: "Invalid Request",
|
|
38
|
+
},
|
|
39
|
+
id,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (typeof obj.method !== "string") {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: {
|
|
46
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
47
|
+
message: "Invalid Request",
|
|
48
|
+
},
|
|
49
|
+
id,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!hasId) {
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
isNotification: true,
|
|
56
|
+
request: {
|
|
57
|
+
jsonrpc: "2.0",
|
|
58
|
+
method: obj.method,
|
|
59
|
+
params: obj.params,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (id === null) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: {
|
|
67
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
68
|
+
message: "Invalid Request",
|
|
69
|
+
},
|
|
70
|
+
id: null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
isNotification: false,
|
|
76
|
+
request: {
|
|
77
|
+
jsonrpc: "2.0",
|
|
78
|
+
id,
|
|
79
|
+
method: obj.method,
|
|
80
|
+
params: obj.params,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function formatSuccessResponse(id, result) {
|
|
85
|
+
const response = {
|
|
86
|
+
jsonrpc: "2.0",
|
|
87
|
+
id,
|
|
88
|
+
result,
|
|
89
|
+
};
|
|
90
|
+
return JSON.stringify(response);
|
|
91
|
+
}
|
|
92
|
+
export function formatErrorResponse(id, error) {
|
|
93
|
+
const response = {
|
|
94
|
+
jsonrpc: "2.0",
|
|
95
|
+
id,
|
|
96
|
+
error,
|
|
97
|
+
};
|
|
98
|
+
return JSON.stringify(response);
|
|
99
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { JSONSchema } from "./types.js";
|
|
2
|
+
type SchemaPropertyType = "string" | "number" | "boolean" | "object" | "array";
|
|
3
|
+
interface SchemaPropertyDef {
|
|
4
|
+
type: SchemaPropertyType;
|
|
5
|
+
description?: string;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type SchemaDefinition = Record<string, SchemaPropertyDef>;
|
|
9
|
+
type InferType<T extends SchemaPropertyType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : T extends "object" ? Record<string, unknown> : T extends "array" ? unknown[] : never;
|
|
10
|
+
type InferSchema<T extends SchemaDefinition> = {
|
|
11
|
+
[K in keyof T as T[K]["optional"] extends true ? never : K]: InferType<T[K]["type"]>;
|
|
12
|
+
} & {
|
|
13
|
+
[K in keyof T as T[K]["optional"] extends true ? K : never]?: InferType<T[K]["type"]>;
|
|
14
|
+
};
|
|
15
|
+
export interface TypedSchema<T> extends JSONSchema {
|
|
16
|
+
__type?: T;
|
|
17
|
+
}
|
|
18
|
+
export declare function defineSchema<T extends SchemaDefinition>(definition: T): TypedSchema<InferSchema<T>>;
|
|
19
|
+
export {};
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function defineSchema(definition) {
|
|
2
|
+
const properties = {};
|
|
3
|
+
const required = [];
|
|
4
|
+
for (const [key, prop] of Object.entries(definition)) {
|
|
5
|
+
properties[key] = {
|
|
6
|
+
type: prop.type,
|
|
7
|
+
...(prop.description !== undefined && { description: prop.description }),
|
|
8
|
+
};
|
|
9
|
+
if (!prop.optional) {
|
|
10
|
+
required.push(key);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties,
|
|
16
|
+
required,
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ServerOptions, ToolHandler, Transport, SDKTransport } from "./types.js";
|
|
2
|
+
import type { TypedSchema } from "./schema.js";
|
|
3
|
+
export interface Server {
|
|
4
|
+
tool<T>(name: string, description: string, inputSchema: TypedSchema<T>, handler: ToolHandler<T>): Server;
|
|
5
|
+
removeTool(name: string): boolean;
|
|
6
|
+
notifyToolsChanged(): Promise<void>;
|
|
7
|
+
listen(): Promise<void>;
|
|
8
|
+
connect(transport: Transport): Promise<void>;
|
|
9
|
+
connectSDK(transport: SDKTransport): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createServer(options: ServerOptions): Server;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as readline from "readline";
|
|
2
|
+
import { JSON_RPC_ERROR_CODES } from "./types.js";
|
|
3
|
+
import { parseMessage, formatSuccessResponse, formatErrorResponse, } from "./jsonrpc.js";
|
|
4
|
+
import { toContentBlocks } from "./content/convert.js";
|
|
5
|
+
const PROTOCOL_VERSION = "2025-11-25";
|
|
6
|
+
export function createServer(options) {
|
|
7
|
+
const tools = new Map();
|
|
8
|
+
let initialized = false;
|
|
9
|
+
let activeTransport = null;
|
|
10
|
+
let activeSDKTransport = null;
|
|
11
|
+
const handleRequest = async (method, params) => {
|
|
12
|
+
// Allow ping and initialize before initialization
|
|
13
|
+
if (method === "ping") {
|
|
14
|
+
return { result: {} };
|
|
15
|
+
}
|
|
16
|
+
if (method === "initialize") {
|
|
17
|
+
initialized = true;
|
|
18
|
+
const requestedProtocol = typeof params?.protocolVersion === "string"
|
|
19
|
+
? params.protocolVersion
|
|
20
|
+
: null;
|
|
21
|
+
const result = {
|
|
22
|
+
protocolVersion: requestedProtocol ?? PROTOCOL_VERSION,
|
|
23
|
+
capabilities: {
|
|
24
|
+
tools: {
|
|
25
|
+
listChanged: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
serverInfo: {
|
|
29
|
+
name: options.name,
|
|
30
|
+
version: options.version,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
return { result };
|
|
34
|
+
}
|
|
35
|
+
if (method === "notifications/initialized") {
|
|
36
|
+
return { result: undefined };
|
|
37
|
+
}
|
|
38
|
+
// All other methods require initialization
|
|
39
|
+
if (!initialized) {
|
|
40
|
+
return {
|
|
41
|
+
error: {
|
|
42
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
43
|
+
message: "Server not initialized",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (method === "tools/list") {
|
|
48
|
+
const toolList = [];
|
|
49
|
+
for (const tool of tools.values()) {
|
|
50
|
+
toolList.push({
|
|
51
|
+
name: tool.name,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
inputSchema: tool.inputSchema,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { result: { tools: toolList } };
|
|
57
|
+
}
|
|
58
|
+
if (method === "tools/call") {
|
|
59
|
+
const toolName = params?.name;
|
|
60
|
+
const toolArgs = params?.arguments || {};
|
|
61
|
+
if (!toolName) {
|
|
62
|
+
return {
|
|
63
|
+
error: {
|
|
64
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
65
|
+
message: "Tool name required",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const tool = tools.get(toolName);
|
|
70
|
+
if (!tool) {
|
|
71
|
+
return {
|
|
72
|
+
error: {
|
|
73
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
74
|
+
message: `Tool not found: ${toolName}`,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const handlerResult = await tool.handler(toolArgs);
|
|
80
|
+
const result = { content: toContentBlocks(handlerResult) };
|
|
81
|
+
return { result };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
85
|
+
const result = {
|
|
86
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
return { result };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
error: {
|
|
94
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
95
|
+
message: "Method not found",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
const processLine = async (line, write) => {
|
|
100
|
+
const parsed = parseMessage(line);
|
|
101
|
+
if (!parsed.success) {
|
|
102
|
+
write(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const { request, isNotification } = parsed;
|
|
106
|
+
const { result, error } = await handleRequest(request.method, request.params);
|
|
107
|
+
if (isNotification) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const requestWithId = request;
|
|
111
|
+
if (error) {
|
|
112
|
+
write(formatErrorResponse(requestWithId.id, error) + "\n");
|
|
113
|
+
}
|
|
114
|
+
else if (result !== undefined) {
|
|
115
|
+
write(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const sendNotification = async (method) => {
|
|
119
|
+
const notification = {
|
|
120
|
+
jsonrpc: "2.0",
|
|
121
|
+
method,
|
|
122
|
+
};
|
|
123
|
+
if (activeSDKTransport) {
|
|
124
|
+
await activeSDKTransport.send(notification);
|
|
125
|
+
}
|
|
126
|
+
else if (activeTransport) {
|
|
127
|
+
activeTransport.writable.write(JSON.stringify(notification) + "\n");
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const server = {
|
|
131
|
+
tool(name, description, inputSchema, handler) {
|
|
132
|
+
tools.set(name, {
|
|
133
|
+
name,
|
|
134
|
+
description,
|
|
135
|
+
inputSchema: inputSchema,
|
|
136
|
+
handler: handler,
|
|
137
|
+
});
|
|
138
|
+
return server;
|
|
139
|
+
},
|
|
140
|
+
removeTool(name) {
|
|
141
|
+
return tools.delete(name);
|
|
142
|
+
},
|
|
143
|
+
async notifyToolsChanged() {
|
|
144
|
+
if (initialized) {
|
|
145
|
+
await sendNotification("notifications/tools/list_changed");
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
async listen() {
|
|
149
|
+
return server.connect({
|
|
150
|
+
readable: process.stdin,
|
|
151
|
+
writable: process.stdout,
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
async connect(transport) {
|
|
155
|
+
activeTransport = transport;
|
|
156
|
+
activeSDKTransport = null;
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
const rl = readline.createInterface({
|
|
159
|
+
input: transport.readable,
|
|
160
|
+
crlfDelay: Infinity,
|
|
161
|
+
});
|
|
162
|
+
rl.on("line", (line) => {
|
|
163
|
+
processLine(line, (data) => transport.writable.write(data));
|
|
164
|
+
});
|
|
165
|
+
rl.on("close", () => {
|
|
166
|
+
activeTransport = null;
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
async connectSDK(transport) {
|
|
172
|
+
activeSDKTransport = transport;
|
|
173
|
+
activeTransport = null;
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
transport.onmessage = async (message) => {
|
|
176
|
+
// Ignore responses (we only handle requests/notifications)
|
|
177
|
+
if (!("method" in message)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Handle notifications (no id) - don't respond
|
|
181
|
+
if (!("id" in message) || message.id === undefined) {
|
|
182
|
+
await handleRequest(message.method, message.params);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const request = message;
|
|
186
|
+
const { result, error } = await handleRequest(request.method, request.params);
|
|
187
|
+
if (error) {
|
|
188
|
+
const response = {
|
|
189
|
+
jsonrpc: "2.0",
|
|
190
|
+
id: request.id,
|
|
191
|
+
error,
|
|
192
|
+
};
|
|
193
|
+
await transport.send(response);
|
|
194
|
+
}
|
|
195
|
+
else if (result !== undefined) {
|
|
196
|
+
const response = {
|
|
197
|
+
jsonrpc: "2.0",
|
|
198
|
+
id: request.id,
|
|
199
|
+
result,
|
|
200
|
+
};
|
|
201
|
+
await transport.send(response);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
transport.onclose = () => {
|
|
205
|
+
activeSDKTransport = null;
|
|
206
|
+
resolve();
|
|
207
|
+
};
|
|
208
|
+
transport.start();
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return server;
|
|
213
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import type { Server } from "./server.js";
|
|
3
|
+
export interface TestPair {
|
|
4
|
+
client: Client;
|
|
5
|
+
cleanup: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createTestPair(server: Server): Promise<TestPair>;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
3
|
+
export async function createTestPair(server) {
|
|
4
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
5
|
+
const client = new Client({
|
|
6
|
+
name: "test-client",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
});
|
|
9
|
+
// Start server connection (runs in background)
|
|
10
|
+
const serverPromise = server.connectSDK(serverTransport);
|
|
11
|
+
// Connect client
|
|
12
|
+
await client.connect(clientTransport);
|
|
13
|
+
const cleanup = async () => {
|
|
14
|
+
await client.close();
|
|
15
|
+
await clientTransport.close();
|
|
16
|
+
await serverTransport.close();
|
|
17
|
+
await serverPromise;
|
|
18
|
+
};
|
|
19
|
+
return { client, cleanup };
|
|
20
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export interface JSONRPCRequest {
|
|
2
|
+
jsonrpc: "2.0";
|
|
3
|
+
id: string | number;
|
|
4
|
+
method: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface JSONRPCResponse {
|
|
8
|
+
jsonrpc: "2.0";
|
|
9
|
+
id: string | number | null;
|
|
10
|
+
result?: unknown;
|
|
11
|
+
error?: JSONRPCError;
|
|
12
|
+
}
|
|
13
|
+
export interface JSONRPCError {
|
|
14
|
+
code: number;
|
|
15
|
+
message: string;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare const JSON_RPC_ERROR_CODES: {
|
|
19
|
+
readonly PARSE_ERROR: -32700;
|
|
20
|
+
readonly INVALID_REQUEST: -32600;
|
|
21
|
+
readonly METHOD_NOT_FOUND: -32601;
|
|
22
|
+
readonly INVALID_PARAMS: -32602;
|
|
23
|
+
readonly INTERNAL_ERROR: -32603;
|
|
24
|
+
};
|
|
25
|
+
export interface ToolsCapability {
|
|
26
|
+
listChanged?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface InitializeResult {
|
|
29
|
+
protocolVersion: string;
|
|
30
|
+
capabilities: {
|
|
31
|
+
tools?: ToolsCapability;
|
|
32
|
+
};
|
|
33
|
+
serverInfo: {
|
|
34
|
+
name: string;
|
|
35
|
+
version: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface Tool {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
inputSchema: JSONSchema;
|
|
42
|
+
}
|
|
43
|
+
export interface CallToolResult {
|
|
44
|
+
content: ContentItem[];
|
|
45
|
+
isError?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type ContentItem = {
|
|
48
|
+
type: "text";
|
|
49
|
+
text: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: "image";
|
|
52
|
+
data: string;
|
|
53
|
+
mimeType: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: "audio";
|
|
56
|
+
data: string;
|
|
57
|
+
mimeType: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: "resource";
|
|
60
|
+
resource: {
|
|
61
|
+
uri: string;
|
|
62
|
+
mimeType: string;
|
|
63
|
+
text: string;
|
|
64
|
+
} | {
|
|
65
|
+
uri: string;
|
|
66
|
+
mimeType: string;
|
|
67
|
+
blob: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export interface JSONSchema {
|
|
71
|
+
type: "object";
|
|
72
|
+
properties: Record<string, JSONSchemaProperty>;
|
|
73
|
+
required?: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface JSONSchemaProperty {
|
|
76
|
+
type: "string" | "number" | "boolean" | "object" | "array";
|
|
77
|
+
description?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface ServerOptions {
|
|
80
|
+
name: string;
|
|
81
|
+
version: string;
|
|
82
|
+
}
|
|
83
|
+
import type { Image } from "./content/image.js";
|
|
84
|
+
import type { Audio } from "./content/audio.js";
|
|
85
|
+
import type { File } from "./content/file.js";
|
|
86
|
+
export type ToolReturn = string | Image | Audio | File | ContentItem | Array<string | Image | Audio | File | ContentItem>;
|
|
87
|
+
export type ToolHandler<T = Record<string, unknown>> = (args: T) => Promise<ToolReturn> | ToolReturn;
|
|
88
|
+
export interface ToolDefinition<T = Record<string, unknown>> {
|
|
89
|
+
name: string;
|
|
90
|
+
description: string;
|
|
91
|
+
inputSchema: JSONSchema;
|
|
92
|
+
handler: ToolHandler<T>;
|
|
93
|
+
}
|
|
94
|
+
export interface Transport {
|
|
95
|
+
readable: NodeJS.ReadableStream;
|
|
96
|
+
writable: NodeJS.WritableStream;
|
|
97
|
+
}
|
|
98
|
+
export interface SDKTransport {
|
|
99
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
100
|
+
onclose?: () => void;
|
|
101
|
+
onerror?: (error: Error) => void;
|
|
102
|
+
start: () => Promise<void>;
|
|
103
|
+
close: () => Promise<void>;
|
|
104
|
+
send: (message: JSONRPCMessage) => Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
export type JSONRPCMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification;
|
|
107
|
+
export interface JSONRPCNotification {
|
|
108
|
+
jsonrpc: "2.0";
|
|
109
|
+
method: string;
|
|
110
|
+
params?: Record<string, unknown>;
|
|
111
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tiny-stdio-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimal MCP server over stdio with typed tools and rich content helpers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "tsc"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/poe-platform/poe-code.git",
|
|
27
|
+
"directory": "packages/tiny-mcp-server"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"stdio",
|
|
34
|
+
"server"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
38
|
+
}
|
|
39
|
+
}
|