【Typescript】最低限の手順でMCPサーバーを構築するメモ

Model Context Protocol (MCP)はLLMアプリケーションとデータソースやツールを標準化された方法で連携させるためのプロトコルです。AIアプリケーションのための「USB-C」のようなものと考えると理解しやすいです（公式より）。

このメモでは、TypeScript SDKを使用してMCPサーバーを最小限のステップで構築する手順をまとめます。

## 前提環境

- Node.js: v18.0.0以上
- TypeScript: v5.0.0以上
- MCP SDK: v1.8.0以上

## 1. プロジェクトセットアップ

新しいディレクトリを作成し、必要なパッケージをインストール。

```bash
mkdir minimal-mcp-server
cd minimal-mcp-server

npm init -y

npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
```

パッケージの役割：
- `@modelcontextprotocol/sdk`: MCP用TypeScript実装
- `zod`: 型安全なスキーマ検証ライブラリ
- `typescript`: TSコンパイラ
- `@types/node`: Node.js型定義

## 2. TypeScript設定

`tsconfig.json`を作成。

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["./**/*"],
  "exclude": ["node_modules"]
}
```

重要点：
- ES2022ターゲット
- Node16モジュール設定
- 出力先は`./build`
- ルートは`./src`
- 厳格な型チェック有効

## 3. package.json設定

`package.json`を編集し、ESMモジュール形式設定とスクリプトを追加。

```json
{
  "name": "minimal-mcp-server",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module", // 追加
  "scripts": {
    "build": "tsc", // 追加
    "start": "node build/index.js" // 追加
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.8.0",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^22.0.2",
    "typescript": "^5.5.4"
  }
}
```

重要点：
- `"type": "module"`でESM形式指定
- ビルド・実行用スクリプト追加 (build, start)

## 4. MCPサーバー実装

`src`ディレクトリを作成し`index.ts`を作成。

```typescript:src/index.ts
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: "minimal-mcp-server",
  version: "1.0.0",
});

// エコーツール定義
server.tool(
  "echo",
  "入力されたメッセージをそのまま返す簡単なツール",
  {
    message: z.string().describe("エコーするメッセージ"),
  },
  async ({ message }) => {
    return {
      content: [
        {
          type: "text",
          text: `Echo: ${message}`,
        },
      ],
    };
  }
);

// 日時ツール追加
server.tool(
  "get-current-time",
  "現在の日時を取得するツール",
  {},
  async () => {
    const now = new Date();
    return {
      content: [
        {
          type: "text",
          text: `現在の日時: ${now.toLocaleString()}`,
        },
      ],
    };
  }
);

// サーバー起動
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.log("MCP Server running on stdio");
}

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

MCPツールの構成要素：
- 名前：一意の識別子
- 説明：機能説明
- パラメータスキーマ：入力パラメータはZodで検証
- ハンドラ関数：実際の処理

:::note info
公式の例はZodを使っていますがArkTypeでも可です
:::

https://qiita.com/yonaka15/items/39dbe97206d6fe6e6f7b

## 5. ビルドと実行

サーバーをビルドして実行。

```bash
npm run build
node build/index.js
```

「MCP Server running on stdio」メッセージが表示されれば成功。

## 6. テストと検証

MCP Inspectorでテスト。

```bash
npx @modelcontextprotocol/inspector node build/index.js
```

Inspectorが起動し、ツールやリソースが確認できる。

## 7. LLMクライアント接続

### Claude Desktop設定

設定ファイルを編集：
- MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%AppData%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "minimal-mcp": {
      "command": "node",
      "args": ["/絶対パス/minimal-mcp-server/build/index.js"]
    }
  }
}
```

### Cursor設定

1. Settings → MCPセクション → 「Add new MCP server」
2. 入力情報：
   - Name: minimal-mcp
   - Type: command
   - Command: `node /絶対パス/minimal-mcp-server/build/index.js`

## 8. 拡張方法

### リソース追加

リソースについて、私も十分に理解しているわけではないですが、Claude Desktopではメッセージへの添付などがしやすくなります（プラグのアイコンから）

![Screenshot 2025-04-06 at 21.07.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79562/fd96e22a-d8ba-48e3-a88a-ee305d3a49b7.png)



```typescript
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";

// 静的リソース
server.resource(
  "info",
  "info://server",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "このサーバーに関する情報"
    }]
  })
);

// 動的リソース
server.resource(
  "user-info",
  new ResourceTemplate("users://{userId}", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `ユーザーID: ${userId} の情報`
    }]
  })
);
```

### HTTP/SSEトランスポート

```typescript
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const app = express();
const transports = {};

app.get("/sse", async (_, res) => {
  const transport = new SSEServerTransport('/messages', res);
  transports[transport.sessionId] = transport;
  res.on("close", () => {
    delete transports[transport.sessionId];
  });
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  const sessionId = req.query.sessionId;
  const transport = transports[sessionId];
  if (transport) {
    await transport.handlePostMessage(req, res);
  } else {
    res.status(400).send('セッションが見つかりません');
  }
});

app.listen(3000, () => {
  console.log("MCP Server running on http://localhost:3000");
});
```


## 参考

- [Model Context Protocol 公式](https://modelcontextprotocol.io/)
- [MCP GitHub](https://github.com/modelcontextprotocol)
- [MCP 仕様書](https://spec.modelcontextprotocol.io/)
- [サンプルサーバー](https://github.com/modelcontextprotocol/servers)