mcp-eth-test-subgraph-3
Version:
MCP server for ethereum_transactions_test subgraph - Track all Ethereum ERC20 transfers for testing
126 lines (121 loc) • 5.15 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { GraphDatabaseService } from './services/GraphDatabaseService.js';
import { SchemaService } from './services/SchemaService.js';
import { CONFIG } from './config.js';
// Validation schema for Cypher query tool
const CypherQuerySchema = z.object({
query: z.string().describe('Cypher query to execute'),
params: z.record(z.any()).optional().describe('Query parameters'),
});
class SubgraphMCPServer {
server;
graphDb;
schemaService;
constructor() {
this.server = new Server({
name: 'ethereum_transactions_test-mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.graphDb = new GraphDatabaseService(CONFIG.QUERY);
this.schemaService = new SchemaService(CONFIG.SUBGRAPH_ID);
this.setupToolHandlers();
}
setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const schema = await this.schemaService.getSchema();
return {
tools: [
{
name: 'cypher_query',
description: `Execute Cypher queries on the ${CONFIG.SUBGRAPH_NAME} subgraph.
SUBGRAPH ID: ${CONFIG.SUBGRAPH_ID}
${this.schemaService.formatSchemaForToolDescription(schema)}
IMPORTANT: All queries MUST include the subgraph_id filter: WHERE n.subgraph_id = $subgraph_id
The subgraph_id parameter is automatically added to your query parameters.
Generate only valid Cypher syntax, no explanations or markdown formatting.`,
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Cypher query to execute (subgraph_id filter will be automatically added)',
},
params: {
type: 'object',
description: 'Query parameters (subgraph_id will be automatically included)',
additionalProperties: true,
},
},
required: ['query'],
},
},
],
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'cypher_query') {
try {
const { query, params = {} } = CypherQuerySchema.parse(args);
// Validate that query includes subgraph_id filter for data isolation
if (!query.includes('subgraph_id')) {
throw new McpError(ErrorCode.InvalidParams, 'Query must include subgraph_id filter for data isolation. Example: WHERE n.subgraph_id = $subgraph_id');
}
// Automatically add subgraph_id to parameters
const queryParams = {
...params,
subgraph_id: CONFIG.SUBGRAPH_ID,
};
const result = await this.graphDb.executeCypherQuery(query, queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, `Query execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`ethereum_transactions_test MCP server running on stdio`);
}
async close() {
await this.graphDb.close();
}
}
// Start the server
const server = new SubgraphMCPServer();
process.on('SIGINT', async () => {
await server.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
await server.close();
process.exit(0);
});
server.start().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});