dzql 0.5.33 → 0.6.1
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/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +309 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +653 -0
- package/docs/project-setup.md +456 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +166 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
package/src/server/mcp.js
DELETED
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
import { sql, db } from "./db.js";
|
|
2
|
-
import { serverLogger } from "./logger.js";
|
|
3
|
-
import { verify_jwt_token, create_jwt, createSIDPromise, resolveSID, rejectSID } from "./ws.js";
|
|
4
|
-
import { callAuthFunction } from "./db.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* MCP (Model Context Protocol) Server using JSON-RPC 2.0
|
|
8
|
-
*
|
|
9
|
-
* Provides database tools for Claude to interact with the system.
|
|
10
|
-
* Tools are auto-generated from database function metadata.
|
|
11
|
-
*
|
|
12
|
-
* Implements MCP specification: https://modelcontextprotocol.io/specification/2025-03-26
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Server info
|
|
16
|
-
const SERVER_INFO = {
|
|
17
|
-
name: "dzql-mcp-server",
|
|
18
|
-
version: "1.0.0",
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const PROTOCOL_VERSION = "2025-03-26";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Discover available entities from dzql.entities table
|
|
25
|
-
*/
|
|
26
|
-
async function discoverEntities() {
|
|
27
|
-
const result = await sql`
|
|
28
|
-
SELECT table_name, label_field, searchable_fields
|
|
29
|
-
FROM dzql.entities
|
|
30
|
-
ORDER BY table_name
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
// Build entities object with descriptions
|
|
34
|
-
const entities = {};
|
|
35
|
-
for (const row of result) {
|
|
36
|
-
const searchFields = row.searchable_fields?.join(', ') || 'none';
|
|
37
|
-
entities[row.table_name] = `DZQL entity: ${row.table_name} (label: ${row.label_field}, searchable: ${searchFields})`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { entities };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Generate static tool list (action-based pattern)
|
|
45
|
-
*/
|
|
46
|
-
async function generateToolList() {
|
|
47
|
-
serverLogger.info("Generating MCP tool list...");
|
|
48
|
-
|
|
49
|
-
const tools = [
|
|
50
|
-
{
|
|
51
|
-
name: "list_entities",
|
|
52
|
-
description: "Discover all available database entities with descriptions. Call this first to see what entities you can work with.",
|
|
53
|
-
inputSchema: {
|
|
54
|
-
type: "object",
|
|
55
|
-
properties: {},
|
|
56
|
-
required: [],
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "search",
|
|
61
|
-
description: "Search any entity. Calls search_<entity> with provided parameters. Common params: query (search string).",
|
|
62
|
-
inputSchema: {
|
|
63
|
-
type: "object",
|
|
64
|
-
properties: {
|
|
65
|
-
entity: {
|
|
66
|
-
type: "string",
|
|
67
|
-
description: "Entity name (e.g., 'organisations', 'venues')",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ["entity"],
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: "get",
|
|
75
|
-
description: "Get entity by ID. Calls get_<entity> with provided parameters. Requires entity-specific ID param (e.g., organisation_id, venue_id).",
|
|
76
|
-
inputSchema: {
|
|
77
|
-
type: "object",
|
|
78
|
-
properties: {
|
|
79
|
-
entity: {
|
|
80
|
-
type: "string",
|
|
81
|
-
description: "Entity name (singular, e.g., 'organisation', 'venue')",
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
required: ["entity"],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: "save",
|
|
89
|
-
description: "Create or update entity. Calls save_<entity> with provided parameters. Pass entity fields directly (e.g., name, description). Include id to update.",
|
|
90
|
-
inputSchema: {
|
|
91
|
-
type: "object",
|
|
92
|
-
properties: {
|
|
93
|
-
entity: {
|
|
94
|
-
type: "string",
|
|
95
|
-
description: "Entity name (singular, e.g., 'organisation', 'venue')",
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
required: ["entity"],
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: "delete",
|
|
103
|
-
description: "Delete entity by ID. Calls delete_<entity> with provided parameters. Requires entity-specific ID param (e.g., organisation_id, venue_id).",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
type: "object",
|
|
106
|
-
properties: {
|
|
107
|
-
entity: {
|
|
108
|
-
type: "string",
|
|
109
|
-
description: "Entity name (singular, e.g., 'organisation', 'venue')",
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
required: ["entity"],
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
// Custom tools
|
|
116
|
-
{
|
|
117
|
-
name: "screenshot_map",
|
|
118
|
-
description: "Capture a screenshot of the current map view. Sends a request to the client and waits for the screenshot to be captured and uploaded.",
|
|
119
|
-
inputSchema: {
|
|
120
|
-
type: "object",
|
|
121
|
-
properties: {},
|
|
122
|
-
required: [],
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
serverLogger.info(`Generated ${tools.length} MCP tools (5 CRUD + 1 custom)`);
|
|
128
|
-
|
|
129
|
-
return tools;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Create JSON-RPC 2.0 success response
|
|
134
|
-
*/
|
|
135
|
-
function createSuccessResponse(id, result) {
|
|
136
|
-
return {
|
|
137
|
-
jsonrpc: "2.0",
|
|
138
|
-
id,
|
|
139
|
-
result,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create JSON-RPC 2.0 error response
|
|
145
|
-
*/
|
|
146
|
-
function createErrorResponse(id, code, message, data = undefined) {
|
|
147
|
-
const response = {
|
|
148
|
-
jsonrpc: "2.0",
|
|
149
|
-
id,
|
|
150
|
-
error: {
|
|
151
|
-
code,
|
|
152
|
-
message,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
if (data !== undefined) {
|
|
157
|
-
response.error.data = data;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return response;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* JSON-RPC error codes
|
|
165
|
-
*/
|
|
166
|
-
const ErrorCodes = {
|
|
167
|
-
ParseError: -32700,
|
|
168
|
-
InvalidRequest: -32600,
|
|
169
|
-
MethodNotFound: -32601,
|
|
170
|
-
InvalidParams: -32602,
|
|
171
|
-
InternalError: -32603,
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// Session management
|
|
175
|
-
const sessions = new Map();
|
|
176
|
-
|
|
177
|
-
function generateSessionId() {
|
|
178
|
-
return crypto.randomUUID();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Create SSE (Server-Sent Events) response stream
|
|
183
|
-
*/
|
|
184
|
-
function createSSEStream() {
|
|
185
|
-
const stream = new ReadableStream({
|
|
186
|
-
start(controller) {
|
|
187
|
-
// Store controller for later use
|
|
188
|
-
this.controller = controller;
|
|
189
|
-
|
|
190
|
-
// Send initial comment to establish connection
|
|
191
|
-
controller.enqueue(`: MCP SSE stream established\n\n`);
|
|
192
|
-
},
|
|
193
|
-
cancel() {
|
|
194
|
-
// Clean up when client disconnects
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
stream,
|
|
200
|
-
send(data) {
|
|
201
|
-
try {
|
|
202
|
-
const encoder = new TextEncoder();
|
|
203
|
-
const message = `data: ${JSON.stringify(data)}\n\n`;
|
|
204
|
-
stream.controller?.enqueue(encoder.encode(message));
|
|
205
|
-
} catch (error) {
|
|
206
|
-
serverLogger.error("SSE send error:", error.message);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Create MCP route handler
|
|
214
|
-
* Returns an async function that handles HTTP requests for the /mcp endpoint
|
|
215
|
-
*/
|
|
216
|
-
export async function createMCPRoute(broadcastFn) {
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Handle initialize request
|
|
220
|
-
*/
|
|
221
|
-
async function handleInitialize(params) {
|
|
222
|
-
serverLogger.info("MCP initialize request received");
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
226
|
-
capabilities: {
|
|
227
|
-
tools: {
|
|
228
|
-
listChanged: true,
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
serverInfo: SERVER_INFO,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Handle tools/list request
|
|
237
|
-
*/
|
|
238
|
-
async function handleToolsList(params) {
|
|
239
|
-
const tools = await generateToolList();
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
tools,
|
|
243
|
-
// nextCursor is optional - we don't support pagination yet
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Handle tools/call request
|
|
249
|
-
*/
|
|
250
|
-
async function handleToolsCall(userId, params) {
|
|
251
|
-
const { name: toolName, arguments: toolInput } = params;
|
|
252
|
-
|
|
253
|
-
serverLogger.debug(`MCP tool called by user ${userId}: ${toolName}`, toolInput);
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
// Handle list_entities
|
|
257
|
-
if (toolName === "list_entities") {
|
|
258
|
-
const entities = await discoverEntities();
|
|
259
|
-
serverLogger.info(`User ${userId} listed ${Object.keys(entities.entities).length} entities`);
|
|
260
|
-
return {
|
|
261
|
-
content: [{
|
|
262
|
-
type: "text",
|
|
263
|
-
text: JSON.stringify(entities, null, 2),
|
|
264
|
-
}],
|
|
265
|
-
isError: false,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Handle CRUD actions (search, get, save, delete, lookup)
|
|
270
|
-
if (["search", "get", "save", "delete", "lookup"].includes(toolName)) {
|
|
271
|
-
const { entity, ...restParams } = toolInput || {};
|
|
272
|
-
|
|
273
|
-
if (!entity) {
|
|
274
|
-
return {
|
|
275
|
-
content: [{
|
|
276
|
-
type: "text",
|
|
277
|
-
text: "Missing required parameter: entity",
|
|
278
|
-
}],
|
|
279
|
-
isError: true,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
serverLogger.debug(`Calling DZQL ${toolName} on ${entity}`, restParams);
|
|
284
|
-
|
|
285
|
-
// Call DZQL generic operation via db.api
|
|
286
|
-
const result = await db.api[toolName][entity](restParams, userId);
|
|
287
|
-
|
|
288
|
-
serverLogger.info(`User ${userId} called ${toolName} on ${entity} successfully`);
|
|
289
|
-
|
|
290
|
-
// Format result as MCP content response
|
|
291
|
-
return {
|
|
292
|
-
content: [{
|
|
293
|
-
type: "text",
|
|
294
|
-
text: JSON.stringify(result, null, 2),
|
|
295
|
-
}],
|
|
296
|
-
isError: false,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Special case: screenshot_map - custom tool (not database function)
|
|
301
|
-
if (toolName === "screenshot_map") {
|
|
302
|
-
if (!broadcastFn) {
|
|
303
|
-
return {
|
|
304
|
-
content: [{
|
|
305
|
-
type: "text",
|
|
306
|
-
text: "Screenshot service not available",
|
|
307
|
-
}],
|
|
308
|
-
isError: true,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const { sid, promise } = createSIDPromise(15000); // 15 second timeout
|
|
313
|
-
|
|
314
|
-
// Broadcast take_screenshot request to client
|
|
315
|
-
const message = JSON.stringify({
|
|
316
|
-
jsonrpc: "2.0",
|
|
317
|
-
method: "take_screenshot",
|
|
318
|
-
params: { sid },
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
broadcastFn(message, userId);
|
|
322
|
-
serverLogger.debug(`Broadcast take_screenshot to user ${userId} with SID ${sid}`);
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
const result = await promise;
|
|
326
|
-
serverLogger.info(`Screenshot completed for user ${userId}: ${result.file_path}`);
|
|
327
|
-
|
|
328
|
-
// Format as MCP content response
|
|
329
|
-
return {
|
|
330
|
-
content: [{
|
|
331
|
-
type: "text",
|
|
332
|
-
text: JSON.stringify(result, null, 2),
|
|
333
|
-
}],
|
|
334
|
-
isError: false,
|
|
335
|
-
};
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return {
|
|
338
|
-
content: [{
|
|
339
|
-
type: "text",
|
|
340
|
-
text: error.message,
|
|
341
|
-
}],
|
|
342
|
-
isError: true,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Unknown tool
|
|
348
|
-
return {
|
|
349
|
-
content: [{
|
|
350
|
-
type: "text",
|
|
351
|
-
text: `Unknown tool: ${toolName}`,
|
|
352
|
-
}],
|
|
353
|
-
isError: true,
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
} catch (error) {
|
|
357
|
-
serverLogger.error(`Tool call error for ${toolName}:`, error.message);
|
|
358
|
-
|
|
359
|
-
// Return error as content with isError flag
|
|
360
|
-
return {
|
|
361
|
-
content: [{
|
|
362
|
-
type: "text",
|
|
363
|
-
text: error.message,
|
|
364
|
-
}],
|
|
365
|
-
isError: true,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Handle auth methods (login/register)
|
|
372
|
-
*/
|
|
373
|
-
async function handleAuthMethod(method, params) {
|
|
374
|
-
try {
|
|
375
|
-
const data = await callAuthFunction(method, params.email, params.password);
|
|
376
|
-
|
|
377
|
-
if (!data || !data.user_id) {
|
|
378
|
-
return {
|
|
379
|
-
user_id: null,
|
|
380
|
-
error: `${method} failed`,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Create JWT token for client storage
|
|
385
|
-
const token = await create_jwt({
|
|
386
|
-
user_id: data.user_id,
|
|
387
|
-
email: data.email,
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
serverLogger.info(`User authenticated via MCP: ${data.email} (id: ${data.user_id})`);
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
user_id: data.user_id,
|
|
394
|
-
email: data.email,
|
|
395
|
-
token,
|
|
396
|
-
profile: data.profile,
|
|
397
|
-
};
|
|
398
|
-
} catch (error) {
|
|
399
|
-
serverLogger.error(`MCP ${method} error:`, error.message);
|
|
400
|
-
return { error: error.message };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Authenticate request and extract user_id from JWT token
|
|
406
|
-
*/
|
|
407
|
-
async function authenticateRequest(authHeader) {
|
|
408
|
-
if (!authHeader) {
|
|
409
|
-
throw new Error("Missing Authorization header");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const token = authHeader.replace("Bearer ", "");
|
|
413
|
-
const payload = await verify_jwt_token(token);
|
|
414
|
-
|
|
415
|
-
if (!payload || !payload.user_id) {
|
|
416
|
-
throw new Error("Invalid or expired token");
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return payload.user_id;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Main route handler - processes JSON-RPC 2.0 requests
|
|
424
|
-
* Supports both HTTP POST (with optional SSE) and GET (SSE stream)
|
|
425
|
-
*/
|
|
426
|
-
return async (req) => {
|
|
427
|
-
const acceptHeader = req.headers.get("Accept") || "";
|
|
428
|
-
const wantsSSE = acceptHeader.includes("text/event-stream");
|
|
429
|
-
|
|
430
|
-
// Handle GET requests - open SSE stream
|
|
431
|
-
if (req.method === "GET") {
|
|
432
|
-
if (!wantsSSE) {
|
|
433
|
-
return new Response("GET requests require Accept: text/event-stream", {
|
|
434
|
-
status: 400
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
serverLogger.info("Opening SSE stream for MCP client");
|
|
439
|
-
|
|
440
|
-
// Create SSE stream
|
|
441
|
-
const { stream } = createSSEStream();
|
|
442
|
-
|
|
443
|
-
return new Response(stream, {
|
|
444
|
-
headers: {
|
|
445
|
-
"Content-Type": "text/event-stream",
|
|
446
|
-
"Cache-Control": "no-cache",
|
|
447
|
-
"Connection": "keep-alive",
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Handle POST requests
|
|
453
|
-
if (req.method !== "POST") {
|
|
454
|
-
return new Response("Method not allowed", { status: 405 });
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
let requestId = null;
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
// Parse JSON-RPC 2.0 request
|
|
461
|
-
const body = await req.json();
|
|
462
|
-
const { jsonrpc, id, method, params } = body;
|
|
463
|
-
|
|
464
|
-
requestId = id;
|
|
465
|
-
|
|
466
|
-
// Validate JSON-RPC version
|
|
467
|
-
if (jsonrpc !== "2.0") {
|
|
468
|
-
const errorResponse = createErrorResponse(
|
|
469
|
-
id,
|
|
470
|
-
ErrorCodes.InvalidRequest,
|
|
471
|
-
"Invalid JSON-RPC version. Expected '2.0'"
|
|
472
|
-
);
|
|
473
|
-
return new Response(JSON.stringify(errorResponse), {
|
|
474
|
-
status: 400,
|
|
475
|
-
headers: { "Content-Type": "application/json" },
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Validate method is present
|
|
480
|
-
if (!method) {
|
|
481
|
-
const errorResponse = createErrorResponse(
|
|
482
|
-
id,
|
|
483
|
-
ErrorCodes.InvalidRequest,
|
|
484
|
-
"Missing 'method' field"
|
|
485
|
-
);
|
|
486
|
-
return new Response(JSON.stringify(errorResponse), {
|
|
487
|
-
status: 400,
|
|
488
|
-
headers: { "Content-Type": "application/json" },
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
let result;
|
|
493
|
-
let sessionId = req.headers.get("Mcp-Session-Id");
|
|
494
|
-
|
|
495
|
-
// Route to appropriate handler based on method
|
|
496
|
-
switch (method) {
|
|
497
|
-
case "initialize":
|
|
498
|
-
result = await handleInitialize(params || {});
|
|
499
|
-
|
|
500
|
-
// Generate session ID on initialize if not present
|
|
501
|
-
if (!sessionId) {
|
|
502
|
-
sessionId = generateSessionId();
|
|
503
|
-
sessions.set(sessionId, { createdAt: Date.now() });
|
|
504
|
-
serverLogger.info(`Created MCP session: ${sessionId}`);
|
|
505
|
-
}
|
|
506
|
-
break;
|
|
507
|
-
|
|
508
|
-
case "notifications/initialized":
|
|
509
|
-
// Client confirms initialization is complete - no response needed
|
|
510
|
-
serverLogger.info("MCP client initialization complete");
|
|
511
|
-
// For notifications (no id), return 204 No Content
|
|
512
|
-
if (id === null || id === undefined) {
|
|
513
|
-
return new Response(null, { status: 204 });
|
|
514
|
-
}
|
|
515
|
-
// If id is present, it's not a proper notification, but acknowledge it
|
|
516
|
-
result = {};
|
|
517
|
-
break;
|
|
518
|
-
|
|
519
|
-
case "tools/list":
|
|
520
|
-
// Optional authentication - try to get userId from token, but don't require it
|
|
521
|
-
let listUserId = null;
|
|
522
|
-
const listAuthHeader = req.headers.get("Authorization");
|
|
523
|
-
if (listAuthHeader) {
|
|
524
|
-
try {
|
|
525
|
-
listUserId = await authenticateRequest(listAuthHeader);
|
|
526
|
-
} catch (error) {
|
|
527
|
-
// Ignore auth errors for tools/list - Claude Code doesn't support auth headers
|
|
528
|
-
serverLogger.warn("MCP tools/list: Invalid auth header, continuing without authentication");
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
result = await handleToolsList(params || {});
|
|
532
|
-
break;
|
|
533
|
-
|
|
534
|
-
case "tools/call":
|
|
535
|
-
// Optional authentication - try to get userId from token, but use default user if not available
|
|
536
|
-
let callUserId = 1; // Default to user 1 for local dev
|
|
537
|
-
const callAuthHeader = req.headers.get("Authorization");
|
|
538
|
-
if (callAuthHeader) {
|
|
539
|
-
try {
|
|
540
|
-
callUserId = await authenticateRequest(callAuthHeader);
|
|
541
|
-
} catch (error) {
|
|
542
|
-
// Use default user ID for Claude Code (which doesn't support auth headers)
|
|
543
|
-
serverLogger.warn("MCP tools/call: Invalid/missing auth header, using default user_id=1");
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
result = await handleToolsCall(callUserId, params || {});
|
|
547
|
-
break;
|
|
548
|
-
|
|
549
|
-
// Custom auth methods (non-standard MCP extension)
|
|
550
|
-
case "login_user":
|
|
551
|
-
case "register_user":
|
|
552
|
-
result = await handleAuthMethod(method, params || {});
|
|
553
|
-
break;
|
|
554
|
-
|
|
555
|
-
default:
|
|
556
|
-
const errorResponse = createErrorResponse(
|
|
557
|
-
id,
|
|
558
|
-
ErrorCodes.MethodNotFound,
|
|
559
|
-
`Unknown method: ${method}`
|
|
560
|
-
);
|
|
561
|
-
return new Response(JSON.stringify(errorResponse), {
|
|
562
|
-
status: 400,
|
|
563
|
-
headers: { "Content-Type": "application/json" },
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Build response with session ID if present
|
|
568
|
-
const successResponse = createSuccessResponse(id, result);
|
|
569
|
-
const responseHeaders = { "Content-Type": "application/json" };
|
|
570
|
-
|
|
571
|
-
if (sessionId) {
|
|
572
|
-
responseHeaders["Mcp-Session-Id"] = sessionId;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return new Response(JSON.stringify(successResponse), {
|
|
576
|
-
headers: responseHeaders,
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
} catch (error) {
|
|
580
|
-
serverLogger.error("MCP request error:", error.message);
|
|
581
|
-
|
|
582
|
-
// Return JSON-RPC error response
|
|
583
|
-
const errorResponse = createErrorResponse(
|
|
584
|
-
requestId,
|
|
585
|
-
ErrorCodes.InternalError,
|
|
586
|
-
error.message
|
|
587
|
-
);
|
|
588
|
-
return new Response(JSON.stringify(errorResponse), {
|
|
589
|
-
status: 500,
|
|
590
|
-
headers: { "Content-Type": "application/json" },
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
}
|