[FTCHAT] Tool registry - Notion/repo tools for model

This commit is contained in:
bingshuo 2026-05-18 19:44:09 +08:00
parent d258f9a85d
commit 4585b0d283

View File

@ -0,0 +1,181 @@
/**
* 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
};