はじめに
この記事は、QiitaのModel Context Protocol(以下、MCP)解説シリーズの第13回です。
今回は、MCPの3つのコア機能の最後であるPromptsに焦点を当てます。ResourcesやToolsの出力結果を動的に組み込むことで、柔軟かつ強力なプロンプトを生成する方法を学びます。
💡 なぜプロンプトをテンプレート化するのか?
LLMへの指示(プロンプト)は、多くの場合、固定された部分と、状況に応じて変わる動的な部分(変数)で構成されています。
従来の課題
- 動的なプロンプトを生成する際に、手動で文字列を連結する必要があり、コードが読みにくくなる
- 変数が多いと、プロンプトの構成を把握しにくくなる
- プロンプトの再利用が難しく、同じようなコードが何度も書かれてしまう
MCPでの解決
MCPは、プロンプトをテンプレートとして扱うことを推奨しています。このテンプレートに、ResourcesやToolsから取得したデータを流し込むことで、動的で再利用可能なプロンプトを簡単に生成できます。
📝 実装:テンプレートを使った動的なメール作成
今回は、ユーザーから受け取った情報を元に、顧客への挨拶メールを生成する簡単なMCPサーバーを実装します。
🛠️ プロジェクトのセットアップ
TypeScriptを使って実装します。
mkdir mcp-prompts-example
cd mcp-prompts-example
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript ts-node @types/node
npx tsc --init
💻 サーバーのコード
server.ts
ファイルを作成し、以下のコードを貼り付けてください。
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListToolsRequestSchema,
PromptMessage,
TextContent,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
// ユーザー情報の型定義
interface UserInfo {
name: string;
email: string;
}
// モックデータベース
const mockDb: Record<string, UserInfo> = {
'user_001': { name: '山田 太郎', email: 'yamada@example.com' },
'user_002': { name: '鈴木 花子', email: 'suzuki@example.com' },
'user_003': { name: '佐藤 一郎', email: 'sato@example.com' },
};
// メールテンプレート定義
const EMAIL_TEMPLATES = {
welcome: {
name: "welcome_email",
description: "新規登録ユーザーへのウェルカムメールテンプレート",
arguments: [
{
name: "user_id",
description: "メール送信対象のユーザーID",
required: true
}
],
template: `件名:【ご案内】サービス登録完了のお知らせ
{{user_name}} 様
この度は、弊社のサービスにご登録いただき、誠にありがとうございます。
ご登録いただいたメールアドレスは {{user_email}} です。
本サービスを最大限にご活用いただくため、以下の機能をご利用ください:
• ダッシュボードでのデータ分析
• カスタムレポートの作成
• チームコラボレーション機能
• 24時間365日のサポート
ご不明な点がございましたら、お気軽にお問い合わせください。
今後とも、どうぞよろしくお願い申し上げます。
株式会社MCPテクノロジーズ
サポートチーム`
},
reminder: {
name: "reminder_email",
description: "サービス利用促進のリマインダーメールテンプレート",
arguments: [
{
name: "user_id",
description: "メール送信対象のユーザーID",
required: true
}
],
template: `件名:【リマインド】{{user_name}}様へ - サービスをもっと活用しませんか?
{{user_name}} 様
いつもご利用いただき、ありがとうございます。
最近ログインされていないようですが、いかがお過ごしでしょうか?
新機能が追加されておりますので、ぜひ一度ご確認ください。
ご質問やご要望がございましたら、{{user_email}} 宛にご返信ください。
株式会社MCPテクノロジーズ`
}
};
// 入力スキーマの定義
const GetUserInfoSchema = z.object({
userId: z.string().describe("取得したいユーザーのID")
});
class MCPPromptsServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: "mcp-prompts-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
prompts: {},
},
}
);
this.setupHandlers();
}
private setupHandlers() {
// Toolsリストの取得
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_user_info",
description: "ユーザーIDから、名前とメールアドレスを取得する",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "取得したいユーザーのID"
}
},
required: ["userId"]
}
}
]
}));
// Tool実行の処理
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "get_user_info") {
const parsed = GetUserInfoSchema.parse(args);
const userInfo = mockDb[parsed.userId];
if (!userInfo) {
throw new Error(`ユーザーID ${parsed.userId} が見つかりません`);
}
return {
content: [
{
type: "text",
text: JSON.stringify(userInfo, null, 2)
} as TextContent
]
};
}
throw new Error(`未知のツール: ${name}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "不明なエラー";
return {
content: [
{
type: "text",
text: `エラー: ${errorMessage}`
} as TextContent
],
isError: true
};
}
});
// Promptsリストの取得
this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: Object.values(EMAIL_TEMPLATES).map(template => ({
name: template.name,
description: template.description,
arguments: template.arguments
}))
}));
// Prompt取得の処理
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// テンプレートを検索
const template = Object.values(EMAIL_TEMPLATES).find(t => t.name === name);
if (!template) {
throw new Error(`プロンプトテンプレート '${name}' が見つかりません`);
}
try {
// user_idが指定されている場合、ユーザー情報を取得してテンプレートに埋め込み
let content = template.template;
if (args && 'user_id' in args) {
const userId = args.user_id as string;
const userInfo = mockDb[userId];
if (!userInfo) {
throw new Error(`ユーザーID ${userId} が見つかりません`);
}
// テンプレート変数を実際の値で置換
content = content
.replace(/\{\{user_name\}\}/g, userInfo.name)
.replace(/\{\{user_email\}\}/g, userInfo.email);
}
return {
description: template.description,
messages: [
{
role: "user",
content: {
type: "text",
text: content
}
} as PromptMessage
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "不明なエラー";
throw new Error(`プロンプト生成エラー: ${errorMessage}`);
}
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("MCP Prompts Server が開始されました");
}
}
// サーバー開始
async function main() {
const server = new MCPPromptsServer();
await server.start();
}
// エラーハンドリング
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
if (require.main === module) {
main().catch((error) => {
console.error("サーバー開始エラー:", error);
process.exit(1);
});
}
🔧 Claude Desktop設定
claude_desktop_config.json
に以下の設定を追加してください:
{
"mcpServers": {
"mcp-prompts": {
"command": "npx",
"args": ["ts-node", "/path/to/your/project/server.ts"]
}
}
}
👆 ポイント解説
テンプレート変数システム
-
{{variable_name}}
形式でテンプレート変数を定義 - サーバー側で動的に実際の値に置換される仕組み
- エラーハンドリングも含めた堅牢な実装
プロンプト引数の活用
-
arguments
配列で必要なパラメータを定義 - Claude側で適切なパラメータの入力を促すことができる
- 型安全性を保った実装
複数テンプレートの管理
- 用途別にテンプレートを整理
- 再利用可能で保守しやすい構造
🚀 動作の確認
サーバーを起動して動作確認を行います:
npx ts-node server.ts
Claude Desktopでの使用例:
-
ウェルカムメール生成
"welcome_email プロンプトを使って、user_001 向けのメールを生成して"
-
リマインダーメール生成
"reminder_email プロンプトで user_002 にリマインドメールを送りたい"
📊 実行フロー
Claudeは以下のプロセスでメールを生成します:
- プロンプト取得: 指定されたテンプレート名でプロンプトを要求
- パラメータ解析: user_idパラメータを解析
- データ取得: 必要に応じてget_user_infoツールを実行
- テンプレート展開: 取得した情報をテンプレート変数に埋め込み
- 結果出力: 完成したメール文面を出力
🎯 まとめ:Prompts活用のメリット
MCPのPrompts機能を活用することで、プロンプトをデータとロジックから分離できます。
主な利点
- 可読性の向上: テンプレートを使うことで、プロンプトの全体構造が一目で分かりやすくなります
- 保守性の向上: プロンプトの修正はテンプレート定義を編集するだけで済み、コード全体を書き換える必要がありません
- 動的な生成: ToolsやResourcesから得た情報を活用することで、状況に応じた最適なプロンプトを自動的に生成できます
- エラー処理: 適切なエラーハンドリングにより、堅牢なシステムを構築できます
応用例
- カスタマーサポートメールの自動生成
- レポート作成用テンプレート
- 多言語対応プロンプト
- 条件分岐を含む複雑なプロンプト
この機能は、複雑なタスクや反復的なタスクを自動化する上で非常に強力な武器となります。
次回は、これまでの実装で重要なエラー処理により深く焦点を当て、運用レベルで使える堅牢なMCPサーバーを構築するためのベストプラクティスを学びます。お楽しみに!