mcp-framework
Version:
Framework for building Model Context Protocol (MCP) servers in Typescript
112 lines (111 loc) • 3.48 kB
JavaScript
import { z } from "zod";
export class MCPTool {
get inputSchema() {
return {
type: "object",
properties: Object.fromEntries(Object.entries(this.schema).map(([key, schema]) => [
key,
{
type: this.getJsonSchemaType(schema.type),
description: schema.description,
},
])),
};
}
get toolDefinition() {
return {
name: this.name,
description: this.description,
inputSchema: this.inputSchema,
};
}
async toolCall(request) {
try {
const args = request.params.arguments || {};
const validatedInput = await this.validateInput(args);
const result = await this.execute(validatedInput);
return this.createSuccessResponse(result);
}
catch (error) {
return this.createErrorResponse(error);
}
}
async validateInput(args) {
const zodSchema = z.object(Object.fromEntries(Object.entries(this.schema).map(([key, schema]) => [key, schema.type])));
return zodSchema.parse(args);
}
getJsonSchemaType(zodType) {
if (zodType instanceof z.ZodString)
return "string";
if (zodType instanceof z.ZodNumber)
return "number";
if (zodType instanceof z.ZodBoolean)
return "boolean";
if (zodType instanceof z.ZodArray)
return "array";
if (zodType instanceof z.ZodObject)
return "object";
return "string";
}
createSuccessResponse(data) {
if (this.isImageContent(data)) {
return {
content: [data],
};
}
if (Array.isArray(data)) {
const validContent = data.filter(item => this.isValidContent(item));
if (validContent.length > 0) {
return {
content: validContent,
};
}
}
return {
content: [{ type: "text", text: JSON.stringify(data) }],
};
}
createErrorResponse(error) {
return {
content: [{ type: "error", text: error.message }],
};
}
isImageContent(data) {
return (typeof data === "object" &&
data !== null &&
"type" in data &&
data.type === "image" &&
"data" in data &&
"mimeType" in data &&
typeof data.data === "string" &&
typeof data.mimeType === "string");
}
isTextContent(data) {
return (typeof data === "object" &&
data !== null &&
"type" in data &&
data.type === "text" &&
"text" in data &&
typeof data.text === "string");
}
isErrorContent(data) {
return (typeof data === "object" &&
data !== null &&
"type" in data &&
data.type === "error" &&
"text" in data &&
typeof data.text === "string");
}
isValidContent(data) {
return (this.isImageContent(data) ||
this.isTextContent(data) ||
this.isErrorContent(data));
}
async fetch(url, init) {
const response = await fetch(url, init);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
}