UNPKG

frappe-mcp-server

Version:

Enhanced Model Context Protocol server for Frappe Framework with comprehensive API instructions and helper tools

276 lines 10.8 kB
import { ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { getDocTypeSchema, getFieldOptions, FrappeApiError, getAllDocTypes, getAllModules } from "./frappe-api.js"; import { formatFilters } from "./frappe-helpers.js"; /** * Format error response with detailed information */ function formatErrorResponse(error, operation) { console.error(`Error in ${operation}:`, error); let errorMessage = `Error in ${operation}: ${error.message || 'Unknown error'}`; let errorDetails = null; if (error instanceof FrappeApiError) { errorMessage = error.message; errorDetails = { statusCode: error.statusCode, endpoint: error.endpoint, details: error.details }; } return { content: [ { type: "text", text: errorMessage, }, ...(errorDetails ? [ { type: "text", text: `\nDetails: ${JSON.stringify(errorDetails, null, 2)}`, } ] : []) ], isError: true, }; } // Export a handler function for schema tool calls export async function handleSchemaToolCall(request) { const { name, arguments: args } = request.params; if (!args) { return { content: [ { type: "text", text: "Missing arguments for tool call", }, ], isError: true, }; } try { console.error(`Handling schema tool: ${name} with args:`, args); if (name === "get_doctype_schema") { const doctype = args.doctype; if (!doctype) { return { content: [ { type: "text", text: "Missing required parameter: doctype", }, ], isError: true, }; } try { const schema = await getDocTypeSchema(doctype); // Add a summary of the schema for easier understanding const fieldTypes = schema.fields.reduce((acc, field) => { acc[field.fieldtype] = (acc[field.fieldtype] || 0) + 1; return acc; }, {}); const requiredFields = schema.fields .filter((field) => field.required) .map((field) => field.fieldname); const summary = { name: schema.name, module: schema.module, isSingle: schema.issingle, isTable: schema.istable, isCustom: schema.custom, autoname: schema.autoname, fieldCount: schema.fields.length, fieldTypes: fieldTypes, requiredFields: requiredFields, permissions: schema.permissions.length, }; return { content: [ { type: "text", text: `Schema Summary:\n${JSON.stringify(summary, null, 2)}\n\nFull Schema:\n${JSON.stringify(schema, null, 2)}`, }, ], }; } catch (error) { return formatErrorResponse(error, `get_doctype_schema(${doctype})`); } } else if (name === "get_field_options") { const doctype = args.doctype; const fieldname = args.fieldname; if (!doctype || !fieldname) { return { content: [ { type: "text", text: "Missing required parameters: doctype and fieldname are required", }, ], isError: true, }; } const filters = args.filters; const formattedFilters = filters ? formatFilters(filters) : undefined; try { // First get the field metadata to understand what we're dealing with const schema = await getDocTypeSchema(doctype); const field = schema.fields.find((f) => f.fieldname === fieldname); if (!field) { return { content: [ { type: "text", text: `Field ${fieldname} not found in DocType ${doctype}`, }, ], isError: true, }; } // Get the options const options = await getFieldOptions(doctype, fieldname, formattedFilters); // Add field metadata to the response const fieldInfo = { fieldname: field.fieldname, label: field.label, fieldtype: field.fieldtype, required: field.required, description: field.description, options: field.options, }; return { content: [ { type: "text", text: `Field Information:\n${JSON.stringify(fieldInfo, null, 2)}\n\nAvailable Options (${options.length}):\n${JSON.stringify(options, null, 2)}`, }, ], }; } catch (error) { return formatErrorResponse(error, `get_field_options(${doctype}, ${fieldname})`); } } return { content: [ { type: "text", text: `Schema operations module doesn't handle tool: ${name}`, }, ], isError: true, }; } catch (error) { return formatErrorResponse(error, `schema_operations.${name}`); } } export function setupSchemaTools(server) { // We no longer register tools here, only resources // Tools are now registered in the central handler in index.ts // Register schema resources server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [ { uriTemplate: "schema://{doctype}", name: "DocType Schema", mimeType: "application/json", description: "Schema information for a DocType including field definitions and validations", }, { uriTemplate: "schema://{doctype}/{fieldname}/options", name: "Field Options", mimeType: "application/json", description: "Available options for a Link or Select field", }, { uriTemplate: "schema://modules", name: "Module List", mimeType: "application/json", description: "List of all modules in the system", }, { uriTemplate: "schema://doctypes", name: "DocType List", mimeType: "application/json", description: "List of all DocTypes in the system", }, ], })); // Handle schema resource requests server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; try { // Handle DocType schema resource const schemaMatch = uri.match(/^schema:\/\/([^\/]+)$/); if (schemaMatch) { const doctype = decodeURIComponent(schemaMatch[1]); // Special case for modules list if (doctype === "modules") { const modules = await getAllModules(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(modules, null, 2), }, ], }; } // Special case for doctypes list if (doctype === "doctypes") { const doctypes = await getAllDocTypes(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(doctypes, null, 2), }, ], }; } // Regular DocType schema const schema = await getDocTypeSchema(doctype); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(schema, null, 2), }, ], }; } // Handle field options resource const optionsMatch = uri.match(/^schema:\/\/([^\/]+)\/([^\/]+)\/options$/); if (optionsMatch) { const doctype = decodeURIComponent(optionsMatch[1]); const fieldname = decodeURIComponent(optionsMatch[2]); const options = await getFieldOptions(doctype, fieldname); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(options, null, 2), }, ], }; } throw new McpError(ErrorCode.InvalidRequest, `Unknown resource URI: ${uri}`); } catch (error) { console.error(`Error handling resource request for ${uri}:`, error); if (error instanceof McpError) { throw error; } if (error instanceof FrappeApiError) { throw new McpError(ErrorCode.InternalError, error.message); } throw new McpError(ErrorCode.InternalError, `Error processing resource request: ${error.message}`); } }); } //# sourceMappingURL=schema-operations.js.map