Skip to main content
Glama
index-multimode.ts9.52 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { logger } from './logger.js'; import { FileStorage } from './storage.js'; import { HttpMCPServer } from './http-server.js'; import { v4 as uuidv4 } from 'uuid'; // 環境変数から設定を読み込み const MODE = process.env.MCP_MODE || 'stdio'; // 'stdio' or 'http' const HTTP_PORT = parseInt(process.env.MCP_HTTP_PORT || '3000'); const HTTP_HOST = process.env.MCP_HTTP_HOST || '0.0.0.0'; const API_KEY = process.env.MCP_API_KEY; // オプション:認証用APIキー // ストレージインスタンス const storage = new FileStorage(); // MCPサーバー作成 function createMCPServer(): Server { const server = new Server( { name: 'universal-mcp-server', version: '1.0.0', }, { capabilities: { resources: {}, tools: {}, }, } ); logger.info('MCP Server initializing', { mode: MODE, sessionId: logger.getSessionId(), capabilities: ['resources', 'tools'] }); // ======================================== // Resources(読み込み) // ======================================== // リソース一覧 server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'file:///{key}', name: 'File Resource', description: 'Read a file by key', mimeType: 'text/plain', }, { uri: 'file:///list', name: 'File List', description: 'List all available files', mimeType: 'application/json', }, ], }; }); // リソース読み込み server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const requestId = uuidv4(); const uri = request.params.uri; try { logger.info('Resource request received', { operation: 'resource:read', uri, requestId }); if (uri === 'file:///list') { // ファイル一覧 const files = await storage.list(undefined, requestId); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ files }, null, 2) }] }; } else if (uri.startsWith('file:///')) { // 個別ファイル読み込み const key = uri.slice(8); // Remove 'file:///' const content = await storage.read(key, requestId); if (content === null) { throw new Error(`File not found: ${key}`); } return { contents: [{ uri, mimeType: 'text/plain', text: content }] }; } else { throw new Error(`Unknown resource URI: ${uri}`); } } catch (error) { logger.error('Resource request failed', error as Error, { operation: 'resource:read', uri, requestId }); throw error; } }); // ======================================== // Tools(書き込み・実行・検索) // ======================================== // ツール一覧 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'write_file', description: 'Write content to a file', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'The file key/name', }, content: { type: 'string', description: 'The content to write', }, }, required: ['key', 'content'], }, }, { name: 'delete_file', description: 'Delete a file', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'The file key/name to delete', }, }, required: ['key'], }, }, { name: 'search_files', description: 'Search for files containing specific text', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The text to search for', }, }, required: ['query'], }, }, ], }; }); // ツール実行 server.setRequestHandler(CallToolRequestSchema, async (request) => { const requestId = uuidv4(); const { name, arguments: args } = request.params; try { switch (name) { case 'write_file': { const { key, content } = args as { key: string; content: string }; logger.info('Tool request received', { operation: 'tool:write', toolName: 'write_file', key, requestId }); await storage.write(key, content, requestId); return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `File '${key}' written successfully`, key }, null, 2) }] }; } case 'delete_file': { const { key } = args as { key: string }; logger.info('Tool request received', { operation: 'tool:delete', toolName: 'delete_file', key, requestId }); const deleted = await storage.delete(key, requestId); return { content: [{ type: 'text', text: JSON.stringify({ success: deleted, message: deleted ? `File '${key}' deleted successfully` : `File '${key}' not found`, key }, null, 2) }] }; } case 'search_files': { const { query } = args as { query: string }; logger.info('Tool request received', { operation: 'tool:search', toolName: 'search_files', query, requestId }); const results = await storage.search(query, requestId); return { content: [{ type: 'text', text: JSON.stringify({ success: true, query, resultsCount: results.length, results: results.map(r => ({ key: r.key, preview: r.content.slice(0, 100) + '...' })) }, null, 2) }] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error('Tool request failed', error as Error, { operation: `tool:${name}`, toolName: name, requestId }); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: (error as Error).message }, null, 2) }], isError: true }; } }); return server; } // ======================================== // サーバー起動 // ======================================== async function main() { try { const server = createMCPServer(); if (MODE === 'http') { // HTTPモード(OpenAI、ngrok対応) const httpServer = new HttpMCPServer(server, { port: HTTP_PORT, host: HTTP_HOST, apiKey: API_KEY, enableCors: true }); await httpServer.start(); logger.info('MCP Server ready in HTTP mode', { mode: 'http', port: HTTP_PORT, host: HTTP_HOST, authentication: !!API_KEY, sessionId: logger.getSessionId(), status: 'ready' }); if (API_KEY) { logger.info('Authentication enabled', { hint: 'Use Authorization: Bearer <API_KEY> header' }); } else { logger.warn('Authentication disabled', { warning: 'Server is running without authentication. Set MCP_API_KEY environment variable for security.' }); } } else { // STDIOモード(Claude Desktop) const transport = new StdioServerTransport(); logger.info('Connecting to transport', { mode: 'stdio', transport: 'stdio', sessionId: logger.getSessionId() }); await server.connect(transport); logger.info('MCP Server connected and ready', { mode: 'stdio', sessionId: logger.getSessionId(), status: 'ready' }); } // Graceful shutdown process.on('SIGINT', async () => { logger.info('Shutdown signal received', { signal: 'SIGINT' }); await server.close(); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('Shutdown signal received', { signal: 'SIGTERM' }); await server.close(); process.exit(0); }); } catch (error) { logger.error('Failed to start MCP server', error as Error); process.exit(1); } } main();

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/Amana03/universal-mcp-server'

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