TypeScript版の公式SDK で自作MCPクライアントを試す（自作MCPサーバーのツール利用）

## はじめに

今回の記事の内容は、[TypeScript版の公式SDK](https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file) で自作MCPクライアントを試してみます。

### MCP関連で試していたこと： 自作MCPサーバーの話

MCP関連の話で、過去に既存の MCPサーバーを試した後、自作MCPサーバーを試しましていました。具体的には、以下の [TypeScript版の公式SDK](https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file) を使ったものと、[TypeScript版の FastMCP](https://github.com/punkpeye/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](https://modelcontextprotocol.io/introduction#general-architecture)」を見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/733a42ec-4ebe-4dd6-b716-c098c27f61f4.png)

例えば「自作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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/830f343f-2f9b-4347-a1b6-ab7632a2a3de.png)



### クライアントが接続する先の自作サーバーについて

今回、前に作成した自作MCPサーバーへの接続・ツールの利用を試します。
それらについて、実装内容だけ補足しておきます。

今回、[TypeScript版の公式SDK](https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file) を使ったものと、[TypeScript版の FastMCP](https://github.com/punkpeye/fastmcp) を使ったものの両方を利用してみます。


#### TypeScript版の公式SDK を使ったもの

過去に作った MCPサーバーのうち、TypeScript版の公式SDK を使ったものの内容は以下でした。


```javascript:app.mjs
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
   - https://github.com/punkpeye/fastmcp

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6d12b92f-cfec-42aa-835c-9181bd12f47c.png)


コードは、以下のとおりです。


```javascript:app.mjs
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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/4bc98460-cb5d-4c28-b8d8-be19907e3918.png)


#### 公式で示されている実装

公式で示されている実装は以下のとおりです。


```javascript
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クライアントを実装します。

```zsh
npm install @modelcontextprotocol/sdk
```


#### TypeScript版の公式SDK で作った自作MCPサーバーとの組み合わせ

以下は、「TypeScript版の公式SDK で作った自作MCPサーバー」を使うためのクライアントです。

`args: ["【実装した自作MCPサーバーへのフルパス】"]` の部分は、自作MCPサーバーのファイルの実際のフルパスに置きかえてください。


```javascript
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 という値を得ることができています。

![2025-05-06_16-12-40.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/cb4f7c97-611e-4f21-8320-e15201057e41.jpeg)


ちなみに、取得できたツールの一覧の部分について、名前を取り出すのではなく `console.dir(tools, { depth: null });` など全体を出力する処理を行うと、以下の内容が返ってきているのが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/56c8f21a-1629-48f7-98c9-62b24242e058.png)


#### TypeScript版の FastMCP で作った自作MCPサーバーとの組み合わせ

以下は、「TypeScript版の FastMCP で作った自作MCPサーバー」を使うためのクライアントです。

先ほどとほぼ同じ内容のコードになるので、コード・補足は折りたたんだ中に記載します。

<details><summary>【折りたたみ】TypeScript版の FastMCP で作った自作MCPサーバーを使うクライアント</summary>

`args: ["【実装した自作MCPサーバーへのフルパス】"]` の部分は、自作MCPサーバーのファイルの実際のフルパスに置きかえてください。

```javascript
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);
});
```

</details>


これを実行した結果、10・20 という入力値に対して、35 という値を得ることができました。

![2025-05-06_16-12-40のコピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e50f778f-a0ac-4fb9-91f2-ede3b3412fed.jpeg)

また取得できたツール一覧の全体の内容については、以下となっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/fb827494-e570-44ba-b90b-34d4cea16ec2.png)



## おわりに

簡単なお試しではありましたが、TypeScript版の公式SDK を使って MCPクライアントを作ってみました。そのクライアントでは、自作MCPサーバーのツールを利用する処理を試し、想定通りの結果を得られることが確認できました。

今後、MCP に関連した内容の自作について、MCPホストの自作も試せればと思っています。


### おまけ

今回、自作MCPサーバーのツールを使う MCPクライアントを実装したついでに、公式の [Filesystem MCP Server（filesystem）](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) のツール一覧を取得する処理も軽く試してみました。

#### 実装内容

具体的なコードは、以下のとおりです。


```javascript
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);
});
```


得られたツール一覧は以下となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e6ea8b58-3de5-475f-8de7-91e0f66c4596.png)


また、名前以外の部分を含む全体を取得した結果も、部分的に掲載してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/abbd7a66-a965-4fd6-b467-a05f73230d47.png)