Skip to main content
Glama
server.ts10.3 kB
// MCP TypeScript SDK ベース実装 import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { readFile } from "fs/promises"; import { acomoFetch } from "./shared/http.js"; import { getConfig } from "./shared/config.js"; import { buildRequestTemplate, findOperationById, getComponentSchema, getOperationSchemas, listComponents, listOperations, } from "./openapi.js"; async function main() { const server = new McpServer({ name: "acomo-mcp", version: "0.1.0" }); // ----- Tools ----- server.registerTool( "health", { title: "Health", description: "acomo MCP server health check (fixed response)", inputSchema: {}, }, async () => ({ content: [{ type: "text", text: "ok" }] }) ); server.registerTool( "list_apis", { title: "List APIs", description: "acomoのAPI一覧を返す", inputSchema: {}, }, async () => ({ content: [ { type: "text", text: JSON.stringify(await listOperations()) }, ], }) ); server.registerTool( "describe_api", { title: "Describe API", description: "operationIdの詳細(paths/method/要約/原文)を返す", inputSchema: { operationId: z.string() }, }, async ({ operationId }: { operationId: string }) => { const op = await findOperationById(operationId); if (!op) return { content: [ { type: "text", text: `Unknown operationId: ${operationId}` }, ], isError: true, }; const cfg = getConfig(); const base = cfg.baseUrl.replace(/\/$/, ""); const completeUrl = `${base}${op.path.startsWith("/") ? "" : "/"}${op.path}`; return { content: [ { type: "text", text: JSON.stringify( { operationId: op.operationId, method: op.method, path: op.path, summary: op.summary, baseUrlExample: base, completeUrl, raw: op.raw, }, null, 2 ), }, ], }; } ); server.registerTool( "api_schemas", { title: "API schemas", description: "operationIdからparameters/requestBody/responsesを抜粋", inputSchema: { operationId: z.string() }, }, async ({ operationId }: { operationId: string }) => { const schemas = await getOperationSchemas(operationId); if (!schemas) return { content: [ { type: "text", text: `Unknown operationId: ${operationId}` }, ], isError: true, }; return { content: [ { type: "text", text: JSON.stringify(schemas, null, 2) }, ], }; } ); server.registerTool( "generate_request_template", { title: "Generate API request template", description: "operationIdからpath/query/body雛形を生成", inputSchema: { operationId: z.string() }, }, async ({ operationId }: { operationId: string }) => { const schemas = await getOperationSchemas(operationId); if (!schemas) return { content: [ { type: "text", text: `Unknown operationId: ${operationId}` }, ], isError: true, }; const tmpl = buildRequestTemplate(schemas); return { content: [ { type: "text", text: JSON.stringify(tmpl, null, 2) }, ], }; } ); server.registerTool( "call_api", { title: "Call API", description: "operationIdを指定してAPIを呼び出す", inputSchema: { operationId: z.string(), pathParams: z.record(z.any()).optional(), query: z.record(z.any()).optional(), body: z.any().optional(), }, }, async ({ operationId, pathParams, query, body, }: { operationId: string; pathParams?: Record<string, unknown>; query?: Record<string, unknown>; body?: unknown; }) => { const cfg = getConfig(); const missingVars: string[] = []; if (!cfg.tenantId) missingVars.push("ACOMO_TENANT_ID"); if (!cfg.token) missingVars.push("ACOMO_ACCESS_TOKEN"); if (missingVars.length) { return { content: [ { type: "text", text: `環境変数が未設定のため実行できません: ${missingVars.join(", ")}`, }, ], isError: true, }; } const op = await findOperationById(operationId); if (!op) return { content: [ { type: "text", text: `Unknown operationId: ${operationId}` }, ], isError: true, }; let path = op.path; for (const [k, v] of Object.entries(pathParams || {})) { path = path.replace( new RegExp(`{${k}}`, "g"), encodeURIComponent(String(v)) ); } const normalizedPath = path; const qs = new URLSearchParams(); if (query) { for (const [k, v] of Object.entries(query as Record<string, any>)) { qs.append(k, typeof v === "string" ? v : JSON.stringify(v)); } } const search = qs.toString() ? `?${qs.toString()}` : ""; const res = await acomoFetch( `${normalizedPath}${search}`, { method: op.method, body: body ? JSON.stringify(body) : undefined } ); return { content: [{ type: "text", text: JSON.stringify(res) }] }; } ); // ----- Prompts ----- server.registerPrompt( "guide", { title: "guide", description: "Assists with design and implementation guided by the acomo API and principles.", argsSchema: { request: z.string().describe("User request message").optional(), }, }, async (args: { request?: string } = {}, _extra) => { const request = args.request?.trim(); let guideText = ""; try { guideText = await readFile(new URL("../resources/guide-acomo.md", import.meta.url), "utf-8"); } catch { guideText = "acomo MCP ガイドを取得できませんでした。resources/guide-acomo.md を確認してください。"; } const userText = request || "(no request provided)"; return { description: "Implementation support based on acomo API and concepts", messages: [ { role: "user", content: { type: "resource", resource: { uri: "acomo://guide", text: guideText } }, }, { role: "user", content: { type: "text", text: userText }, }, ], }; } ); server.registerTool( "list_components", { title: "List components", description: "acomoのAPIスキーマ(components.schemas)の一覧を返す", inputSchema: {}, }, async () => ({ content: [ { type: "text", text: JSON.stringify(await listComponents()) }, ], }) ); server.registerTool( "describe_component", { title: "Describe component", description: "指定schema名の詳細(JSON Schema)を返す", inputSchema: { name: z.string() }, }, async ({ name }: { name: string }) => { const schema = await getComponentSchema(name); if (!schema) return { content: [{ type: "text", text: `Unknown component: ${name}` }], isError: true, }; return { content: [ { type: "text", text: JSON.stringify(schema, null, 2) }, ], }; } ); // ----- Resources ----- server.registerResource( "acomo-guide", new ResourceTemplate("acomo://guide", { list: undefined }), { title: "acomo MCP guide", description: "acomo開発の前提・認証・MCPの使い方の要点", mimeType: "text/markdown", }, async (uri: URL) => { try { const text = await readFile(new URL("../resources/guide-acomo.md", import.meta.url), "utf-8"); return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }], }; } catch { const fallback = `# acomo MCP ガイド\n\nこのドキュメントは、acomo MCP を使って acomo API を探索・呼び出す際に最低限必要な前提と手順をまとめたものです。\n\n## 認証とテナント\n- Authorization: Bearer <ACCESS_TOKEN>\n- x-tenant-id: <TENANT_ID>\n- 環境変数: \n - ACOMO_TENANT_ID\n - ACOMO_ACCESS_TOKEN\n - (任意) ACOMO_API_BASE = https://acomo.app\n\n## MCP ツールの流れ\n1. list_apis: 利用可能な operationId 一覧を取得\n2. describe_api: operationId ごとの詳細(method/path/summary/raw)を確認\n3. api_schemas: parameters / requestBody / responses のスキーマを確認\n4. generate_request_template: path/query/body の雛形を作成\n5. call_api: operationId とテンプレートを使って実行(要: 環境変数)\n\n## call_api の入力\n- pathParams: OpenAPI の {id} のようなパス変数を置換\n- query: URLSearchParams でエンコード(オブジェクトは JSON 文字列化)\n- body: JSON で送信\n\n## 実装上の約束事\n- OpenAPI の先頭パス (/api/v{n}) はそのまま使用\n- 失敗時はエラーメッセージとこのガイドを返す\n- ページネーションやフィルタは API ごとのスキーマに準拠\n\n## よくあるエラー\n- 環境変数未設定: ACOMO_TENANT_ID, ACOMO_ACCESS_TOKEN を設定\n- 不明な operationId: list_apis で再確認\n\n## 参考\n- OpenAPI 自体はツール (list_apis/describe_api/api_schemas) から参照可能です。\n`; return { contents: [ { uri: uri.href, mimeType: "text/markdown", text: fallback }, ], }; } } ); // ----- Start (stdio) ----- const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((err) => { console.error("[acomo-mcp] server error:", err); process.exit(1); });

Implementation Reference

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/progress-all/acomo-mcp-server'

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