Skip to main content
Glama
openai-server.ts13.6 kB
#!/usr/bin/env node import express, { Request, Response } from 'express'; import cors from 'cors'; import { logger } from './logger.js'; import { FileStorage } from './storage.js'; import { v4 as uuidv4 } from 'uuid'; const app = express(); const storage = new FileStorage(); // CORS設定 app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], })); app.use(express.json()); // 認証ミドルウェア(オプション) const authenticate = (req: Request, res: Response, next: Function) => { const apiKey = process.env.OPENAI_API_KEY; if (apiKey) { const authHeader = req.headers.authorization; if (!authHeader || authHeader !== `Bearer ${apiKey}`) { return res.status(401).json({ error: 'Unauthorized' }); } } next(); }; // ======================================== // Well-known エンドポイント (MCPを提供していないので404) // ======================================== // このサーバーは MCP (Model Context Protocol) サーバーではなく、 // シンプルなREST APIサーバーです。 // MCPクライアント(ChatGPT等)が探しに来るエンドポイントは // すべて404を返すことで「MCPは提供していない」ことを示します。 // RFC 9728: OAuth 2.0 Protected Resource Metadata // MCPを提供していないので404 app.get('/.well-known/oauth-protected-resource', (req, res) => { res.status(404).end(); }); app.get('/.well-known/oauth-protected-resource/sse', (req, res) => { res.status(404).end(); }); // RFC 8414: OAuth 2.0 Authorization Server Metadata // MCPを提供していないので404 app.get('/.well-known/oauth-authorization-server', (req, res) => { res.status(404).end(); }); app.get('/.well-known/oauth-authorization-server/sse', (req, res) => { res.status(404).end(); }); // OpenID Connect Discovery // MCPを提供していないので404 app.get('/.well-known/openid-configuration', (req, res) => { res.status(404).end(); }); app.get('/.well-known/openid-configuration/sse', (req, res) => { res.status(404).end(); }); app.get('/sse/.well-known/openid-configuration', (req, res) => { res.status(404).end(); }); // JWKS app.get('/.well-known/jwks.json', (req, res) => { res.status(404).end(); }); // SSEエンドポイント (MCPトランスポートではないので404) app.get('/sse', (req, res) => { res.status(404).end(); }); app.post('/sse', (req, res) => { res.status(404).end(); }); // Dynamic Client Registration app.post('/oauth/register', (req, res) => { res.status(404).end(); }); // 任意のwell-knownパス app.all('/.well-known/*', (req, res) => { res.status(404).end(); }); app.all('*/.well-known/*', (req, res) => { res.status(404).end(); }); // ======================================== // OpenAPI Schema エンドポイント // ======================================== app.get('/.well-known/openapi.json', (req, res) => { const schema = { openapi: '3.1.0', info: { title: 'Universal MCP Server API', description: 'A simple key-value storage server with file operations. No authentication required.', version: '1.0.0', }, servers: [ { url: process.env.PUBLIC_URL || `https://${req.headers.host}`, }, ], // 認証スキームは定義しない = 認証不要 paths: { '/storage/write': { post: { operationId: 'writeFile', summary: 'Write or update a file', description: 'Store content with a specified key', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['key', 'content'], properties: { key: { type: 'string', description: 'The key/filename to store the content under', }, content: { type: 'string', description: 'The content to store', }, }, }, }, }, }, responses: { '200': { description: 'File written successfully', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, key: { type: 'string' }, message: { type: 'string' }, }, }, }, }, }, }, }, }, '/storage/read/{key}': { get: { operationId: 'readFile', summary: 'Read a file', description: 'Retrieve content by key', parameters: [ { name: 'key', in: 'path', required: true, schema: { type: 'string', }, description: 'The key/filename to read', }, ], responses: { '200': { description: 'File content', content: { 'application/json': { schema: { type: 'object', properties: { key: { type: 'string' }, content: { type: 'string' }, }, }, }, }, }, '404': { description: 'File not found', }, }, }, }, '/storage/delete/{key}': { delete: { operationId: 'deleteFile', summary: 'Delete a file', description: 'Remove a file by key', parameters: [ { name: 'key', in: 'path', required: true, schema: { type: 'string', }, description: 'The key/filename to delete', }, ], responses: { '200': { description: 'File deleted successfully', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, key: { type: 'string' }, message: { type: 'string' }, }, }, }, }, }, }, }, }, '/storage/list': { get: { operationId: 'listFiles', summary: 'List all files', description: 'Get a list of all stored files', parameters: [ { name: 'pattern', in: 'query', required: false, schema: { type: 'string', }, description: 'Optional pattern to filter files', }, ], responses: { '200': { description: 'List of files', content: { 'application/json': { schema: { type: 'object', properties: { files: { type: 'array', items: { type: 'string' }, }, count: { type: 'number' }, }, }, }, }, }, }, }, }, '/storage/search': { get: { operationId: 'searchFiles', summary: 'Search file contents', description: 'Search for files containing specific text', parameters: [ { name: 'query', in: 'query', required: true, schema: { type: 'string', }, description: 'Text to search for in file contents', }, ], responses: { '200': { description: 'Search results', content: { 'application/json': { schema: { type: 'object', properties: { results: { type: 'array', items: { type: 'object', properties: { key: { type: 'string' }, content: { type: 'string' }, }, }, }, count: { type: 'number' }, }, }, }, }, }, }, }, }, }, }; res.json(schema); }); // ======================================== // Storage API エンドポイント // ======================================== // Write/Update file app.post('/storage/write', authenticate, async (req, res) => { const requestId = uuidv4(); try { const { key, content } = req.body; if (!key || content === undefined) { return res.status(400).json({ error: 'Missing key or content' }); } logger.info('Write request', { operation: 'write', key, requestId }); await storage.write(key, content, requestId); res.json({ success: true, key, message: 'File written successfully', }); } catch (error: any) { logger.error('Write failed', error, { requestId }); res.status(500).json({ error: error.message }); } }); // Read file app.get('/storage/read/:key', authenticate, async (req, res) => { const requestId = uuidv4(); try { const { key } = req.params; logger.info('Read request', { operation: 'read', key, requestId }); const content = await storage.read(key, requestId); if (content === null) { return res.status(404).json({ error: 'File not found' }); } res.json({ key, content }); } catch (error: any) { logger.error('Read failed', error, { requestId }); res.status(500).json({ error: error.message }); } }); // Delete file app.delete('/storage/delete/:key', authenticate, async (req, res) => { const requestId = uuidv4(); try { const { key } = req.params; logger.info('Delete request', { operation: 'delete', key, requestId }); await storage.delete(key, requestId); res.json({ success: true, key, message: 'File deleted successfully', }); } catch (error: any) { logger.error('Delete failed', error, { requestId }); res.status(500).json({ error: error.message }); } }); // List files app.get('/storage/list', authenticate, async (req, res) => { const requestId = uuidv4(); try { const pattern = req.query.pattern as string | undefined; logger.info('List request', { operation: 'list', pattern, requestId }); const files = await storage.list(pattern, requestId); res.json({ files, count: files.length }); } catch (error: any) { logger.error('List failed', error, { requestId }); res.status(500).json({ error: error.message }); } }); // Search files app.get('/storage/search', authenticate, async (req, res) => { const requestId = uuidv4(); try { const query = req.query.query as string; if (!query) { return res.status(400).json({ error: 'Missing query parameter' }); } logger.info('Search request', { operation: 'search', query, requestId }); const results = await storage.search(query, requestId); res.json({ results, count: results.length }); } catch (error: any) { logger.error('Search failed', error, { requestId }); res.status(500).json({ error: error.message }); } }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Root endpoint app.get('/', (req, res) => { res.json({ name: 'Universal MCP Server - OpenAI Compatible API', version: '1.0.0', endpoints: { schema: '/.well-known/openapi.json', health: '/health', storage: { write: 'POST /storage/write', read: 'GET /storage/read/:key', delete: 'DELETE /storage/delete/:key', list: 'GET /storage/list', search: 'GET /storage/search?query=...', }, }, authentication: process.env.OPENAI_API_KEY ? 'API Key (Bearer Token)' : 'None', }); }); // Start server const PORT = parseInt(process.env.PORT || '3000', 10); const HOST = process.env.HOST || '0.0.0.0'; app.listen(PORT, HOST, () => { logger.info('OpenAI-compatible server started', { port: PORT, host: HOST, schemaUrl: `http://${HOST}:${PORT}/.well-known/openapi.json`, }); console.log(`\n🚀 OpenAI-compatible API Server running on http://${HOST}:${PORT}`); console.log(`📋 OpenAPI Schema: http://${HOST}:${PORT}/.well-known/openapi.json`); console.log(`\nAvailable endpoints:`); console.log(` POST /storage/write`); console.log(` GET /storage/read/:key`); console.log(` DELETE /storage/delete/:key`); console.log(` GET /storage/list`); console.log(` GET /storage/search?query=...`); console.log(` GET /health`); console.log(`\nSet OPENAI_API_KEY environment variable to enable authentication.\n`); });

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