🐱

MCP入門

に公開
2025/04/16
4ä»¶

本蚘事は、最近話題のMCPの入門蚘事です。
MCPModel Context Protocolに぀いお、以䞋の4ステップで玹介したす。

  1. ざっくり理解する
  2. 䜿っおみる
  3. 深く理解する
  4. 䜜っおみる

初心者でも順番に読み進めれば、MCPに぀いおざっず理解、かんたんな実装ができるようになるこずを目指したす💪


ざっくり理解する

MCPずは、ざっくり蚀うず、LLMアプリず倖郚サヌビスを連携するための統䞀されたむンタヌフェヌス(プロトコル)です。
1
LLMアプリずは、ChatGPTやClaude、Cursorなど、LLMを䜿甚するためアプリケヌションを指したす。(⚠ GPT-4oやclaude-3-5-sonnetなどのLLM自䜓ずは区別しおください。)

初期のLLMアプリは、どこたでいっおもすごく賢いチャットツヌルでしかなく、結局はテキストを返答するこずしかできたせんでした。

そのため、LLMアプリの返答に察しお䜕をするかは人間が刀断する必芁がありたした。

3

この問題を最初に解決したのが、OpenAIのFunction Callingです。

Function Callingを䜿甚するこずで、LLMにどのようなアクションを行うか刀断させられるず共に、指定したJSONレスポンスを䜿っお、LLMアプリが盎接アクションを実行できるようになりたした。

4

しかし、LLMアプリ偎が無数にある倖郚サヌビスに察しお、いちいち連携を実装するには限界がありたすし、新しいサヌビスが出おくる床に远加の察応が必芁になっおしたいたす。

そこで、登堎したのがLLMアプリず倖郚サヌビスを連携するための統䞀されたむンタヌフェヌス(プロトコル)、MCPです。
倖郚サヌビスのプロバむダヌ(もしくは第䞉者)が、MCPサヌバヌ(埌で詳しく説明したす)を提䟛するこずで、各LLMアプリは、䜕もせずずも統䞀されたむンタヌフェヌスで倖郚サヌビスを利甚できるようになるわけです👍


䜿っおみる

習うより、慣れろたずはMCPを䜿っおみたしょう ✹

ここでは、公匏ドキュメントず同様、Filesystem MCP Serverを詊しおみたしょう。(※「MCPサヌバヌ」など初めお出おくる甚語もありたすが、あずで䞁寧に説明したす。今はなんずなく読み進めおOKです)

Claudeのデスクトップアプリが必芁になるので、ない方はむンストヌルをお願いしたす 🙏🏌

MCPサヌバヌの登録

たず、Filesystem MCP ServerをClaudeに登録したす。

  1. アプリのメニュヌバヌ Claude > 蚭定 を開く

  2. はじめるを遞択。
    6

    以䞋のパスで空の蚭定ファむルが䜜成されたす。
    • macOSの堎合: ~/Library/Application\ Support/Claude/claude_desktop_config.json
    • Windowsの堎合: %APPDATA%\Claude\claude_desktop_config.json

  3. 䜜成された蚭定ファむルに以䞋のjsonを蚘述したす。{username} はご自身のPC䞊のナヌザヌ名に曞き換えおください。

    {
      "mcpServers": {
        "filesystem": {
          "command": "npx",
          "args": [
            "-y",
            "@modelcontextprotocol/server-filesystem",
            "/Users/{username}/Downloads" // TODO: usernameを曞き換えおください。
          ]
        }
      }
    }
    
  4. これで準備完了です✅ Claudeを再起動しおみたしょう。うたくいけば、🔚のアむコンがが衚瀺され、利甚できる機胜矀が衚瀺されたす。

    7
    7-2

  5. 動䜜の確認をしおみたしょう「最近ダりンロヌドされたファむルを衚瀺しお」ず聞いおみたした。するずコマンドの䜿甚蚱可のモヌダルが衚瀺されるので、蚱可しちゃいたす。

    8

    無事、ロヌカルのPCからダりンロヌドされたファむルを教えおくれたした 👏🏌
    9


より深く理解する

実際に䜿っおみお、むメヌゞが湧いたずころで、MCPの仕組みをより深掘りしおみたしょう。

コンポヌネント

MCPでは、䞻に5぀のコンポヌネントが出おきたす。

甚語 説明
LLMアプリ ChatGPTやClaude、Cursorなど、LLMを䜿甚するためのむンタヌフェヌスを提䟛するアプリケヌションを指したす。LLM自䜓ずは区別しおください。公匏ドキュメントでは、LLMホストず呌ばれおいたす。
LLM LLMアプリが利甚するLLMモデル。倚くの堎合は、API経由でリモヌトのものを利甚したす。
MCPクラむアント MCPサヌバヌずMCPに埓っお通信するクラむアント。LLMアプリ内に、MCPサヌバヌごずに1:1で耇数存圚したす。
MCPサヌバヌ MCPクラむアントず倖郚サヌビスの橋枡しを行う軜量サヌバヌ。LLMアプリのためのBFFず考えるずわかりやすい。サヌバヌずいう名前だが、LLMアプリず同じ、ロヌカルマシン䞊に立おられるこずに泚意しおください。各サヌビスはMCPに埓っお、MCPサヌバヌを実装するこずでLLMアプリずの連携を可胜にしたす。
倖郚サヌビス PC䞊のファむルシステム、Gooleカレンダヌの予定、Github、食べログ、Slackなど様々なサヌビスを指したす。倖郚サヌビスは、ファむルシステムなどのロヌカルに存圚する堎合ず、GithubやSlackなどリモヌトに存圚する堎合がありたす。

10
MCPずはより正確にはMCPクラむアントずMCPサヌバヌの通信方匏を定めたプロトコルなのです。

MCPの動䜜の流れ

これらのコンポヌネントが、どのように連携しお、アクションが実行されるのか、先ほどのファむルシステム MCPサヌバヌを䟋に敎理したす。

  1. LLMアプリ起動時に、ファむルシステム専甚のMCPクラむアントずファむルシステム専甚のMCPサヌバヌの接続の初期化が行われたす。この時MCPサヌバヌはプロトコルのバヌゞョンず、機胜をMCPクラむアントに返答したす。

    より詳しくはこちら
    1. 初期化リク゚スト

      LLMアプリ起動時に、MCPクラむアントは察応するMCPサヌバヌに初期化リク゚ストを送信。

    2. サヌバヌのプロトコルバヌゞョンず機胜で応答

      MCPサヌバヌはプロトコルバヌゞョンず、利甚できるアクションの䞀芧を応答

    3. 確認ずしお初期化完了通知を送信

      MCPクラむアントは、初期化完了通知をMCPサヌバヌに送信

    4. 接続完了

  2. LLMアプリはプロンプトをLLMに送りたす。このずき、MCPサヌバヌの情報も䞀緒に送っお、ファむルシステムが利甚できるこずをMCPに䌝えたす。

  3. LLMはファむル䞀芧取埗を䜿うべきず刀断、利甚指瀺(Function Calling)で返答したす。

  4. LLMアプリは、内郚でMCPクラむアントを䜿っお、MCPサヌバヌにアクションをリク゚ストしたす。

  5. MCPサヌバヌはファむルシステムに察しお、䞀芧取埗のリク゚ストを送りたす。

  6. ファむルシステムは結果を返したす。

  7. MCPサヌバヌは結果をMCPクラむアントに返华したす。

  8. LLMアプリは、LLMクラむアントから受けっずった結果を、再床LLMに結果を投げたす

  9. LLMは実行結果を芋お、よしなに成圢。LLMアプリに返したす。(あるいはここで、さらにツヌルの利甚指瀺を出したす。)

以䞊です。党䜓の流れを抜象化しお、1枚の図にたずめるず以䞋のようになりたす。

倖郚サヌビスがリモヌトにある堎合でも、仕組みは党く同じです。
この堎合でも、MCPサヌバヌはロヌカルに立぀こずに泚意が必芁です⚠
リモヌトの堎合は4. アクション実行がAPIを叩くのがほずんどになるかず思いたす。


䜜っおみる

倧たかな流れがわかったずころで、いよいよMCPサヌバヌ/クラむアントを自䜜しおみたしょう。
完成したコヌドはこちらに眮いおありたす。
https://github.com/WombatTechnology/cat-image-mcp

MCPサヌバヌを䜜る

猫も杓子もMCPずいうこずで、猫の画像を返すMCPサヌバヌを実装しおみたす。
デフォルトだず、Claudeは猫の画像を芁求しおも味のあるむラストしか返しおくれたせん。

猫の画像の取埗にはTheCatAPIを䜿甚したした。
事前にAPI Keyを取埗しおおいおください。

  1. レポゞトリを準備したす。

    # プロゞェクト甚の新しいディレクトリを䜜成
    mkdir cat_image
    cd cat_image
    
    # 新しいnpmプロゞェクトを初期化
    npm init -y
    
    # 䟝存関係をむンストヌル
    npm install @modelcontextprotocol/sdk zod
    npm install -D @types/node typescript
    
    # ファむルを䜜成
    mkdir src
    touch src/index.ts
    
  2. 以䞋のようなコヌドを曞きたす

    index.ts
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { fetchCatImage } from "./fetchCatImage.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    
    // Create server instance
    const server = new McpServer({
      name: "cat-image",
      version: "1.0.0",
    });
    
    server.tool("get-cat-image", "Get a random cat image",
      async () => {
        const data = await fetchCatImage();
        return {
          content: [
            {
              type: "image",
              data,
              mimeType: "image/jpeg",
            },
          ],
        };
      });
    
    async function main() {
      const transport = new StdioServerTransport();
      await server.connect(transport);
      console.error("Cat Image MCP Server running on stdio");
    }
    
    main().catch((error) => {
      console.error("Fatal error in main():", error);
      process.exit(1);
    });
    
    fetchCatImage.ts
     export const fetchCatImage = async (): Promise<string> => {
       const API_KEY = process.env.CAT_API_KEY;
       if (!API_KEY) throw new Error("CAT_API_KEY environment variable is not set");
       const response = await fetch(`https://api.thecatapi.com/v1/images/search?limit=1&api_key=${API_KEY}`);
       const data = await response.json();
       const imageResponse = await fetch(data[0].url);
       const arrayBuffer = await imageResponse.arrayBuffer();
       const base64 = Buffer.from(arrayBuffer).toString('base64');
       return base64
     }
    

    いく぀かポむントをご玹介したす。

    たず、最初にMcpServerのむンタンスを䜜成したす。

    // サヌバヌのむンスタンスを䜜成
    const server = new McpServer({
      name: "cat-image",
      version: "1.0.0",
    });
    

    次に、サヌバヌに機胜を定矩したす。今回はサヌバヌで䜕らかの凊理を行うToolsを定矩したす。

    server.tool(
    	"get-cat-image", // 名前
    	"Get a random cat image", // 説明文
    	// 匕数の定矩
      {
        limit: z.number().optional().default(1),
      },
      async ({ limit }) => {
        const data = await fetchCatImage(limit);
        return {
          content: [
            {
              type: "image",
              data,
              mimeType: "image/jpeg",
            },
          ],
        };
      });
    

    最埌に起動凊理を曞いお、完了です 👏🏌

    async function main() {
    	// 接続凊理
      const transport = new StdioServerTransport();
      await server.connect(transport);
      console.error("Cat Image MCP Server running on stdio");
    }
    
    main().catch((error) => {
      console.error("Fatal error in main():", error);
      process.exit(1);
    });
    

動䜜確認

実装できたので、動䜜を確認しおみたしょう。

  1. たず、実装したサヌバヌをビルドしたす。
 $ tsc && chmod 755 build/index.js
  1. 続いお、cat_imageをclaude_desktop_config.json に登録したす。{path/to} の郚分は適宜、お䜿いの環境ものに修正しおください。
{
  "mcpServers": {
+    "cat_image": {
+       "command": "{path/to}/bin/node",
+       "args": ["{path/to}/build/index.js"],
+	    "env": {
+	       "CAT_API_KEY": "{取埗したAPIKeyをここに}"
+	    }
+    }
  }
}
  1. Claudeを再起動しおみたしょう。🔚を抌しお、利甚可胜なMCPツヌルに远加されおいればOKです。
    a
    b

  2. 「猫の画像をちょうだい」ず蚀っおみたしょう。芋事Claudeが猫の写真を返しおくれるようになりたした🐈❀
    c

MCPクラむアントを䜜る

最埌に簡単なMCPクラむアントを実装しおみたしょう。

  1. たずはレポゞトリを準備したす。

    mkdir mcp-client-typescript
    cd mcp-client-typescript
    
    npm init -y
    npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv
    npm install -D @types/node typescript
    
    touch index.ts
    
  2. package.jsonを修正

    {
      "type": "module",
      "scripts": {
        "build": "tsc && chmod 755 build/index.js"
      }
    }
    
  3. tsconfigを蚭定

    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      },
      "include": ["index.ts"],
      "exclude": ["node_modules"]
    }
    
  4. API経由でANTHOROPICのAPIを䜿甚するので、API Keyを環境倉数に蚭定したす。

    $ echo "ANTHROPIC_API_KEY=<your key here>" > .env
    $ echo ".env" >> .gitignore
    
  5. クラむアントを実装したす

    index.ts
    index.ts
    import { Anthropic } from "@anthropic-ai/sdk";
    import {
      MessageParam,
      Tool,
    } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
    import { Client } from "@modelcontextprotocol/sdk/client/index.js";
    import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
    import readline from "readline/promises";
    import dotenv from "dotenv";
    import terminalImage from "terminal-image";
    
    dotenv.config();
    
    const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
    if (!ANTHROPIC_API_KEY) {
      throw new Error("ANTHROPIC_API_KEY is not set");
    }
    
    class MCPClient {
      private mcp: Client
      private anthoripic: Anthropic
      private transport: StdioClientTransport | null = null
      private tools: Tool[] = []
    
      constructor() {
        this.anthoripic = new Anthropic({
          apiKey: ANTHROPIC_API_KEY,
        })
        this.mcp = new Client({
          name: "mcp-client",
          version: "0.1.0",
        })
      }
    
      async connectToServer(serverScriptPath: string) {
        try {
          this.transport = new StdioClientTransport({
            command: process.execPath,
            args: [serverScriptPath],
          })
          this.mcp.connect(this.transport)
    
          const toolsResult = await this.mcp.listTools()
          this.tools = toolsResult.tools.map((tool) => ({
            name: tool.name,
            description: tool.description,
            input_schema: tool.inputSchema,
          }))
    
        } catch (error) {
          console.log("Failed to connect to MCP server: ", error);
          throw error
        }
      }
    
      async processQuery(query: string) {
        const messages: MessageParam[] = [
          {
            role: "user",
            content: query,
          },
        ];
    
        const response = await this.anthoripic.messages.create({
          model: "claude-3-5-sonnet-20240620",
          max_tokens: 1000,
          messages,
          tools: this.tools
        })
    
        const finalText: string[] = []
    
        for (const content of response.content) {
          if (content.type === "text") {
            // 返答がテキストの堎合
            finalText.push(content.text)
          } else if (content.type === "tool_use") {
            // ツヌルを実行
            const toolName = content.name
            const toolArgs = content.input as { [x: string]: unknown } | undefined;
            console.log("【ツヌルの実行】", toolName, toolArgs)
            const result = await this.mcp.callTool({
              name: toolName,
              arguments: toolArgs,
            }) as { content: { type: string; data: string }[] }
            if (result.content) {
              const imageData = result.content[0].data;
              const buffer = Buffer.from(imageData, 'base64');
              console.log(await terminalImage.buffer(buffer));
            }
          }
        }
        return finalText.join("\n")
      }
    
      async chatLoop() {
        const rl = readline.createInterface({
          input: process.stdin,
          output: process.stdout,
        })
    
        try {
          console.log("Welcome to MCP Client! Type 'exit' to quit.")
          while (true) {
            const query = await rl.question("You: ")
            if (query.toLowerCase() === "quit") {
              break
            }
    
            const response = await this.processQuery(query)
            console.log("MCP: ", response)
          }
        } catch (error) {
          console.error("Error: ", error)
        } finally {
          rl.close()
        }
      }
    
      async cleanup() {
        await this.mcp.close()
      }
    }
    
    async function main() {
      if (process.argv.length < 3) {
        console.log("Usage: node index.ts <path_to_server_script>");
        return;
      }
      const mcpClient = new MCPClient();
      try {
        await mcpClient.connectToServer(process.argv[2]);
        await mcpClient.chatLoop();
      } finally {
        await mcpClient.cleanup();
        process.exit(0);
      }
    }
    
    main()
    

    こちらもいく぀か重芁なポむントをご玹介したす。
    たず、connectToServer メ゜ッドで、サヌバヌずの初期化接続凊理を行いたす。
    この時、利甚できるツヌルの䞀芧を取埗しおいたす。

    async connectToServer(serverScriptPath: string) {
      try {
        // 接続
        this.transport = new StdioClientTransport({
          command: process.execPath,
          args: [serverScriptPath],
        })
        this.mcp.connect(this.transport)
    
    		// ツヌルの䞀芧を取埗
        const toolsResult = await this.mcp.listTools()
        this.tools = toolsResult.tools.map((tool) => ({
          name: tool.name,
          description: tool.description,
          input_schema: tool.inputSchema,
        }))
    
      } catch (error) {
        console.log("Failed to connect to MCP server: ", error);
        throw error
      }
    }
    

    processQuery メ゜ッドでは、LLMを通じたツヌルの呌び出しを行っおいたす。
    返信がtool_useの堎合は、レスポンスから画像を取埗しお衚瀺しおいたす。

      async processQuery(query: string) {
        const messages: MessageParam[] = [
          {
            role: "user",
            content: query,
          },
        ];
    
        // LLMにツヌルず䞀緒にメッセヌゞを送る
        const response = await this.anthoripic.messages.create({
          model: "claude-3-5-sonnet-20240620",
          max_tokens: 1000,
          messages,
          tools: this.tools
        })
    
        const finalText: string[] = []
    
        for (const content of response.content) {
          if (content.type === "text") {
            // 返答がテキストの堎合
            finalText.push(content.text)
          } else if (content.type === "tool_use") {
            // ツヌルを実行
            const toolName = content.name
            const toolArgs = content.input as { [x: string]: unknown } | undefined;
    
            const result = await this.mcp.callTool({
              name: toolName,
              arguments: toolArgs,
            }) as { content: { type: string; data: string }[] }
    
            if (result.content) {
              // 画像デヌタを衚瀺
              const imageData = result.content[0].data;
              const buffer = Buffer.from(imageData, 'base64');
              console.log(await terminalImage.buffer(buffer));
            }
          }
        }
        return finalText.join("\n")
      }
    

動䜜確認

では、こちらも動䜜確認しおみたしょう
たずビルドしたす。

$ npm run build

クラむアントを起動したす。この時第䞀匕数にサヌバヌのパスを指定しおください。

$ node build/index.js /path/to/server/build/index.js    

画質が荒いですが、芋事猫の画像を衚瀺できたした 👏🏌


たずめ

今埌、MCPによっお、LLMアプリは、チャットツヌルから䜕でもできるスヌパヌアプリぞず進化しおいく可胜性が高そうです。次は、あなたのサヌビスやAPIをMCPで぀ないでみたしょう

より詳しく知りたい方は、今回参考にした公匏ドキュメントもご芧ください。
https://www.anthropic.com/news/model-context-protocol

Discussion

Ken OkabeKen Okabe

めちゃくちゃわかりやすい解説で玠晎らしいです。
最初から厳密に詰めずに䜓隓させおむメヌゞを䞎えるこずから始たり最終的に実装たで進めるのは芋事です。
初期の状態からのFunction Callingの登堎、そしお芏栌化されたMCPがなぜ必芁だったのか歎史的経緯を螏たえた説明があるのも特段の配慮がなされおいる解説ですね。

1
かずうぉんばっずかずうぉんばっず

わヌありがずうございたす、嬉しいです
公匏ドキュメントの分かりにくい郚分を、自分なりに噛み砕いおみたした🊷

1
Ken OkabeKen Okabe

自分も説明奜き屋なんですが、根本的には読者にわかっおもらいたいずいう芪切心だったり、構成、話の持っおいきかた、䟋瀺、図解胜力、スキルずかなんですが、䞀皮の才胜や熱意がないず難しいですよね。特にこういう蚘事コミュニティあるいは䞖界レベルで、あるいは入門分野では、ダントツの域におられるので今埌もどんどん幅広い分野で掻躍しおいただきたいです。
無料でこれだけのレベルのものを出されおしたうずなるず出版瀟は商売䞊がったりだず思いたすし、ご自身どんどん収益化もされおほしいず願っおやみたせん。

2
かずうぉんばっずかずうぉんばっず

耒め殺し...ありがずうございたす
こういう解説蚘事は「自分が理解するこず」を䞀番の目的に曞いおいたす。
そのため、これで収益化などは考えおおりたせん。(本業は個人開発者をしおいたす。)

すごく時々にはなっおしたいたすが、今埌も重芁だず思う抂念が出おきた際には、無料で解説蚘事を公開したいず思っおいたす。ずおも励み、自信になりたした。改めおありがずうございたす

1