🎃

簡単なMCPサーバーを作成してみよう

に公開

はじめに

MCPサーバーを結構簡単に作成できることを学んだので、自分の環境でも簡単に実装してみる。

構築するぞ

こんな感じで必要なパッケージを追加していく。

$ pnpm init
$ pnpm add @modelcontextprotocol/sdk zod @anthropic-ai/sdk dotenv
$ pnpm add -D @types/node typescript

そのあとこのようなディレクトリ構成で作成していきます。

├── README.md
├── package.json
├── pnpm-lock.yaml
├── src
│   └── index.ts // ここに処理を書いていく
└── tsconfig.json

tsconfig.json はこんな感じ。

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build"]
}

定義した処理はこんな感じ。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import Anthropic from "@anthropic-ai/sdk";
import dotenv from "dotenv";

// 環境変数の読み込み
dotenv.config();

// 環境変数の検証
if (!process.env.ANTHROPIC_API_KEY) {
  console.error("エラー: ANTHROPIC_API_KEY が設定されていません。");
  process.exit(1);
}

// デフォルト設定
const DEFAULT_MODEL = "claude-3-sonnet-20240229"; // より経済的なモデル
const DEFAULT_MAX_TOKENS = 1000;

// Claude API クライアントの初期化
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

// Claude APIを使用してメッセージを送信し、テキスト応答を取得する関数
async function askClaude(
  prompt: string,
  model: string = DEFAULT_MODEL,
  maxTokens: number = DEFAULT_MAX_TOKENS
): Promise<string> {
  try {
    const message = await anthropic.messages.create({
      model: model,
      max_tokens: maxTokens,
      messages: [{ role: "user", content: prompt }],
    });

    // メッセージの内容を抽出
    if (message.content.length > 0) {
      if (message.content[0].type === "text") {
        return message.content[0].text;
      } else {
        console.warn("警告: 非テキスト形式のレスポンスを受信しました");
        return "非テキスト形式のレスポンスを受信しました";
      }
    }
    return "レスポンスにコンテンツがありませんでした";
  } catch (error) {
    console.error("Claude API エラー:", error);
    throw error; // エラーを上位に伝播させる
  }
}

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

// Claude APIを使用して応答を生成するツール
server.tool(
  "ask_claude",
  "入力された文言に対してClaudeの回答を返すツール",
  {
    prompt: z.string().describe("Claudeに質問する文言"),
    model: z
      .string()
      .optional()
      .describe("使用するClaudeモデル(デフォルト: claude-3-sonnet-20240229)"),
    max_tokens: z
      .number()
      .optional()
      .describe("生成する最大トークン数(デフォルト: 1000)"),
  },
  async ({ prompt, model, max_tokens }) => {
    try {
      const responseText = await askClaude(
        prompt,
        model || DEFAULT_MODEL,
        max_tokens || DEFAULT_MAX_TOKENS
      );
      return {
        content: [
          {
            type: "text",
            text: responseText,
          },
        ],
      };
    } catch (error) {
      console.error("Claude API エラー:", error);
      let errorMessage = "申し訳ありませんが、エラーが発生しました。";

      // より詳細なエラーメッセージ
      if (error instanceof Error) {
        errorMessage += ` エラー詳細: ${error.message}`;
      }

      return {
        content: [
          {
            type: "text",
            text: errorMessage,
          },
        ],
      };
    }
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(`MCP Server 起動完了 - ${new Date().toLocaleString()}`);
  console.error(`デフォルトモデル: ${DEFAULT_MODEL}`);
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

ビルドして、いざ実践!

とりあえずビルドしていこう。

$ pnpm run build

> mcp-server@1.0.0 build /Users/xxx/Documents/workspace/typescript/mcp-server
> tsc && chmod 755 build/index.js

良い感じ。
あとは、各種ツールのMCPの設定箇所に挿入すれば良いっぽい。今回はCursor!

mcp.json
{
  "mcpServers": {
    "mcp-example": {
      "command": "node",
      "args": [
        "/Users/xxx/Documents/workspace/typescript/mcp-server/build/index.js"
      ],
      "env": {
        "ANTHROPIC_API_KEY": {key}
      }
    }
  }
}

おー。登録された。あとは利用してみる。

起動されたけど、あれ?エラーになっちゃった。

モデルをもう一度見てみたらないっぽいので、変更してみよう。

できた。

まとめ

この例だとやってること一緒やーん感がありますが、こんな感じでサクッと作成できたりするから、ぜひ試してみてください。

Discussion