MCPサーバー自作入門(TypeScript)

5
スポンサーリンク

この記事でわかること
– MCP(Model Context Protocol)の仕組みと3つの提供物
– TypeScriptでMCPサーバーをゼロから作る手順
– ツール・リソース・プロンプトの実装方法
– Claude Codeへの登録と動作確認

Claude Codeに「社内DBを直接クエリしてほしい」「自社APIを叩いてほしい」——そんな要望に応えるのがMCPサーバー自作だ。

MCPはAnthropicが策定したオープンプロトコルで、AIクライアントに外部ツールを接続するための標準仕様。TypeScriptの公式SDKを使えば、30分程度で独自のMCPサーバーが作れる。


スポンサーリンク

MCPが提供できる3つのもの

種類説明
ToolsAIが呼び出せる関数DB検索・API呼び出し・ファイル変換
ResourcesAIが読めるデータソースファイル・DB・ログ・設定値
Prompts再利用可能なプロンプトテンプレートコードレビュー定型・ドキュメント生成

今回はToolsの実装をメインに、Resourcesも触れる。


セットアップ

mkdirmy-mcp-server
cdmy-mcp-server
npminit-y
npminstall@modelcontextprotocol/sdk
npminstall-Dtypescript@types/nodetsx
npxtsc--init

tsconfig.json を調整:

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

最小構成のMCPサーバー

src/index.ts を作る:

import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";
import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";
import{z}from"zod";

constserver=newMcpServer({
 name:"my-mcp-server",
 version:"1.0.0",
});

// ツールを定義
server.tool(
 "add",                         // ツール名
 "2つの数を足す",                // 説明(AIが使い方を判断する)
 {
   a:z.number().describe("1つ目の数"),
   b:z.number().describe("2つ目の数"),
 },
 async({a,b})=>({
   content:[{type:"text",text:String(a+b)}],
 })
);

// stdioトランスポートで起動
consttransport=newStdioServerTransport();
awaitserver.connect(transport);

これで動くMCPサーバーの完成。ツール名・説明・入力スキーマ・ハンドラの4つを書くだけだ。


実践例:天気APIを叩くツール

import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";
import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";
import{z}from"zod";

constserver=newMcpServer({
 name:"weather-server",
 version:"1.0.0",
});

server.tool(
 "get_weather",
 "指定した都市の現在の天気を取得する",
 {
   city:z.string().describe("都市名(例: Tokyo, Osaka)"),
 },
 async({city})=>{
   constapiKey=process.env.WEATHER_API_KEY;
   constres=awaitfetch(
     `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=ja`
   );

   if(!res.ok){
     return{
       content:[{type:"text",text:`エラー:${res.status}${res.statusText}`}],
       isError:true,
     };
   }

   constdata=awaitres.json()as{
     name:string;
     main:{temp:number;humidity:number};
     weather:Array<{description:string}>;
   };

   return{
     content:[{
       type:"text",
       text:`${data.name}:${data.weather[0].description}, 気温${data.main.temp}℃, 湿度${data.main.humidity}%`,
     }],
   };
 }
);

consttransport=newStdioServerTransport();
awaitserver.connect(transport);

実践例:SQLiteを直接クエリするツール

社内ツールや個人プロジェクトでよくあるパターン。

npminstallbetter-sqlite3
npminstall-D@types/better-sqlite3
importDatabasefrom"better-sqlite3";
import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";
import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";
import{z}from"zod";

constdb=newDatabase(process.env.DB_PATH??"./data.db");

constserver=newMcpServer({
 name:"sqlite-server",
 version:"1.0.0",
});

// SELECT専用(書き込みは別ツールに分離してリスク管理)
server.tool(
 "query_db",
 "SQLiteデータベースにSELECTクエリを実行する",
 {
   sql:z.string().describe("実行するSELECT文"),
   limit:z.number().default(50).describe("最大行数"),
 },
 async({sql,limit})=>{
   // SELECT以外は拒否
   if(!/^\s*SELECT/i.test(sql)){
     return{
       content:[{type:"text",text:"SELECTクエリのみ許可されています"}],
       isError:true,
     };
   }

   constrows=db.prepare(`${sql} LIMIT${limit}`).all();
   return{
     content:[{type:"text",text:JSON.stringify(rows,null,2)}],
   };
 }
);

server.tool(
 "list_tables",
 "データベースのテーブル一覧を返す",
 {},
 async()=>{
   consttables=db
     .prepare("SELECT name FROM sqlite_master WHERE type='table'")
     .all()asArray<{name:string}>;
   return{
     content:[{type:"text",text:tables.map(t=>t.name).join(", ")}],
   };
 }
);

consttransport=newStdioServerTransport();
awaitserver.connect(transport);

Resourcesの実装

ToolsはAIが能動的に呼ぶ関数だが、Resourcesは「読めるデータ」として公開する仕組みだ。

// ログファイルをリソースとして公開
server.resource(
 "app-logs",
 "logs://app/latest",
 async(uri)=>{
   constcontent=awaitfs.readFile("/var/log/app.log","utf-8");
   constlines=content.split("\n").slice(-100).join("\n");// 直近100行

   return{
     contents:[{
       uri:uri.href,
       mimeType:"text/plain",
       text:lines,
     }],
   };
 }
);

Claude Codeへの登録

~/.claude/settings.json に追記:

{
 "mcpServers":{
   "my-mcp-server":{
     "command":"node",
     "args":["/path/to/my-mcp-server/dist/index.js"],
     "env":{
       "WEATHER_API_KEY":"your-api-key",
       "DB_PATH":"/path/to/data.db"
     }
   }
 }
}

TypeScriptをビルドせず tsx で直接実行する場合:

{
 "mcpServers":{
   "my-mcp-server":{
     "command":"npx",
     "args":["tsx","/path/to/my-mcp-server/src/index.ts"]
   }
 }
}

登録後、Claude Codeを再起動すれば自動的にMCPサーバーが接続される。Claude Codeに「天気を調べて」と話しかけると、定義した get_weather ツールを呼び出してくれる。


セキュリティ上の考慮点

ツール名・説明は攻撃者に見える

MCPサーバーの情報はAIに渡されるため、悪意あるプロンプトでツールを悪用しようとする攻撃が成立しうる。特にDBアクセス系ツールは「SELECT専用」「対象テーブルを限定」など最小権限で設計する。

環境変数でシークレット管理

APIキー・DB接続文字列はコードに直書きせず、env フィールドで渡す。settings.jsonはgit管理外(~/.claude/ 配下)に置く。

信頼できないMCPサーバーを追加しない

claude-code-third-party-skills-securityで書いたように、野良MCPサーバーはプロンプトインジェクションのベクターになりうる。自作か信頼できるソースのものだけを使う。


まとめ

// 最小構成テンプレート
constserver=newMcpServer({name:"...",version:"1.0.0"});

server.tool("tool_name","説明",{param:z.string()},async({param})=>({
 content:[{type:"text",text:"結果"}],
}));

awaitserver.connect(newStdioServerTransport());

やること:
1. npm install @modelcontextprotocol/sdk
2. ツール定義(名前・説明・スキーマ・ハンドラ)
3. settings.json に登録

この3ステップだけ。Claude Codeに独自のDB・API・ファイル操作を追加したいなら、まず小さいツールを1つ作って動作確認するのが早道だ。


あわせて読みたい


参考: Model Context Protocol Documentation, @modelcontextprotocol/sdk (npm)


見てもらえるだけで応援になります

このブログはアフィリエイトリンクで運営されています。以下のリンクから気になるサービスをチェックしてもらえると、僕たちの活動の支えになります。


この記事を書いたのは わさび(ニホンイシガメ / 3歳 / VTuberあかはら。の家族)です。

あかはらVラボ — Claude特化の情報を発信中。

この記事が参考になったら|以下のリンクから見てもらえるだけで、ブログ運営の応援になります。




  • MCP自作ツールの実運用はVPS環境が便利です。

コメント

タイトルとURLをコピーしました