MCP TypeScript SDK は完全な MCP (Model Context Protocol) 仕様を実装しています。MCP はアプリケーションがコンテキストを LLM に提供する方法を標準化するオープン・プロトコルです。MCP はアプリケーションが標準化された方法でコンテキストを LLM に提供することを可能にし、コンテキストの提供の問題点を実際の LLM インタラクションから分離します。
MCP TypeScript SDK : 概要
作成 : クラスキャット・セールスインフォメーション
作成日時 : 05/02/2025
* 本記事は github: modelcontextprotocol/python-sdk の以下のページを独自に翻訳した上でまとめ直し、補足説明を加えています :
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
◆ お問合せ : 下記までお願いします。
- クラスキャット セールス・インフォメーション
- sales-info@classcat.com
- ClassCatJP
MCP TypeScript SDK : 概要
概要
Model Context Protocol はアプリケーションが標準化された方法でコンテキストを LLM に提供することを可能にし、コンテキストの提供の問題点を実際の LLM インタラクションから分離します。この TypeScript SDK は完全な MCP 仕様を実装し、以下を簡単にします :
- 任意の MCP サーバに接続可能な MCP クライアントの構築
- リソース、プロンプトとツールを公開する MCP サーバの作成
- stdio と Streamable HTTP のような標準トランスポートの使用
- すべての MCP プロトコルメッセージとライフサイクル・イベントの処理
インストール
npm install @modelcontextprotocol/sdk
クイックスタート
計算機ツールと幾つかのデータを公開する単純な MCP サーバを作成しましょう :
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// Add an addition tool
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
What is MCP?
Model Context Protocol (MCP) は、LLM アプリケーションにデータと機能を安全に、標準化された方法で公開するサーバの構築を可能にします。LLM とのインタラクションに特別に設計された、web API のようなものと考えてください。MCP サーバは以下のことができます :
- リソース (これらを GET エンドポイントのようなものと考えてください ; 情報を LLM のコンテキストにロードするために使用されます) を通してデータを公開する
- ツール (POST エンドポイントのようなものです ; コードを実行したり、そうでないなら副作用を生成するために使用されます) を通して機能を提供する
- プロンプト (LLM インタラクション用の再利用可能なテンプレート) を通してインタラクション・パターンを定義する
- And more!
中核コンセプト
サーバ
McpServer は MCP プロトコルへのコア・インターフェイスです。接続管理、プロトコル準拠、そしてメッセージ・ルーティングを処理します :
const server = new McpServer({
name: "My App",
version: "1.0.0"
});
リソース
リソースはデータを LLM に公開する方法です。それらは REST API の GET エンドポイントに類似しています – それらはデータを提供しますが、大きな計算を実行したり副作用を持つべきではありません :
// Static resource
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
text: "App configuration here"
}]
})
);
// Dynamic resource with parameters
server.resource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
async (uri, { userId }) => ({
contents: [{
uri: uri.href,
text: `Profile data for user ${userId}`
}]
})
);
ツール
ツールは LLM がサーバを通してアクションを実行することを可能にします。リソースとは異なり、ツールは計算を実行したり副作用を持つことが想定されます :
// Simple tool with parameters
server.tool(
"calculate-bmi",
{
weightKg: z.number(),
heightM: z.number()
},
async ({ weightKg, heightM }) => ({
content: [{
type: "text",
text: String(weightKg / (heightM * heightM))
}]
})
);
// Async tool with external API call
server.tool(
"fetch-weather",
{ city: z.string() },
async ({ city }) => {
const response = await fetch(`https://api.weather.com/${city}`);
const data = await response.text();
return {
content: [{ type: "text", text: data }]
};
}
);
プロンプト
プロンプトは再利用可能なテンプレートで、LLM がサーバと効果的にやり取りするのに役立ちます :
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`
}
}]
})
);
サーバの実行
TypeScript の MCP サーバはクライアントと通信するためにトランスポートに接続される必要があります。サーバの起動方法はトランスポートの選択に依存します :
stdio
コマンドラインツールと直接的な統合の場合 :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "example-server",
version: "1.0.0"
});
// ... set up server resources, tools, and prompts ...
const transport = new StdioServerTransport();
await server.connect(transport);
Streamable HTTP
リモートサーバについては、クライアントのリクエストとサーバ-to-クライアントの通知の両方を処理できる、Streamable HTTP トランスポートをセットアップします。
With Session Management
ある場合には、サーバはステートフルである必要があります。これは セッション管理 により実現されます。
import express from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"
const app = express();
app.use(express.json());
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
// Handle POST requests for client-to-server communication
app.post('/mcp', async (req, res) => {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
// Store the transport by session ID
transports[sessionId] = transport;
}
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
const server = new McpServer({
name: "example-server",
version: "1.0.0"
});
// ... set up server resources, tools, and prompts ...
// Connect to the MCP server
await server.connect(transport);
} else {
// Invalid request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided',
},
id: null,
});
return;
}
// Handle the request
await transport.handleRequest(req, res, req.body);
});
// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);
// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);
app.listen(3000);
Without Session Management (ステートレス)
セッション管理が不要な単純なユースケースについては :
const app = express();
app.use(express.json());
app.post('/mcp', async (req: Request, res: Response) => {
// In stateless mode, create a new instance of transport and server for each request
// to ensure complete isolation. A single instance would cause request ID collisions
// when multiple clients connect concurrently.
try {
const server = getServer();
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on('close', () => {
console.log('Request closed');
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});
app.get('/mcp', async (req: Request, res: Response) => {
console.log('Received GET MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});
app.delete('/mcp', async (req: Request, res: Response) => {
console.log('Received DELETE MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});
// Start the server
const PORT = 3000;
app.listen(PORT, () => {
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
});
このステートレスなアプローチは以下の場合に役立ちます :
- 単純な API ラッパー
- 各リクエストが独立な RESTful シナリオ
- 共有されるセッション状態なしに水平方向にスケールされた配備
テストとデバッグ
サーバをテストするには、MCP Inspector を使用できます。詳細はその README をご覧ください。
例
Echo サーバ
リソース、ツールとプロンプトを実演する単純なサーバ :
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "Echo",
version: "1.0.0"
});
server.resource(
"echo",
new ResourceTemplate("echo://{message}", { list: undefined }),
async (uri, { message }) => ({
contents: [{
uri: uri.href,
text: `Resource echo: ${message}`
}]
})
);
server.tool(
"echo",
{ message: z.string() },
async ({ message }) => ({
content: [{ type: "text", text: `Tool echo: ${message}` }]
})
);
server.prompt(
"echo",
{ message: z.string() },
({ message }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please process this message: ${message}`
}
}]
})
);
SQLite Explorer
データベース統合を示すより複雑な例 :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";
const server = new McpServer({
name: "SQLite Explorer",
version: "1.0.0"
});
// Helper to create DB connection
const getDb = () => {
const db = new sqlite3.Database("database.db");
return {
all: promisify(db.all.bind(db)),
close: promisify(db.close.bind(db))
};
};
server.resource(
"schema",
"schema://main",
async (uri) => {
const db = getDb();
try {
const tables = await db.all(
"SELECT sql FROM sqlite_master WHERE type='table'"
);
return {
contents: [{
uri: uri.href,
text: tables.map((t: {sql: string}) => t.sql).join("\n")
}]
};
} finally {
await db.close();
}
}
);
server.tool(
"query",
{ sql: z.string() },
async ({ sql }) => {
const db = getDb();
try {
const results = await db.all(sql);
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2)
}]
};
} catch (err: unknown) {
const error = err as Error;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
} finally {
await db.close();
}
}
);
以上