最近最火の AI 技術は MCP でしょう。MCP の概念をまだ理解していない方は、Model Context Protocol 文書を確認することをお勧めします。そこでは MCP の概念と規範が詳しく説明されています。この記事では公式の TypeScript SDK を使用し、DeepSeek にリクエストを送信し、サーバーからクライアントまで簡単に Sqlite MCP を実装し、MCP 開発を順調に開始します。
プロジェクトの初期化#
本プロジェクトのすべてのリソースはこの git リポジトリTypeScript-MCP-Sqlite-Quickstartにあります。そこにはケースで使用される sqlite ファイルが含まれており、直接プルすることも、手順に従って初期化して自分でデータベースを作成することもできます。
プロジェクトの作成
フォルダーを作成
mkdir TypeScript-MCP-Sqlite-Quickstart
cd TypeScript-MCP-Sqlite-Quickstart
npmを初期化
npm init -y
関連依存関係をインストール
npm install @modelcontextprotocol/sdk zod sqlite3 express @types/express openai
npm install -D @types/node typescript
touch index.ts
touch server/sqlite_stdio.ts
touch server/sqlite_sse.ts
package.json と tsconfig.json の設定 package.json
{
"name": "typescript-mcp-sqlite-quickstart",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.8.0",
"@types/express": "^5.0.1",
"express": "^5.1.0",
"openai": "^4.91.1",
"sqlite3": "^5.1.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.17",
"typescript": "^5.8.2"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["index.ts","server/**/*"],
"exclude": ["node_modules"]
}
MCP サーバーの作成#
MCP サーバーには 2 つの起動方法があります。1 つは Stdio、もう 1 つは SSE です。このセクションでは両方を紹介します。まずは Stdio 方式を紹介します。
Stdio トランスポート#
./server/sqlite_stdio.ts ファイルに追加
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";
//MCPServerインスタンスを作成
export const sqliteServer = new McpServer({
name: "SQLite Explorer",
version: "1.0.0"
});
//Sqliteを初期化
const getDb = () => {
const db = new sqlite3.Database("database.db");
return {
all: promisify<string, any[]>(db.all.bind(db)),
close: promisify(db.close.bind(db))
};
};
//サーバーを定義
//最初の引数はツール名
//2番目の引数は説明、大モデルにこのツールの用途を紹介する
//3番目の引数は入力パラメータ
//4番目の引数は呼び出すメソッド
sqliteServer.tool(
"query",
"これはSQLiteクエリを実行するためのツールです。これを使用して、SELECT sql FROM sqlite_master WHERE type='table'やSELECT * FROM table_nameなど、任意の有効なSQLクエリを実行できます。",
{ 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.message}`
}],
isError: true
};
} finally {
await db.close();
}
}
);
//StdioTransportを使用してサーバーに接続
const transport = new StdioServerTransport();
await sqliteServer.connect(transport);
build を使用して ts ファイルをコンパイルします。inspectorは公式が提供する MCP サーバーの検査ツールで、inspector を使用してサーバーサービスが正常かどうかを確認します。
//tsをコンパイル
npm run build
//検査ツールを実行
npx @modelcontextprotocol/inspector
SSE トランスポート#
./server/sqlite_sse.ts に追加
import express, { Request, Response } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";
//MCPServerインスタンスを初期化
const sqliteServer = new McpServer({
name: "SQLite Explorer",
version: "1.0.0"
});
//Sqliteを初期化
const getDb = () => {
const db = new sqlite3.Database("database.db");
return {
all: promisify<string, any[]>(db.all.bind(db)),
close: promisify(db.close.bind(db))
};
};
const app = express();
//サーバーを定義、この部分はstdioと同じ
sqliteServer.tool(
"query",
"これはSQLiteクエリを実行するためのツールです。これを使用して、SELECT sql FROM sqlite_master WHERE type='table'やSELECT * FROM table_nameなど、任意の有効なSQLクエリを実行できます。",
{ 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.message}`
}],
isError: true
};
} finally {
await db.close();
}
}
);
//SSETransportを使用してサーバーに接続
const transports: {[sessionId: string]: SSEServerTransport} = {};
//SSE接続を処理するルート
app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
res.on("close", () => {
delete transports[transport.sessionId];
});
await sqliteServer.connect(transport);
});
//複数の接続を処理するルート
app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('sessionIdに対するトランスポートが見つかりません');
}
});
app.listen(3001);
サーバーの作成が完了したら、同様にコンパイルを行い、express サービスを実行し、Inspector を使用して機能が正常かどうかをテストします。
//tsをコンパイル
npm run build
//expressを実行
node ./build/server/sqlite_sse.js
Inspector を開き、トランスポートタイプを sse に設定し、URL をhttp://localhost:3001/sseに設定して接続し、ツールをテストします。
#
MCP クライアントの作成#
MCP クライアントも同様に 2 つのトランスポートをサポートしています。1 つは stdio、もう 1 つは sse で、サーバー側に対応しています。index.ts に追加
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
//StdioTransportを使用してサーバーに接続
//ここに記入するパラメータはInspectorテストで記入したパラメータと一致します
const stdioTransport = new StdioClientTransport({
command: "node",
args: ["./build/server/sqlite_stdio.js"]
});
//SSETransportを使用してサーバーに接続
//ここに記入するパラメータはInspectorテストで記入したパラメータと一致します
//const sseTransport = new SSEClientTransport(new URL("http://localhost:3001/sse"));
const client = new Client(
{
name: "example-client",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
//StdioTransportとSSETransportを接続しますが、同時に接続できるのは1つのようです。
//必要に応じて接続方法を選択します
await client.connect(stdioTransport);
// await client.connect(sseTransport);
//ツールリストを取得
const tools = await client.listTools();
console.log("ツール:", tools);
//リソースリストを取得
// const resources = await client.listResources();
//プロンプトリストを取得
// const prompts = await client.listPrompts();
MCP クライアントの作成が完了したら、ts をコンパイルし、MCP クライアントを実行します。利用可能なツールリストが表示されれば、クライアントは正常に動作しています。
//tsをコンパイル
npm run build
//クライアントを実行
node build/index.js
#
Deepseek にリクエストを送信して MCP を呼び出す#
deepseek は openai sdk を使用してリクエストを送信できます。リクエストを送信する際に、パラメータに tools を含めることができます。openai の Tools パラメータの定義と anthropic の tools パラメータの定義は異なるため、取得した tools を必要に応じて変換します。 変換関数
const toolsResult = await client.listTools();
//anthropic形式のtools
const anthropicTools = toolsResult.tools.map((tool) => {
return {
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
};
});
//openai形式のtools
const openaiTools = toolsResult.tools.map((tool) => {
return {
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema,
}
}
})
これで、tools 情報を正常に取得しました。deepseek にリクエストを送信する際に tools パラメータを含め、response の finish_reson を監視します。もし tools_calls であれば、response の toll_calls から name と args を取得します。その後、MCP クライアントの callTool メソッドを呼び出し、取得した結果を再度 Deepseek に送信することで、Deepseek はデータベースの情報を正常に取得できます。
const openai = new OpenAI({
apiKey: 'apikey', // あなたのapikeyを使用してください。環境変数から読み取ることをお勧めします
baseURL: 'https://api.deepseek.com/v1',
});
//あなたの質問に置き換えてください
const messages: ChatCompletionMessageParam[] = [{ role: "user", content: "私はsqliteデータベースを持っており、データベースにはexpensesテーブルとincomesテーブルがあります。私の収入と支出はそれぞれいくらですか?" }];
// 最初のリクエストを送信し、toolsパラメータを含めます
const response = await openai.chat.completions.create({
model: "deepseek-chat",
messages: messages,
max_tokens: 1000,
tools: openaiTools,
}
);
const content = response.choices[0];
//finish_reasonを監視します。もしtool_callsであれば、大モデルがツールを呼び出したことを示します
console.log("finish_resaon",content.finish_reason);
if (content.finish_reason === "tool_calls" && content.message.tool_calls && content.message.tool_calls.length > 0) {
//大モデルが返したツール呼び出しのパラメータを取得します
const tool_call = content.message.tool_calls[0];
const toolName = tool_call.function.name;
const toolArgs = JSON.parse(tool_call.function.arguments) as { [x: string]: unknown } | undefined;
const result = await client.callTool({
name: toolName,
arguments: toolArgs,
});
console.log(`[大モデルがツール ${toolName} を呼び出しました。パラメータは ${JSON.stringify(toolArgs)}]`)
//取得した結果をmessagesに追加し、大モデルに送信する準備をします
messages.push({
role: "user",
content: result.content as string,
});
//取得した結果をmessagesに追加し、再度リクエストを送信します。このリクエストにもtoolsを含めることができ、多段階の呼び出しを実現できますが、より良いプロセス処理ロジックも必要です
const response = await openai.chat.completions.create({
model: "deepseek-chat",
max_tokens: 1000,
messages,
});
console.log(response.choices[0].message.content);
}
これで、Deepseek が MCP を介してデータベース情報を取得することができました。以下のコマンドを使用して呼び出し状況を確認します。
//tsをコンパイル
npm run build
//index.jsを実行
node ./build/index.js
次のステップ#
これで、最もシンプルな MCP プロセスが完了しました。改善できる点がたくさんあります。
-
このプロセスは最もシンプルな SQL クエリ機能を提供します。サーバーの説明も粗く、大モデルがツールを成功裏に呼び出せるとは限りません。
-
現在のリクエスト方式は非ストリーミングであり、ストリーミングに改善できます。
-
現在は一度のツール呼び出ししかサポートしておらず、多段階の呼び出しを実現できません。MCP の能力をより良く発揮するために多段階の呼び出しを許可する必要があります。
-
現在のところ、1 つのクライアントは 1 つのサーバーにしか接続できないようで、複数のサービスがある場合、クライアントを効率的に管理する方法も問題です。