UNPKG

@totoy/totoy-mcp

Version:

MCP Server for Totoy Document AI. Explain documents in simple, plain or detailed language and create Knowledge Bases from your documents in 19 languages.

130 lines (129 loc) 4.78 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { enclose, getConfigExample } from "./lib.js"; import { SERVER_NAME, SERVER_VERSION, OPERATION_FILES_RELATIVE, } from "./constants.js"; const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION, }); function cleanUrl(url) { if (!url) { return url; } return url.endsWith("/") ? url.slice(0, -1) : url; } async function registerToolFromOperation(operationFileRelativePath) { const operation = (await import(operationFileRelativePath)); const requiredKeys = [ "path", "method", "toolName", "inputParams", ]; for (const key of requiredKeys) { if (!operation[key]) { throw new Error(`Parameter '${key}' in '${operationFileRelativePath}' is not well-defined`); } } const { baseUrl, path: opPath, method, toolName, toolDescription, inputParams, security, } = operation; const customBaseUrl = cleanUrl(process.env.OPEN_MCP_BASE_URL || baseUrl); if (!customBaseUrl.startsWith("http://") && !customBaseUrl.startsWith("https://")) { throw new Error(`Base URL must start with 'http://' or 'https://', received '${customBaseUrl}'`); } if (!opPath.startsWith("/")) { throw new Error("path must start with slash"); } server.tool(toolName, toolDescription, inputParams, async (params) => { const securityHeadersObj = {}; const securityQueryObj = {}; for (const item of security) { const ENV_VAR = process.env[item.envVarName]; if (ENV_VAR) { const value = item.value.replace(enclose(item.envVarName), ENV_VAR); if (item.in === "header") { securityHeadersObj[item.key] = value; } else if (item.in === "query") { securityQueryObj[item.key] = value; } } } if (Object.keys(securityHeadersObj).length === 0 && Object.keys(securityQueryObj).length === 0 && security.length > 0) { const envVarsString = security .map((x) => `\`${x.envVarName}\``) .join(", "); const sampleConfig = getConfigExample(security.map((x) => x.envVarName)); return { content: [ { type: "text", text: `Must provide at least one of the following environment variables: ${envVarsString}.`, }, { type: "text", text: `For example, in your MCP client config file:\n\n${sampleConfig}`, }, ], }; } let opPathResolved = opPath; for (const [key, value] of Object.entries(params.path || {})) { if (typeof value === "undefined") { continue; } opPathResolved = opPathResolved.replaceAll(`{${key}}`, typeof value === "object" ? JSON.stringify(value) : value.toString()); } const url = new URL(`${customBaseUrl}${opPathResolved}`); for (const [key, value] of Object.entries({ ...securityQueryObj, ...(params.query || {}), })) { url.searchParams.set(key, typeof value === "undefined" ? "" : typeof value === "object" ? JSON.stringify(value) : value.toString()); } const headers = { 'Content-Type': 'application/json', ...(params.header || {}), ...securityHeadersObj, }; const response = await fetch(url, { method, headers }); const text = await response.text(); return { content: [ { type: "text", text: `Response from ${url.toString()}`, }, { type: "text", text, }, ], }; }); } async function main() { try { for (const file of OPERATION_FILES_RELATIVE) { await registerToolFromOperation(file); } const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Server running on stdio"); } catch (error) { console.error("Error during initialization:", error); process.exit(1); } } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });