182 lines
4.9 KiB
JavaScript
182 lines
4.9 KiB
JavaScript
/**
|
|
* Tool registry: Notion read/write + code repo read/write
|
|
*
|
|
* Defines tools the model can call during conversation.
|
|
* Tool call format in model output:
|
|
* <<tool:tool_name param1="value" param2="value">>
|
|
*
|
|
* 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: <<tool:tool_name param1="value" param2="value">>
|
|
*/
|
|
function parseToolCall(text) {
|
|
if (!text) return null;
|
|
const match = text.match(/<<tool:(\w+)\s+([^>]+)>>/);
|
|
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 <<tool:name key="value">> 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
|
|
};
|