Agent-almanac build-custom-mcp-server
install
source · Clone the upstream repo
git clone https://github.com/pjt222/agent-almanac
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/ja/skills/build-custom-mcp-server" ~/.claude/skills/pjt222-agent-almanac-build-custom-mcp-server-394bdc && rm -rf "$T"
manifest:
i18n/ja/skills/build-custom-mcp-server/SKILL.mdsource content
カスタムMCPサーバーの構築
AIアシスタントにドメイン固有のツールを公開するカスタムMCPサーバーを作成する。
使用タイミング
- Claude CodeまたはClaude Desktopにカスタム機能を公開する必要がある時
- mcptoolsが提供する以上の特化したツールを構築する時
- ドメイン固有のAIアシスタント統合を作成する時
- 既存のAPIやサービスをMCPツールとしてラップする時
入力
- 必須: 公開するツールのリスト(名前、説明、パラメータ、動作)
- 必須: 実装言語(Node.jsまたはR)
- 必須: トランスポートタイプ(stdioまたはHTTP)
- 任意: 認証要件
- 任意: Dockerパッケージングのニーズ
手順
ステップ1: ツール仕様の定義
コードを書く前に、各ツールを定義する:
tools: - name: query_database description: Execute a read-only SQL query against the analysis database parameters: query: type: string description: SQL SELECT query to execute required: true limit: type: integer description: Maximum rows to return default: 100 returns: JSON array of result rows - name: run_analysis description: Execute a predefined statistical analysis by name parameters: analysis_name: type: string description: Name of the analysis to run enum: [descriptive, regression, survival] dataset: type: string description: Dataset identifier required: true
期待結果: 各ツールのYAMLまたはmarkdown仕様。名前、説明、パラメータ(型、デフォルト、必須フラグを含む)、戻り値型がコードを書く前に文書化される。
失敗時: ツール仕様が不明確な場合、ドメイン専門家にインタビューするか、既存のAPIドキュメントをレビューしてパラメータの型と戻り値フォーマットを判定する。
ステップ2: Node.jsでの実装(MCP SDKを使用)
// server.js 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: "my-analysis-server", version: "1.0.0", }); // Define tools server.tool( "query_database", "Execute a read-only SQL query against the analysis database", { query: z.string().describe("SQL SELECT query"), limit: z.number().default(100).describe("Max rows to return"), }, async ({ query, limit }) => { // Validate read-only if (!/^\s*SELECT/i.test(query)) { return { content: [{ type: "text", text: "Error: Only SELECT queries allowed" }], isError: true, }; } const results = await executeQuery(query, limit); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } ); server.tool( "run_analysis", "Execute a predefined statistical analysis", { analysis_name: z.enum(["descriptive", "regression", "survival"]), dataset: z.string().describe("Dataset identifier"), }, async ({ analysis_name, dataset }) => { const result = await runAnalysis(analysis_name, dataset); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } ); // Start server with stdio transport const transport = new StdioServerTransport(); await server.connect(transport);
期待結果: MCP SDKをインポートし、Zodスキーマでツールを定義し、stdioトランスポートで接続する動作する
server.jsファイル。node server.jsを実行するとエラーなしでサーバーが起動する。
失敗時:
@modelcontextprotocol/sdkとzodがインストールされていることを確認する(npm install)。インポートパスがSDKバージョンと一致することを確認する(SDKはバージョン間でエクスポートを再構成した)。
ステップ3: Rでの実装(mcptoolsを使用)
# server.R library(mcptools) # Register custom tools mcp_tool( name = "query_database", description = "Execute a read-only SQL query", parameters = list( query = list(type = "string", description = "SQL SELECT query"), limit = list(type = "integer", description = "Max rows", default = 100) ), handler = function(query, limit = 100) { if (!grepl("^\\s*SELECT", query, ignore.case = TRUE)) { stop("Only SELECT queries allowed") } result <- DBI::dbGetQuery(con, paste(query, "LIMIT", limit)) jsonlite::toJSON(result, auto_unbox = TRUE) } ) # Start server mcptools::mcp_server()
期待結果:
mcp_tool()でカスタムツールを登録し、mcp_server()でサーバーを起動する動作するserver.Rファイル。Rscript server.Rを実行するとMCPサーバーが起動する。
失敗時:
mcptoolsがGitHubからインストールされていることを確認する(remotes::install_github("posit-dev/mcptools"))。ハンドラー関数のシグネチャがパラメータ定義と一致していることを確認する。
ステップ4: プロジェクト構造のセットアップ
my-mcp-server/ ├── package.json # Node.js dependencies ├── server.js # Server implementation ├── tools/ # Tool implementations │ ├── database.js │ └── analysis.js ├── test/ # Tests │ └── tools.test.js ├── Dockerfile # Container packaging └── README.md # Setup instructions
期待結果:
server.js(またはserver.R)、package.json、モジュラーなツール実装のためのtools/ディレクトリ、テスト用のtest/ディレクトリを含むプロジェクトディレクトリが作成される。
失敗時: ディレクトリ構造が実装言語と一致しない場合、適切に調整する。Rサーバーは
tools/の代わりにR/を、test/の代わりにtests/testthat/を使用する場合がある。
ステップ5: サーバーのテスト
stdioでの手動テスト:
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node server.js
Claude Codeへの登録:
claude mcp add my-server stdio "node" "/path/to/server.js"
ツールの表示確認:
Claude Codeセッションを開始し、カスタムツールがリストされ機能することを確認する。
期待結果:
tools/list JSON-RPC呼び出しが正しい名前とスキーマを持つすべての定義されたツールを返す。claude mcp listがサーバーの登録を表示する。Claude Codeセッションからツールが呼び出し可能。
失敗時:
tools/listが空の配列を返す場合、ツールがserver.connect()の前に登録されていない。Claude Codeがサーバーを見つけられない場合、claude mcp addのコマンドパスが絶対パスでバイナリが実行可能であることを確認する。
ステップ6: エラーハンドリングの追加
server.tool("risky_operation", "...", schema, async (params) => { try { const result = await performOperation(params); return { content: [{ type: "text", text: JSON.stringify(result) }], }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true, }; } });
期待結果: 各ツールハンドラーがtry/catchでラップされている。無効な入力はサーバープロセスをクラッシュさせるのではなく、説明的なメッセージ付きの
isError: trueを返す。
失敗時: 不正な入力でサーバーがまだクラッシュする場合、try/catchが非同期操作を含むハンドラー本体全体をラップしていることを確認する。promiseがtryブロック内でawaitされていることを確認する。
ステップ7: 配布用パッケージング
binエントリ付きの
package.jsonを作成する:
{ "name": "my-mcp-server", "version": "1.0.0", "bin": { "my-mcp-server": "./server.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "zod": "^3.22.0" } }
ユーザーはインストールと設定が可能:
npm install -g my-mcp-server claude mcp add my-server stdio "my-mcp-server"
期待結果: サーバーエントリポイントを指す
binエントリ付きのpackage.json。ユーザーはnpm install -gでグローバルインストールし、claude mcp addで登録できる。
失敗時: グローバルインストール後にbinエントリが動作しない場合、
server.jsにシバン行(#!/usr/bin/env node)があり実行可能としてマークされていることを確認する。パッケージ名が既存のnpmパッケージと競合しないことを確認する。
バリデーション
- サーバーがエラーなしで起動する
-
が正しいスキーマを持つすべての定義されたツールを返すtools/list - 各ツールが有効な入力で正しく実行される
- ツールが無効な入力に対して適切なエラーを返す
- サーバーがstdioトランスポートでClaude Codeと動作する
- ツールがClaudeセッションで検出可能かつ使用可能
よくある落とし穴
- ブロッキング操作: MCPサーバーはリクエストを非同期で処理すべき。長時間実行される操作は他のツール呼び出しをブロックする
- エラーハンドリングの欠如: 未処理の例外はサーバーをクラッシュさせる。常にツールハンドラーをtry/catchでラップする
- スキーマの不一致: ツールパラメータスキーマはハンドラーが期待するものと正確に一致しなければならない
- stdioバッファリング: stdioトランスポートを使用する時、出力がフラッシュされることを確認する。Node.jsはデフォルトでstdoutをバッファリングする
- セキュリティ: MCPサーバーはプロセスと同じアクセス権を持つ。入力を慎重に検証する、特にシェルコマンドやデータベースクエリ
関連スキル
-- 構築したサーバーをクライアントに接続するconfigure-mcp-server
-- 接続性の問題をデバッグするtroubleshoot-mcp-connection
-- サーバーをDockerでパッケージングするcontainerize-mcp-server