import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { Config, ToolConfig, MCPTool, ToolCallArgs } from './types.js';
export class ToolError extends Error {
constructor(message: string) {
super(message);
this.name = 'ToolError';
}
}
export function buildInputSchema(tool: ToolConfig): MCPTool['inputSchema'] {
const keys = Object.keys(tool.instructions);
return {
type: 'object',
properties: {
instruction: {
type: 'string',
enum: keys,
description: `Which instruction to retrieve. Options: ${keys.join(', ')}`,
...(tool.default && { default: tool.default }),
},
},
required: [],
};
}
export function buildTools(config: Config): MCPTool[] {
return Object.entries(config.tools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: buildInputSchema(tool),
}));
}
export function handleToolCall(
config: Config,
toolName: string,
args: ToolCallArgs,
basePath: string = '.'
): string {
const tool = config.tools[toolName];
if (!tool) {
throw new ToolError(`Unknown tool: ${toolName}`);
}
const key = args.instruction || tool.default;
if (!key) {
const available = Object.keys(tool.instructions).join(', ');
throw new ToolError(
`No instruction specified and no default set. Available: ${available}`
);
}
const filepath = tool.instructions[key];
if (!filepath) {
const available = Object.keys(tool.instructions).join(', ');
throw new ToolError(`Unknown instruction: "${key}". Available: ${available}`);
}
const resolvedPath = resolve(basePath, filepath);
if (!existsSync(resolvedPath)) {
throw new ToolError(`File not found: ${filepath}`);
}
try {
return readFileSync(resolvedPath, 'utf-8');
} catch (err) {
throw new ToolError(`Failed to read file: ${(err as Error).message}`);
}
}