本文へ移動
AI Dev Lab Japan AI開発ツール、AIコーディングエージェント、M...

TypeScriptでMCPサーバーを作る:Tools・Resources・Promptsの最小構成

TypeScriptでMCPサーバーを作る:Tools・Resources・Promptsの最小構成の要点をタイトルと確認軸で示すアイキャッチ

3行まとめ

このテーマをもう少し広げて見るなら、MCP更新で壊さないための確認手順:仕様・SDK・認可・Toolsの見方Chrome DevTools MCPでAIエージェントに性能調査を任せる:Performance・Network・Console・Lighthouseの分け方 も合わせて確認してください。最小サーバーを作った後に、仕様、SDK、認可、Toolsの更新確認を運用手順へ入れやすいため。

VisualMCP最小サーバーの3要素Tools、Resources、Promptsを分けて小さく確認します。
Tools

AIが実行できる関数として設計します。

Resources

文脈として読ませる情報を分けます。

Prompts

ユーザーが選ぶ作業型として扱います。

最小構成でも、実行、参照、依頼テンプレートを混ぜないことが大事です。

  • MCPサーバーの最小構成は、AIが実行するTools、文脈として読むResources、ユーザーが選ぶPromptsを分けて設計すると実務に持ち込みやすいです。
  • 2026年5月28日時点では、検証に使った安定版のnpmパッケージは@modelcontextprotocol/sdk@1.29.0です。分割パッケージの@modelcontextprotocol/server2.0.0-alpha.2だったため、この記事では本番導入前提の最小実装としてv1系を使いました。
  • MCPは便利な接続口ですが、ローカルサーバーはクライアントと同じ権限で動きます。まずread-only、stdio、明示的な承認、秘密情報をログに出さない方針から始めるのが安全です。

本文の事実確認には、公式ドキュメント、公式ヘルプ、関連する仕様・SDKドキュメントを使っています。実リポジトリでの性能ベンチマークや更新代行は、本文で明記した場合を除き実施していません。

この記事でわかること

Visual読後に確認できる範囲TypeScriptでMCPサーバーを作る入口を整理します。
最小実装

stdio型サーバーの構成を確認できます。

3要素

Tools、Resources、Promptsの役割を分けられます。

検証

クライアントからlist/call/read/getを確認できます。

安全策

read-only、承認、ログの注意点を見られます。

MCPの入口を、小さく動く構成として確認します。

  • TypeScriptでstdio型のMCPサーバーを作る最小手順
  • Tools、Resources、Promptsの役割の分け方
  • MCPクライアントからlistToolscallToollistResourcesreadResourcelistPromptsgetPromptを呼ぶ検証方法
  • v1系SDKとv2 alpha情報をどう扱うか
  • 実務導入前に見るべきセキュリティ、ログ、権限、コストの注意点

前提知識

VisualMCPの基本構成Host、Client、Serverと3種類の提供要素を分けます。
項目内容見方
HostLLMアプリケーション側の親環境です。
ClientServerへ接続する通信単位です。
ServerTools、Resources、Promptsを提供します。
JSON-RPCMCP通信の基本形式として使われます。

どこが実行し、どこが読むのかを分けると安全設計に入れます。

MCPは、LLMアプリケーションと外部データ、外部ツールをつなぐためのプロトコルです。公式仕様の最新版は、検証時点で2025-11-25でした。仕様では、MCPがJSON-RPC 2.0を使い、Host、Client、Serverの関係で通信すること、Server側がResources、Prompts、Toolsを提供できることが説明されています。

実務で混乱しやすいのは、Tools、Resources、Promptsを全部「AIに渡す機能」としてまとめてしまうことです。公式仕様上も役割は分かれています。

部品主な役割実務での例注意点
Toolsモデルが呼び出せる関数Issue検索、テスト実行、差分解析、チケット起票副作用がある処理は承認と権限分離が必要
Resourcesクライアントが読める文脈データ設定、ドキュメント、DBスキーマ、Issue本文まずread-onlyにする。秘密情報を混ぜない
Promptsユーザーが選べるプロンプトテンプレートコードレビュー依頼、障害調査、リリース判定暗黙の命令ではなく、選択可能な作業型として扱う

今回の検証では、AIが実行する処理をestimate_issueというToolにし、参照情報をapp://runtime/summaryというResourceにし、レビュー依頼の型をreview-planというPromptにしました。社内ツール連携の前に、この3つを小さく通しておくと、MCPサーバーの責務が見えやすくなります。

なぜ今回はv1系SDKを使うのか

VisualSDK選定時の確認項目latestだけでなく安定性と実装対象を確認します。
項目内容見方
versionnpmのlatestとdist-tagsを確認します。
alphaalpha版は本番前提の記事では扱いを分けます。
docs公式ドキュメントとサンプルの対応を見ます。
scopeこの記事で扱う実装範囲を固定します。

動きが速い領域では、確認日と利用バージョンを残します。

MCP TypeScript SDKの周辺は動きが速く、ここは必ず確認してから記事や社内手順に落とすべきです。今回、実装前に以下を確認しました。

npm view @modelcontextprotocol/sdk version dist-tags --json

結果は次の通りです。

{
  "version": "1.29.0",
  "dist-tags": {
    "latest": "1.29.0"
  }
}

一方で、分割パッケージ側も確認しました。

npm view @modelcontextprotocol/server version dist-tags --json

結果は次の通りです。

{
  "version": "2.0.0-alpha.2",
  "dist-tags": {
    "latest": "2.0.0-alpha.2",
    "alpha": "2.0.0-alpha.2"
  }
}

TypeScript SDKのv1ドキュメント@modelcontextprotocol/sdkを使う手順を示しています。v2側のドキュメントは開発中の扱いで、分割パッケージのREADMEにも新しい構成が出ています。本番導入やチーム内テンプレートにするなら、alphaを前提にするより、まず安定版のv1系でTools、Resources、Promptsの境界を固めるのが現実的です。

もちろん、この記事の判断は2026年5月28日時点のものです。SDKのmajor更新、MCP仕様の改定、クライアント側の対応状況は変わります。社内の導入手順に入れる場合は、npm viewと公式リリースノートをCIや更新チェックリストに含めてください。

実験内容

Visual最小検証の流れサーバーを作り、クライアントから基本操作を確認します。
  1. 1Server

    Tools、Resources、Promptsを定義します。

  2. 2Client

    stdioでサーバーへ接続します。

  3. 3List

    提供要素を一覧取得します。

  4. 4Call/Read/Get

    Tool実行、Resource読取、Prompt取得を確認します。

最小構成では、できることを広げるより通信の往復を確認します。

やることは4つです。

  1. @modelcontextprotocol/sdk@1.29.0でstdioのMCPサーバーを作る。
  2. Toolとしてestimate_issueを登録し、Issue実装リスクとレビュー項目を返す。
  3. Resourceとして固定URIとテンプレートURIを登録し、read-onlyの参照データを返す。
  4. Promptとしてreview-planを登録し、AI生成コードのレビュー依頼文を返す。

HTTPではなくstdioを使う理由は、ローカル開発の最小検証に向いているからです。最新仕様のTransportsでは標準の通信方法としてstdioとStreamable HTTPが説明されています。stdioではクライアントがサーバーをサブプロセスとして起動し、標準入出力でJSON-RPCメッセージをやり取りします。重要なのは、サーバーがstdoutにMCPメッセージ以外を書かないことです。ログはstderrへ出す設計にしておくと、MCP通信を壊しにくくなります。

セットアップ

Visualセットアップで固定するもの環境差分で迷わないように最小条件を分けます。
項目内容見方
Node.js利用するruntimeの前提を確認します。
packageSDKバージョンを固定します。
script実行コマンドをpackage.jsonに置きます。
stdioまずはローカル通信で検証します。

セットアップは、後から再現できる形で残します。

検証用ディレクトリを作ります。

mkdir -p mcp-ts-minimal/src
cd mcp-ts-minimal
npm init -y
npm install @modelcontextprotocol/sdk@1.29.0 zod
npm install -D typescript tsx @types/node

package.jsonは次の形です。記事の検証ではバージョンを固定しました。

{
  "name": "mcp-ts-minimal-verification",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "smoke": "tsx src/client-smoke.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.29.0",
    "zod": "^4.1.12"
  },
  "devDependencies": {
    "@types/node": "^22.15.3",
    "tsx": "^4.19.4",
    "typescript": "^5.8.3"
  }
}

tsconfig.jsonです。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src/**/*.ts"]
}

MCPサーバー本体

Visualサーバーに持たせる責務最小サーバーでも責務を分けて実装します。
Tool

入力schemaと実行結果を定義します。

Resource

読み取り用の文脈データを返します。

Prompt

作業依頼の型を返します。

Transport

stdioで接続します。

小さな実装でも、将来の権限設計を見据えて分けます。

src/server.tsを作ります。

import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod/v4';

const server = new McpServer({
  name: 'ai-dev-lab-minimal-mcp',
  version: '0.1.0',
});

server.registerResource(
  'runtime-summary',
  'app://runtime/summary',
  {
    title: 'Runtime summary',
    description: 'Read-only metadata for the sample MCP server.',
    mimeType: 'application/json',
  },
  async (uri) => ({
    contents: [
      {
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(
          {
            service: 'ai-dev-lab-minimal-mcp',
            permission: 'read-only sample',
            verifiedAt: '2026-05-28',
          },
          null,
          2,
        ),
      },
    ],
  }),
);

server.registerResource(
  'ticket-summary',
  new ResourceTemplate('ticket://{ticketId}/summary', { list: undefined }),
  {
    title: 'Ticket summary',
    description: 'Synthetic ticket summary. Replace this with a read-only issue API.',
    mimeType: 'text/plain',
  },
  async (uri, { ticketId }) => ({
    contents: [
      {
        uri: uri.href,
        mimeType: 'text/plain',
        text: `Ticket ${String(ticketId)} is ready for AI-assisted review.`,
      },
    ],
  }),
);

server.registerTool(
  'estimate_issue',
  {
    title: 'Estimate issue risk',
    description: 'Estimate implementation risk from a small issue summary.',
    inputSchema: {
      title: z.string().min(1),
      filesChanged: z.number().int().nonnegative().default(0),
      hasMigration: z.boolean().default(false),
    },
    outputSchema: {
      risk: z.enum(['low', 'medium', 'high']),
      checklist: z.array(z.string()),
    },
  },
  async ({ title, filesChanged, hasMigration }) => {
    const risk = hasMigration ? 'high' : filesChanged >= 6 ? 'medium' : 'low';
    const checklist = [
      `Confirm scope: ${title}`,
      'Run unit tests for touched modules',
      'Review generated code before merge',
    ];

    if (hasMigration) {
      checklist.push('Require human approval for migration and rollback plan');
    }

    const structuredContent = { risk, checklist };
    return {
      structuredContent,
      content: [
        {
          type: 'text',
          text: JSON.stringify(structuredContent, null, 2),
        },
      ],
    };
  },
);

server.registerPrompt(
  'review-plan',
  {
    title: 'Review plan',
    description: 'Create a human review prompt for AI-generated code.',
    argsSchema: {
      diffSummary: z.string().min(1),
      risk: z.enum(['low', 'medium', 'high']).default('medium'),
    },
  },
  ({ diffSummary, risk }) => ({
    messages: [
      {
        role: 'user',
        content: {
          type: 'text',
          text: [
            'AI生成コードのレビュー計画を作ってください。',
            `リスク: ${risk}`,
            `差分概要: ${diffSummary}`,
            '品質、セキュリティ、テスト、運用影響を分けて確認してください。',
          ].join('\n'),
        },
      },
    ],
  }),
);

const transport = new StdioServerTransport();
await server.connect(transport);

このサーバーは外部APIにもファイルシステムにも触りません。ticket://{ticketId}/summaryはテンプレートResourceの形だけを示すためのダミーです。実務でGitHub IssueやLinear、Jiraへ接続する場合も、最初は読み取り専用APIだけをつなぎ、書き込みや削除は別Toolに分離します。

スモークテスト用クライアント

Visualクライアント確認の順序接続後に基本操作を順番に確認します。
  1. 1connect

    stdio transportで接続します。

  2. 2listTools

    Tool一覧を確認します。

  3. 3callTool

    Tool実行結果を確認します。

  4. 4readResource

    Resourceの内容を読みます。

  5. 5getPrompt

    Promptテンプレートを取得します。

最初のテストは、広い機能より往復の成功を見ます。

MCP InspectorでUIから試す方法もありますが、記事では再現しやすいようにSDKのClientから呼び出しました。MCP Inspectorの公式ドキュメントにも、Tools、Resources、Promptsを確認できることが説明されています。チームで手動QAをするならInspector、CIや記事検証に残すなら小さなクライアントの両方を持つのが便利です。

src/client-smoke.tsです。

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import path from 'node:path';
import process from 'node:process';

const client = new Client({
  name: 'ai-dev-lab-smoke-client',
  version: '0.1.0',
});

const transport = new StdioClientTransport({
  command: process.execPath,
  args: ['--import', 'tsx', path.resolve('src/server.ts')],
});

await client.connect(transport);

const tools = await client.listTools();
const toolResult = await client.callTool({
  name: 'estimate_issue',
  arguments: {
    title: 'Add read-only GitHub issue lookup',
    filesChanged: 4,
    hasMigration: false,
  },
});
const resources = await client.listResources();
const runtime = await client.readResource({ uri: 'app://runtime/summary' });
const ticket = await client.readResource({ uri: 'ticket://AI-42/summary' });
const prompts = await client.listPrompts();
const prompt = await client.getPrompt({
  name: 'review-plan',
  arguments: {
    diffSummary: 'MCP server adds one read-only tool and one resource.',
    risk: 'medium',
  },
});

const contentText = (content: (typeof runtime.contents)[number] | undefined) =>
  content && 'text' in content ? content.text : '';

console.log(
  JSON.stringify(
    {
      tools: tools.tools.map((tool) => tool.name),
      toolStructuredContent: toolResult.structuredContent,
      resources: resources.resources.map((resource) => resource.uri),
      runtimeText: contentText(runtime.contents[0]),
      ticketText: contentText(ticket.contents[0]),
      prompts: prompts.prompts.map((item) => item.name),
      promptPreview: prompt.messages[0]?.content,
    },
    null,
    2,
  ),
);

await client.close();

ポイントは、サーバーをtsx src/server.tsとして直接起動していることです。本番運用ではビルド済みJSを起動したほうが起動時間や依存関係の見通しはよくなります。ローカル検証では、この形でもTools、Resources、Promptsの疎通確認には十分でした。

実行結果

Visual結果で確認すること成功ログから何を判断するかを分けます。
一覧取得

Tool、Resource、Promptが見えているか確認します。

実行

Toolが期待した形式で返るか見ます。

読取

Resourceが安全な文脈だけ返すか見ます。

Prompt

作業依頼の型として使えるか見ます。

実行できたことと、本番で許可してよいことは分けて判断します。

依存関係のインストール後、npm list --depth=0 --jsonで主要パッケージを確認しました。

{
  "dependencies": {
    "@modelcontextprotocol/sdk": {
      "version": "1.29.0"
    },
    "@types/node": {
      "version": "22.19.19"
    },
    "tsx": {
      "version": "4.22.3"
    },
    "typescript": {
      "version": "5.9.3"
    },
    "zod": {
      "version": "4.4.3"
    }
  }
}

TypeScriptの型チェックです。

npm run build

結果は終了コード0でした。途中で一度、Resourceの戻り値がtextまたはblobのunionとして扱われるため、runtime.contents[0]?.textをそのまま読むコードが型エラーになりました。最終版では'text' in contentで判定するcontentText関数を入れて通しています。MCPのResourceはテキストだけでなくバイナリも返せるため、クライアント側は最初からunionを意識したほうが安全です。

スモークテストです。

npm run smoke

結果は次の通りです。

{
  "tools": [
    "estimate_issue"
  ],
  "toolStructuredContent": {
    "risk": "low",
    "checklist": [
      "Confirm scope: Add read-only GitHub issue lookup",
      "Run unit tests for touched modules",
      "Review generated code before merge"
    ]
  },
  "resources": [
    "app://runtime/summary"
  ],
  "runtimeText": "{\n  \"service\": \"ai-dev-lab-minimal-mcp\",\n  \"permission\": \"read-only sample\",\n  \"verifiedAt\": \"2026-05-28\"\n}",
  "ticketText": "Ticket AI-42 is ready for AI-assisted review.",
  "prompts": [
    "review-plan"
  ],
  "promptPreview": {
    "type": "text",
    "text": "AI生成コードのレビュー計画を作ってください。\nリスク: medium\n差分概要: MCP server adds one read-only tool and one resource.\n品質、セキュリティ、テスト、運用影響を分けて確認してください。"
  }
}

確認できたことは3つです。

  • listToolsestimate_issueが見える。
  • callToolで構造化されたriskchecklistが返る。
  • readResourcegetPromptで、参照データとプロンプトテンプレートを取得できる。

テンプレートResourceのticket://AI-42/summaryも読めています。ただしlistResourcesには固定Resourceのapp://runtime/summaryだけが出ています。今回のResourceTemplatelist: undefinedにしているため、列挙ではなくURIを指定して読む使い方です。Issue IDやユーザーIDのように候補一覧を出したくない情報では、この挙動のほうが扱いやすい場面があります。

Tools、Resources、Promptsの設計メモ

Visual3要素の設計境界役割ごとに注意点を変えます。
項目内容見方
Tools副作用、承認、入力schemaを確認します。
Resourcesread-only、秘密情報、更新頻度を見ます。
Prompts暗黙命令ではなく選択可能な型にします。
共通ログ、権限、エラー時の戻し方を決めます。

同じServer内でも、実務上のリスクは要素ごとに違います。

Toolは「実行」と「権限」を一緒に設計する

条件

最初のToolはread-onlyに限定し、対象システム、入力schema、timeout、レスポンスサイズ、実行ログを決めてから登録します。

注意点

Tool名やdescriptionはモデルへの案内であり、権限境界そのものではありません。書き込み、削除、送信、デプロイのような副作用は、承認IDや監査ログを持つ別Toolに分けます。

Tools仕様では、Toolはモデルが発見して呼び出せるものとして説明されています。つまり、Toolにした瞬間に「モデルが呼ぶ可能性がある関数」になります。

今回のestimate_issueは計算だけなので副作用がありません。実務で危ないのは、次のような処理を同じ感覚でTool化することです。

  • DB更新
  • Issueのステータス変更
  • Pull Requestへのコメント投稿
  • デプロイやロールバック
  • ファイル削除や一括置換

これらは、Tool名、description、入力スキーマだけで安全になるわけではありません。MCPの仕様でも、Toolの安全性やユーザー同意は重要な前提として扱われています。最初はsearch_issuesget_issuelist_pull_requestsのようなread-only Toolから始め、書き込み系はdryRun、承認ID、監査ログ、ロールバック手順を持つ別Toolに分けるのがよいです。

Resourceは「読ませたい文脈」に絞る

根拠

ResourceはAIの文脈へ入り得る情報です。read-onlyであっても、秘密情報、顧客情報、内部構造、障害ログをそのまま渡すと、利用者の意図しない範囲まで文脈が広がります。

評価基準

Resource候補は、公開情報、社内公開情報、制限付き情報、禁止情報に分け、URI単位で許可リストを作れるかを確認します。

Resources仕様では、Resourcesはファイル、DBスキーマ、アプリケーション固有情報などをクライアントに提供するものとして説明されています。実務では、ここに何でも詰め込みたくなります。

しかし、ResourceはAIの文脈に入り得るデータです。社内の設定、顧客情報、秘密情報、未公開の事業情報を雑に入れると、利用者の意図しない形でモデルや外部サービスに渡る可能性があります。まずは次のように分類します。

Resource候補最初の扱い理由
公開README秘密情報が少なく、文脈として有用
Issue本文条件付き非公開情報や顧客名を含む場合がある
DBスキーマ条件付きテーブル名だけでも内部構造が出る
.env不可秘密情報の混入リスクが高い
顧客ログ原則不可個人情報、契約情報、障害情報を含みやすい

Resourcesを実務投入するなら、URI単位で許可リストを作る、レスポンスから秘密情報を除去する、ログに全文を残さない、取得元APIの認可をサーバー側で確認する、といった制御が必要です。

Promptは「チームの作業型」にする

確認項目

Promptには、目的、入力として必要な情報、出力形式、禁止事項、人間レビューへ戻す条件を含めます。

注意点

Promptは暗黙の安全策ではありません。権限、承認、ログ、CI、ブランチ保護は別の仕組みで担保し、Promptは作業の型をそろえるために使います。

Prompts仕様では、Promptsはユーザーが選べるテンプレートとして説明されています。ここは、チームの開発ルールをAIクライアントに持ち込むのに向いています。

今回のreview-planは、差分概要とリスクを受け取り、品質、セキュリティ、テスト、運用影響を分けてレビューする依頼文を返します。これを拡張すると、次のようなPromptにできます。

  • security-reviewで認証、認可、入力検証、ログ出力を確認する。
  • migration-reviewでDB変更、ロールバック、データ移行を確認する。
  • release-noteでユーザー向け変更点と運用向け変更点を分ける。
  • incident-debugで再現条件、影響範囲、暫定対応、恒久対応を整理する。

AIコーディングエージェントをチームで使うなら、Promptは個人のプロンプト職人芸にしないほうが安定します。AGENTS.mdやCursor Rules、Claude用のプロジェクトルールと合わせて、共通の作業型として管理するのがおすすめです。運用ルールの整理はチーム向けAGENTS.mdテンプレートと合わせて見ると、Prompt、権限、テスト、レビュー基準を分けやすくなります。

良かった点

Visual最小構成で見えた利点小さく作ることで確認しやすい点です。
責務

Tools、Resources、Promptsを分けて理解できます。

再現性

stdioならローカルで試しやすいです。

拡張

後から権限や承認を追加しやすくなります。

最小実装は、理解とレビューのための足場になります。

まず、最小構成でもTools、Resources、Promptsの境界がはっきりします。Toolは実行、Resourceは参照、Promptは作業型です。この分離ができると、AIエージェントに何を許可しているのか説明しやすくなります。

次に、stdioはローカル検証が楽です。HTTPサーバー、認証、Origin検証を考える前に、MCPの基本的な登録と呼び出しを確認できます。VS Code、Claude Desktop、Cursorなどのクライアントに接続する前に、SDKのClientでスモークテストを持っておくと、問題がサーバー側かクライアント設定側か切り分けやすくなります。

もう1つは、Zodで入力スキーマを書けることです。Toolの引数を型と実行時バリデーションの両方で扱えます。今回のようにfilesChangedを非負整数にし、hasMigrationにdefaultを付けるだけでも、AIから来る曖昧な入力を少し狭められます。

失敗した点

Visual実装時に詰まりやすい点MCP周辺で起きやすい混乱を分けます。
version差

SDKや分割パッケージの状態を混同しやすいです。

役割混同

ToolとResourceを同じものとして扱いがちです。

権限過多

最初からwrite操作を入れると危険です。

ログ不足

実行結果やエラー原因を追いにくくなります。

詰まりどころを先に知ると、検証範囲を小さく保てます。

1つ目は、Resourceレスポンスをテキスト前提で読むとTypeScriptの型チェックに落ちたことです。MCPのResourceはtextだけでなくblobも返せるので、クライアント側は最初から両方を考える必要があります。記事の最終コードではcontentTexttextプロパティの有無を見ています。

2つ目は、SDK情報の更新が速いことです。公式GitHubのmainブランチやv2ドキュメントを見ると、分割パッケージの説明が出ています。一方、npmの安定版@modelcontextprotocol/sdkはv1.29.0でした。検索で見つけた古い記事や古いコードをそのまま使うと、import pathやパッケージ名が混ざる可能性があります。記事、社内テンプレート、READMEには検証日とパッケージバージョンを必ず残すべきです。

3つ目は、stdioのログ出力です。今回のサンプルではログを出していませんが、実務コードではconsole.logを不用意に入れがちです。stdioのMCPサーバーではstdoutがプロトコルメッセージに使われます。デバッグログはstderrに寄せ、構造化ログに秘密情報を含めない方針を最初に決めてください。

エラーハンドリングの考え方

Visualエラー時に返す情報失敗しても調査できる形にします。
  1. 1分類

    入力不備、権限不足、外部失敗を分けます。

  2. 2message

    ユーザーに見せる説明を短く返します。

  3. 3log

    調査用ログには識別子を残します。

  4. 4retry

    再試行可能かどうかを分けます。

Tool失敗時は、モデルが勝手に埋めない情報を返します。

Toolの中で業務エラーが起きた場合は、例外をそのまま落とすより、クライアントとモデルが扱える形で返すほうが実用的です。たとえば読み取り対象のIssueが見つからない、外部APIが一時的に失敗した、権限が足りない、といったケースです。

server.registerTool(
  'read_issue',
  {
    description: 'Read one issue from an approved tracker.',
    inputSchema: {
      issueId: z.string().min(1),
    },
  },
  async ({ issueId }) => {
    try {
      const issue = await readIssueFromApprovedSource(issueId);
      return {
        content: [{ type: 'text', text: JSON.stringify(issue, null, 2) }],
      };
    } catch (error) {
      return {
        isError: true,
        content: [
          {
            type: 'text',
            text: 'Issueを取得できませんでした。ID、権限、接続状態を確認してください。',
          },
        ],
      };
    }
  },
);

この例のreadIssueFromApprovedSourceは擬似関数です。重要なのは、エラー文にアクセストークン、接続文字列、内部URL、顧客名を入れないことです。AIクライアントに返るエラーは、そのままチャット画面やログ、外部モデルに渡る可能性があります。

セキュリティとコストの注意

Visual実務導入前の注意ローカルサーバーでも権限と費用を確認します。
項目内容見方
read-only最初は読み取り中心で始めます。
secretログやResourceに秘密情報を混ぜません。
approval副作用のあるToolは承認を入れます。
costLLM呼び出し、外部API、運用監視の費用を見ます。

MCPは接続口なので、便利さと同時に権限境界を設計します。

MCPの公式Security Best Practicesでは、ローカルMCPサーバーのリスクや、HTTP利用時のDNS rebinding、OAuth proxy、セッション、スコープ最小化などが扱われています。この記事の最小サンプルは外部接続を持ちませんが、実務ではここを避けて通れません。

最低限、次のルールを置いてから社内検証に進むのがよいです。

  • 最初のMCPサーバーはread-onlyにする。
  • 書き込み、削除、デプロイ、課金に影響するToolは人間承認を必須にする。
  • ローカルサーバーの起動コマンドを省略表示しない。レビュー時に実行コマンド全文を確認する。
  • HTTP transportを使う場合はOrigin検証、認証、localhost限定、セッションIDの扱いを設計する。
  • Tool descriptionやResourceの内容を信頼しすぎない。信頼済みサーバー以外から来たメタデータは未検証入力として扱う。
  • 秘密情報はResource、Prompt、Tool結果、ログに入れない。
  • LLM API、MCPクライアント、外部APIの料金を分けて見積もる。

コスト面では、MCP SDK自体よりも、接続先と利用頻度が効きます。たとえばIssue本文、PR差分、ログ、DBスキーマを毎回Resourceとして読ませると、LLM側の入力トークンが増えます。MCPサーバー側で要約済みResourceを用意する、ページングする、必要なURIだけ読ませる、Tool結果を短くする、といった工夫が必要です。

実務で使うなら

Visual導入ステップ小さなMCPサーバーから業務利用へ広げる順序です。
  1. 最小Server

    read-only Resourceと安全なToolで始めます。

  2. Client検証

    list/call/read/getを確認します。

  3. 権限設計

    許可する操作と承認条件を決めます。

  4. 監査

    実行ログと失敗時の調査方法を整えます。

最初から広い社内連携にせず、確認できた範囲だけ増やします。

小規模チームで最初に作るなら、次の順番がおすすめです。

  1. get_project_policyruntime-summaryのようなread-only Resourceを作る。
  2. search_issuesget_issueのような読み取りToolを作る。
  3. review-planrelease-checkのようなPromptを作る。
  4. SDK ClientかInspectorで疎通テストを保存する。
  5. AIコーディングエージェントに接続する前に、起動コマンド、権限、ログ、秘密情報の扱いをレビューする。
  6. 書き込みToolは別サーバーまたは別設定に分け、承認フローと監査ログを入れてから試す。

AIに任せるIssueの粒度も重要です。MCPサーバーだけ整えても、Issueに受け入れ条件、禁止事項、テスト条件、セキュリティ制約が書かれていなければ、AIエージェントは危ない方向に広げてしまいます。実装前の確認事項はCodexに実装前の確認事項を5つ質問させるのように、対象範囲、期待挙動、制約、テスト、承認条件へ分けると効果が出やすいです。

FAQ

Visualよくある疑問MCPサーバー作成前に迷いやすい点です。
v1かv2か

本番前提では安定版と公式情報を確認します。

Tool優先か

まずResourceとread-only Toolから始めます。

HTTP化

stdioで責務を確認してから検討します。

迷ったら、小さく動く構成と権限境界へ戻ります。

Streamable HTTPではなくstdioでよいですか

ローカルのAI開発ツールに接続する最小検証ならstdioで十分です。リモートサーバーとして複数クライアントから使うならStreamable HTTPを検討します。ただしHTTPにした瞬間に、Origin検証、認証、セッション、CORS、ネットワーク境界の設計が必要になります。

Toolsだけ作ればよいですか

最初はToolsだけでも動きます。ただ、実務ではResourcesとPromptsを分けたほうが運用しやすいです。参照データをTool呼び出しに押し込むと、AIが「読むだけ」の操作と「実行する」操作を区別しにくくなります。

v2 alphaを使うべきですか

新機能を追う検証ならありです。本番導入やチームの標準テンプレートなら、この記事の検証時点ではv1系の安定版を基準にするほうが無難でした。v2のAPIやパッケージ構成は公式ドキュメントとリリースノートで必ず再確認してください。

MCPサーバーにAPIキーを渡してもよいですか

必要な場合はありますが、記事やログ、Prompt、Resourceには出さないでください。環境変数やシークレット管理から読み、Tool結果やエラーメッセージに含めない実装にします。ローカル検証でも、ダミー値を使うほうが安全です。

まとめ

Visual最小構成で持ち帰る要点MCPサーバーを実務へ近づけるための最終確認です。
分ける

Tools、Resources、Promptsを混ぜません。

小さく試す

stdioとスモークテストで確認します。

権限を絞る

read-onlyと承認から始めます。

更新を追う

仕様とSDKの確認日を残します。

MCPは最初の小さな設計が、その後の安全性を左右します。

TypeScriptでMCPサーバーを作る最小構成は難しくありません。McpServerを作り、Tool、Resource、Promptを登録し、StdioServerTransportで接続すれば、ローカルのスモークテストまで進められます。

実務で大事なのは、動くことよりも境界を決めることです。Toolは実行、Resourceは参照、Promptは作業型。ここを分けてからGitHub、DB、チケット管理、社内ドキュメントにつなぐと、権限レビューやログ設計がしやすくなります。

今回のサンプルはread-only中心です。次に進むなら、GitHub Issueを読み取るだけのMCP Starter Kitを作り、AIコーディングエージェントに渡すIssueテンプレートと一緒に検証するのが自然です。

関連資料と導線

Visual次に確認する資料仕様、SDK、実装例を分けて読みます。
仕様

ProtocolとCapabilitiesを確認します。

SDK

TypeScript SDKのREADMEとexamplesを見ます。

実装

自社のTool権限とログ設計へ落とします。

資料を読むときも、仕様、SDK、運用設計を分けます。

この記事ではスポンサー、アフィリエイト、無償提供、検証環境提供はありません。検証はローカル環境で実施しました。

AI Dev Lab Japanでは、MCP Starter Kit、Issueテンプレート、AI開発ワークフローテンプレートを順次整理しています。無料版は記事内の再現手順として公開し、有料版を出す場合は無料版との差分と用途を明記します。更新通知だけ追いたい方は、記事読了後のタイミングでNewsletterに登録しておくと、SDKやMCP仕様の変更をまとめて確認できます。

次に読むなら


次に読むなら

参照した主な情報源

Visual確認した一次情報仕様、SDK、npm情報を分けて確認します。
MCP仕様

Protocol、Tools、Resources、Promptsを確認します。

TypeScript SDK

README、examples、APIの使い方を確認します。

npm

公開バージョンとdist-tagsを確認します。

MCP周辺は更新が速いため、確認日と対象バージョンを残します。

更新履歴

Visual記事の確認履歴MCP関連情報は更新が速いため確認日を残します。
  1. 2026年5月28日

    MCP仕様とTypeScript SDKのnpm情報を確認して初版を作成しました。

導入時には最新の仕様とSDKバージョンを再確認します。

  • 2026年5月28日:@modelcontextprotocol/sdk@1.29.0、MCP仕様2025-11-25、Node.js v25.9.0で初回検証。WordPress下書きとして作成。