2025 年 5 月時点 Streamable HTTP MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携するメモ
2025 年 5 月時点 Streamable HTTP MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携するメモです。
背景
あくまで2025 年 5 月時点の仕様での試してみたお話です。ここから仕様が変わったり、より良いアプローチが見つかるかもしれません。
2025 年 5 月時点 stdio 標準入出力 MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携するメモ でうまくいった仕組みを、Streamable HTTP MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携するものにしてみます。

こんな仕組みです。
足し算ツールを使うだけの Streamable HTTP MCP クライアントを TypeScript で作る
足し算ツールを使うだけの Streamable HTTP MCP クライアントを TypeScript で作ります。ツール推定はせずに「2+3」と打ち込んだら足し算をするものです。
2025 年 5 月時点 stdio 標準入出力 MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携するメモ では Claude Desktop で行っていましたが、まだ Streamable HTTP MCP サーバーへつながるクライアントに仕組みが広まってない 2025 年 5 月ごろなので TypeScript SDK で自作します。
フォルダ準備
mkdir mcp-client-streamable-http
まず mcp-client-streamable-http というフォルダを作ります。
cd mcp-client-streamable-http
フォルダに移動します。
npm 初期設定
npm init -y
npm の初期設定をします。

このようなファイル構成になりました。
{
"name": "mcp-client-streamable-http",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
package.json ができあがりました。
"type": "module",
"scripts": {
"mcp-client-streamable-http": "node --no-warnings --loader ts-node/esm mcp-client-streamable-http.ts"
}
package.json に type module で ESM で動くようにして、mcp-client-streamable-http で、このあと作成する mcp-client-streamable-http.ts のプログラムを TypeScript で動作させるコマンドを準備しておきます。
モジュールのインストール
npm install @modelcontextprotocol/sdk
と、
npm install --save-dev typescript ts-node @types/node
でモジュールをインストールします。
tsconfig.json の準備
tsconfig.json もフォルダ内に準備します。
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["node"],
"strict": true
},
"include": ["**/*.ts"]
}
これらの作業を終えて package.json は以下のようになりました。
{
"name": "mcp-client-streamable-http",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"mcp-client-streamable-http": "node --no-warnings --loader ts-node/esm mcp-client-streamable-http.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0"
},
"devDependencies": {
"@types/node": "^22.15.21",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}
mcp-client-streamable-http.ts に MCP クライアントのプログラムを作成
mcp-client-streamable-http.ts で MCP クライアントのプログラムを作成します。
modelcontextprotocol/typescript-sdk: The official Typescript SDK for Model Context Protocol servers and clients の Writing MCP Clients を参考に作りました。
内容は以下の通りです。
// mcp-client-streamable-http.ts
import readline from "readline";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
async function main() {
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{ sessionId: "stateful-001" }
);
const client = new Client({
name: "add-cli",
version: "1.0.0",
});
await client.connect(transport);
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
console.log("============================================================");
console.log("足し算ツールのみ呼び出し MCPクライアント Streamable HTTP 方式");
console.log("例: 3+4 の形式で入力してください");
console.log("============================================================");
rl.setPrompt("> ");
rl.prompt();
rl.on("line", async (line) => {
const m = line.trim().match(/^(\d+)\s*\+\s*(\d+)$/);
if (!m) {
console.log("例: 3+4 の形式で入力してください");
} else {
const a = Number(m[1]);
const b = Number(m[2]);
console.log(`${a} と ${b} を足します。\n`);
try {
const { content } = await client.callTool({
name: "add",
arguments: { a, b }
});
console.log(`MCP サーバーからの結果 : ${JSON.stringify(content)}\n`);
if (Array.isArray(content)) {
content.forEach((c) => {
if (c.type === "text") {
console.log(`答え : ${c.text}`);
}
});
}
} catch (e) {
console.error("ツール呼び出しに失敗:", e);
}
}
rl.prompt();
});
rl.on("close", () => {
transport.close?.();
process.exit(0);
});
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
現状のファイル構成

このようなファイル構成になっています。
ブリッジする Streamable HTTP MCP サーバーを TypeScript で作る
Node.js v18.19.0 で動かしています。fetch 関数を素直に動かすためです。これ以上のバージョンであれば fetch 関数は問題なく動きます。
フォルダ準備
cd ..
一旦、プロジェクトフォルダに戻ります。
mkdir streamable-server-node-red-bridge
まず streamable-server-node-red-bridge というフォルダを作ります。
cd streamable-server-node-red-bridge
フォルダに移動します。
npm 初期設定
npm init -y
npm の初期設定をします。

このようなファイル構成になりました。
{
"name": "streamable-server-node-red-bridge",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
package.json ができあがりました。
"type": "module",
"scripts": {
"streamable-server-node-red-bridge": "node --no-warnings --loader ts-node/esm streamable-server-node-red-bridge.ts"
}
package.json に type module で ESM で動くようにして、streamable-server-node-red-bridge で、このあと作成する streamable-server-node-red-bridge.ts のプログラムを TypeScript で動作させるコマンドを準備しておきます。
モジュールのインストール
こちらをもとに、
npm install @modelcontextprotocol/sdk zod express
と
npm install -D typescript ts-node @types/node @types/express
を実行してモジュールの準備をします。
streamable-server-node-red-bridge.ts 用意
こちらの記事の Running Your Server の stdio のソースを元に足し算のツールの入り口と Node-RED への実際の足し算の処理をつなげる streamable-server-node-red-bridge.ts で用意します。
// streamable-server-node-red-bridge.ts
import express, { Request, Response } from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
const app = express();
app.use(express.json());
// ─── サーバー/トランスポートをセッションIDで管理 ───
type Session = {
server: McpServer;
transport: StreamableHTTPServerTransport;
};
const sessions: Record<string, Session> = {};
// ─── MCP サーバー(ツール登録) ───
function makeServer(): McpServer {
const server = new McpServer({ name: 'Demo', version: '1.0.0' });
server.tool(
'add',
{ a: z.number(), b: z.number() },
async ({ a, b }) => {
// Node-RED ブリッジ
const res = await fetch('http://127.0.0.1:1880/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ a, b }),
});
if (!res.ok) {
throw new Error(`Node-RED error: ${res.statusText}`);
}
const json = (await res.json()) as { result: number };
return { content: [{ type: 'text', text: String(json.result) }] };
}
);
return server;
}
// ─── 共通:リクエストを捌く関数 ───
async function handleMcp(
req: Request,
res: Response,
body: any | undefined
) {
// セッションヘッダー
const sessionIdHeader = req.headers['mcp-session-id'] as string | undefined;
const initRequest = body && isInitializeRequest(body);
let session: Session;
if (sessionIdHeader && sessions[sessionIdHeader]) {
// ── 既存セッションを再利用 ──
session = sessions[sessionIdHeader]!;
}
else if (initRequest) {
// ── 新規ステートフルセッションの初期化 ──
const server = makeServer();
const transport = new StreamableHTTPServerTransport({
// ランダムUUIDでセッションIDを発行
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sid) => {
sessions[sid] = { server, transport };
},
});
// 切断時にクリーンアップ
transport.onclose = () => {
const sid = transport.sessionId;
if (sid) delete sessions[sid];
};
// 初回は connect が必要
await server.connect(transport);
session = { server, transport };
}
else {
// ── ステートレスフォールバック ──
const server = makeServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
// リクエスト完了で即クローズ
res.on('close', () => {
transport.close();
server.close();
});
await server.connect(transport);
session = { server, transport };
}
// ── 実際のハンドリング ──
await session.transport.handleRequest(req, res, body);
}
// ── ルーティング ──
// POST: initialize / tools/list / tools/call など
app.post('/mcp', async (req, res) => {
try {
await handleMcp(req, res, req.body);
} catch (err) {
console.error('POST /mcp error:', err);
if (!res.headersSent) {
res
.status(500)
.json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal error' }, id: null });
}
}
});
// GET: SSE ストリーム開始
app.get('/mcp', async (req, res) => {
try {
await handleMcp(req, res, undefined);
} catch (err) {
console.error('GET /mcp error:', err);
if (!res.headersSent) {
res.status(500).send('SSE error');
}
}
});
// DELETE: SSE セッション終了
app.delete('/mcp', async (req, res) => {
try {
await handleMcp(req, res, undefined);
} catch (err) {
console.error('DELETE /mcp error:', err);
if (!res.headersSent) {
res.status(500).send('Close error');
}
}
});
// サーバー起動
const PORT = 3000;
app.listen(PORT, () => {
console.log(`✅ MCP server running at http://localhost:${PORT}/mcp`);
});
tsconfig.json
tsconfig.json も準備して、この後、TypeScript を動かす設定もしておきます。
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["node"],
"strict": true
},
"include": ["**/*.ts"]
}
これらの作業を終えて package.json は以下のようになりました。
{
"name": "streamable-server-node-red-bridge",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"streamable-server-node-red-bridge": "node --no-warnings --loader ts-node/esm streamable-server-node-red-bridge.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"express": "^5.1.0",
"zod": "^3.25.28"
},
"devDependencies": {
"@types/express": "^5.0.2",
"@types/node": "^22.15.21",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}
現状のファイル構成

このようなファイル構成になっています。
Node-RED でフローを作る
Node-RED については別途インストールを済ませて起動している前提です。

このようなフローを作りました。

stdio 標準入出力 MCP サーバーから足し算ツールの情報を受け付ける http in ノードです。

POST リクエストで /add というパスから stdio 標準入出力 MCP サーバーから足し算する値を受信します。

実際の足し算をする function ノードです。

// 足し算
const result = msg.payload.a + msg.payload.b;
msg.payload = {};
msg.payload.result = result;
return msg;
ごくシンプルに足し算をして result 値に入れて返しています。

http response ノードは、デフォルトの設定のままでシンプルに返答します。
今回の JSON フローデータです。
[{"id":"c764f7c40134fe66","type":"http in","z":"6b422e4d3cdf58d8","name":"","url":"/add","method":"post","upload":false,"swaggerDoc":"","x":140,"y":140,"wires":[["9ae546d31023c8e0","76fd73c7e1f6391f"]]},{"id":"e69eb256b6f3ad6c","type":"http response","z":"6b422e4d3cdf58d8","name":"","statusCode":"","headers":{},"x":550,"y":140,"wires":[]},{"id":"9ae546d31023c8e0","type":"function","z":"6b422e4d3cdf58d8","name":"足し算","func":"// 足し算\nconst result = msg.payload.a + msg.payload.b;\n\nmsg.payload = {};\nmsg.payload.result = result;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":140,"wires":[["e69eb256b6f3ad6c"]]},{"id":"76fd73c7e1f6391f","type":"debug","z":"6b422e4d3cdf58d8","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":340,"y":100,"wires":[]}]
動かしてみる
さきほどの Node-RED は http://127.0.0.1:1880/ で起動しています。
Node-RED へブリッジする MCP サーバーを起動
ターミナルを新しく起動して、
cd streamable-server-node-red-bridge
MCP サーバーのフォルダに移動して
npm run streamable-server-node-red-bridge
を実行します。

MCP サーバーが起動しました。
MCP クライアントを起動
ターミナルを新しく起動して、
cd mcp-client-streamable-http
MCP クライアントのフォルダに移動して
npm run mcp-client-streamable-http
を実行します。

MCP クライアントが起動しました。
足し算ツールのみ呼び出しでツール推定は考えなくて良いものです。数値+数値 の形式で入力します。
3+4
と入力しました。

Node-RED 側に無事にデータが送られて計算処理がされます。

MCP クライアントから MCP サーバーがブリッジして Node-RED に届くまでの流れです。

実行結果が返ってきて足し算結果が無事に表示されました。

Node-RED が計算した結果を MCP サーバーが受け取って JSONRPC 形式で MCP クライアントに返答するの流れです。
あくまで 2025 年 5 月時点ですが Streamable HTTP MCP サーバーをブリッジにして Node-RED が MCP クライアントと連携することができました!