Skip to main content
Glama
server.ts6.89 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { FinixContext } from './types.js'; import { FinixClient } from './utils/finixClient.js'; import { allTools } from './tools/index.js'; class FinixMCPServer { private server: Server; private context: FinixContext; private client: FinixClient; private tools: Map<string, any>; constructor() { this.context = { username: process.env.FINIX_USERNAME, password: process.env.FINIX_PASSWORD, environment: process.env.FINIX_ENVIRONMENT || 'Sandbox', application: process.env.FINIX_APPLICATION_ID, }; this.client = new FinixClient(this.context); this.tools = new Map(); // Initialize tools for (const toolFactory of allTools) { const tool = toolFactory(this.context); this.tools.set(tool.method, tool); } this.server = new Server( { name: 'finix-mcp-server', version: '2.0.0', }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } private setupHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: this.getToolSchemas(), })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const tool = this.tools.get(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } try { // Validate parameters using Zod schema const validatedArgs = tool.parameters.parse(args); // Execute the tool const result = await tool.execute(this.client, this.context, validatedArgs); // Handle different result types if (typeof result === 'string') { return { content: [ { type: 'text', text: result, }, ], }; } else { // Return structured data as formatted JSON return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } } catch (error) { return { content: [ { type: 'text', text: `Error executing ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); } private getToolSchemas(): Tool[] { return Array.from(this.tools.values()).map(tool => ({ name: tool.method, description: tool.description, inputSchema: zodToJsonSchema(tool.parameters), })); } async start(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); const hasCredentials = this.client.hasCredentials(); const toolCount = this.tools.size; const apiToolCount = Array.from(this.tools.values()).filter(t => t.actions && Object.keys(t.actions).some(key => key !== 'documentation') ).length; console.error(`Finix MCP Server v2.0 running...`); console.error(`Environment: ${this.context.environment}`); console.error(`Total tools available: ${toolCount}`); console.error(`Documentation search: ✓ Available`); console.error(`API operations: ${hasCredentials ? `✓ Available (${apiToolCount} tools)` : '✗ Requires FINIX_USERNAME & FINIX_PASSWORD'}`); if (!hasCredentials) { console.error(`Set FINIX_USERNAME and FINIX_PASSWORD environment variables to enable API operations`); } } } // Simple Zod to JSON Schema converter for basic schemas function zodToJsonSchema(schema: any): any { if (!schema || !schema._def) { return { type: 'object' }; } if (schema._def.typeName === 'ZodObject') { const properties: any = {}; const required: string[] = []; const shape = schema._def.shape(); for (const [key, value] of Object.entries(shape)) { const fieldDef = (value as any)._def; properties[key] = zodFieldToJsonSchema(value as any); if (fieldDef.typeName !== 'ZodOptional' && fieldDef.typeName !== 'ZodDefault') { required.push(key); } } return { type: 'object', properties, required: required.length > 0 ? required : undefined, }; } return { type: 'object' }; } function zodFieldToJsonSchema(field: any): any { if (!field || !field._def) { return { type: 'string' }; } const def = field._def; switch (def.typeName) { case 'ZodString': const stringSchema: any = { type: 'string' }; if (def.description) stringSchema.description = def.description; return stringSchema; case 'ZodNumber': const numberSchema: any = { type: 'number' }; if (def.description) numberSchema.description = def.description; if (def.checks) { for (const check of def.checks) { if (check.kind === 'min') numberSchema.minimum = check.value; if (check.kind === 'max') numberSchema.maximum = check.value; if (check.kind === 'int') numberSchema.type = 'integer'; } } return numberSchema; case 'ZodBoolean': const booleanSchema: any = { type: 'boolean' }; if (def.description) booleanSchema.description = def.description; return booleanSchema; case 'ZodEnum': const enumSchema: any = { type: 'string', enum: def.values }; if (def.description) enumSchema.description = def.description; return enumSchema; case 'ZodArray': const arraySchema: any = { type: 'array', items: zodFieldToJsonSchema(def.type) }; if (def.description) arraySchema.description = def.description; return arraySchema; case 'ZodOptional': return zodFieldToJsonSchema(def.innerType); case 'ZodDefault': const defaultSchema = zodFieldToJsonSchema(def.innerType); defaultSchema.default = def.defaultValue(); return defaultSchema; case 'ZodObject': return zodToJsonSchema(field); default: const unknownSchema: any = { type: 'string' }; if (def.description) unknownSchema.description = def.description; return unknownSchema; } } async function main() { try { const server = new FinixMCPServer(); await server.start(); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } main().catch((error) => { console.error('Unhandled error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bquigley1/finix-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server