はじめに
今回の記事の内容は、TypeScript版の公式SDK で自作MCPクライアントを試してみます。
MCP関連で試していたこと: 自作MCPサーバーの話
MCP関連の話で、過去に既存の MCPサーバーを試した後、自作MCPサーバーを試しましていました。具体的には、以下の TypeScript版の公式SDK を使ったものと、TypeScript版の FastMCP を使った自作MCPサーバーです。
●シンプルな自作MCPサーバーを VS Code に設定して GitHub Copilot の Agent mode で利用(Node.js で TypeScript を直接扱う) - Qiita
https://qiita.com/youtoy/items/1d46724e40ab96607b18
●TypeScript版の FastMCP を少し試す(自作MCPサーバー) - Qiita
https://qiita.com/youtoy/items/22bcaf99fe4444fe8215
今回試す内容
それで、ふと MCPクライアントや MCPホストの自作も試してみようと思い、今回は以下の TypeScript版の公式SDK を使った MCPクライアントの自作を試しました。
●modelcontextprotocol/typescript-sdk: The official Typescript SDK for Model Context Protocol servers and clients
https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file
自作MCPクライアントで行う処理は、冒頭に書いていた自作MCPサーバーのツールの一覧取得と、簡単なツールの利用になります。
MCPクライアントを実装する
MCPクライアントについて
MCPクライアントを作る前に、少し MCPクライアントの位置付けについて公式情報などを見ておきます。
以下の「公式ドキュメントの General architecture」を見てみます。
例えば「自作MCPサーバーを Claudeデスクトップで使う」という構成の場合、上記の図の真ん中の部分が自作MCPサーバーに該当します。
そして Claudeデスクトップは、図の左の「Host with MCP Client」という部分に含まれるようです。
また図の下の説明を見ると、ホスト・ツールに関しては以下のように書かれています。
- MCP Hosts: Programs like Claude Desktop, IDEs, or AI tools that want to access data through MCP
- MCP Clients: Protocol clients that maintain 1:1 connections with servers
Claudeデスクトップは MCPホストにあたり、そのホストがサーバーと接続するのに使うのがクライアントになるようです。
他の説明
その他、MCPサーバー・クライアント・ホストの説明・図が書かれた記事を見かけたので、それも該当箇所などを掲載してみます。
●最小限のMCP Host/Client/Serverをスクラッチで実装する
https://zenn.dev/razokulover/articles/9a0aee8ceb9f3f
クライアントが接続する先の自作サーバーについて
今回、前に作成した自作MCPサーバーへの接続・ツールの利用を試します。
それらについて、実装内容だけ補足しておきます。
今回、TypeScript版の公式SDK を使ったものと、TypeScript版の FastMCP を使ったものの両方を利用してみます。
TypeScript版の公式SDK を使ったもの
過去に作った MCPサーバーのうち、TypeScript版の公式SDK を使ったものの内容は以下でした。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "Demo",
version: "1.0.0",
});
server.tool(
"add_test",
"与えられた数値の足し算をする(さらに10を足す)",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b + 10) }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
"add_test"
という名前でツールを実装していました。
また、処理の内容は、入力された 2つの数字に 10を加算した値を返すというものでした。
TypeScript版の FastMCP を使ったもの
もう1つ作成した MCPサーバーは、以下の TypeScript版の FastMCP を使って実装したものです。
- 【TypeScript版】punkpeye / fastmcp
コードは、以下のとおりです。
import { FastMCP } from "fastmcp";
import { z } from "zod";
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
});
server.addTool({
name: "add",
description: "2つの数を足して、さらに5を足す",
parameters: z.object({
a: z.number(),
b: z.number(),
}),
execute: async (args) => {
return String(args.a + args.b + 5);
},
});
server.start({
transportType: "stdio",
});
基本的な処理は、1つ目と同じです。
1つ目との違いとしてはツールの名前が少し異なる "add"
というものになっていたり、処理の中身で入力された 2つの数字に対して加算する値が 5 になっていたりする部分です。
TypeScript版の公式SDK を使った MCPクライアントの実装
それでは、以下の GitHubリポジトリの「Writing MCP Clients」という部分を参考にしつつ、TypeScript版の公式SDK で MCPクライアントを作ってみます。
※●modelcontextprotocol/typescript-sdk: The official Typescript SDK for Model Context Protocol servers and clients
https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#writing-mcp-clients
公式で示されている実装
公式で示されている実装は以下のとおりです。
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);
// List prompts
const prompts = await client.listPrompts();
// Get a prompt
const prompt = await client.getPrompt({
name: "example-prompt",
arguments: {
arg1: "value"
}
});
// List resources
const resources = await client.listResources();
// Read a resource
const resource = await client.readResource({
uri: "file:///example.txt"
});
// Call a tool
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});
「prompts・resources・tool」のそれぞれに関する実装が盛り込まれています。今回は、自作MCPサーバー側の実装が tool(ツール)のみなので、「prompts・resources」の部分は削ります。
自分が試した内容(下準備・実装)
それでは、以下のコマンドでパッケージのインストールを行い、自作MCPクライアントを実装します。
npm install @modelcontextprotocol/sdk
TypeScript版の公式SDK で作った自作MCPサーバーとの組み合わせ
以下は、「TypeScript版の公式SDK で作った自作MCPサーバー」を使うためのクライアントです。
args: ["【実装した自作MCPサーバーへのフルパス】"]
の部分は、自作MCPサーバーのファイルの実際のフルパスに置きかえてください。
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function main() {
const transport = new StdioClientTransport({
command: "node",
args: ["【実装した自作MCPサーバーへのフルパス】"],
});
const client = new Client(
{ name: "add-tool-client", version: "1.0.0" },
{ capabilities: {} }
);
await client.connect(transport);
const listRes = await client.listTools();
const tools = listRes.tools;
console.log(
"Available tools:",
tools.map((t) => t.name)
);
const a = 10;
const b = 20;
const callRes = await client.callTool({
name: "add_test",
arguments: { a, b },
});
console.log(`add(${a}, ${b}) + 10 =`, callRes);
await client.close();
}
main().catch((err) => {
console.error("Error in add-tool client:", err);
process.exit(1);
});
これを実行した結果は以下のとおりです。
10・20 という入力値に対して、40 という値を得ることができています。
ちなみに、取得できたツールの一覧の部分について、名前を取り出すのではなく console.dir(tools, { depth: null });
など全体を出力する処理を行うと、以下の内容が返ってきているのが確認できました。
TypeScript版の FastMCP で作った自作MCPサーバーとの組み合わせ
以下は、「TypeScript版の FastMCP で作った自作MCPサーバー」を使うためのクライアントです。
先ほどとほぼ同じ内容のコードになるので、コード・補足は折りたたんだ中に記載します。
【折りたたみ】TypeScript版の FastMCP で作った自作MCPサーバーを使うクライアント
args: ["【実装した自作MCPサーバーへのフルパス】"]
の部分は、自作MCPサーバーのファイルの実際のフルパスに置きかえてください。
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function main() {
const transport = new StdioClientTransport({
command: "node",
args: ["【実装した自作MCPサーバーのファイルへのフルパス】"],
});
const client = new Client(
{ name: "add-tool-client", version: "1.0.0" },
{ capabilities: {} }
);
await client.connect(transport);
const listRes = await client.listTools();
const tools = listRes.tools;
console.log(
"Available tools:",
tools.map((t) => t.name)
);
const a = 10;
const b = 20;
const callRes = await client.callTool({
name: "add",
arguments: { a, b },
});
console.log(`add(${a}, ${b}) + 5 =`, callRes);
await client.close();
}
main().catch((err) => {
console.error("Error in add-tool client:", err);
process.exit(1);
});
これを実行した結果、10・20 という入力値に対して、35 という値を得ることができました。
また取得できたツール一覧の全体の内容については、以下となっていました。
おわりに
簡単なお試しではありましたが、TypeScript版の公式SDK を使って MCPクライアントを作ってみました。そのクライアントでは、自作MCPサーバーのツールを利用する処理を試し、想定通りの結果を得られることが確認できました。
今後、MCP に関連した内容の自作について、MCPホストの自作も試せればと思っています。
おまけ
今回、自作MCPサーバーのツールを使う MCPクライアントを実装したついでに、公式の Filesystem MCP Server(filesystem) のツール一覧を取得する処理も軽く試してみました。
実装内容
具体的なコードは、以下のとおりです。
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function main() {
const transport = new StdioClientTransport({
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"【対象とするフォルダのフルパス】",
],
});
const client = new Client(
{ name: "filesystem-tool-client", version: "1.0.0" },
{ capabilities: {} }
);
await client.connect(transport);
const listRes = await client.listTools();
const tools = listRes.tools;
console.log(
"Available tools:",
tools.map((t) => t.name)
);
await client.close();
}
main().catch((err) => {
console.error("Error in add-tool client:", err);
process.exit(1);
});
得られたツール一覧は以下となりました。
また、名前以外の部分を含む全体を取得した結果も、部分的に掲載してみます。