Claude-GAS-Bridge
by KaishuShito
// bridge.mjs
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch"; // if using Node <18, or remove if on Node 18+
// === 追加: Node.js の例外キャッチ
process.on("uncaughtException", (err) => {
console.error("UncaughtException:", err);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("UnhandledRejection:", reason);
});
// 1) Create an MCP server instance
console.error("Starting MCP server instance...");
const server = new Server(
{
name: "gas-bridge",
version: "1.0.0"
},
{
capabilities: {
tools: {}
}
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("ListToolsRequest received");
return {
tools: [
{
name: "execute-gas",
description: "Execute custom GAS script",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
script: { type: "string" },
apiKey: { type: "string" }
},
required: ["script", "apiKey"]
}
}
]
};
});
// 3) Handler: 実際にツールが呼ばれたとき
server.setRequestHandler(CallToolRequestSchema, async (request) => {
console.error("CallToolRequest received:", JSON.stringify(request, null, 2));
const { name, arguments: args } = request.params;
if (name !== "execute-gas") {
console.error(`Unknown tool called: ${name}`);
throw new Error(`Unknown tool: ${name}`);
}
const { title = "untitled", script, apiKey } = args;
// 外部に投げる前にログを残す
console.error("Sending request to GAS endpoint with:", {
title,
scriptLength: script?.length,
apiKeyMask: apiKey ? "***" : "(none)"
});
const GAS_ENDPOINT = "https://script.google.com/macros/s/AKfycbxxxxx/exec"; // ← あなたのGASエンドポイント
try {
const response = await fetch(GAS_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, script, apiKey })
});
if (!response.ok) {
const text = await response.text();
console.error(`GAS HTTP error: ${response.status} - ${response.statusText}`);
return {
isError: true,
content: [
{
type: "text",
text: `GAS HTTP error: ${response.status} - ${response.statusText}\n${text}`
}
]
};
}
const data = await response.json();
console.error("Response from GAS endpoint:", data);
if (!data.success) {
return {
isError: true,
content: [
{
type: "text",
text: data.error || "GAS returned error"
}
]
};
}
return {
content: [
{
type: "text",
text: data.result || "No result from GAS"
}
]
};
} catch (err) {
console.error("Fetch error:", err);
return {
isError: true,
content: [
{ type: "text", text: `Fetch error: ${err.message}` }
]
};
}
});
// 4) 起動用: Stdioを介してClaudeと通信
console.error("Connecting transport...");
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Bridge server started (Stdio). Waiting for requests...");
// ★ Node がすぐ終了しないため、イベントループをブロック
await new Promise(() => {});