#!/usr/bin/env node
/**
* Narrative Graph MCP Server
* Implements the Model Context Protocol for Random Tree Model operations
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import winston from 'winston';
// Configure Winston logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/server.log' }),
],
});
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
// Import our tools
import createNarrativeTree from './tools/createNarrativeTree.js';
import generateEnsemble from './tools/generateEnsemble.js';
import traverseNarrative from './tools/traverseNarrative.js';
import findOptimalDepth from './tools/findOptimalDepth.js';
// Import schemas
import { createNarrativeTreeSchema } from './schemas/createNarrativeTreeSchema.js';
import { generateEnsembleSchema } from './schemas/generateEnsembleSchema.js';
import { traverseNarrativeSchema } from './schemas/traverseNarrativeSchema.js';
import { findOptimalDepthSchema } from './schemas/findOptimalDepthSchema.js';
// Tool registry
const tools = {
rtm_create_narrative_tree: createNarrativeTree,
rtm_generate_ensemble: generateEnsemble,
rtm_traverse_narrative: traverseNarrative,
rtm_find_optimal_depth: findOptimalDepth,
};
// Centralized tool definitions
const toolDefinitions = [
{
name: 'rtm_create_narrative_tree',
description: 'Create a Random Tree Model encoding of a narrative text',
inputSchema: createNarrativeTreeSchema,
},
{
name: 'rtm_generate_ensemble',
description: 'Generate a statistical ensemble of Random Trees to model population-level recall',
inputSchema: generateEnsembleSchema,
},
{
name: 'rtm_traverse_narrative',
description: 'Traverse a narrative tree at different depths to get summaries at varying abstraction levels',
inputSchema: traverseNarrativeSchema,
},
{
name: 'rtm_find_optimal_depth',
description: 'Find the optimal traversal depth to achieve a target recall length',
inputSchema: findOptimalDepthSchema,
},
];
// Create server instance
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJsonPath = join(__dirname, '../package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const server = new Server(
{
name: packageJson.name,
version: packageJson.version,
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
logger.debug('Received ListToolsRequest', { request });
return {
tools: toolDefinitions.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: zodToJsonSchema(tool.inputSchema, { target: 'openApi3' }), // Convert Zod schema to JSON schema
})),
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const toolDefinition = toolDefinitions.find(tool => tool.name === name);
if (!toolDefinition) {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
try {
// Validate arguments against the schema
const parsedArgs = toolDefinition.inputSchema.parse(args);
// Get the tool handler
const toolHandler = tools[name as keyof typeof tools];
// Execute the tool
const result = await toolHandler(parsedArgs as any);
return result;
} catch (error) {
logger.error(`Error executing tool ${name}:`, error);
if (error instanceof z.ZodError) {
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for tool ${name}: ${error.errors.map(e => e.message).join(', ')}`
);
} else {
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
);
}
}
});
// Start the server
async function main() {
logger.debug('Entering main function.');
logger.info('Starting Narrative Graph MCP Server...');
logger.debug('Creating StdioServerTransport.');
const transport = new StdioServerTransport();
logger.debug('Connecting server to transport.');
await server.connect(transport);
logger.debug('Server connected to transport.');
logger.debug('Resuming stdin.');
process.stdin.resume();
logger.info('Narrative Graph MCP Server running on stdio');
}
// Handle errors
process.on('unhandledRejection', (error) => {
logger.error('Unhandled rejection:', error);
process.exit(1);
});
// Run the server
main().catch((error) => {
logger.error('Fatal error:', error);
process.exit(1);
});