tiny-stdio-mcp-server 0.1.5 → 0.1.7
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/README.md +29 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +130 -12
- package/dist/types.d.ts +3 -3
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -42,9 +42,9 @@ Creates a new MCP server.
|
|
|
42
42
|
const server = createServer({ name: "my-server", version: "1.0.0" });
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
### `.tool(name, description, schema, handler)`
|
|
45
|
+
### `.tool(name, description, schema, handler, outputSchema?)`
|
|
46
46
|
|
|
47
|
-
Register a tool. The handler receives typed args matching the schema and returns a string, content helper,
|
|
47
|
+
Register a tool. The handler receives typed args matching the schema and returns a string, content helper, array of content, or a typed object when `outputSchema` is supplied.
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
const schema = defineSchema({
|
|
@@ -58,6 +58,33 @@ server.tool("search", "Search for things", schema, async ({ query, limit }) => {
|
|
|
58
58
|
});
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
For structured-data tools, pass a root-object output schema. The server advertises it as MCP `Tool.outputSchema`, validates the handler result, returns it as `CallToolResult.structuredContent`, and also includes a JSON text backstop in `content[]` for older clients.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const input = defineSchema({
|
|
65
|
+
query: { type: "string" },
|
|
66
|
+
});
|
|
67
|
+
const output = defineSchema({
|
|
68
|
+
items: {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
title: { type: "string" },
|
|
74
|
+
score: { type: "number" },
|
|
75
|
+
},
|
|
76
|
+
required: ["title", "score"],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
server.tool("search", "Search", input, async ({ query }) => ({
|
|
82
|
+
items: [{ title: query, score: 1 }],
|
|
83
|
+
}), output);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Output schemas must have `type: "object"` at the root. Tools whose natural result is prose, images, audio, files, or other content blocks should omit `outputSchema` and keep returning content.
|
|
87
|
+
|
|
61
88
|
### `.listen()`
|
|
62
89
|
|
|
63
90
|
Start listening on stdin/stdout (standard MCP stdio transport).
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
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
|
-
tool<
|
|
5
|
-
registerTool<
|
|
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
6
|
prompt(definition: Prompt, handler: PromptHandler): Server;
|
|
7
7
|
resource(definition: Resource, handler: ResourceHandler): Server;
|
|
8
8
|
resourceTemplate(definition: ResourceTemplate, handler: ResourceHandler): Server;
|
package/dist/server.js
CHANGED
|
@@ -131,16 +131,9 @@ export function createServer(options) {
|
|
|
131
131
|
}
|
|
132
132
|
try {
|
|
133
133
|
const handlerResult = await tool.handler(toolArgs);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const result = isCallToolResult(handlerResult)
|
|
138
|
-
? handlerResult
|
|
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");
|
|
134
|
+
const result = normalizeToolResult(handlerResult, tool.outputSchema);
|
|
135
|
+
if (tool.outputSchema !== undefined && !jsonSchemaValidator.validate(tool.outputSchema, result.structuredContent)) {
|
|
136
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Invalid structured tool result");
|
|
144
137
|
}
|
|
145
138
|
return { result };
|
|
146
139
|
}
|
|
@@ -326,16 +319,23 @@ export function createServer(options) {
|
|
|
326
319
|
}));
|
|
327
320
|
};
|
|
328
321
|
const server = {
|
|
329
|
-
tool(name, description, inputSchema, handler) {
|
|
322
|
+
tool(name, description, inputSchema, handler, outputSchema) {
|
|
323
|
+
if (outputSchema !== undefined) {
|
|
324
|
+
assertSupportedOutputSchema(outputSchema);
|
|
325
|
+
}
|
|
330
326
|
tools.set(name, {
|
|
331
327
|
name,
|
|
332
328
|
description,
|
|
333
329
|
inputSchema: inputSchema,
|
|
330
|
+
...(outputSchema === undefined ? {} : { outputSchema: outputSchema }),
|
|
334
331
|
handler: handler,
|
|
335
332
|
});
|
|
336
333
|
return server;
|
|
337
334
|
},
|
|
338
335
|
registerTool(definition, handler) {
|
|
336
|
+
if (definition.outputSchema !== undefined) {
|
|
337
|
+
assertSupportedOutputSchema(definition.outputSchema);
|
|
338
|
+
}
|
|
339
339
|
tools.set(definition.name, {
|
|
340
340
|
...definition,
|
|
341
341
|
handler: handler,
|
|
@@ -571,7 +571,125 @@ function matchesUriTemplate(template, uri) {
|
|
|
571
571
|
}
|
|
572
572
|
}
|
|
573
573
|
function isCallToolResult(value) {
|
|
574
|
-
|
|
574
|
+
if (!hasContentArray(value) || !value.content.every(isContentItem)) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
if (hasOwnProperty(value, "structuredContent")
|
|
578
|
+
&& value.structuredContent !== undefined
|
|
579
|
+
&& !isJsonObject(value.structuredContent)) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
return !(hasOwnProperty(value, "isError")
|
|
583
|
+
&& value.isError !== undefined
|
|
584
|
+
&& typeof value.isError !== "boolean");
|
|
585
|
+
}
|
|
586
|
+
function normalizeToolResult(handlerResult, outputSchema) {
|
|
587
|
+
if (hasContentArray(handlerResult) && !isCallToolResult(handlerResult)) {
|
|
588
|
+
throw new Error("Invalid tool result");
|
|
589
|
+
}
|
|
590
|
+
if (outputSchema === undefined) {
|
|
591
|
+
return isCallToolResult(handlerResult)
|
|
592
|
+
? handlerResult
|
|
593
|
+
: { content: toContentBlocks(handlerResult) };
|
|
594
|
+
}
|
|
595
|
+
const structuredContent = isCallToolResult(handlerResult)
|
|
596
|
+
? handlerResult.structuredContent
|
|
597
|
+
: handlerResult;
|
|
598
|
+
if (!isJsonObject(structuredContent)) {
|
|
599
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Structured tool result must be an object");
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
603
|
+
...(isCallToolResult(handlerResult) && handlerResult.isError !== undefined
|
|
604
|
+
? { isError: handlerResult.isError }
|
|
605
|
+
: {}),
|
|
606
|
+
structuredContent,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function assertSupportedOutputSchema(schema) {
|
|
610
|
+
assertObjectRootSchema(schema, "outputSchema");
|
|
611
|
+
assertSupportedJsonSchema(schema, "outputSchema");
|
|
612
|
+
}
|
|
613
|
+
function assertObjectRootSchema(schema, path) {
|
|
614
|
+
if (schema.type !== "object") {
|
|
615
|
+
throw new Error(`${path} root type must be "object"`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function assertSupportedJsonSchema(schema, path) {
|
|
619
|
+
for (const keyword of ["anyOf", "allOf", "not", "if", "then", "else", "contains", "prefixItems"]) {
|
|
620
|
+
if (schema[keyword] !== undefined) {
|
|
621
|
+
throw new Error(`${path} uses unsupported keyword "${keyword}"`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const type = schema.type;
|
|
625
|
+
if (Array.isArray(type)) {
|
|
626
|
+
const supported = new Set(["string", "number", "integer", "boolean", "object", "array", "null"]);
|
|
627
|
+
for (const item of type) {
|
|
628
|
+
if (!supported.has(item)) {
|
|
629
|
+
throw new Error(`${path} uses unsupported type "${item}"`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else if (type !== undefined &&
|
|
634
|
+
type !== "string" &&
|
|
635
|
+
type !== "number" &&
|
|
636
|
+
type !== "integer" &&
|
|
637
|
+
type !== "boolean" &&
|
|
638
|
+
type !== "object" &&
|
|
639
|
+
type !== "array") {
|
|
640
|
+
throw new Error(`${path} uses unsupported type "${type}"`);
|
|
641
|
+
}
|
|
642
|
+
if (isJsonObject(schema.properties)) {
|
|
643
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
644
|
+
if (isJsonObject(child)) {
|
|
645
|
+
assertSupportedJsonSchema(child, `${path}.properties.${key}`);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
throw new Error(`${path}.properties.${key} must be an object schema`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
else if (schema.properties !== undefined) {
|
|
653
|
+
throw new Error(`${path}.properties must be an object`);
|
|
654
|
+
}
|
|
655
|
+
const additionalProperties = schema.additionalProperties;
|
|
656
|
+
if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
657
|
+
if (Array.isArray(additionalProperties)) {
|
|
658
|
+
throw new Error(`${path}.additionalProperties must be an object schema or boolean`);
|
|
659
|
+
}
|
|
660
|
+
assertSupportedJsonSchema(additionalProperties, `${path}.additionalProperties`);
|
|
661
|
+
}
|
|
662
|
+
else if (additionalProperties !== undefined
|
|
663
|
+
&& typeof additionalProperties !== "boolean") {
|
|
664
|
+
throw new Error(`${path}.additionalProperties must be an object schema or boolean`);
|
|
665
|
+
}
|
|
666
|
+
const items = schema.items;
|
|
667
|
+
if (Array.isArray(items)) {
|
|
668
|
+
throw new Error(`${path}.items uses unsupported tuple array schemas`);
|
|
669
|
+
}
|
|
670
|
+
if (typeof items === "object" && items !== null && !Array.isArray(items)) {
|
|
671
|
+
assertSupportedJsonSchema(items, `${path}.items`);
|
|
672
|
+
}
|
|
673
|
+
else if (items !== undefined && typeof items !== "boolean") {
|
|
674
|
+
throw new Error(`${path}.items must be an object schema or boolean`);
|
|
675
|
+
}
|
|
676
|
+
const oneOf = schema.oneOf;
|
|
677
|
+
if (Array.isArray(oneOf)) {
|
|
678
|
+
for (const [index, child] of oneOf.entries()) {
|
|
679
|
+
if (typeof child === "object" && child !== null && !Array.isArray(child)) {
|
|
680
|
+
assertSupportedJsonSchema(child, `${path}.oneOf[${index}]`);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
throw new Error(`${path}.oneOf[${index}] must be an object schema`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else if (oneOf !== undefined) {
|
|
688
|
+
throw new Error(`${path}.oneOf must be an array`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function isJsonObject(value) {
|
|
692
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
575
693
|
}
|
|
576
694
|
function isGetPromptResult(value) {
|
|
577
695
|
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "messages")) {
|
package/dist/types.d.ts
CHANGED
|
@@ -215,8 +215,8 @@ export interface ServerOptions {
|
|
|
215
215
|
supportResourceSubscriptions?: boolean;
|
|
216
216
|
}
|
|
217
217
|
import type { ToolReturn } from "./content/index.js";
|
|
218
|
-
export type ToolHandler<T = Record<string, unknown
|
|
219
|
-
export interface ToolDefinition<T = Record<string, unknown
|
|
218
|
+
export type ToolHandler<T = Record<string, unknown>, TOut = ToolReturn> = (args: T) => Promise<TOut | CallToolResult> | TOut | CallToolResult;
|
|
219
|
+
export interface ToolDefinition<T = Record<string, unknown>, TOut = ToolReturn> {
|
|
220
220
|
name: string;
|
|
221
221
|
title?: string;
|
|
222
222
|
description?: string;
|
|
@@ -226,7 +226,7 @@ export interface ToolDefinition<T = Record<string, unknown>> {
|
|
|
226
226
|
execution?: ToolExecution;
|
|
227
227
|
icons?: Icon[];
|
|
228
228
|
_meta?: Record<string, unknown>;
|
|
229
|
-
handler: ToolHandler<T>;
|
|
229
|
+
handler: ToolHandler<T, TOut>;
|
|
230
230
|
}
|
|
231
231
|
export interface Transport {
|
|
232
232
|
readable: NodeJS.ReadableStream;
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiny-stdio-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"bugs": {
|
|
5
|
+
"url": "https://github.com/poe-platform/poe-code/issues"
|
|
6
|
+
},
|
|
4
7
|
"description": "Minimal MCP server over stdio with typed tools and rich content helpers",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"main": "dist/index.js",
|