@grackle-ai/mcp 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +20 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +243 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/standalone.d.ts +3 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +77 -0
- package/dist/standalone.js.map +1 -0
- package/dist/tool-registry.d.ts +52 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +24 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +11 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-environments.d.ts +4 -0
- package/dist/tools/list-environments.d.ts.map +1 -0
- package/dist/tools/list-environments.js +28 -0
- package/dist/tools/list-environments.js.map +1 -0
- package/dist/tools/list-projects.d.ts +4 -0
- package/dist/tools/list-projects.d.ts.map +1 -0
- package/dist/tools/list-projects.js +28 -0
- package/dist/tools/list-projects.js.map +1 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
/** Options for creating an MCP server. */
|
|
3
|
+
export interface McpServerOptions {
|
|
4
|
+
/** Host address to bind the MCP server to. */
|
|
5
|
+
bindHost: string;
|
|
6
|
+
/** Port for the MCP server to listen on. */
|
|
7
|
+
mcpPort: number;
|
|
8
|
+
/** Port of the co-located gRPC server for backend calls. */
|
|
9
|
+
grpcPort: number;
|
|
10
|
+
/** API key used for authenticating both inbound MCP and outbound gRPC requests. */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create an HTTP server that serves the MCP Streamable HTTP protocol on `/mcp`.
|
|
15
|
+
*
|
|
16
|
+
* The server manages stateful sessions — each MCP client gets its own transport
|
|
17
|
+
* and Server instance, tracked by session ID.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createMcpServer(options: McpServerOptions): http.Server;
|
|
20
|
+
//# sourceMappingURL=mcp-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AA8B7B,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AA+ED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAuCtE"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createClient } from "@connectrpc/connect";
|
|
7
|
+
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
8
|
+
import { grackle } from "@grackle-ai/common";
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import pino from "pino";
|
|
13
|
+
import { createToolRegistry } from "./tools/index.js";
|
|
14
|
+
/** Read the package version from package.json at module load time. */
|
|
15
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")).version;
|
|
16
|
+
const logger = pino({
|
|
17
|
+
name: "grackle-mcp",
|
|
18
|
+
level: process.env.LOG_LEVEL || "info",
|
|
19
|
+
transport: process.env.NODE_ENV !== "production"
|
|
20
|
+
? { target: "pino/file", options: { destination: 1 } }
|
|
21
|
+
: undefined,
|
|
22
|
+
});
|
|
23
|
+
/** Create a ConnectRPC client pointing at the co-located Grackle gRPC server. */
|
|
24
|
+
function createGrpcClient(bindHost, grpcPort, apiKey) {
|
|
25
|
+
const transport = createGrpcTransport({
|
|
26
|
+
baseUrl: `http://${bindHost}:${grpcPort}`,
|
|
27
|
+
interceptors: [
|
|
28
|
+
(next) => async (req) => {
|
|
29
|
+
req.header.set("Authorization", `Bearer ${apiKey}`);
|
|
30
|
+
return next(req);
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
return createClient(grackle.Grackle, transport);
|
|
35
|
+
}
|
|
36
|
+
/** Create a low-level MCP Server instance with tool handlers wired to the ConnectRPC backend. */
|
|
37
|
+
function createMcpServerInstance(grpcClient) {
|
|
38
|
+
const registry = createToolRegistry();
|
|
39
|
+
const server = new Server({ name: "grackle-mcp", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
40
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
41
|
+
const tools = registry.list();
|
|
42
|
+
return {
|
|
43
|
+
tools: tools.map((t) => ({
|
|
44
|
+
name: t.name,
|
|
45
|
+
description: t.description,
|
|
46
|
+
inputSchema: t.inputSchema,
|
|
47
|
+
annotations: t.annotations,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
52
|
+
const { name, arguments: args } = request.params;
|
|
53
|
+
const tool = registry.get(name);
|
|
54
|
+
if (!tool) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
logger.info({ tool: name }, "Executing MCP tool: %s", name);
|
|
62
|
+
const result = await tool.handler(args ?? {}, grpcClient);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
logger.error({ tool: name, err: error }, "Tool execution failed: %s", name);
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return server;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Verify the Bearer token on an incoming HTTP request.
|
|
78
|
+
* Uses constant-time comparison via timingSafeEqual.
|
|
79
|
+
*/
|
|
80
|
+
function verifyBearer(req, apiKey) {
|
|
81
|
+
const authHeader = req.headers.authorization || "";
|
|
82
|
+
const token = authHeader.replace(/^Bearer\s+/i, "");
|
|
83
|
+
if (!token || token.length !== apiKey.length) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const a = Buffer.from(token);
|
|
87
|
+
const b = Buffer.from(apiKey);
|
|
88
|
+
return timingSafeEqual(a, b);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create an HTTP server that serves the MCP Streamable HTTP protocol on `/mcp`.
|
|
92
|
+
*
|
|
93
|
+
* The server manages stateful sessions — each MCP client gets its own transport
|
|
94
|
+
* and Server instance, tracked by session ID.
|
|
95
|
+
*/
|
|
96
|
+
export function createMcpServer(options) {
|
|
97
|
+
const { bindHost, grpcPort, apiKey } = options;
|
|
98
|
+
const grpcClient = createGrpcClient(bindHost, grpcPort, apiKey);
|
|
99
|
+
/** Map of active session transports, keyed by session ID. */
|
|
100
|
+
const transports = new Map();
|
|
101
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
102
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
103
|
+
// Only serve the /mcp endpoint
|
|
104
|
+
if (url.pathname !== "/mcp") {
|
|
105
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
106
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Auth check on every request
|
|
110
|
+
if (!verifyBearer(req, apiKey)) {
|
|
111
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const method = req.method?.toUpperCase();
|
|
116
|
+
if (method === "POST") {
|
|
117
|
+
await handlePost(req, res, grpcClient, transports);
|
|
118
|
+
}
|
|
119
|
+
else if (method === "GET") {
|
|
120
|
+
await handleGet(req, res, transports);
|
|
121
|
+
}
|
|
122
|
+
else if (method === "DELETE") {
|
|
123
|
+
await handleDelete(req, res, transports);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
127
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return httpServer;
|
|
131
|
+
}
|
|
132
|
+
/** Maximum request body size in bytes (1 MB). */
|
|
133
|
+
const MAX_BODY_SIZE = 1_048_576;
|
|
134
|
+
/** Parse the JSON body from an incoming HTTP request with size limit enforcement. */
|
|
135
|
+
async function parseBody(req) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const chunks = [];
|
|
138
|
+
let totalSize = 0;
|
|
139
|
+
req.on("data", (chunk) => {
|
|
140
|
+
totalSize += chunk.length;
|
|
141
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
142
|
+
req.destroy();
|
|
143
|
+
reject(new Error("Request body too large"));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
chunks.push(chunk);
|
|
147
|
+
});
|
|
148
|
+
req.on("end", () => {
|
|
149
|
+
try {
|
|
150
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
151
|
+
resolve(body);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
reject(err);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
req.on("error", reject);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** Handle POST requests to /mcp — initialization or tool calls. */
|
|
161
|
+
async function handlePost(req, res, grpcClient, transports) {
|
|
162
|
+
try {
|
|
163
|
+
const body = await parseBody(req);
|
|
164
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
165
|
+
if (sessionId && transports.has(sessionId)) {
|
|
166
|
+
// Existing session — route to its transport
|
|
167
|
+
const transport = transports.get(sessionId);
|
|
168
|
+
await transport.handleRequest(req, res, body);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (isInitializeRequest(body) && (!sessionId || !transports.has(sessionId))) {
|
|
172
|
+
// New initialization — create a new transport + server
|
|
173
|
+
const transport = new StreamableHTTPServerTransport({
|
|
174
|
+
sessionIdGenerator: () => randomUUID(),
|
|
175
|
+
onsessioninitialized: (sid) => {
|
|
176
|
+
logger.info({ sessionId: sid }, "MCP session initialized");
|
|
177
|
+
transports.set(sid, transport);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
transport.onclose = () => {
|
|
181
|
+
const sid = transport.sessionId;
|
|
182
|
+
if (sid && transports.has(sid)) {
|
|
183
|
+
logger.info({ sessionId: sid }, "MCP session closed");
|
|
184
|
+
transports.delete(sid);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const mcpServer = createMcpServerInstance(grpcClient);
|
|
188
|
+
await mcpServer.connect(transport);
|
|
189
|
+
await transport.handleRequest(req, res, body);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Invalid request — no session or not an init request
|
|
193
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
194
|
+
res.end(JSON.stringify({
|
|
195
|
+
jsonrpc: "2.0",
|
|
196
|
+
error: { code: -32000, message: "Bad Request: No valid session ID provided" },
|
|
197
|
+
id: null,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
logger.error({ err: error }, "Error handling MCP POST request");
|
|
202
|
+
if (!res.headersSent) {
|
|
203
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
204
|
+
res.end(JSON.stringify({
|
|
205
|
+
jsonrpc: "2.0",
|
|
206
|
+
error: { code: -32603, message: "Internal server error" },
|
|
207
|
+
id: null,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Handle GET requests to /mcp — SSE streams for server-initiated messages. */
|
|
213
|
+
async function handleGet(req, res, transports) {
|
|
214
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
215
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
216
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
217
|
+
res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const transport = transports.get(sessionId);
|
|
221
|
+
await transport.handleRequest(req, res);
|
|
222
|
+
}
|
|
223
|
+
/** Handle DELETE requests to /mcp — session termination. */
|
|
224
|
+
async function handleDelete(req, res, transports) {
|
|
225
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
226
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
227
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
228
|
+
res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const transport = transports.get(sessionId);
|
|
232
|
+
try {
|
|
233
|
+
await transport.handleRequest(req, res);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
logger.error({ err: error }, "Error handling session termination");
|
|
237
|
+
if (!res.headersSent) {
|
|
238
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
239
|
+
res.end(JSON.stringify({ error: "Error processing session termination" }));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { DEFAULT_MCP_PORT, DEFAULT_SERVER_PORT, GRACKLE_DIR, API_KEY_FILENAME } from "@grackle-ai/common";
|
|
6
|
+
import { createMcpServer } from "./mcp-server.js";
|
|
7
|
+
/** Allowed loopback bind addresses — security policy: never expose to the network. */
|
|
8
|
+
const ALLOWED_BIND_HOSTS = new Set(["127.0.0.1", "::1"]);
|
|
9
|
+
/** Load the API key from the default file location. */
|
|
10
|
+
function loadApiKey() {
|
|
11
|
+
const grackleHome = process.env.GRACKLE_HOME
|
|
12
|
+
? join(process.env.GRACKLE_HOME, GRACKLE_DIR)
|
|
13
|
+
: join(homedir(), GRACKLE_DIR);
|
|
14
|
+
const keyPath = join(grackleHome, API_KEY_FILENAME);
|
|
15
|
+
try {
|
|
16
|
+
const key = readFileSync(keyPath, "utf8").trim();
|
|
17
|
+
if (!key) {
|
|
18
|
+
console.error(`Error: API key file is empty: ${keyPath}\nRun "grackle serve" first to generate a key.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.error(`Error: Could not read API key from ${keyPath}\nRun "grackle serve" first to generate a key, or set GRACKLE_API_KEY.`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Start the standalone MCP server, pointed at an already-running Grackle gRPC server. */
|
|
29
|
+
function main() {
|
|
30
|
+
const mcpPort = parseInt(process.env.GRACKLE_MCP_PORT || String(DEFAULT_MCP_PORT), 10);
|
|
31
|
+
const bindHost = process.env.GRACKLE_HOST || "127.0.0.1";
|
|
32
|
+
const apiKey = process.env.GRACKLE_API_KEY || loadApiKey();
|
|
33
|
+
if (!ALLOWED_BIND_HOSTS.has(bindHost)) {
|
|
34
|
+
console.error(`Error: GRACKLE_HOST must be a loopback address (127.0.0.1 or ::1). Got: ${bindHost}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// Parse the gRPC server URL to extract host and port
|
|
38
|
+
const grackleUrl = process.env.GRACKLE_URL || `http://127.0.0.1:${DEFAULT_SERVER_PORT}`;
|
|
39
|
+
let grpcPort;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(grackleUrl);
|
|
42
|
+
grpcPort = parseInt(parsed.port || String(DEFAULT_SERVER_PORT), 10);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
console.error(`Error: Invalid GRACKLE_URL: ${grackleUrl}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const urlHost = bindHost.includes(":") ? `[${bindHost}]` : bindHost;
|
|
49
|
+
const server = createMcpServer({ bindHost, mcpPort, grpcPort, apiKey });
|
|
50
|
+
server.on("error", (err) => {
|
|
51
|
+
if (err.code === "EADDRINUSE") {
|
|
52
|
+
console.error(`Error: Port ${mcpPort} is already in use.`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error("MCP server error:", err);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
59
|
+
server.listen(mcpPort, bindHost, () => {
|
|
60
|
+
console.log(`Grackle MCP server listening on http://${urlHost}:${mcpPort}/mcp`);
|
|
61
|
+
console.log(`Connected to gRPC server at ${grackleUrl}`);
|
|
62
|
+
});
|
|
63
|
+
function shutdown() {
|
|
64
|
+
console.log("Shutting down MCP server...");
|
|
65
|
+
server.close(() => {
|
|
66
|
+
process.exit(0);
|
|
67
|
+
});
|
|
68
|
+
// Force exit after 5 seconds
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}, 5000).unref();
|
|
72
|
+
}
|
|
73
|
+
process.on("SIGINT", shutdown);
|
|
74
|
+
process.on("SIGTERM", shutdown);
|
|
75
|
+
}
|
|
76
|
+
main();
|
|
77
|
+
//# sourceMappingURL=standalone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Client } from "@connectrpc/connect";
|
|
2
|
+
import type { grackle } from "@grackle-ai/common";
|
|
3
|
+
/** Content item returned by an MCP tool handler. */
|
|
4
|
+
export interface ToolContent {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
/** Result returned by a tool handler to the MCP protocol layer. */
|
|
9
|
+
export interface ToolResult {
|
|
10
|
+
content: ToolContent[];
|
|
11
|
+
isError?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** JSON Schema describing a tool's input parameters. */
|
|
14
|
+
export interface ToolInputSchema {
|
|
15
|
+
type: "object";
|
|
16
|
+
properties?: Record<string, unknown>;
|
|
17
|
+
required?: string[];
|
|
18
|
+
}
|
|
19
|
+
/** Optional hints about a tool's behavior for the MCP client. */
|
|
20
|
+
export interface ToolAnnotations {
|
|
21
|
+
title?: string;
|
|
22
|
+
readOnlyHint?: boolean;
|
|
23
|
+
destructiveHint?: boolean;
|
|
24
|
+
idempotentHint?: boolean;
|
|
25
|
+
openWorldHint?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Declarative definition of an MCP tool backed by a ConnectRPC call. */
|
|
28
|
+
export interface ToolDefinition {
|
|
29
|
+
/** Unique tool name (snake_case by convention). */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Human-readable description shown to the AI client. */
|
|
32
|
+
description: string;
|
|
33
|
+
/** JSON Schema for the tool's input arguments. */
|
|
34
|
+
inputSchema: ToolInputSchema;
|
|
35
|
+
/** Optional behavioral hints for the client. */
|
|
36
|
+
annotations?: ToolAnnotations;
|
|
37
|
+
/** Execute the tool, forwarding to the ConnectRPC backend. */
|
|
38
|
+
handler: (args: Record<string, unknown>, client: Client<typeof grackle.Grackle>) => Promise<ToolResult>;
|
|
39
|
+
}
|
|
40
|
+
/** Predicate function for filtering tools (used by persona-scoped filtering). */
|
|
41
|
+
export type ToolPredicate = (tool: ToolDefinition) => boolean;
|
|
42
|
+
/** Registry of MCP tool definitions with lookup and filtering support. */
|
|
43
|
+
export declare class ToolRegistry {
|
|
44
|
+
private readonly tools;
|
|
45
|
+
/** Register a tool definition. Throws if a tool with the same name already exists. */
|
|
46
|
+
register(tool: ToolDefinition): void;
|
|
47
|
+
/** Return all registered tools, optionally filtered by a predicate. */
|
|
48
|
+
list(predicate?: ToolPredicate): ToolDefinition[];
|
|
49
|
+
/** Look up a tool by name. Returns undefined if not found. */
|
|
50
|
+
get(name: string): ToolDefinition | undefined;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=tool-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,WAAW,EAAE,eAAe,CAAC;IAC7B,gDAAgD;IAChD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CACzG;AAED,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC;AAE9D,0EAA0E;AAC1E,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;IAEhE,sFAAsF;IAC/E,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAO3C,uEAAuE;IAChE,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,cAAc,EAAE;IAQxD,8DAA8D;IACvD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;CAGrD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Registry of MCP tool definitions with lookup and filtering support. */
|
|
2
|
+
export class ToolRegistry {
|
|
3
|
+
tools = new Map();
|
|
4
|
+
/** Register a tool definition. Throws if a tool with the same name already exists. */
|
|
5
|
+
register(tool) {
|
|
6
|
+
if (this.tools.has(tool.name)) {
|
|
7
|
+
throw new Error(`Duplicate tool name: ${tool.name}`);
|
|
8
|
+
}
|
|
9
|
+
this.tools.set(tool.name, tool);
|
|
10
|
+
}
|
|
11
|
+
/** Return all registered tools, optionally filtered by a predicate. */
|
|
12
|
+
list(predicate) {
|
|
13
|
+
const all = Array.from(this.tools.values());
|
|
14
|
+
if (predicate) {
|
|
15
|
+
return all.filter(predicate);
|
|
16
|
+
}
|
|
17
|
+
return all;
|
|
18
|
+
}
|
|
19
|
+
/** Look up a tool by name. Returns undefined if not found. */
|
|
20
|
+
get(name) {
|
|
21
|
+
return this.tools.get(name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=tool-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAgDA,0EAA0E;AAC1E,MAAM,OAAO,YAAY;IACN,KAAK,GAAgC,IAAI,GAAG,EAAE,CAAC;IAEhE,sFAAsF;IAC/E,QAAQ,CAAC,IAAoB;QAClC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,uEAAuE;IAChE,IAAI,CAAC,SAAyB;QACnC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5C,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,8DAA8D;IACvD,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAInD,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,YAAY,CAKjD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ToolRegistry } from "../tool-registry.js";
|
|
2
|
+
import { listEnvironmentsTool } from "./list-environments.js";
|
|
3
|
+
import { listProjectsTool } from "./list-projects.js";
|
|
4
|
+
/** Create a ToolRegistry pre-populated with all available MCP tools. */
|
|
5
|
+
export function createToolRegistry() {
|
|
6
|
+
const registry = new ToolRegistry();
|
|
7
|
+
registry.register(listEnvironmentsTool);
|
|
8
|
+
registry.register(listProjectsTool);
|
|
9
|
+
return registry;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,wEAAwE;AACxE,MAAM,UAAU,kBAAkB;IAChC,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-environments.d.ts","sourceRoot":"","sources":["../../src/tools/list-environments.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAEtE,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,EAAE,cAyBlC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** MCP tool that lists all registered Grackle environments. */
|
|
2
|
+
export const listEnvironmentsTool = {
|
|
3
|
+
name: "list_environments",
|
|
4
|
+
description: "List all registered Grackle environments with their connection status, adapter type, and configuration.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {},
|
|
8
|
+
},
|
|
9
|
+
annotations: {
|
|
10
|
+
readOnlyHint: true,
|
|
11
|
+
destructiveHint: false,
|
|
12
|
+
idempotentHint: true,
|
|
13
|
+
openWorldHint: false,
|
|
14
|
+
},
|
|
15
|
+
async handler(_args, client) {
|
|
16
|
+
const response = await client.listEnvironments({});
|
|
17
|
+
const environments = response.environments.map((env) => ({
|
|
18
|
+
id: env.id,
|
|
19
|
+
displayName: env.displayName,
|
|
20
|
+
adapterType: env.adapterType,
|
|
21
|
+
status: env.status,
|
|
22
|
+
}));
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(environments, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=list-environments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-environments.js","sourceRoot":"","sources":["../../src/tools/list-environments.ts"],"names":[],"mappings":"AAIA,+DAA+D;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAmB;IAClD,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE,yGAAyG;IACtH,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE;KACf;IACD,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,KAAK;KACrB;IACD,KAAK,CAAC,OAAO,CAAC,KAA8B,EAAE,MAAsC;QAClF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACvD,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC,CAAC;QACJ,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-projects.d.ts","sourceRoot":"","sources":["../../src/tools/list-projects.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAEtE,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,EAAE,cAyB9B,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** MCP tool that lists all Grackle projects. */
|
|
2
|
+
export const listProjectsTool = {
|
|
3
|
+
name: "list_projects",
|
|
4
|
+
description: "List all Grackle projects with their names, descriptions, and associated repositories.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {},
|
|
8
|
+
},
|
|
9
|
+
annotations: {
|
|
10
|
+
readOnlyHint: true,
|
|
11
|
+
destructiveHint: false,
|
|
12
|
+
idempotentHint: true,
|
|
13
|
+
openWorldHint: false,
|
|
14
|
+
},
|
|
15
|
+
async handler(_args, client) {
|
|
16
|
+
const response = await client.listProjects({});
|
|
17
|
+
const projects = response.projects.map((project) => ({
|
|
18
|
+
id: project.id,
|
|
19
|
+
name: project.name,
|
|
20
|
+
description: project.description,
|
|
21
|
+
repoUrl: project.repoUrl,
|
|
22
|
+
}));
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=list-projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-projects.js","sourceRoot":"","sources":["../../src/tools/list-projects.ts"],"names":[],"mappings":"AAIA,gDAAgD;AAChD,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,wFAAwF;IACrG,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE;KACf;IACD,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,KAAK;KACrB;IACD,KAAK,CAAC,OAAO,CAAC,KAA8B,EAAE,MAAsC;QAClF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACnD,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC,CAAC;QACJ,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC;IACJ,CAAC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grackle-ai/mcp",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "MCP (Model Context Protocol) server for Grackle — translates MCP tool calls to ConnectRPC",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/nick-pape/grackle.git",
|
|
9
|
+
"directory": "packages/mcp"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"grackle",
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"ai-agents"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.0.0"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"grackle-mcp": "dist/standalone.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@bufbuild/protobuf": "^2.5.0",
|
|
31
|
+
"@connectrpc/connect": "^2.0.0",
|
|
32
|
+
"@connectrpc/connect-node": "^2.0.0",
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
34
|
+
"pino": "^10.3.1",
|
|
35
|
+
"@grackle-ai/common": "0.19.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@rushstack/heft": "1.2.4",
|
|
39
|
+
"@types/node": "^22.0.0",
|
|
40
|
+
"vitest": "^3.1.1",
|
|
41
|
+
"@grackle-ai/heft-rig": "0.0.1"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "heft build --clean",
|
|
45
|
+
"start": "node dist/standalone.js",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"clean": "heft clean"
|
|
48
|
+
}
|
|
49
|
+
}
|