🔌

自社サービスAPIをMCPサーバー公開して分かったこと - acomo MCP Server

に公開

1. はじめに

acomo の開発チームとして、公開している OpenAPI 仕様を活用し、API を扱う MCP(Model Context Protocol)サーバーとして公開しました。

リポジトリはこちら(使い方・設定など): acomo-mcp-server(GitHub)

この記事では、今回の MCP サーバーを実装・公開する中で得た知見を紹介します。

2. acomoとは

acomo は、申請・承認などの社内業務フロー(稟議・経費申請・人事手続きなど)を API で扱えるヘッドレス型のワークフロー SaaS です。
バックエンドのワークフロー基盤は acomo が担い API を公開するため、フロントエンドは開発者が好きなフレームワークで UI/UX を自由に設計できる思想です。
API 仕様は OpenAPI 仕様として公開し、 信頼できる唯一の情報源 (SSOT: single source of truth) にしています。

公式サイトはこちら(サービス概要・機能紹介・ドキュメントなど): acomo 公式サイト

3. AIコーディングアシスタントとMCP

最近、AI をアシスタントにコードを書く開発が一般化してきました。Cursor や Claude Code のような「AI コーディングアシスタント」を使うスタイルです。
その中でも、ざっくりした意図や方向性を自然言語で伝え、アシスタントがコード生成→実行→修正を対話的に回すスタイルは Vibe Coding と呼ばれます。
acomo の開発チームでも、日頃から AI コーディングアシスタントを積極的に活用しています。

3.1 MCPの役割

このスタイルを下支えする基盤が MCP(Model Context Protocol)です。
生成 AI が外部データや API/ツールに安全にアクセスするための共通インターフェースです。
クライアント(例: AI コーディングアシスタント)とサーバーが連携し、サーバー側は次の 3 要素を公開します。

  • ツール(Tools): 外部システムとの相互作用を可能にする機能です。API 呼び出し、ファイルシステム操作、データベースアクセスなど、AI モデルが直接実行できない外部操作を代理実行する役割を果たします。
  • リソース(Resources): URI ベースでコンテンツを提供し、ドキュメント、仕様書、ガイドラインなどの静的な参照情報をモデルのコンテキストに組み込む仕組みとして機能します。
  • プロンプト(Prompts): テンプレート化された会話の開始点として、複数のリソースやツールを組み合わせ、引数を受け取って一貫した手順を実行するための仕組みです。

3.2 acomo MCP Serverの位置づけと狙い

図の上段は、AI コーディングアシスタント登場以前の従来の開発プロセスを示しています。
acomo は API ベースで OpenAPI 仕様を公開しているものの、開発者は仕様を読み解き、正しく理解したうえで自分のアプリに組み込む必要がありました。

図の中段は、AI コーディングアシスタント登場後の状況を示しています。
AI コーディングアシスタントが登場したことで、開発者は自然言語で指示すればコード生成できるようになりました。
ただし、開発者は OpenAPI 仕様を生成 AI に正しく伝える必要があり、仕様書の内容を自然言語で説明したり、関連するエンドポイントやパラメータを手動で提示したりする手間が発生します。
つまり、仕様を読み解く作業は依然として開発者側の責務となっています。

この課題を解決する手段として、OpenAPI 仕様を参照する acomo MCP Server を用意しました。
図の下段のように、MCP サーバー連携ができれば、AI が直接 OpenAPI 仕様を参照できるため、開発者は「acomo のモデル一覧を取得して」といった自然な指示だけで済みます。
開発者は AI コーディングアシスタントに自然言語で問い合せて仕様理解を進められ、そのまま API を呼び出すコードを生成・実行して検証まで一気通貫で回せます。
これにより、仕様理解 → サンプル作成 → 呼び出し検証の往復が短くなり、アプリへの組み込みまでのリードタイムを圧縮できます。

acomo × MCP アーキテクチャ

4. MCPサーバーを開発する

ここからは、実際に MCP サーバーをどう作るかを解説します。
前章で説明した「ツール」「リソース」「プロンプト」の 3 要素を、acomo MCP Server でも全て提供する形で実装しました。
本実装では OpenAPI 仕様を読み込み、次の機能を提供します。加えて、acomo の前提知識をまとめたガイドをリソースとして用意し、プロンプトから常時埋め込む構成にしています。

  • ツール
    • API ドキュメント応答
      • list_apis: API 一覧を取得する
      • describe_api: API 詳細を説明する
      • api_schemas: パラメータやボディやレスポンスのスキーマを参照する
      • generate_request_template: リクエスト雛形を生成する
      • list_components: components.schemas の一覧を取得する
      • describe_component: コンポーネント定義を JSON Schema で参照する
    • API コール
      • call_api: API を実行する
  • リソース
    • acomo://guide: ガイドのリソースを提供する
  • プロンプト
    • guide: ガイドのプロンプトを提供する

ライブラリとして、MCP プロジェクトの TypeScript SDK @modelcontextprotocol/sdk を採用します。
これを使うと、本来必要な JSON-RPC の送受信やライフサイクル管理といったプロトコル層の実装を意識せず、ビジネスロジックへの集中が可能です。
「どの機能をツール化し、どの知識をリソース化し、どの起点をプロンプト化するか」という点へ注力できます。

また、Zod による型検証などの自動化も可能です。

まずはライブラリをインストールします。

npm install @modelcontextprotocol/sdk zod

最小限のサーバー起動スクリプトは以下です。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "acomo-mcp", version: "0.1.0" });

// ここにMCPサーバーの機能を実装する。
// 例: server.registerTool(...);

const transport = new StdioServerTransport();
await server.connect(transport);

4.1 ツールを作る

ここからは、実際にツールを開発してみます。
最初は、OpenAPI 仕様から API 一覧を取得するツール list_apis を作ります。
@modelcontextprotocol/sdkserver.registerTool というツール登録関数が用意されているので、
API 一覧を content として返す部分を実装します。

具体的なコード例は次のとおりです。

server.registerTool(
  "list_apis",
  {
    title: "List APIs",
    description: "acomoのAPI一覧を返す",
    inputSchema: {},
  },
  async () => ({
    content: [
      // listOperations(): OpenAPI仕様ファイルを読み込み、
      // operationId / method / path / summary などを含むAPIオブジェクト配列を返す
      { type: "text", text: JSON.stringify(await listOperations()) },
    ],
  })
);

このように、SDK を使った MCP サーバー開発全般は「返すアウトプット」の実装に集中できます。
list_apis は OpenAPI 仕様書を読み、それを加工して返すだけで実装できました。
API を実際に呼び出すツールである call_api も同様に、「API を実行し、その結果を返す」部分さえ書けばよく、シンプルに実装できます。

コード例は次のとおりです。

server.registerTool(
  "call_api",
  {
    title: "Call API",
    description: "operationIdを指定してAPIを呼び出す",
    inputSchema: {
      operationId: z.string(), // 実行対象のAPIを一意に識別する OpenAPI の operationId
      pathParams: z.record(z.any()).optional(), // パス変数のマップ 例: { id: "123" } → /users/{id}
      query: z.record(z.any()).optional(), // クエリパラメータのマップ(オブジェクト値は JSON 文字列化を想定)
      body: z.any().optional(), // リクエストボディ(JSON)
    },
  },
  async ({ operationId, pathParams, query, body }) => {
    // acomo API 呼び出しに必要な認証情報を環境変数から取得
    const { accessToken, tenantId, baseUrl } = getEnv();

    // 入力から OpenAPI 仕様に照らし合わせて HTTP リクエストを作成
    const { method, path, search } = buildHttpRequestFromOpenApi({
        operationId,
        pathParams,
        query
    });

    // acomo API を呼び出す
    const url = new URL(path + search, baseUrl);
    const res = await fetch(url, {
      method,
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-Tenant-ID': tenantId,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data) }] };
  }
);

あわせて、OpenAPI のリクエスト/レスポンス型は schemacomponents に定義されています。
その詳細を扱うツール(api_schemasgenerate_request_templatelist_componentsdescribe_component)も同じ要領で実装できます。
これで、API 仕様の確認から実行までをツール化でき、MCP サーバー経由で acomo API を呼び出せるようになりました。

4.2 ツールの動作確認

ツールの動作を確かめるため、ローカルで MCP サーバーとして起動します。
今回は TypeScript ベースなので、npm run build で生成された dist/server.js を直接指定して起動します。

MCP クライアント設定例は次のとおりです。

まず、ビルドします。

npm run build

MCP クライアント設定はツールごとに手順が異なります。
公式ドキュメント等に従って MCP サーバーの登録設定をします。
ACOMO_TENANT_IDACOMO_ACCESS_TOKEN は acomo にログインして取得します。

{
  "mcpServers": {
    "acomo": {
      "command": "node",
      "args": ["/absolute/path/to/repo/dist/server.js"],
      "env": {
        "ACOMO_OPENAPI_PATH": "/absolute/path/to/repo/openapi.json",
        "ACOMO_TENANT_ID": "<tenant-id>",
        "ACOMO_ACCESS_TOKEN": "<access-token>"
      }
    }
  }
}

今回は Cursor から接続して動作確認してみます。
Cursor で acomo MCP Server と設定した状態で、「acomo のモデル一覧を取得して」と依頼します。

すると、AI が list_apis で対象の API を特定して型を確認したうえで、call_api に切り替えてモデル一覧取得 API を実行し、その結果を回答してくれました。
期待どおりの応答が得られることを確認できました。

Cursor からの実行例

これで MCP サーバー開発と動作確認のサイクルが回ることを確認できました。
続いて、リソースとプロンプトに進みます。

4.3 リソースを作る

ツールがあれば API の一覧や実行が可能ですが、API 仕様を理解するだけでは不十分です。
acomo の製品コンセプトやデータモデルの前提、ドメイン知識がないと、どの場面でどの API を使うかといった適切な選択ができません。
例えば、OpenAPI 仕様から「モデル一覧」「プロセス一覧」の API があることまで理解できます。
acomo ではモデルはワークフローの定義、プロセスはそのモデルにもとづき実行される業務を指します。
こうしたドメイン知識がなければ、API を正しく扱うのは難しいでしょう。

そこで、こうした前提(コンセプト/設計思想/ドメイン知識/API の読み方)を要点に絞ったガイドを「リソース」として提供します。
MCP におけるリソースは、URI で参照できる読み物(コンテンツ)を公開する仕組みです。
後続のプロンプトから必要に応じて取り込み、会話の文脈に載せられます。
URI は acomo://guide です。

server.registerResource(
  "acomo-guide",
  new ResourceTemplate("acomo://guide", { list: undefined }),
    {
      title: "acomo MCP guide",
      description: "acomo開発の前提・認証・MCPの使い方の要点",
      mimeType: "text/markdown",
    },
  async (uri: URL) => ({
    const text = 
      await readFile(new URL("/path/to/guide-acomo.md", import.meta.url), "utf-8");
    contents: [{
      uri: uri.href,
      mimeType: "text/markdown",
      text
    }],
  })
);

MCP クライアントが「リソース」として、acomo://guide で参照できるようになります。

4.4 プロンプトを作る

リソースはユーザーが必要に応じて参照できますが、MCP サーバー側で「最初からリソースを埋め込んだ会話の雛形」を提供できるとさらに便利です。
これが MCP の「プロンプト」です。
acomo MCP Server では、先ほどのガイドリソースを読み込ませた状態で会話を開始するプロンプトを用意します。
ガイドを最初からコンテキストに含めることで、acomo の前提が共有された状態から対話が始まります。
意図に沿ったツール選択やパラメータ設計がしやすくなります。
結果として、「設計や使い方の相談 → 必要に応じてツールを実行」という流れを、ひとつの呼び出しで始められます。

server.registerPrompt(
  "guide",
  {
    title: "guide",
    description: "Assists with design and implementation guided by the acomo API",
    argsSchema: { request: z.string().optional() },
  },
  async ({ request } = {}) => {
    // リソースと同じ方法でガイド本文を読み込む
    const text =
     await readFile(new URL("/path/to/guide-acomo.md", import.meta.url), "utf-8");
    const userText = request ?? "(no request)";
    return {
      description: "Implementation support based on acomo API and concepts",
      messages: [
        // ガイドリソース
        {
          role: "user",
          content: { type: "resource", resource: { uri: "acomo://guide", text } }
        },
         // ユーザーからの入力
        { 
          role: "user",
          content: { type: "text", text: userText }
        },
      ],
    };
  }
);

このプロンプトでは、acomo://guide をユーザーリクエストより先に置き、モデルがリクエストを読む前に前提を読むようにしています。
API 選択やパラメータ設計の精度向上を期待できます。

4.5 プロンプトの動作確認

Claude Code では MCP のプロンプトをスラッシュコマンドとして呼び出せます。
guide プロンプトが認識され、選択して呼び出すと、ガイドリソースを含んだ状態で会話が開始されます。

Claude Code のプロンプト選択例

このあと、Claude Code に「モデル ID が XX のワークフローを処理するフロントエンドを Next.js で実装して」のように依頼してみました。
結果として、一覧取得からワークフロー開始までのフロントエンドを、Vibe Coding でもある程度の精度で実装できました。
詳細はモデルの精度や AI コーディングアシスタントツールのノウハウに依存するため本稿では割愛しますが、acomo の知識や API を踏まえた実装が十分現実的になった手応えがあります。
もちろん、一発でそのまま本番運用に耐える完成物ができるわけではありませんが、モックレベルの検証や、この成果物を土台に開発者が具体的な実装を進めるには十分に有用でした。

5. 公開(Docker ビルドと配布)

4.2 の動作確認はローカルビルド前提でした。
配布と再現性を高めるには、Docker イメージ化してレジストリへ公開するのが実用的です。
本プロジェクトでは、stdio トランスポートで起動する MCP サーバーを Docker 化し、GitHub Container Registry(GHCR)で配布する前提にしています。
リモート公開方式もありますが、本稿では扱いません。

Dockerfile と GitHub Actions(ビルド/配布フロー)の定義はリポジトリに用意しています。
利用手順、mcpServers の設定例、必要な環境変数の詳細は README を参照してください。

6. まとめ

OpenAPI を土台として @modelcontextprotocol/sdk を使った MCP サーバー化をおこないました。
これにより、API の選択から実行までを AI コーディングアシスタントが呼び出せるようになりました。
最小構成として「ツール(API 仕様問合せ・API 実行)」「リソース(ガイド)」「プロンプト(ガイド込みの会話雛形)」を MCP サーバー機能として実装しました。

OpenAPI 仕様はバックエンド・フレームワークから自動生成できる仕組みも一般的です。
既存の OpenAPI をそのまま MCP サーバー化すれば、API だけのバックエンドでもチャットから直接操作できる導線が整い、チャット AI の自社サービス組み込みのハードルがぐっと下がります。

今後も AI コーディングアシスタントを前提とした開発が今後さらに主流になることが予想されます。
そのため acomo サービス提供者としても、 MCP サーバーとして公開しておくことには、開発者の学習コストの圧縮と実装着手までのリードタイム短縮という意味で大きな価値があると考えています。

参考リンク

Discussion