diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 0b3d69b6..5b091d7c 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -1,4 +1,4 @@ -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; import { ClientCapabilities, @@ -155,20 +155,11 @@ const EXAMPLE_COMPLETIONS = { }; export const createServer = () => { - const server = new Server( + const server = new McpServer( { name: "example-servers/everything", title: "Everything Example Server", version: "1.0.0", - }, - { - capabilities: { - prompts: {}, - resources: { subscribe: true }, - tools: {}, - logging: {}, - completions: {} - }, instructions } ); @@ -274,15 +265,15 @@ export const createServer = () => { // Register all resources dynamically ALL_RESOURCES.forEach(resource => { - server.registerResource({ - uri: resource.uri, - name: resource.name, - mimeType: resource.mimeType, - description: `Static resource ${resource.name}`, - handler: async () => { + server.resource( + resource.uri, + resource.name, + resource.mimeType, + `Static resource ${resource.name}`, + async () => { return resource.text ? { text: resource.text } : { blob: resource.blob }; } - }); + ); }); server.setRequestHandler(SubscribeRequestSchema, async (request, extra) => { @@ -297,10 +288,11 @@ export const createServer = () => { }); // Register prompts - server.registerPrompt({ - name: PromptName.SIMPLE, - description: "A prompt without arguments", - handler: async () => { + server.prompt( + PromptName.SIMPLE, + "A prompt without arguments", + {}, + async () => { return { messages: [ { @@ -313,24 +305,26 @@ export const createServer = () => { ], }; } - }); + ); - server.registerPrompt({ - name: PromptName.COMPLEX, - description: "A prompt with arguments", - arguments: [ - { - name: "temperature", - description: "Temperature setting", - required: true, + server.prompt( + PromptName.COMPLEX, + "A prompt with arguments", + { + type: "object", + properties: { + temperature: { + type: "string", + description: "Temperature setting" + }, + style: { + type: "string", + description: "Output style" + } }, - { - name: "style", - description: "Output style", - required: false, - }, - ], - handler: async (args) => { + required: ["temperature"] + }, + async (args) => { return { messages: [ { @@ -358,19 +352,22 @@ export const createServer = () => { ], }; } - }); + ); - server.registerPrompt({ - name: PromptName.RESOURCE, - description: "A prompt that includes an embedded resource reference", - arguments: [ - { - name: "resourceId", - description: "Resource ID to include (1-100)", - required: true, + server.prompt( + PromptName.RESOURCE, + "A prompt that includes an embedded resource reference", + { + type: "object", + properties: { + resourceId: { + type: "string", + description: "Resource ID to include (1-100)" + } }, - ], - handler: async (args) => { + required: ["resourceId"] + }, + async (args) => { const resourceId = parseInt(args?.resourceId as string, 10); if (isNaN(resourceId) || resourceId < 1 || resourceId > 100) { throw new Error( @@ -400,26 +397,26 @@ export const createServer = () => { ], }; } - }); + ); // Register tools - server.registerTool({ - name: ToolName.ECHO, - description: "Echoes back the input", - inputSchema: zodToJsonSchema(EchoSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.ECHO, + "Echoes back the input", + zodToJsonSchema(EchoSchema) as ToolInput, + async (args) => { const validatedArgs = EchoSchema.parse(args); return { content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }], }; } - }); + ); - server.registerTool({ - name: ToolName.ADD, - description: "Adds two numbers", - inputSchema: zodToJsonSchema(AddSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.ADD, + "Adds two numbers", + zodToJsonSchema(AddSchema) as ToolInput, + async (args) => { const validatedArgs = AddSchema.parse(args); const sum = validatedArgs.a + validatedArgs.b; return { @@ -431,13 +428,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.LONG_RUNNING_OPERATION, - description: "Demonstrates a long running operation with progress updates", - inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput, - handler: async (args, extra) => { + server.tool( + ToolName.LONG_RUNNING_OPERATION, + "Demonstrates a long running operation with progress updates", + zodToJsonSchema(LongRunningOperationSchema) as ToolInput, + async (args, extra) => { const validatedArgs = LongRunningOperationSchema.parse(args); const { duration, steps } = validatedArgs; const stepDuration = duration / steps; @@ -469,13 +466,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.PRINT_ENV, - description: "Prints all environment variables, helpful for debugging MCP server configuration", - inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput, - handler: async () => { + server.tool( + ToolName.PRINT_ENV, + "Prints all environment variables, helpful for debugging MCP server configuration", + zodToJsonSchema(PrintEnvSchema) as ToolInput, + async () => { return { content: [ { @@ -485,13 +482,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.SAMPLE_LLM, - description: "Samples from an LLM using MCP's sampling feature", - inputSchema: zodToJsonSchema(SampleLLMSchema) as ToolInput, - handler: async (args, extra) => { + server.tool( + ToolName.SAMPLE_LLM, + "Samples from an LLM using MCP's sampling feature", + zodToJsonSchema(SampleLLMSchema) as ToolInput, + async (args, extra) => { const validatedArgs = SampleLLMSchema.parse(args); const { prompt, maxTokens } = validatedArgs; @@ -507,13 +504,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.GET_TINY_IMAGE, - description: "Returns the MCP_TINY_IMAGE", - inputSchema: zodToJsonSchema(GetTinyImageSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.GET_TINY_IMAGE, + "Returns the MCP_TINY_IMAGE", + zodToJsonSchema(GetTinyImageSchema) as ToolInput, + async (args) => { GetTinyImageSchema.parse(args); return { content: [ @@ -533,13 +530,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.ANNOTATED_MESSAGE, - description: "Demonstrates how annotations can be used to provide metadata about content", - inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.ANNOTATED_MESSAGE, + "Demonstrates how annotations can be used to provide metadata about content", + zodToJsonSchema(AnnotatedMessageSchema) as ToolInput, + async (args) => { const { messageType, includeImage } = AnnotatedMessageSchema.parse(args); const content = []; @@ -589,13 +586,13 @@ export const createServer = () => { return { content }; } - }); + ); - server.registerTool({ - name: ToolName.GET_RESOURCE_REFERENCE, - description: "Returns a resource reference that can be used by MCP clients", - inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.GET_RESOURCE_REFERENCE, + "Returns a resource reference that can be used by MCP clients", + zodToJsonSchema(GetResourceReferenceSchema) as ToolInput, + async (args) => { const validatedArgs = GetResourceReferenceSchema.parse(args); const resourceId = validatedArgs.resourceId; @@ -623,13 +620,13 @@ export const createServer = () => { ], }; } - }); + ); - server.registerTool({ - name: ToolName.GET_RESOURCE_LINKS, - description: "Returns multiple resource links that reference different types of resources", - inputSchema: zodToJsonSchema(GetResourceLinksSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.GET_RESOURCE_LINKS, + "Returns multiple resource links that reference different types of resources", + zodToJsonSchema(GetResourceLinksSchema) as ToolInput, + async (args) => { const { count } = GetResourceLinksSchema.parse(args); const content = []; @@ -657,14 +654,13 @@ export const createServer = () => { return { content }; } - }); + ); - server.registerTool({ - name: ToolName.STRUCTURED_CONTENT, - description: "Returns structured content along with an output schema for client data validation", - inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput, - outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput, - handler: async (args) => { + server.tool( + ToolName.STRUCTURED_CONTENT, + "Returns structured content along with an output schema for client data validation", + zodToJsonSchema(StructuredContentSchema.input) as ToolInput, + async (args) => { // The same response is returned for every input. const validatedArgs = StructuredContentSchema.input.parse(args); @@ -684,13 +680,13 @@ export const createServer = () => { structuredContent: weather }; } - }); + ); - server.registerTool({ - name: ToolName.ZIP_RESOURCES, - description: "Compresses the provided resource files (mapping of name to URI, which can be a data URI) to a zip file, which it returns as a data URI resource link.", - inputSchema: zodToJsonSchema(ZipResourcesInputSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.ZIP_RESOURCES, + "Compresses the provided resource files (mapping of name to URI, which can be a data URI) to a zip file, which it returns as a data URI resource link.", + zodToJsonSchema(ZipResourcesInputSchema) as ToolInput, + async (args) => { const { files } = ZipResourcesInputSchema.parse(args); const zip = new JSZip(); @@ -720,7 +716,7 @@ export const createServer = () => { ], }; } - }); + ); server.setRequestHandler(CompleteRequestSchema, async (request) => { const { ref, argument } = request.params; @@ -784,11 +780,11 @@ export const createServer = () => { clientSupportsRoots = true; // Register LIST_ROOTS tool - server.registerTool({ - name: ToolName.LIST_ROOTS, - description: "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", - inputSchema: zodToJsonSchema(ListRootsSchema) as ToolInput, - handler: async (args) => { + server.tool( + ToolName.LIST_ROOTS, + "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", + zodToJsonSchema(ListRootsSchema) as ToolInput, + async (args) => { ListRootsSchema.parse(args); if (!clientSupportsRoots) { @@ -833,7 +829,7 @@ export const createServer = () => { ] }; } - }); + ); try { const response = await server.listRoots(); @@ -869,11 +865,11 @@ export const createServer = () => { if (clientCapabilities?.elicitation) { // Register ELICITATION tool - server.registerTool({ - name: ToolName.ELICITATION, - description: "Elicitation test tool that demonstrates how to request user input with various field types (string, boolean, email, uri, date, integer, number, enum)", - inputSchema: zodToJsonSchema(ElicitationSchema) as ToolInput, - handler: async (args, extra) => { + server.tool( + ToolName.ELICITATION, + "Elicitation test tool that demonstrates how to request user input with various field types (string, boolean, email, uri, date, integer, number, enum)", + zodToJsonSchema(ElicitationSchema) as ToolInput, + async (args, extra) => { ElicitationSchema.parse(args); const elicitationResult = await extra.sendRequest({ @@ -992,7 +988,7 @@ export const createServer = () => { return { content }; } - }); + ); } }; diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index adb025bf..aa91423a 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ToolSchema, @@ -145,17 +145,14 @@ const ToolInputSchema = ToolSchema.shape.inputSchema; type ToolInput = z.infer; // Server setup -const server = new Server( - { - name: "secure-filesystem-server", - version: "0.2.0", +const server = new McpServer({ + name: "secure-filesystem-server", + version: "0.2.0", +}, { + capabilities: { + tools: {}, }, - { - capabilities: { - tools: {}, - }, - }, -); +}); // Reads a file as a stream of buffers, concatenates them, and then encodes // the result to a Base64 string. This is a memory-efficient way to handle @@ -176,11 +173,13 @@ async function readFileAsBase64Stream(filePath: string): Promise { } // Tool handlers -server.registerTool({ - name: "read_file", - description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", - inputSchema: zodToJsonSchema(ReadTextFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "read_file", + { + description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", + inputSchema: ReadTextFileArgsSchema.shape + }, + async (args: any) => { const parsed = ReadTextFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for read_file: ${parsed.error}`); @@ -211,20 +210,21 @@ server.registerTool({ content: [{ type: "text", text: content }], }; } -}); +); -server.registerTool({ - name: "read_text_file", - description: - "Read the complete contents of a file from the file system as text. " + - "Handles various text encodings and provides detailed error messages " + - "if the file cannot be read. Use this tool when you need to examine " + - "the contents of a single file. Use the 'head' parameter to read only " + - "the first N lines of a file, or the 'tail' parameter to read only " + - "the last N lines of a file. Operates on the file as text regardless of extension. " + - "Only works within allowed directories.", - inputSchema: zodToJsonSchema(ReadTextFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "read_text_file", + { + description: "Read the complete contents of a file from the file system as text. " + + "Handles various text encodings and provides detailed error messages " + + "if the file cannot be read. Use this tool when you need to examine " + + "the contents of a single file. Use the 'head' parameter to read only " + + "the first N lines of a file, or the 'tail' parameter to read only " + + "the last N lines of a file. Operates on the file as text regardless of extension. " + + "Only works within allowed directories.", + inputSchema: ReadTextFileArgsSchema.shape + }, + async (args: any) => { const parsed = ReadTextFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for read_text_file: ${parsed.error}`); @@ -255,15 +255,16 @@ server.registerTool({ content: [{ type: "text", text: content }], }; } -}); +); -server.registerTool({ - name: "read_media_file", - description: - "Read an image or audio file. Returns the base64 encoded data and MIME type. " + - "Only works within allowed directories.", - inputSchema: zodToJsonSchema(ReadMediaFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "read_media_file", + { + description: "Read an image or audio file. Returns the base64 encoded data and MIME type. " + + "Only works within allowed directories.", + inputSchema: ReadMediaFileArgsSchema.shape + }, + async (args: any) => { const parsed = ReadMediaFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for read_media_file: ${parsed.error}`); @@ -294,18 +295,19 @@ server.registerTool({ content: [{ type, data, mimeType }], }; } -}); +); -server.registerTool({ - name: "read_multiple_files", - description: - "Read the contents of multiple files simultaneously. This is more " + - "efficient than reading files one by one when you need to analyze " + - "or compare multiple files. Each file's content is returned with its " + - "path as a reference. Failed reads for individual files won't stop " + - "the entire operation. Only works within allowed directories.", - inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "read_multiple_files", + { + description: "Read the contents of multiple files simultaneously. This is more " + + "efficient than reading files one by one when you need to analyze " + + "or compare multiple files. Each file's content is returned with its " + + "path as a reference. Failed reads for individual files won't stop " + + "the entire operation. Only works within allowed directories.", + inputSchema: ReadMultipleFilesArgsSchema.shape + }, + async (args: any) => { const parsed = ReadMultipleFilesArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for read_multiple_files: ${parsed.error}`); @@ -326,16 +328,17 @@ server.registerTool({ content: [{ type: "text", text: results.join("\n---\n") }], }; } -}); +); -server.registerTool({ - name: "write_file", - description: - "Create a new file or completely overwrite an existing file with new content. " + - "Use with caution as it will overwrite existing files without warning. " + - "Handles text content with proper encoding. Only works within allowed directories.", - inputSchema: zodToJsonSchema(WriteFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "write_file", + { + description: "Create a new file or completely overwrite an existing file with new content. " + + "Use with caution as it will overwrite existing files without warning. " + + "Handles text content with proper encoding. Only works within allowed directories.", + inputSchema: WriteFileArgsSchema.shape + }, + async (args: any) => { const parsed = WriteFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for write_file: ${parsed.error}`); @@ -346,16 +349,17 @@ server.registerTool({ content: [{ type: "text", text: `Successfully wrote to ${parsed.data.path}` }], }; } -}); +); -server.registerTool({ - name: "edit_file", - description: - "Make line-based edits to a text file. Each edit replaces exact line sequences " + - "with new content. Returns a git-style diff showing the changes made. " + - "Only works within allowed directories.", - inputSchema: zodToJsonSchema(EditFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "edit_file", + { + description: "Make line-based edits to a text file. Each edit replaces exact line sequences " + + "with new content. Returns a git-style diff showing the changes made. " + + "Only works within allowed directories.", + inputSchema: EditFileArgsSchema.shape + }, + async (args: any) => { const parsed = EditFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for edit_file: ${parsed.error}`); @@ -366,17 +370,18 @@ server.registerTool({ content: [{ type: "text", text: result }], }; } -}); +); -server.registerTool({ - name: "create_directory", - description: - "Create a new directory or ensure a directory exists. Can create multiple " + - "nested directories in one operation. If the directory already exists, " + - "this operation will succeed silently. Perfect for setting up directory " + - "structures for projects or ensuring required paths exist. Only works within allowed directories.", - inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "create_directory", + { + description: "Create a new directory or ensure a directory exists. Can create multiple " + + "nested directories in one operation. If the directory already exists, " + + "this operation will succeed silently. Perfect for setting up directory " + + "structures for projects or ensuring required paths exist. Only works within allowed directories.", + inputSchema: CreateDirectoryArgsSchema.shape + }, + async (args: any) => { const parsed = CreateDirectoryArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for create_directory: ${parsed.error}`); @@ -387,17 +392,18 @@ server.registerTool({ content: [{ type: "text", text: `Successfully created directory ${parsed.data.path}` }], }; } -}); +); -server.registerTool({ - name: "list_directory", - description: - "Get a detailed listing of all files and directories in a specified path. " + - "Results clearly distinguish between files and directories with [FILE] and [DIR] " + - "prefixes. This tool is essential for understanding directory structure and " + - "finding specific files within a directory. Only works within allowed directories.", - inputSchema: zodToJsonSchema(ListDirectoryArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "list_directory", + { + description: "Get a detailed listing of all files and directories in a specified path. " + + "Results clearly distinguish between files and directories with [FILE] and [DIR] " + + "prefixes. This tool is essential for understanding directory structure and " + + "finding specific files within a directory. Only works within allowed directories.", + inputSchema: ListDirectoryArgsSchema.shape + }, + async (args: any) => { const parsed = ListDirectoryArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for list_directory: ${parsed.error}`); @@ -411,17 +417,18 @@ server.registerTool({ content: [{ type: "text", text: formatted }], }; } -}); +); -server.registerTool({ - name: "list_directory_with_sizes", - description: - "Get a detailed listing of all files and directories in a specified path, including sizes. " + - "Results clearly distinguish between files and directories with [FILE] and [DIR] " + - "prefixes. This tool is useful for understanding directory structure and " + - "finding specific files within a directory. Only works within allowed directories.", - inputSchema: zodToJsonSchema(ListDirectoryWithSizesArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "list_directory_with_sizes", + { + description: "Get a detailed listing of all files and directories in a specified path, including sizes. " + + "Results clearly distinguish between files and directories with [FILE] and [DIR] " + + "prefixes. This tool is useful for understanding directory structure and " + + "finding specific files within a directory. Only works within allowed directories.", + inputSchema: ListDirectoryWithSizesArgsSchema.shape + }, + async (args: any) => { const parsed = ListDirectoryWithSizesArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for list_directory_with_sizes: ${parsed.error}`); @@ -486,17 +493,18 @@ server.registerTool({ }], }; } -}); +); -server.registerTool({ - name: "directory_tree", - description: - "Get a recursive tree view of files and directories as a JSON structure. " + - "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " + - "Files have no children array, while directories always have a children array (which may be empty). " + - "The output is formatted with 2-space indentation for readability. Only works within allowed directories.", - inputSchema: zodToJsonSchema(DirectoryTreeArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "directory_tree", + { + description: "Get a recursive tree view of files and directories as a JSON structure. " + + "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " + + "Files have no children array, while directories always have a children array (which may be empty). " + + "The output is formatted with 2-space indentation for readability. Only works within allowed directories.", + inputSchema: DirectoryTreeArgsSchema.shape + }, + async (args: any) => { const parsed = DirectoryTreeArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for directory_tree: ${parsed.error}`); @@ -553,17 +561,18 @@ server.registerTool({ }], }; } -}); +); -server.registerTool({ - name: "move_file", - description: - "Move or rename files and directories. Can move files between directories " + - "and rename them in a single operation. If the destination exists, the " + - "operation will fail. Works across different directories and can be used " + - "for simple renaming within the same directory. Both source and destination must be within allowed directories.", - inputSchema: zodToJsonSchema(MoveFileArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "move_file", + { + description: "Move or rename files and directories. Can move files between directories " + + "and rename them in a single operation. If the destination exists, the " + + "operation will fail. Works across different directories and can be used " + + "for simple renaming within the same directory. Both source and destination must be within allowed directories.", + inputSchema: MoveFileArgsSchema.shape + }, + async (args: any) => { const parsed = MoveFileArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for move_file: ${parsed.error}`); @@ -575,18 +584,19 @@ server.registerTool({ content: [{ type: "text", text: `Successfully moved ${parsed.data.source} to ${parsed.data.destination}` }], }; } -}); +); -server.registerTool({ - name: "search_files", - description: - "Recursively search for files and directories matching a pattern. " + - "The patterns should be glob-style patterns that match paths relative to the working directory. " + - "Use pattern like '*.ext' to match files in current directory, and '**/*.ext' to match files in all subdirectories. " + - "Returns full paths to all matching items. Great for finding files when you don't know their exact location. " + - "Only searches within allowed directories.", - inputSchema: zodToJsonSchema(SearchFilesArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "search_files", + { + description: "Recursively search for files and directories matching a pattern. " + + "The patterns should be glob-style patterns that match paths relative to the working directory. " + + "Use pattern like '*.ext' to match files in current directory, and '**/*.ext' to match files in all subdirectories. " + + "Returns full paths to all matching items. Great for finding files when you don't know their exact location. " + + "Only searches within allowed directories.", + inputSchema: SearchFilesArgsSchema.shape + }, + async (args: any) => { const parsed = SearchFilesArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for search_files: ${parsed.error}`); @@ -597,17 +607,18 @@ server.registerTool({ content: [{ type: "text", text: results.length > 0 ? results.join("\n") : "No matches found" }], }; } -}); +); -server.registerTool({ - name: "get_file_info", - description: - "Retrieve detailed metadata about a file or directory. Returns comprehensive " + - "information including size, creation time, last modified time, permissions, " + - "and type. This tool is perfect for understanding file characteristics " + - "without reading the actual content. Only works within allowed directories.", - inputSchema: zodToJsonSchema(GetFileInfoArgsSchema) as ToolInput, - handler: async (args: any) => { +server.registerTool( + "get_file_info", + { + description: "Retrieve detailed metadata about a file or directory. Returns comprehensive " + + "information including size, creation time, last modified time, permissions, " + + "and type. This tool is perfect for understanding file characteristics " + + "without reading the actual content. Only works within allowed directories.", + inputSchema: GetFileInfoArgsSchema.shape + }, + async (args: any) => { const parsed = GetFileInfoArgsSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for get_file_info: ${parsed.error}`); @@ -620,21 +631,18 @@ server.registerTool({ .join("\n") }], }; } -}); +); -server.registerTool({ - name: "list_allowed_directories", - description: - "Returns the list of directories that this server is allowed to access. " + - "Subdirectories within these allowed directories are also accessible. " + - "Use this to understand which directories and their nested paths are available " + - "before trying to access files.", - inputSchema: { - type: "object", - properties: {}, - required: [], +server.registerTool( + "list_allowed_directories", + { + description: "Returns the list of directories that this server is allowed to access. " + + "Subdirectories within these allowed directories are also accessible. " + + "Use this to understand which directories and their nested paths are available " + + "before trying to access files.", + inputSchema: z.object({}).shape }, - handler: async (args: any) => { + async (args: any) => { return { content: [{ type: "text", @@ -642,7 +650,7 @@ server.registerTool({ }], }; } -}); +); // Updates allowed directories based on MCP client roots async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) { diff --git a/src/filesystem/package.json b/src/filesystem/package.json index c26ba40d..adc37da4 100644 --- a/src/filesystem/package.json +++ b/src/filesystem/package.json @@ -24,6 +24,7 @@ "diff": "^5.1.0", "glob": "^10.3.10", "minimatch": "^10.0.1", + "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5" }, "devDependencies": { diff --git a/src/memory/index.ts b/src/memory/index.ts index db53d34b..2ea8c138 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -1,10 +1,11 @@ #!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { z } from "zod"; // Define memory file path using environment variable with fallback const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.json'); @@ -191,239 +192,170 @@ class KnowledgeGraphManager { const knowledgeGraphManager = new KnowledgeGraphManager(); +// Define Zod schemas for all tools +const CreateEntitiesSchema = z.object({ + entities: z.array(z.object({ + name: z.string().describe("The name of the entity"), + entityType: z.string().describe("The type of the entity"), + observations: z.array(z.string()).describe("An array of observation contents associated with the entity") + })) +}); + +const CreateRelationsSchema = z.object({ + relations: z.array(z.object({ + from: z.string().describe("The name of the entity where the relation starts"), + to: z.string().describe("The name of the entity where the relation ends"), + relationType: z.string().describe("The type of the relation") + })) +}); + +const AddObservationsSchema = z.object({ + observations: z.array(z.object({ + entityName: z.string().describe("The name of the entity to add the observations to"), + contents: z.array(z.string()).describe("An array of observation contents to add") + })) +}); + +const DeleteEntitiesSchema = z.object({ + entityNames: z.array(z.string()).describe("An array of entity names to delete") +}); + +const DeleteObservationsSchema = z.object({ + deletions: z.array(z.object({ + entityName: z.string().describe("The name of the entity containing the observations"), + observations: z.array(z.string()).describe("An array of observations to delete") + })) +}); + +const DeleteRelationsSchema = z.object({ + relations: z.array(z.object({ + from: z.string().describe("The name of the entity where the relation starts"), + to: z.string().describe("The name of the entity where the relation ends"), + relationType: z.string().describe("The type of the relation") + })).describe("An array of relations to delete") +}); + +const ReadGraphSchema = z.object({}); + +const SearchNodesSchema = z.object({ + query: z.string().describe("The search query to match against entity names, types, and observation content") +}); + +const OpenNodesSchema = z.object({ + names: z.array(z.string()).describe("An array of entity names to retrieve") +}); // The server instance and tools exposed to Claude -const server = new Server({ +const server = new McpServer({ name: "memory-server", version: "0.6.3", -}, { - capabilities: { - tools: {}, - }, - },); - -server.registerTool({ - name: "create_entities", - description: "Create multiple new entities in the knowledge graph", - inputSchema: { - type: "object", - properties: { - entities: { - type: "array", - items: { - type: "object", - properties: { - name: { type: "string", description: "The name of the entity" }, - entityType: { type: "string", description: "The type of the entity" }, - observations: { - type: "array", - items: { type: "string" }, - description: "An array of observation contents associated with the entity" - }, - }, - required: ["name", "entityType", "observations"], - additionalProperties: false, - }, - }, - }, - required: ["entities"], - additionalProperties: false, +}, { + capabilities: { + tools: {}, }, - handler: async (args) => { +}); + +server.registerTool( + "create_entities", + { + description: "Create multiple new entities in the knowledge graph", + inputSchema: CreateEntitiesSchema.shape + }, + async (args) => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[]), null, 2) }] }; } -}); +); -server.registerTool({ - name: "create_relations", - description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", - inputSchema: { - type: "object", - properties: { - relations: { - type: "array", - items: { - type: "object", - properties: { - from: { type: "string", description: "The name of the entity where the relation starts" }, - to: { type: "string", description: "The name of the entity where the relation ends" }, - relationType: { type: "string", description: "The type of the relation" }, - }, - required: ["from", "to", "relationType"], - additionalProperties: false, - }, - }, - }, - required: ["relations"], - additionalProperties: false, +server.registerTool( + "create_relations", + { + description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", + inputSchema: CreateRelationsSchema.shape }, - handler: async (args) => { + async (args) => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[]), null, 2) }] }; } -}); +); -server.registerTool({ - name: "add_observations", - description: "Add new observations to existing entities in the knowledge graph", - inputSchema: { - type: "object", - properties: { - observations: { - type: "array", - items: { - type: "object", - properties: { - entityName: { type: "string", description: "The name of the entity to add the observations to" }, - contents: { - type: "array", - items: { type: "string" }, - description: "An array of observation contents to add" - }, - }, - required: ["entityName", "contents"], - additionalProperties: false, - }, - }, - }, - required: ["observations"], - additionalProperties: false, +server.registerTool( + "add_observations", + { + description: "Add new observations to existing entities in the knowledge graph", + inputSchema: AddObservationsSchema.shape }, - handler: async (args) => { + async (args) => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] }; } -}); +); -server.registerTool({ - name: "delete_entities", - description: "Delete multiple entities and their associated relations from the knowledge graph", - inputSchema: { - type: "object", - properties: { - entityNames: { - type: "array", - items: { type: "string" }, - description: "An array of entity names to delete" - }, - }, - required: ["entityNames"], - additionalProperties: false, +server.registerTool( + "delete_entities", + { + description: "Delete multiple entities and their associated relations from the knowledge graph", + inputSchema: DeleteEntitiesSchema.shape }, - handler: async (args) => { + async (args) => { await knowledgeGraphManager.deleteEntities(args.entityNames as string[]); return { content: [{ type: "text", text: "Entities deleted successfully" }] }; } -}); +); -server.registerTool({ - name: "delete_observations", - description: "Delete specific observations from entities in the knowledge graph", - inputSchema: { - type: "object", - properties: { - deletions: { - type: "array", - items: { - type: "object", - properties: { - entityName: { type: "string", description: "The name of the entity containing the observations" }, - observations: { - type: "array", - items: { type: "string" }, - description: "An array of observations to delete" - }, - }, - required: ["entityName", "observations"], - additionalProperties: false, - }, - }, - }, - required: ["deletions"], - additionalProperties: false, +server.registerTool( + "delete_observations", + { + description: "Delete specific observations from entities in the knowledge graph", + inputSchema: DeleteObservationsSchema.shape }, - handler: async (args) => { + async (args) => { await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]); return { content: [{ type: "text", text: "Observations deleted successfully" }] }; } -}); +); -server.registerTool({ - name: "delete_relations", - description: "Delete multiple relations from the knowledge graph", - inputSchema: { - type: "object", - properties: { - relations: { - type: "array", - items: { - type: "object", - properties: { - from: { type: "string", description: "The name of the entity where the relation starts" }, - to: { type: "string", description: "The name of the entity where the relation ends" }, - relationType: { type: "string", description: "The type of the relation" }, - }, - required: ["from", "to", "relationType"], - additionalProperties: false, - }, - description: "An array of relations to delete" - }, - }, - required: ["relations"], - additionalProperties: false, +server.registerTool( + "delete_relations", + { + description: "Delete multiple relations from the knowledge graph", + inputSchema: DeleteRelationsSchema.shape }, - handler: async (args) => { + async (args) => { await knowledgeGraphManager.deleteRelations(args.relations as Relation[]); return { content: [{ type: "text", text: "Relations deleted successfully" }] }; } -}); +); -server.registerTool({ - name: "read_graph", - description: "Read the entire knowledge graph", - inputSchema: { - type: "object", - properties: {}, - additionalProperties: false, +server.registerTool( + "read_graph", + { + description: "Read the entire knowledge graph", + inputSchema: ReadGraphSchema.shape }, - handler: async () => { + async () => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] }; } -}); +); -server.registerTool({ - name: "search_nodes", - description: "Search for nodes in the knowledge graph based on a query", - inputSchema: { - type: "object", - properties: { - query: { type: "string", description: "The search query to match against entity names, types, and observation content" }, - }, - required: ["query"], - additionalProperties: false, +server.registerTool( + "search_nodes", + { + description: "Search for nodes in the knowledge graph based on a query", + inputSchema: SearchNodesSchema.shape }, - handler: async (args) => { + async (args) => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string), null, 2) }] }; } -}); +); -server.registerTool({ - name: "open_nodes", - description: "Open specific nodes in the knowledge graph by their names", - inputSchema: { - type: "object", - properties: { - names: { - type: "array", - items: { type: "string" }, - description: "An array of entity names to retrieve", - }, - }, - required: ["names"], - additionalProperties: false, +server.registerTool( + "open_nodes", + { + description: "Open specific nodes in the knowledge graph by their names", + inputSchema: OpenNodesSchema.shape }, - handler: async (args) => { + async (args) => { return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[]), null, 2) }] }; } -}); +); async function main() { const transport = new StdioServerTransport(); diff --git a/src/memory/package.json b/src/memory/package.json index ea7c9beb..3e299fd4 100644 --- a/src/memory/package.json +++ b/src/memory/package.json @@ -19,7 +19,8 @@ "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.20.1" + "@modelcontextprotocol/sdk": "^1.20.1", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^22", diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts index e0682219..6e04d199 100644 --- a/src/sequentialthinking/index.ts +++ b/src/sequentialthinking/index.ts @@ -1,10 +1,8 @@ #!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - Tool, -} from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; // Fixed chalk import for ESM import chalk from 'chalk'; @@ -135,9 +133,19 @@ class SequentialThinkingServer { } } -const SEQUENTIAL_THINKING_TOOL: Tool = { - name: "sequentialthinking", - description: `A detailed tool for dynamic and reflective problem-solving through thoughts. +const SequentialThinkingToolSchema = z.object({ + thought: z.string().describe("Your current thinking step"), + nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"), + thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"), + totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"), + isRevision: z.boolean().optional().describe("Whether this revises previous thinking"), + revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"), + branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"), + branchId: z.string().optional().describe("Branch identifier"), + needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed") +}); + +const TOOL_DESCRIPTION = `A detailed tool for dynamic and reflective problem-solving through thoughts. This tool helps analyze problems through a flexible thinking process that can adapt and evolve. Each thought can build on, question, or revise previous insights as understanding deepens. @@ -190,77 +198,25 @@ You should: 8. Verify the hypothesis based on the Chain of Thought steps 9. Repeat the process until satisfied with the solution 10. Provide a single, ideally correct answer as the final output -11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`, - inputSchema: { - type: "object", - properties: { - thought: { - type: "string", - description: "Your current thinking step" - }, - nextThoughtNeeded: { - type: "boolean", - description: "Whether another thought step is needed" - }, - thoughtNumber: { - type: "integer", - description: "Current thought number (numeric value, e.g., 1, 2, 3)", - minimum: 1 - }, - totalThoughts: { - type: "integer", - description: "Estimated total thoughts needed (numeric value, e.g., 5, 10)", - minimum: 1 - }, - isRevision: { - type: "boolean", - description: "Whether this revises previous thinking" - }, - revisesThought: { - type: "integer", - description: "Which thought is being reconsidered", - minimum: 1 - }, - branchFromThought: { - type: "integer", - description: "Branching point thought number", - minimum: 1 - }, - branchId: { - type: "string", - description: "Branch identifier" - }, - needsMoreThoughts: { - type: "boolean", - description: "If more thoughts are needed" - } - }, - required: ["thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"] - } -}; +11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`; -const server = new Server( - { - name: "sequential-thinking-server", - version: "0.2.0", - }, - { - capabilities: { - tools: {}, - }, - } -); +const server = new McpServer({ + name: "sequential-thinking-server", + version: "0.2.0", +}); const thinkingServer = new SequentialThinkingServer(); -server.registerTool({ - name: SEQUENTIAL_THINKING_TOOL.name, - description: SEQUENTIAL_THINKING_TOOL.description, - inputSchema: SEQUENTIAL_THINKING_TOOL.inputSchema, - handler: async (args) => { +server.registerTool( + "sequentialthinking", + { + description: TOOL_DESCRIPTION, + inputSchema: SequentialThinkingToolSchema.shape, + }, + async (args) => { return thinkingServer.processThought(args); } -}); +); async function runServer() { const transport = new StdioServerTransport(); diff --git a/src/sequentialthinking/package.json b/src/sequentialthinking/package.json index 920dc62f..8ad2492d 100644 --- a/src/sequentialthinking/package.json +++ b/src/sequentialthinking/package.json @@ -21,7 +21,8 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.20.1", "chalk": "^5.3.0", - "yargs": "^17.7.2" + "yargs": "^17.7.2", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^22",