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?

MCPをLangChain.jsで爆速導入してみる!langchain-mcp-adapters解説

Posted at

はじめに

簡単な自作MCPサーバーを作って、ローカルのターミナルの入力に対してLLMが回答するようなTypeScript製のAIチャットボットを作りました。
その中でLangChain.js (langchain-mcp-adapters) について理解したことをまとめます。

langchain-mcp-adaptersについて

langchain-mcp-adapters は、MCPをLangChainやLangGraph上で簡単に利用できるようにする軽量のラッパーです。

SDK(modelcontextprotocol TypeScript SDK)におけるMCP実装

まずは純粋な modelcontextprotocol/typescript-sdk の実装やDeepWikiを読んで、MCPクライアントとサーバーの仕組みを理解した後、LangChain.js (langchain-mcp-adapters) ではどうなっているのかを確認します。

以下の画像のように、MCP クライアントとサーバーは、共通のTransportインターフェースを実装することで、同じプロトコルに基づいて通信を行います。
このアプローチにより、通信方式(例:HTTP、WebSocket、ローカル実行など)を Transport の実装を切り替えるだけで柔軟に変更でき、クライアント・サーバー間の処理ロジックを共通化しやすくなる、ということがMCPの良さだと理解しました。

MCP SDKにおけるTransport/Client/Serverの関係図

langchain-mcp-adaptersのMCP実現

LangChain.jsでMCPを使うには、上画像のClientクラス型をLangChainの型に変換する必要がありますが、この変換を langchain-mcp-adapters 内の関数で提供しています。

ただし実際には、上記のClientクラス以下をラップした MultiServerMCPClient クラスを利用することで開発者は変換を意識せずに済みます。

また、変換以外にもさまざまな機能が提供されています。
開発者はこのクラスを利用することで、純粋なLangChainの恩恵だけでなく、MCPを利用する上で便利な機能も享受でき、開発負担が大幅に軽減されます。

現状では、
ローカルプロセスとの通信用である StdioClientTransport と、
Server-Sent Events(HTTP)による通信用 SSEClientTransport をサポートしています。

MultiServerMCPClientの構造イメージ

以降では、MCPを利用する上で他にどのような機能を享受できるかを記載します。

langchain-mcp-adaptersの利点

公式のREADMEにも概要が記載されています。

マルチサーバー管理

複数のMCPサーバーに同時に接続し、各サーバーが提供するツールを自動的に整理・管理できます。
MultiServerMCPClient クラスは以下のプロパティを持っており、複数のMCPサーバーへの接続を管理します。

  • _clients: Record<string, Client> … 各サーバーのMCPクライアントインスタンスを保持
  • _serverNameToTools: Record<string, StructuredToolInterface[]> … サーバーごとのツール一覧を管理

例えば次のように実装するだけで、以下のような処理をすべて内部で実装してくれます。

  • Zodスキーマによる設定オブジェクトの検証とパース
  • MCPサーバー接続確認、初期化
  • ツールのオプションを内部プロパティに保存
  • MCPサーバー接続
  • ツール読み込み、整理などなど
import { MultiServerMCPClient } from '@langchain/mcp-adapters';

const client = new MultiServerMCPClient({
  mcpServers: {
    crypto: { command: "tsx", args: ["./src/mcp/crypto-server.ts"] },
    news:   { command: "tsx", args: ["./src/mcp/news-server.ts"] },
    stock:  { command: "tsx", args: ["./src/mcp/stock-server.ts"] },
  }
});
const tools = await client.getTools();

純粋なSDK(@modelcontextprotocol/sdk)ではマルチサーバー管理用の統合クラスは提供されていないため、独自ロジックで以下のような処理を自前で実装する必要があります。
接続部分だけ記載しますが、それぞれ個別にエラーハンドリングや接続管理を行う必要があり、負担になります。

StdioClientTransport, Clientをラップしていて、マルチサーバー管理を自動化してくれる MultiServerMCPClient は非常に助かります。

const cryptoTransport = new StdioClientTransport({
  command: "tsx",
  args: ["./src/mcp/crypto-server.ts"],
});
const newsTransport = new StdioClientTransport({
  command: "tsx",
  args: ["./src/mcp/news-server.ts"],
});
const stockTransport = new StdioClientTransport({
  command: "tsx",
  args: ["./src/mcp/stock-server.ts"],
});

const cryptoClient = new Client(
  { name: "crypto", version: "1.0.0" },
  { capabilities: { tools: {} } }
);
const newsClient = new Client(
  { name: "news", version: "1.0.0" },
  { capabilities: { tools: {} } }
);
const stockClient = new Client(
  { name: "stock", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

await cryptoClient.connect(cryptoTransport);
await newsClient.connect(newsTransport);
await stockClient.connect(stockTransport);

ツールの管理と命名

マルチサーバー管理機能の中で、サーバー間でツール名が衝突しないよう自動的にprefixを付与してくれています。
デフォルトでは、MCPサーバーへの接続初期に以下のような形式でツール名を生成されます。

mcp__<サーバー名>__<ツール名>

以下のような処理がされています。

// prefix付与処理
// https://github.com/langchain-ai/langchainjs/blob/main/libs/langchain-mcp-adapters/src/tools.ts#L519-L523

const initialPrefix = additionalToolNamePrefix
  ? `${additionalToolNamePrefix}__`
  : "";
const serverPrefix = prefixToolNameWithServerName ? `${serverName}__` : "";
const toolNamePrefix = `${initialPrefix}${serverPrefix}`;
...
const dst = new DynamicStructuredTool({
  name: `${toolNamePrefix}${tool.name}`,
  description: tool.description || "",
  schema: tool.inputSchema,
  responseFormat: "content_and_artifact",
  func: async (
    args: Record<string, unknown>,
    _runManager?: CallbackManagerForToolRun,
    config?: RunnableConfig
  ) => {
    return _callTool({
      serverName,
      toolName: tool.name,
      client,
      args,
      config,
      useStandardContentBlocks,
      outputHandling,
    });
  }
});

また、これらのprefix(mcpの部分と<サーバー名>の部分)はオプションにより変更可能です。
SDKでは、サーバー間でツール名が衝突しないようにprefix付与、ルーティング層や名前空間管理の実装を独自に行わなければなりません。


ツール呼び出しの簡易実装

これはlangchain-mcp-adaptersの話ではないのですが、ツールが関わる話なので記載します。

LangChain.jsでMCPを使用する際は createReactAgent の利用が推奨されています。
これはLangChainの標準的なReActパターンを実装したエージェント作成関数です。

LangGraphベースで実装されており、ツール呼び出しの実装が非常に簡単になります。
内部では、以下のようなツール呼び出しのワークフローを自動生成してくれます。

エージェント呼び出しワークフロー簡易版

preModelHookpostModelHookresponseFormat などのオプションを指定することで、動的にエージェントを設定することもできます。

エージェント呼び出しワークフローオプション版

さらに、マルチサーバー管理 の実装にて上述した getTools 関数では、すべてのサーバーのツール情報がフラットな配列で取得できます。

これを以下のように渡すだけで、内部でツールバインディングが行われ、LLMがツールを認識できるようになります。

const agent = createReactAgent({
  llm,
  tools,
  prompt: "XXXX",
});

このように、MCPを経由したツール連携とLangChainのエージェント機能を組み合わせることで、非常に簡潔かつ強力なエージェントアプリケーションを構築できます。


OAuth 2.0 認証対応

今回は StdioClientTransport で遊んでおり、使っていませんのでおまけです。
商用環境でのHTTP/SSEトランスポートは、認証対応も必要になるケースが多いので後日試してみます。

@langchain/mcp-adapters により、OAuth 2.0認証の複雑な実装を完全に自動化できます。アクセストークンの自動リフレッシュ、401エラー時の自動リトライ、PKCE セキュリティなどが手動実装不要で提供されるので、開発者は認証ロジックを一から実装する必要がありません。

authProviderを設定するだけでトランスポートレイヤーが自動的にOAuth 2.0フローを処理し、RFC 6750準拠の認証が簡単な設定だけで実現できるようです。

まとめ

  • MCPクライアント/サーバーは同じTransportインターフェースを利用するため、Transportを実装するだけで通信方式を切り替えられる。
  • langchain-mcp-adaptersMultiServerMCPClient を使えば、マルチサーバー管理やツール名衝突防止などの面倒な処理を実装する負担が省ける
  • LangChainエージェントと組み合わせることで、ツール呼び出しの実装がシンプルになり、開発コストを大幅に削減できる。
  • OAuth 2.0対応のSSEClientTransportを使えば、よりセキュアに外部MCPサーバーを利用できる。

おまけ

公開APIを使ってツールを作成し、ローカルに自作MCPサーバーとして立ち上げ、langchain-mcp-adapters を使ってMCPクライアントに接続し、ターミナルから株価・暗号通貨・ニュース情報などを取得できる簡易的なAIチャットボットを作成してみました。

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?