/** * Tool registry: Notion read/write + code repo read/write * * Defines tools the model can call during conversation. * Tool call format in model output: * <> * * Tools connect to MCP endpoints via connector-proxy. */ 'use strict'; const https = require('https'); const http = require('http'); // MCP proxy configuration const MCP_PROXY = process.env.MCP_PROXY_URL || 'http://localhost:3010'; /** * Parse a tool call from model output * Format: <> */ function parseToolCall(text) { if (!text) return null; const match = text.match(/<]+)>>/); if (!match) return null; const toolName = match[1]; const argsStr = match[2]; const args = {}; // Parse key="value" pairs const argRegex = /(\w+)="([^"]*)"/g; let argMatch; while ((argMatch = argRegex.exec(argsStr)) !== null) { args[argMatch[1]] = argMatch[2]; } return { tool: toolName, args }; } /** * Execute a parsed tool call */ async function executeToolCall(toolCall) { if (!toolCall || !toolCall.tool) { throw new Error('Invalid tool call'); } switch (toolCall.tool) { case 'read_notion': return await readNotion(toolCall.args); case 'write_notion': return await writeNotion(toolCall.args); case 'read_repo': return await readRepo(toolCall.args); case 'write_repo': return await writeRepo(toolCall.args); case 'search_repo': return await searchRepo(toolCall.args); default: throw new Error('Unknown tool: ' + toolCall.tool); } } /** * List available tools (for model guidance) */ function listTools() { return [ { name: 'read_notion', description: 'Read content from a Notion page', params: { page_id: 'string - The Notion page ID to read' } }, { name: 'write_notion', description: 'Append content to a Notion page', params: { page_id: 'string - The Notion page ID', content: 'string - Content to append' } }, { name: 'read_repo', description: 'Read a file from the code repository', params: { path: 'string - File path in repo' } }, { name: 'write_repo', description: 'Write a file to the code repository', params: { path: 'string - File path', content: 'string - File content' } }, { name: 'search_repo', description: 'Search code in the repository', params: { query: 'string - Search keyword' } } ]; } /** * Build a tool usage guide string for the model */ function buildToolGuide() { const tools = listTools(); let guide = 'Available tools (use <> to call):\n'; for (const t of tools) { guide += `- ${t.name}: ${t.description}\n`; for (const [k, v] of Object.entries(t.params)) { guide += ` ${k}: ${v}\n`; } } return guide; } // ─── Tool Implementations ─── function mcpRequest(method, path, body) { const url = new URL(MCP_PROXY); const transport = url.protocol === 'https:' ? https : http; return new Promise((resolve, reject) => { const options = { hostname: url.hostname, port: url.port || 80, path: path, method: method, headers: { 'Content-Type': 'application/json' } }; const req = transport.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (e) { resolve({ raw: data }); } }); }); req.on('error', reject); if (body) req.write(JSON.stringify(body)); req.end(); }); } async function readNotion(args) { const pageId = args.page_id || args.pageId; if (!pageId) throw new Error('read_notion requires page_id'); // Use the connector-proxy MCP endpoint for Notion return await mcpRequest('POST', '/api/mcp/notion/read', { page_id: pageId }); } async function writeNotion(args) { const pageId = args.page_id || args.pageId; const content = args.content; if (!pageId || !content) throw new Error('write_notion requires page_id and content'); return await mcpRequest('POST', '/api/mcp/notion/append', { page_id: pageId, content }); } async function readRepo(args) { const path = args.path; if (!path) throw new Error('read_repo requires path'); return await mcpRequest('POST', '/api/mcp/repo/read', { path }); } async function writeRepo(args) { const path = args.path; const content = args.content; if (!path || !content) throw new Error('write_repo requires path and content'); return await mcpRequest('POST', '/api/mcp/repo/write', { path, content }); } async function searchRepo(args) { const query = args.query; if (!query) throw new Error('search_repo requires query'); return await mcpRequest('POST', '/api/mcp/repo/search', { query }); } module.exports = { parseToolCall, executeToolCall, listTools, buildToolGuide };