import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; // Import tool modules import * as fsTools from "./tools/fsTools.js"; import * as spiderTools from "./tools/spiderTools.js"; import * as dbTools from "./tools/dbTools.js"; import * as systemTools from "./tools/systemTools.js"; const server = new Server( { name: "drpy-node-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Map tool names to handler functions const toolHandlers = { // FS Tools list_directory: fsTools.list_directory, read_file: fsTools.read_file, write_file: fsTools.write_file, delete_file: fsTools.delete_file, // Spider Tools list_sources: spiderTools.list_sources, get_routes_info: spiderTools.get_routes_info, fetch_spider_url: spiderTools.fetch_spider_url, debug_spider_rule: spiderTools.debug_spider_rule, get_spider_template: spiderTools.get_spider_template, get_drpy_libs_info: spiderTools.get_drpy_libs_info, validate_spider: spiderTools.validate_spider, check_syntax: spiderTools.check_syntax, // DB Tools sql_query: dbTools.sql_query, // System Tools read_logs: systemTools.read_logs, manage_config: systemTools.manage_config, restart_service: systemTools.restart_service, }; server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "list_directory", description: "List files and directories in the project", inputSchema: { type: "object", properties: { path: { type: "string", description: "Directory path relative to project root (default: '.')", }, }, }, }, { name: "read_file", description: "Read the content of a file (automatically decodes DS sources)", inputSchema: { type: "object", properties: { path: { type: "string", description: "File path relative to project root", }, }, required: ["path"], }, }, { name: "write_file", description: "Write content to a file (creates directories if needed)", inputSchema: { type: "object", properties: { path: { type: "string", description: "File path relative to project root", }, content: { type: "string", description: "Content to write", }, }, required: ["path", "content"], }, }, { name: "delete_file", description: "Delete a file or directory", inputSchema: { type: "object", properties: { path: { type: "string", description: "Path relative to project root", }, }, required: ["path"], }, }, { name: "list_sources", description: "List all spider sources (js and catvod)", inputSchema: { type: "object", properties: {} }, }, { name: "get_routes_info", description: "Get information about registered routes/controllers", inputSchema: { type: "object", properties: {} }, }, { name: "fetch_spider_url", description: "Fetch a URL using drpy-node's request library to debug connectivity and anti-crawling measures (UA/headers).", inputSchema: zodToJsonSchema( z.object({ url: z.string().describe("URL to fetch"), options: z.object({ method: z.string().optional().describe("HTTP method (GET, POST, etc.)"), headers: z.record(z.string()).optional().describe("HTTP headers (User-Agent, Cookie, Referer, etc.)"), data: z.any().optional().describe("Request body for POST/PUT"), }).optional().describe("Request options"), }) ), }, { name: "debug_spider_rule", description: "Debug drpy spider rules by parsing HTML or fetching URL", inputSchema: zodToJsonSchema( z.object({ html: z.string().optional().describe("HTML content to parse"), url: z.string().optional().describe("URL to fetch and parse"), rule: z.string().describe("The drpy rule to apply (e.g. '.list li', 'a&&href')"), mode: z.enum(["pdfa", "pdfh", "pd"]).describe("Parsing mode: pdfa (list), pdfh (html), pd (url)"), baseUrl: z.string().optional().describe("Base URL for resolving relative links"), options: z.object({ method: z.string().optional(), headers: z.record(z.string()).optional(), data: z.any().optional(), }).optional().describe("Request options for URL fetch"), }) ), }, { name: "get_spider_template", description: "Get a standard template for creating a new drpy JS source", inputSchema: { type: "object", properties: {} }, }, { name: "get_drpy_libs_info", description: "Get information about available global helper functions and libraries in drpy environment", inputSchema: { type: "object", properties: {} }, }, { name: "read_logs", description: "Read the latest application logs", inputSchema: { type: "object", properties: { lines: { type: "number", description: "Number of lines to read (default: 50)" } } } }, { name: "sql_query", description: "Execute a read-only SQL query on the project database", inputSchema: { type: "object", properties: { query: { type: "string", description: "SQL query to execute" } }, required: ["query"] } }, { name: "manage_config", description: "Read or update the project configuration (config/env.json)", inputSchema: { type: "object", properties: { action: { type: "string", enum: ["get", "set"], description: "Action to perform" }, key: { type: "string", description: "Config key (dot notation supported for nested keys, optional for 'get')" }, value: { type: "string", description: "Value to set (required for 'set', JSON string supported)" } }, required: ["action"] } }, { name: "validate_spider", description: "Validate a drpy spider file (syntax check + structure validation)", inputSchema: { type: "object", properties: { path: { type: "string", description: "File path relative to project root", }, }, required: ["path"], }, }, { name: "check_syntax", description: "Check syntax of a JavaScript file", inputSchema: { type: "object", properties: { path: { type: "string", description: "File path relative to project root", }, }, required: ["path"], }, }, { name: "restart_service", description: "Restart the drpy-node service (assumes PM2 is used with name 'drpys')", inputSchema: { type: "object", properties: {}, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const handler = toolHandlers[name]; if (!handler) { throw new Error(`Tool not found: ${name}`); } try { return await handler(args); } catch (error) { return { isError: true, content: [{ type: "text", text: `Error executing ${name}: ${error.message}` }] }; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Drpy Node MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });