0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercel AI SDK で MCP を試す 〜その2〜 :公式サンプルを書きかえて使う

Posted at

はじめに

ちょっと前に、主に公式ドキュメントを見ながら実装を試した Vercel AI SDK + MCP の話です。

●Vercel AI SDK でシンプルな MCPクライアント・MCPホストの機能を作る(自作MCPサーバーと組み合わせる) - Qiita
 https://qiita.com/youtoy/items/22fa3faaacd7ae6dd958

今回のお試し

今回は以下の公式サンプルをベースとして利用し、内容を少し書きかえたりなどして使います。

●ai/examples/mcp/src/stdio at main · vercel/ai
 https://github.com/vercel/ai/tree/main/examples/mcp/src/stdio

実際に試してみる

公式の情報を見ると、pnpm を使った手順で試せるようです。

今回は上記の pnpm は使わずにやってみました。

必要なパッケージなどを確認

公式サンプルのコードを流用して進めていくのですが、まずは必要なパッケージ・環境などを確認してみることにします。

コードの冒頭を見てみる

コードの冒頭を見て、必要なパッケージ・環境などを確認します。以下は、「client.ts」と「server.ts」のリンクと、冒頭部分のキャプチャです。

上記を見ると、以下をインストールすれば良さそうです。また OpenAI の APIキーをセットする必要もありそうです。

  • @ai-sdk/openai
  • @modelcontextprotocol/sdk
  • ai

それと dotenv がインポートされていますが、ざっと見た感じ、OpenAI の APIキーを OPENAI_API_KEY という名前でセットしておく形で良さそうです。

パッケージのインストール

必要なパッケージを、以下でインストールします。

npm i @ai-sdk/openai @modelcontextprotocol/sdk ai

この後は、先ほどの公式サンプルを書きかえていきます。

コードを書きかえる

元の内容は TypeScript でしたが、今回 JavaScript にして試してみました。変更後の実装内容は、この後に示します。なお、この後に出てくるサーバー側・クライアント側のファイル(server.mjs、client.mjs)は、同じフォルダ内に置く構成にしています。

サーバー側

以下はサーバー側です。こちらは、基本的に元のものを TypeScript から JavaScript にしただけという内容です。

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

const POKE_API_BASE = "https://pokeapi.co/api/v2";

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

server.tool(
  "get-pokemon",
  "Get Pokemon details by name",
  { name: z.string() },
  async ({ name }) => {
    const path = `/pokemon/${name.toLowerCase()}`;
    const pokemon = await makePokeApiRequest(path);

    if (!pokemon) {
      return {
        content: [
          {
            type: "text",
            text: "Failed to retrieve Pokemon data",
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: formatPokemonData(pokemon),
        },
      ],
    };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.log("Pokemon MCP Server running on stdio");
}

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

async function makePokeApiRequest(path) {
  try {
    const url = `${POKE_API_BASE}${path}`;
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
    return await response.json();
  } catch (error) {
    console.error("[ERROR] Failed to make PokeAPI request:", error);
    return null;
  }
}

function formatPokemonData(pokemon) {
  return [
    `Name: ${pokemon.name}`,
    `Abilities: ${pokemon.abilities
      .map(({ ability }) => ability.name)
      .join(", ")}`,
  ].join("\n");
}

クライアント側

以下はサーバー側です。こちらは、元のものを TypeScript から JavaScript にした後、何ヵ所か書きかえたところがあります。
※ その書きかえを行った後に、「別途書きかえた部分・無効にした部分」をコメントアウトして残しています

import { openai } from "@ai-sdk/openai";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { experimental_createMCPClient, generateText } from "ai";
// import "dotenv/config";
import { z } from "zod";

async function main() {
  let mcpClient;

  try {
    const stdioTransport = new StdioClientTransport({
      // command: "node",
      command: "【nodeコマンドのフルパス】",
      // args: ["src/stdio/dist/server.js"],
      args: ["./server.mjs"],
      // env: { FOO: "bar" },
    });

    mcpClient = await experimental_createMCPClient({
      transport: stdioTransport,
    });

    const { text: answer } = await generateText({
      model: openai("gpt-4o-mini", { structuredOutputs: true }),
      tools: await mcpClient.tools({
        schemas: {
          "get-pokemon": {
            parameters: z.object({ name: z.string() }),
          },
        },
      }),
      maxSteps: 10,
      onStepFinish: async ({ toolResults }) => {
        console.log(`STEP RESULTS: ${JSON.stringify(toolResults, null, 2)}`);
      },
      system: "You are an expert in Pokemon",
      prompt:
        // "Which Pokemon could best defeat Feebas? Choose one and share details about it.",
        "ヒンバスを最も効果的に倒せるポケモンはどれですか?1匹選んで、その詳細を教えてください。回答は日本語で。",
    });

    console.log(`FINAL ANSWER: ${answer}`);
  } finally {
    await mcpClient?.close();
  }
}

main().catch(console.error);

手を加えた部分は、不要そうな部分をコメントアウトしたのと、それ以外は以下の部分です。

  • プロンプトを日本語に変更
  • node コマンドのフルパスを記載
    • 【書きかえた理由】 command: "node" にしていた時、nodeコマンドが見つからないというエラーが出たため
  • server.mjs のパスを変更
    • 【書きかえた背景】 client.mjs と server.js を同じフォルダに置く構成にしていたため

コードの実行結果

上記のコードを実行します。実行するのはクライアント側で、実行した結果は以下のとおりです。

image.png

最終的出力で、回答を得ることができました。さらに「ヒンバス ⇒ ギャラドス」と変えて、再度処理を実行してみました。

それで得られた結果は以下のとおりです。

image.png

今回も最終出力の部分で、回答を得ることができました。

おわりに

Vercel AI SDK で MCP を使う公式サンプルを見かけたので、それを試してみました。

本当はこの続きで、以下の「Filesystem MCP Server」をサーバー側にした内容を試そうとしたのですが、かなり苦戦してしまったので、いったんここまでの内容を公開しておきます。

●servers/src/filesystem at main · modelcontextprotocol/servers
 https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?