인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
개발 및 엔지니어링 · SKILL.md

build-custom-mcp-server

build-custom-mcp-server

AIアシスタントにドメイン固有のツールを公開するカスタムMCP(Model Context Protocol) サーバーを構築する。Node.jsまたはRでのサーバー実装、ツール定義、トランスポート 設定、Claude Codeでのテストをカバーする。mcptoolsが提供する以上のカスタム機能を 公開する必要がある時、特化したドメイン固有のAI統合を構築する時、既存のAPIや サービスをMCPツールとしてラップする時に使用する。

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: build-custom-mcp-server description: > AIアシスタントにドメイン固有のツールを公開するカスタムMCP(Model Context Protocol) サーバーを構築する。Node.jsまたはRでのサーバー実装、ツール定義、トランスポート 設定、Claude Codeでのテストをカバーする。mcptoolsが提供する以上のカスタム機能を 公開する必要がある時、特化したドメイン固有のAI統合を構築する時、既存のAPIや サービスをMCPツールとしてラップする時に使用する。 license: MIT allowed-tools: Read Write Edit Bash Grep Glob metadata: author: Philipp Thoss version: "1.0" domain: mcp-integration complexity: advanced language: multi tags: mcp, server, custom-tools, node-js, protocol locale: ja source_locale: en source_commit: 33b561c9 fence_basis_commit: 33b561c9 translator: claude translation_date: "2026-03-17"

カスタム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 -- 接続性の問題をデバッグする
  • containerize-mcp-server -- サーバーをDockerでパッケージングする
저장한 Skills