📦

自社プロダクトのMCPサーバーを実装したら、プロダクトがより便利になる未来が見えた話

に公開

はじめに

こんにちは!クラウド在庫管理ソフト「zaico」の開発・運営する株式会社ZAICOでWebエンジニアをしている@yutonishiです。
今は製造業のお客様向けの機能開発を行うチームのエンジニアとして開発をしています。

ZAICOではこの度Zennでテックブログを運営していくことになりました。
そこで最近MCPに興味があったので、自社プロダクトであるzaicoのMCPサーバーを実装してみて得られた知見などをまとめてみようと思います。

MCPとは

MCPとはClaudeを提供するAnthropic社によって提唱された、LLMに様々なリソースを渡す方法を標準化するオープンプロトコルのことです。
公式がMCPの役割をUSB-Cのようなものと例えたことでわかりやすいと話題になっていましたね。
各サービスのMCPサーバーが揃うことで、ありとあらゆるサービスの情報をLLM経由で取得・操作できるようになるということです。

個人的にはWeb APIと対応させて考えるとわかりやすいと思いました。
各サービスがAPIサーバーを実装するとユーザーがクライアント(JavaScript, curlコマンド, Postmanなど)からアクセスできるようになるのと同様に、各サービスがMCPサーバーを実装することでユーザーはMCPクライアント(Claude Desktop, Cursorなど)からアクセスできるようになるイメージです。

https://modelcontextprotocol.io/introduction より引用)

なぜ作ろうと思ったか?

zaicoのMCPサーバーを作ればzaicoにある在庫データや入出庫の情報をClaudeやCursorなどのAIツールから取得できるようになるからです。

例えば社内でサポート用に使用したり、お客様に導入することでよりzaicoをより便利に使っていただけると考えたからです。

zaicoをMCPサーバー経由で呼び出す

この章ではzaico-mcp-serverを開発し、実際に動かす手順を解説します。
※ 実装方法の解説はMacであることを前提に書いています。

MCPサーバーの実装

  • 必要なパッケージをインストール
touch src/index.ts tsconfig.json
# Create package.json file.
npm init --y

npm install @modelcontextprotocol/sdk zod axios
npm install dotenv --save
npm install -D @types/node typescript
  • package.jsonに下記の項目を追加
{
  "type": "module",
  "bin": {
    "zaico-mcp-server": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js"
  },
  "files": [
    "build"
  ],
}
  • tsconfig.jsonを編集
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  • メイン処理のindex.tsを編集
    • ひとまず在庫一覧を返すMCP Toolsのみを実装しています
    • 動作確認用にローカルのAPIにアクセスするようにしています
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import axios from "axios";

// Create an MCP server
const server = new McpServer({
  name: "zaico-mcp-server",
  version: "1.0.0"
});

interface InventoryItem {
  id: number | null;
  title: string;
  quantity: number | null;
  logical_quantity: number | null;
  unit: string;
  category: string | null;
  state: string | null;
  place: string | null;
  etc: string | null;
  group_tag: string | null;
  code: string | null;
}

server.tool(
  "get-inventory-list",
  {
    apiToken: z.string().describe(process.env.ZAICO_API_KEY || ''),
    baseUrl: z.string().default("http://localhost:3000").describe("http://localhost:3000")
  },
  async ({ apiToken, baseUrl }) => {
    try {
      // ZAICO APIから在庫一覧を取得
      const response = await axios.get<InventoryItem[]>(`${baseUrl}/api/v1/inventories`, {
        headers: {
          Authorization: `Bearer ${apiToken}`
        }
      });

      const inventories = response.data;

      // 在庫がない場合
      if (inventories.length === 0) {
        return {
          content: [{
            type: "text",
            text: "在庫データが見つかりませんでした。"
          }]
        };
      }

      // 在庫タイトルの一覧を整形
      const inventoryTitles = inventories.map((item, index) => {
        return `${index + 1}. ${item.title} (${item.quantity}${item.unit}) - ${item.state} / ${item.place}`;
      }).join("\n");

      // 在庫の総数
      const totalItems = inventories.length;

      return {
        content: [{
          type: "text",
          text: `在庫一覧 (全${totalItems}件):\n${inventoryTitles}`
        }]
      };
    } catch (error) {
      // エラーハンドリング
      if (axios.isAxiosError(error)) {
        const statusCode = error.response?.status;
        const errorMessage = error.response?.data?.message || error.message;

        return {
          content: [{
            type: "text",
            text: `エラーが発生しました (${statusCode}): ${errorMessage}`
          }]
        };
      }

      return {
        content: [{
          type: "text",
          text: `予期せぬエラーが発生しました: ${error instanceof Error ? error.message : String(error)}`
        }]
      };
    }
  }
);

// Running with stdio ( for Command )
const transport = new StdioServerTransport();
await server.connect(transport);

MCPクライアントの設定

今回はMCPクライアントにClaude Desktopを利用します。

Claude Desktopをインストールするとclaude_desktop_config.jsonが作成されるので、編集
"command": "node"の箇所はnodeをインストールしたパスを記載。

vi ~/Library/Application\ Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "zaico-mcp-server": {
      "command": "node",
      "env": {
        "ZAICO_API_KEY": "zaico API key"
      },
      "args": [
        "/Path to mcp server directory/zaico-mcp-server/build/index.js"
      ]
    }
  }
}

動作確認

  • 事前にMCPサーバーを起動しておく
npm run build
  • Claude Desktopを再起動
    • トンカチマークが出ていて実装したMCPサーバーの名前が表示されていればOK
    • 「在庫状況について教えて」というと、zaicoを使ってとは伝えていないのにClaudeがzaicoを見に行く判断をしてzaicoにある情報を教えてくれた

zaico-mcp-serverの活用

なぜ作ろうと思ったか?で少し触れたzaicoのMCPサーバーを作って具体的にどのようなことが便利になるの?という話題です。
今のところ考えられるのは大きく分けて2つです。

社内向け: 仕様の確認やお問い合わせ対応の手助けに

MCPサーバーにzaicoの機能を揃えることで、LLMがzaicoの仕様について答えられるようになるので、新メンバーのオンボの時とか活用できるかなと思いました。
また、「zaicoでこんなことできますか?」系のお問い合わせもあると思うので、その時にサクッと調べるのに便利かと思います。

社外向け: zaicoをさらに拡張、新しいインターフェースとして

MCPとは?の部分でMCPサーバーはWeb APIのようなものという説明をしました。
ユーザーから見たときにこの2つで大きく違うのはMCPは知識がなくても自然言語で呼び出すことができる点です。
Web APIの場合はHTTP通信の基本的な知識や、各サービスのAPIドキュメントの確認が必要ですが、MCPの場合は設定は必要ですがチャットから自然言語でサービスにアクセスすることができます。

これによりユーザーはzaicoの仕様や使い方を覚えなくても良くなります。例えば「今のとこ発注しないとな商品ある?」と聞いたら、LLMがzaicoの発注点切れ一覧をMCPサーバー経由で教えてくれたら便利ですよね。
さらに例えば将来的にClaudeが音声認識に対応したら、zaicoにも音声でアクセスできるようになります。LLMの機能拡張や進化に伴って、(特別な対応をしなくても)zaicoが便利になっていくのはとても楽しみですね。

将来的なお話

今回実装したzaico-mcp-serverはダウンロードしてMCPクライアントの設定をする必要があり、実際にお客様に使っていただくには少しハードルが高いです。
ですがちょうど今日発表されたリモートMCPサーバーを使うと、はじめの設定も認証するだけになり、より簡単になるんじゃないかと思います。

Until now, support for MCP was limited to Claude Desktop through local servers. Today, we're introducing Integrations, allowing Claude to work seamlessly with remote MCP servers across the web and desktop apps.
(従来はMCPはローカルサーバーに限られていました。リモートMCPサーバーはClaudeとWebやデスクトップアプリを連携できるようにします。)

https://www.anthropic.com/news/integrations より引用)

Claude DesktopにGoogle DriveやGitHubをOAuthでアプリ連携できるようになった時は、まさにリモートMCPサーバー的なものが内部で開発されているのかと妄想しましたが、こんなに早くリリースされるのは驚きですね!

リモートMCPサーバーをはじめMCPは今後どんどん便利になっていくと思うので、どのようにプロダクトに取り入れられるか模索していきたいと思います。

ZAICO Developers Blog

Discussion