@spooky-sync/cli 0.0.1-canary.35 → 0.0.1-canary.37
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/devtools-mcp/bridge.d.ts +23 -0
- package/devtools-mcp/bridge.js +155 -0
- package/devtools-mcp/index.js +37 -0
- package/devtools-mcp/protocol.d.ts +34 -0
- package/devtools-mcp/protocol.js +37 -0
- package/devtools-mcp/server.d.ts +4 -0
- package/devtools-mcp/server.js +204 -0
- package/package.json +9 -9
- package/mcp-proxy/dist/index.js +0 -25
- package/mcp-proxy/dist/server.d.ts +0 -3
- package/mcp-proxy/dist/server.js +0 -74
- package/mcp-proxy/index.d.ts +0 -2
- package/mcp-proxy/index.js +0 -25
- package/mcp-proxy/server.d.ts +0 -3
- package/mcp-proxy/server.js +0 -74
- package/mcp-proxy/surreal.d.ts +0 -13
- package/mcp-proxy/surreal.js +0 -27
- /package/{mcp-proxy/dist → devtools-mcp}/index.d.ts +0 -0
- /package/{mcp-proxy/dist → devtools-mcp}/surreal.d.ts +0 -0
- /package/{mcp-proxy/dist → devtools-mcp}/surreal.js +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface ConnectedTab {
|
|
2
|
+
tabId: number;
|
|
3
|
+
url?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class Bridge {
|
|
7
|
+
private wss;
|
|
8
|
+
private extensionSocket;
|
|
9
|
+
private connectedTabs;
|
|
10
|
+
private pendingRequests;
|
|
11
|
+
private requestCounter;
|
|
12
|
+
private pingInterval;
|
|
13
|
+
get isConnected(): boolean;
|
|
14
|
+
getConnectedTabs(): ConnectedTab[];
|
|
15
|
+
start(): Promise<void>;
|
|
16
|
+
private startPing;
|
|
17
|
+
private stopPing;
|
|
18
|
+
private handleMessage;
|
|
19
|
+
request(method: string, params?: Record<string, unknown>, tabId?: number): Promise<unknown>;
|
|
20
|
+
private getDefaultTabId;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
2
|
+
import { isBridgeResponse, isBridgeNotification, BRIDGE_PORT, } from './protocol.js';
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
4
|
+
export class Bridge {
|
|
5
|
+
wss = null;
|
|
6
|
+
extensionSocket = null;
|
|
7
|
+
connectedTabs = new Map();
|
|
8
|
+
pendingRequests = new Map();
|
|
9
|
+
requestCounter = 0;
|
|
10
|
+
pingInterval = null;
|
|
11
|
+
get isConnected() {
|
|
12
|
+
return this.extensionSocket?.readyState === WebSocket.OPEN;
|
|
13
|
+
}
|
|
14
|
+
getConnectedTabs() {
|
|
15
|
+
return Array.from(this.connectedTabs.values());
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
const port = Number.parseInt(process.env.SP00KY_MCP_PORT || '', 10) || BRIDGE_PORT;
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
this.wss = new WebSocketServer({ host: '127.0.0.1', port }, () => {
|
|
21
|
+
process.stderr.write(`[sp00ky-mcp] Bridge listening on ws://127.0.0.1:${port}\n`);
|
|
22
|
+
resolve();
|
|
23
|
+
});
|
|
24
|
+
this.wss.on('error', (err) => {
|
|
25
|
+
process.stderr.write(`[sp00ky-mcp] Bridge error: ${err.message}\n`);
|
|
26
|
+
reject(err);
|
|
27
|
+
});
|
|
28
|
+
this.wss.on('connection', (ws) => {
|
|
29
|
+
process.stderr.write('[sp00ky-mcp] Extension connected\n');
|
|
30
|
+
// Only allow one extension connection at a time
|
|
31
|
+
if (this.extensionSocket) {
|
|
32
|
+
this.extensionSocket.close();
|
|
33
|
+
}
|
|
34
|
+
this.extensionSocket = ws;
|
|
35
|
+
// Start keepalive pings
|
|
36
|
+
this.startPing(ws);
|
|
37
|
+
ws.on('message', (data) => {
|
|
38
|
+
try {
|
|
39
|
+
const msg = JSON.parse(data.toString());
|
|
40
|
+
this.handleMessage(msg);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
process.stderr.write(`[sp00ky-mcp] Bad message: ${err}\n`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
ws.on('close', () => {
|
|
47
|
+
process.stderr.write('[sp00ky-mcp] Extension disconnected\n');
|
|
48
|
+
if (this.extensionSocket === ws) {
|
|
49
|
+
this.extensionSocket = null;
|
|
50
|
+
this.connectedTabs.clear();
|
|
51
|
+
this.stopPing();
|
|
52
|
+
// Reject all pending requests
|
|
53
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
54
|
+
pending.reject(new Error('Extension disconnected'));
|
|
55
|
+
clearTimeout(pending.timer);
|
|
56
|
+
this.pendingRequests.delete(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
ws.on('error', (err) => {
|
|
61
|
+
process.stderr.write(`[sp00ky-mcp] Socket error: ${err.message}\n`);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
startPing(ws) {
|
|
67
|
+
this.stopPing();
|
|
68
|
+
this.pingInterval = setInterval(() => {
|
|
69
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
70
|
+
ws.ping();
|
|
71
|
+
}
|
|
72
|
+
}, 20_000);
|
|
73
|
+
}
|
|
74
|
+
stopPing() {
|
|
75
|
+
if (this.pingInterval) {
|
|
76
|
+
clearInterval(this.pingInterval);
|
|
77
|
+
this.pingInterval = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
handleMessage(msg) {
|
|
81
|
+
// Handle response to a pending request
|
|
82
|
+
if (isBridgeResponse(msg)) {
|
|
83
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
84
|
+
if (pending) {
|
|
85
|
+
clearTimeout(pending.timer);
|
|
86
|
+
this.pendingRequests.delete(msg.id);
|
|
87
|
+
if (msg.error) {
|
|
88
|
+
pending.reject(new Error(msg.error.message));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
pending.resolve(msg.result);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Handle notifications from extension
|
|
97
|
+
if (isBridgeNotification(msg)) {
|
|
98
|
+
if (msg.method === 'tabsChanged') {
|
|
99
|
+
this.connectedTabs.clear();
|
|
100
|
+
const tabs = msg.params.tabs;
|
|
101
|
+
for (const tab of tabs) {
|
|
102
|
+
this.connectedTabs.set(tab.tabId, tab);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async request(method, params = {}, tabId) {
|
|
109
|
+
if (!this.extensionSocket || this.extensionSocket.readyState !== WebSocket.OPEN) {
|
|
110
|
+
throw new Error('No extension connected. Make sure the Sp00ky DevTools extension is running and has a page with Sp00ky open.');
|
|
111
|
+
}
|
|
112
|
+
const id = `mcp-${++this.requestCounter}`;
|
|
113
|
+
const resolvedTabId = tabId ?? this.getDefaultTabId();
|
|
114
|
+
const request = {
|
|
115
|
+
jsonrpc: '2.0',
|
|
116
|
+
id,
|
|
117
|
+
method,
|
|
118
|
+
params,
|
|
119
|
+
...(resolvedTabId !== undefined ? { tabId: resolvedTabId } : {}),
|
|
120
|
+
};
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const timer = setTimeout(() => {
|
|
123
|
+
this.pendingRequests.delete(id);
|
|
124
|
+
reject(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS}ms: ${method}`));
|
|
125
|
+
}, REQUEST_TIMEOUT_MS);
|
|
126
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
127
|
+
// oxlint-disable-next-line no-non-null-assertion
|
|
128
|
+
this.extensionSocket.send(JSON.stringify(request));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
getDefaultTabId() {
|
|
132
|
+
const tabs = this.getConnectedTabs();
|
|
133
|
+
return tabs.length > 0 ? tabs[0].tabId : undefined;
|
|
134
|
+
}
|
|
135
|
+
async stop() {
|
|
136
|
+
this.stopPing();
|
|
137
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
138
|
+
clearTimeout(pending.timer);
|
|
139
|
+
pending.reject(new Error('Bridge shutting down'));
|
|
140
|
+
this.pendingRequests.delete(id);
|
|
141
|
+
}
|
|
142
|
+
if (this.extensionSocket) {
|
|
143
|
+
this.extensionSocket.close();
|
|
144
|
+
this.extensionSocket = null;
|
|
145
|
+
}
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
if (this.wss) {
|
|
148
|
+
this.wss.close(() => resolve());
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
resolve();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { Bridge } from './bridge.js';
|
|
4
|
+
import { SurrealClient } from './surreal.js';
|
|
5
|
+
import { createServer } from './server.js';
|
|
6
|
+
async function main() {
|
|
7
|
+
const bridge = new Bridge();
|
|
8
|
+
await bridge.start();
|
|
9
|
+
const surreal = process.env.SURREAL_URL
|
|
10
|
+
? new SurrealClient({
|
|
11
|
+
url: process.env.SURREAL_URL,
|
|
12
|
+
namespace: process.env.SURREAL_NS ?? 'main',
|
|
13
|
+
database: process.env.SURREAL_DB ?? 'main',
|
|
14
|
+
username: process.env.SURREAL_USER ?? 'root',
|
|
15
|
+
password: process.env.SURREAL_PASS ?? 'root',
|
|
16
|
+
})
|
|
17
|
+
: null;
|
|
18
|
+
if (surreal) {
|
|
19
|
+
process.stderr.write(`[sp00ky-mcp] Direct DB mode enabled (${process.env.SURREAL_URL})\n`);
|
|
20
|
+
}
|
|
21
|
+
const server = createServer(bridge, surreal);
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
process.stderr.write('[sp00ky-mcp] MCP server running on stdio\n');
|
|
25
|
+
// Graceful shutdown
|
|
26
|
+
const cleanup = async () => {
|
|
27
|
+
process.stderr.write('[sp00ky-mcp] Shutting down...\n');
|
|
28
|
+
await bridge.stop();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
};
|
|
31
|
+
process.on('SIGINT', cleanup);
|
|
32
|
+
process.on('SIGTERM', cleanup);
|
|
33
|
+
}
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
process.stderr.write(`[sp00ky-mcp] Fatal error: ${err.message}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface BridgeRequest {
|
|
2
|
+
jsonrpc: '2.0';
|
|
3
|
+
id: string;
|
|
4
|
+
method: string;
|
|
5
|
+
params: Record<string, unknown>;
|
|
6
|
+
tabId?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface BridgeResponse {
|
|
9
|
+
jsonrpc: '2.0';
|
|
10
|
+
id: string;
|
|
11
|
+
result?: unknown;
|
|
12
|
+
error?: {
|
|
13
|
+
code: number;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface BridgeNotification {
|
|
18
|
+
jsonrpc: '2.0';
|
|
19
|
+
method: string;
|
|
20
|
+
params: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export type BridgeMessage = BridgeRequest | BridgeResponse | BridgeNotification;
|
|
23
|
+
export declare const BRIDGE_METHODS: {
|
|
24
|
+
readonly GET_STATE: "getState";
|
|
25
|
+
readonly RUN_QUERY: "runQuery";
|
|
26
|
+
readonly GET_TABLE_DATA: "getTableData";
|
|
27
|
+
readonly UPDATE_TABLE_ROW: "updateTableRow";
|
|
28
|
+
readonly DELETE_TABLE_ROW: "deleteTableRow";
|
|
29
|
+
readonly CLEAR_HISTORY: "clearHistory";
|
|
30
|
+
};
|
|
31
|
+
export declare const BRIDGE_PORT = 9315;
|
|
32
|
+
export declare function isBridgeResponse(msg: unknown): msg is BridgeResponse;
|
|
33
|
+
export declare function isBridgeRequest(msg: unknown): msg is BridgeRequest;
|
|
34
|
+
export declare function isBridgeNotification(msg: unknown): msg is BridgeNotification;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Shared message types for MCP Server <-> Chrome Extension bridge (JSON-RPC 2.0 style)
|
|
2
|
+
// Methods the MCP server can call on the extension
|
|
3
|
+
export const BRIDGE_METHODS = {
|
|
4
|
+
GET_STATE: 'getState',
|
|
5
|
+
RUN_QUERY: 'runQuery',
|
|
6
|
+
GET_TABLE_DATA: 'getTableData',
|
|
7
|
+
UPDATE_TABLE_ROW: 'updateTableRow',
|
|
8
|
+
DELETE_TABLE_ROW: 'deleteTableRow',
|
|
9
|
+
CLEAR_HISTORY: 'clearHistory',
|
|
10
|
+
};
|
|
11
|
+
export const BRIDGE_PORT = 9315;
|
|
12
|
+
export function isBridgeResponse(msg) {
|
|
13
|
+
return (typeof msg === 'object' &&
|
|
14
|
+
msg !== null &&
|
|
15
|
+
'jsonrpc' in msg &&
|
|
16
|
+
msg.jsonrpc === '2.0' &&
|
|
17
|
+
'id' in msg &&
|
|
18
|
+
('result' in msg || 'error' in msg));
|
|
19
|
+
}
|
|
20
|
+
export function isBridgeRequest(msg) {
|
|
21
|
+
return (typeof msg === 'object' &&
|
|
22
|
+
msg !== null &&
|
|
23
|
+
'jsonrpc' in msg &&
|
|
24
|
+
msg.jsonrpc === '2.0' &&
|
|
25
|
+
'method' in msg &&
|
|
26
|
+
'id' in msg &&
|
|
27
|
+
!('result' in msg) &&
|
|
28
|
+
!('error' in msg));
|
|
29
|
+
}
|
|
30
|
+
export function isBridgeNotification(msg) {
|
|
31
|
+
return (typeof msg === 'object' &&
|
|
32
|
+
msg !== null &&
|
|
33
|
+
'jsonrpc' in msg &&
|
|
34
|
+
msg.jsonrpc === '2.0' &&
|
|
35
|
+
'method' in msg &&
|
|
36
|
+
!('id' in msg));
|
|
37
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BRIDGE_METHODS } from './protocol.js';
|
|
4
|
+
function json(data) {
|
|
5
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
6
|
+
}
|
|
7
|
+
export function createServer(bridge, surreal) {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: 'sp00ky-devtools',
|
|
10
|
+
version: '0.0.1',
|
|
11
|
+
});
|
|
12
|
+
// --- Tools ---
|
|
13
|
+
server.tool('list_connections', 'List browser tabs connected to Sp00ky DevTools', {}, async () => {
|
|
14
|
+
return json({ connected: bridge.isConnected, tabs: bridge.getConnectedTabs() });
|
|
15
|
+
});
|
|
16
|
+
server.tool('get_state', 'Get the full Sp00ky DevTools state (events, queries, auth, database)', { tabId: z.number().optional().describe('Browser tab ID (uses first connected tab if omitted)') }, async ({ tabId }) => {
|
|
17
|
+
if (!bridge.isConnected) {
|
|
18
|
+
throw new Error('No extension connected. get_state requires the Sp00ky DevTools browser extension.');
|
|
19
|
+
}
|
|
20
|
+
const result = await bridge.request(BRIDGE_METHODS.GET_STATE, {}, tabId);
|
|
21
|
+
return json(result);
|
|
22
|
+
});
|
|
23
|
+
server.tool('run_query', 'Execute a SurrealQL query against the database', {
|
|
24
|
+
query: z.string().describe('SurrealQL query to execute'),
|
|
25
|
+
target: z.enum(['local', 'remote']).optional().default('remote').describe('Query target: local or remote database'),
|
|
26
|
+
tabId: z.number().optional().describe('Browser tab ID'),
|
|
27
|
+
}, async ({ query, target, tabId }) => {
|
|
28
|
+
if (bridge.isConnected) {
|
|
29
|
+
const result = await bridge.request(BRIDGE_METHODS.RUN_QUERY, { query, target }, tabId);
|
|
30
|
+
return json(result);
|
|
31
|
+
}
|
|
32
|
+
if (surreal) {
|
|
33
|
+
const result = await surreal.query(query);
|
|
34
|
+
return json(result);
|
|
35
|
+
}
|
|
36
|
+
throw new Error('No extension connected and no direct database configured. Set SURREAL_URL or connect the browser extension.');
|
|
37
|
+
});
|
|
38
|
+
server.tool('list_tables', 'List all database tables', { tabId: z.number().optional().describe('Browser tab ID') }, async ({ tabId }) => {
|
|
39
|
+
if (bridge.isConnected) {
|
|
40
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE, {}, tabId));
|
|
41
|
+
const tables = state?.database?.tables ?? [];
|
|
42
|
+
return json(tables);
|
|
43
|
+
}
|
|
44
|
+
if (surreal) {
|
|
45
|
+
const result = await surreal.query('INFO FOR DB;');
|
|
46
|
+
const info = result;
|
|
47
|
+
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
48
|
+
return json(Object.keys(tables));
|
|
49
|
+
}
|
|
50
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
51
|
+
});
|
|
52
|
+
server.tool('get_table_data', 'Fetch records from a database table', {
|
|
53
|
+
tableName: z.string().describe('Name of the table'),
|
|
54
|
+
limit: z.number().optional().default(100).describe('Max number of records to return'),
|
|
55
|
+
tabId: z.number().optional().describe('Browser tab ID'),
|
|
56
|
+
}, async ({ tableName, limit, tabId }) => {
|
|
57
|
+
if (bridge.isConnected) {
|
|
58
|
+
const result = await bridge.request(BRIDGE_METHODS.GET_TABLE_DATA, { tableName }, tabId);
|
|
59
|
+
return json(result);
|
|
60
|
+
}
|
|
61
|
+
if (surreal) {
|
|
62
|
+
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT ${limit};`);
|
|
63
|
+
return json(result);
|
|
64
|
+
}
|
|
65
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
66
|
+
});
|
|
67
|
+
server.tool('update_table_row', 'Update a record in a database table', {
|
|
68
|
+
tableName: z.string().optional().describe('Name of the table (used when browser extension is connected)'),
|
|
69
|
+
recordId: z.string().describe('Record ID to update (e.g. "users:abc123")'),
|
|
70
|
+
updates: z.record(z.unknown()).describe('Fields to update'),
|
|
71
|
+
tabId: z.number().optional().describe('Browser tab ID'),
|
|
72
|
+
}, async ({ tableName, recordId, updates, tabId }) => {
|
|
73
|
+
if (bridge.isConnected) {
|
|
74
|
+
const result = await bridge.request(BRIDGE_METHODS.UPDATE_TABLE_ROW, { tableName, recordId, updates }, tabId);
|
|
75
|
+
return json(result);
|
|
76
|
+
}
|
|
77
|
+
if (surreal) {
|
|
78
|
+
const result = await surreal.query(`UPDATE ${recordId} MERGE ${JSON.stringify(updates)};`);
|
|
79
|
+
return json(result);
|
|
80
|
+
}
|
|
81
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
82
|
+
});
|
|
83
|
+
server.tool('delete_table_row', 'Delete a record from a database table', {
|
|
84
|
+
tableName: z.string().optional().describe('Name of the table (used when browser extension is connected)'),
|
|
85
|
+
recordId: z.string().describe('Record ID to delete (e.g. "users:abc123")'),
|
|
86
|
+
tabId: z.number().optional().describe('Browser tab ID'),
|
|
87
|
+
}, async ({ tableName, recordId, tabId }) => {
|
|
88
|
+
if (bridge.isConnected) {
|
|
89
|
+
const result = await bridge.request(BRIDGE_METHODS.DELETE_TABLE_ROW, { tableName, recordId }, tabId);
|
|
90
|
+
return json(result);
|
|
91
|
+
}
|
|
92
|
+
if (surreal) {
|
|
93
|
+
const result = await surreal.query(`DELETE ${recordId};`);
|
|
94
|
+
return json(result);
|
|
95
|
+
}
|
|
96
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
97
|
+
});
|
|
98
|
+
server.tool('get_active_queries', 'Get all active live queries and their data', { tabId: z.number().optional().describe('Browser tab ID') }, async ({ tabId }) => {
|
|
99
|
+
if (bridge.isConnected) {
|
|
100
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE, {}, tabId));
|
|
101
|
+
return json(state?.activeQueries ?? []);
|
|
102
|
+
}
|
|
103
|
+
if (surreal) {
|
|
104
|
+
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
105
|
+
return json(result);
|
|
106
|
+
}
|
|
107
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
108
|
+
});
|
|
109
|
+
server.tool('get_events', 'Get event history, optionally filtered by type', {
|
|
110
|
+
eventType: z.string().optional().describe('Filter by event type'),
|
|
111
|
+
limit: z.number().optional().default(50).describe('Max number of events to return'),
|
|
112
|
+
tabId: z.number().optional().describe('Browser tab ID'),
|
|
113
|
+
}, async ({ eventType, limit, tabId }) => {
|
|
114
|
+
if (bridge.isConnected) {
|
|
115
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE, {}, tabId));
|
|
116
|
+
let events = state?.eventsHistory ?? [];
|
|
117
|
+
if (eventType) {
|
|
118
|
+
events = events.filter((e) => e.eventType === eventType);
|
|
119
|
+
}
|
|
120
|
+
if (limit) {
|
|
121
|
+
events = events.slice(-limit);
|
|
122
|
+
}
|
|
123
|
+
return json(events);
|
|
124
|
+
}
|
|
125
|
+
if (surreal) {
|
|
126
|
+
const result = await surreal.query(`SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT ${limit};`);
|
|
127
|
+
return json(result);
|
|
128
|
+
}
|
|
129
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
130
|
+
});
|
|
131
|
+
server.tool('get_auth_state', 'Get the current authentication state', { tabId: z.number().optional().describe('Browser tab ID') }, async ({ tabId }) => {
|
|
132
|
+
if (!bridge.isConnected) {
|
|
133
|
+
throw new Error('No extension connected. get_auth_state requires the Sp00ky DevTools browser extension.');
|
|
134
|
+
}
|
|
135
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE, {}, tabId));
|
|
136
|
+
return json(state?.auth ?? null);
|
|
137
|
+
});
|
|
138
|
+
server.tool('clear_history', 'Clear the event history', { tabId: z.number().optional().describe('Browser tab ID') }, async ({ tabId }) => {
|
|
139
|
+
if (!bridge.isConnected) {
|
|
140
|
+
throw new Error('No extension connected. clear_history requires the Sp00ky DevTools browser extension.');
|
|
141
|
+
}
|
|
142
|
+
await bridge.request(BRIDGE_METHODS.CLEAR_HISTORY, {}, tabId);
|
|
143
|
+
return { content: [{ type: 'text', text: 'History cleared.' }] };
|
|
144
|
+
});
|
|
145
|
+
// --- Resources ---
|
|
146
|
+
server.resource('state', 'sp00ky://state', { description: 'Full Sp00ky DevTools state' }, async (uri) => {
|
|
147
|
+
if (!bridge.isConnected) {
|
|
148
|
+
throw new Error('No extension connected. State resource requires the browser extension.');
|
|
149
|
+
}
|
|
150
|
+
const state = await bridge.request(BRIDGE_METHODS.GET_STATE);
|
|
151
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(state, null, 2) }] };
|
|
152
|
+
});
|
|
153
|
+
server.resource('tables', 'sp00ky://tables', { description: 'List of database tables' }, async (uri) => {
|
|
154
|
+
if (bridge.isConnected) {
|
|
155
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE));
|
|
156
|
+
const tables = state?.database?.tables ?? [];
|
|
157
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(tables, null, 2) }] };
|
|
158
|
+
}
|
|
159
|
+
if (surreal) {
|
|
160
|
+
const result = await surreal.query('INFO FOR DB;');
|
|
161
|
+
const info = result;
|
|
162
|
+
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
163
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(Object.keys(tables), null, 2) }] };
|
|
164
|
+
}
|
|
165
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
166
|
+
});
|
|
167
|
+
server.resource('table-data', new ResourceTemplate('sp00ky://tables/{tableName}', { list: undefined }), { description: 'Contents of a specific database table' }, async (uri, variables) => {
|
|
168
|
+
const tableName = variables.tableName;
|
|
169
|
+
if (bridge.isConnected) {
|
|
170
|
+
const result = await bridge.request(BRIDGE_METHODS.GET_TABLE_DATA, { tableName });
|
|
171
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
172
|
+
}
|
|
173
|
+
if (surreal) {
|
|
174
|
+
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT 100;`);
|
|
175
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
176
|
+
}
|
|
177
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
178
|
+
});
|
|
179
|
+
server.resource('queries', 'sp00ky://queries', { description: 'Active live queries' }, async (uri) => {
|
|
180
|
+
if (bridge.isConnected) {
|
|
181
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE));
|
|
182
|
+
const queries = state?.activeQueries ?? [];
|
|
183
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(queries, null, 2) }] };
|
|
184
|
+
}
|
|
185
|
+
if (surreal) {
|
|
186
|
+
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
187
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
188
|
+
}
|
|
189
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
190
|
+
});
|
|
191
|
+
server.resource('events', 'sp00ky://events', { description: 'Event history' }, async (uri) => {
|
|
192
|
+
if (bridge.isConnected) {
|
|
193
|
+
const state = (await bridge.request(BRIDGE_METHODS.GET_STATE));
|
|
194
|
+
const events = state?.eventsHistory ?? [];
|
|
195
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(events, null, 2) }] };
|
|
196
|
+
}
|
|
197
|
+
if (surreal) {
|
|
198
|
+
const result = await surreal.query('SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT 50;');
|
|
199
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
200
|
+
}
|
|
201
|
+
throw new Error('No extension connected and no direct database configured.');
|
|
202
|
+
});
|
|
203
|
+
return server;
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/cli",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.37",
|
|
4
4
|
"description": "Generate TypeScript/Dart types from SurrealDB schema files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/syncgen.cjs",
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
-
"mcp
|
|
21
|
+
"devtools-mcp"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"dev": "vite",
|
|
25
25
|
"build:rust": "cargo build --release",
|
|
26
26
|
"build:vite": "vite build",
|
|
27
27
|
"build:types": "tsc --emitDeclarationOnly --declaration --declarationDir dist",
|
|
28
|
-
"build:mcp
|
|
28
|
+
"build:devtools-mcp": "cd ../devtools-mcp && npm run build && cp -r dist ../cli/devtools-mcp",
|
|
29
29
|
"build:js": "npm run build:vite && npm run build:types",
|
|
30
|
-
"build": "npm run build:rust && npm run build:mcp
|
|
30
|
+
"build": "npm run build:rust && npm run build:devtools-mcp && npm run build:js",
|
|
31
31
|
"preview": "vite preview",
|
|
32
32
|
"test": "vitest",
|
|
33
33
|
"lint": "eslint src",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"vitest": "^1.0.0"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@spooky-sync/cli-darwin-arm64": "0.0.1-canary.
|
|
62
|
-
"@spooky-sync/cli-darwin-x64": "0.0.1-canary.
|
|
63
|
-
"@spooky-sync/cli-linux-arm64": "0.0.1-canary.
|
|
64
|
-
"@spooky-sync/cli-linux-x64": "0.0.1-canary.
|
|
65
|
-
"@spooky-sync/cli-win32-x64": "0.0.1-canary.
|
|
61
|
+
"@spooky-sync/cli-darwin-arm64": "0.0.1-canary.37",
|
|
62
|
+
"@spooky-sync/cli-darwin-x64": "0.0.1-canary.37",
|
|
63
|
+
"@spooky-sync/cli-linux-arm64": "0.0.1-canary.37",
|
|
64
|
+
"@spooky-sync/cli-linux-x64": "0.0.1-canary.37",
|
|
65
|
+
"@spooky-sync/cli-win32-x64": "0.0.1-canary.37"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/mcp-proxy/dist/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { SurrealClient } from './surreal.js';
|
|
4
|
-
import { createServer } from './server.js';
|
|
5
|
-
function main() {
|
|
6
|
-
const surreal = new SurrealClient({
|
|
7
|
-
url: process.env.SURREAL_URL ?? 'http://localhost:8666',
|
|
8
|
-
namespace: process.env.SURREAL_NS ?? 'main',
|
|
9
|
-
database: process.env.SURREAL_DB ?? 'main',
|
|
10
|
-
username: process.env.SURREAL_USER ?? 'root',
|
|
11
|
-
password: process.env.SURREAL_PASS ?? 'root',
|
|
12
|
-
});
|
|
13
|
-
const server = createServer(surreal);
|
|
14
|
-
const transport = new StdioServerTransport();
|
|
15
|
-
server.connect(transport).then(() => {
|
|
16
|
-
process.stderr.write('[sp00ky-mcp-proxy] MCP server running on stdio\n');
|
|
17
|
-
});
|
|
18
|
-
const cleanup = async () => {
|
|
19
|
-
process.stderr.write('[sp00ky-mcp-proxy] Shutting down...\n');
|
|
20
|
-
process.exit(0);
|
|
21
|
-
};
|
|
22
|
-
process.on('SIGINT', cleanup);
|
|
23
|
-
process.on('SIGTERM', cleanup);
|
|
24
|
-
}
|
|
25
|
-
main();
|
package/mcp-proxy/dist/server.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
export function createServer(surreal) {
|
|
4
|
-
const server = new McpServer({
|
|
5
|
-
name: 'sp00ky-mcp-proxy',
|
|
6
|
-
version: '0.0.1',
|
|
7
|
-
});
|
|
8
|
-
// --- Tools ---
|
|
9
|
-
server.tool('run_query', 'Execute a SurrealQL query against the database', {
|
|
10
|
-
query: z.string().describe('SurrealQL query to execute'),
|
|
11
|
-
}, async ({ query }) => {
|
|
12
|
-
const result = await surreal.query(query);
|
|
13
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
14
|
-
});
|
|
15
|
-
server.tool('list_tables', 'List all database tables', {}, async () => {
|
|
16
|
-
const result = await surreal.query('INFO FOR DB;');
|
|
17
|
-
const info = result;
|
|
18
|
-
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
19
|
-
const tableNames = Object.keys(tables);
|
|
20
|
-
return { content: [{ type: 'text', text: JSON.stringify(tableNames, null, 2) }] };
|
|
21
|
-
});
|
|
22
|
-
server.tool('get_table_data', 'Fetch records from a database table', {
|
|
23
|
-
tableName: z.string().describe('Name of the table'),
|
|
24
|
-
limit: z.number().optional().default(100).describe('Max number of records to return'),
|
|
25
|
-
}, async ({ tableName, limit }) => {
|
|
26
|
-
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT ${limit};`);
|
|
27
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
28
|
-
});
|
|
29
|
-
server.tool('update_table_row', 'Update a record in a database table', {
|
|
30
|
-
recordId: z.string().describe('Record ID to update (e.g. "users:abc123")'),
|
|
31
|
-
updates: z.record(z.unknown()).describe('Fields to update'),
|
|
32
|
-
}, async ({ recordId, updates }) => {
|
|
33
|
-
const result = await surreal.query(`UPDATE ${recordId} MERGE ${JSON.stringify(updates)};`);
|
|
34
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
35
|
-
});
|
|
36
|
-
server.tool('delete_table_row', 'Delete a record from a database table', {
|
|
37
|
-
recordId: z.string().describe('Record ID to delete (e.g. "users:abc123")'),
|
|
38
|
-
}, async ({ recordId }) => {
|
|
39
|
-
const result = await surreal.query(`DELETE ${recordId};`);
|
|
40
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
41
|
-
});
|
|
42
|
-
server.tool('get_active_queries', 'Get all active live queries registered with the SSP', {}, async () => {
|
|
43
|
-
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
44
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
45
|
-
});
|
|
46
|
-
server.tool('get_events', 'Get event history, optionally limited', {
|
|
47
|
-
limit: z.number().optional().default(50).describe('Max number of events to return'),
|
|
48
|
-
}, async ({ limit }) => {
|
|
49
|
-
const result = await surreal.query(`SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT ${limit};`);
|
|
50
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
51
|
-
});
|
|
52
|
-
// --- Resources ---
|
|
53
|
-
server.resource('tables', 'sp00ky://tables', { description: 'List of database tables' }, async (uri) => {
|
|
54
|
-
const result = await surreal.query('INFO FOR DB;');
|
|
55
|
-
const info = result;
|
|
56
|
-
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
57
|
-
const tableNames = Object.keys(tables);
|
|
58
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(tableNames, null, 2) }] };
|
|
59
|
-
});
|
|
60
|
-
server.resource('table-data', new ResourceTemplate('sp00ky://tables/{tableName}', { list: undefined }), { description: 'Contents of a specific database table' }, async (uri, variables) => {
|
|
61
|
-
const tableName = variables.tableName;
|
|
62
|
-
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT 100;`);
|
|
63
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
64
|
-
});
|
|
65
|
-
server.resource('queries', 'sp00ky://queries', { description: 'Active live queries' }, async (uri) => {
|
|
66
|
-
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
67
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
68
|
-
});
|
|
69
|
-
server.resource('events', 'sp00ky://events', { description: 'Event history' }, async (uri) => {
|
|
70
|
-
const result = await surreal.query('SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT 50;');
|
|
71
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
72
|
-
});
|
|
73
|
-
return server;
|
|
74
|
-
}
|
package/mcp-proxy/index.d.ts
DELETED
package/mcp-proxy/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { SurrealClient } from './surreal.js';
|
|
4
|
-
import { createServer } from './server.js';
|
|
5
|
-
function main() {
|
|
6
|
-
const surreal = new SurrealClient({
|
|
7
|
-
url: process.env.SURREAL_URL ?? 'http://localhost:8666',
|
|
8
|
-
namespace: process.env.SURREAL_NS ?? 'main',
|
|
9
|
-
database: process.env.SURREAL_DB ?? 'main',
|
|
10
|
-
username: process.env.SURREAL_USER ?? 'root',
|
|
11
|
-
password: process.env.SURREAL_PASS ?? 'root',
|
|
12
|
-
});
|
|
13
|
-
const server = createServer(surreal);
|
|
14
|
-
const transport = new StdioServerTransport();
|
|
15
|
-
server.connect(transport).then(() => {
|
|
16
|
-
process.stderr.write('[sp00ky-mcp-proxy] MCP server running on stdio\n');
|
|
17
|
-
});
|
|
18
|
-
const cleanup = async () => {
|
|
19
|
-
process.stderr.write('[sp00ky-mcp-proxy] Shutting down...\n');
|
|
20
|
-
process.exit(0);
|
|
21
|
-
};
|
|
22
|
-
process.on('SIGINT', cleanup);
|
|
23
|
-
process.on('SIGTERM', cleanup);
|
|
24
|
-
}
|
|
25
|
-
main();
|
package/mcp-proxy/server.d.ts
DELETED
package/mcp-proxy/server.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
export function createServer(surreal) {
|
|
4
|
-
const server = new McpServer({
|
|
5
|
-
name: 'sp00ky-mcp-proxy',
|
|
6
|
-
version: '0.0.1',
|
|
7
|
-
});
|
|
8
|
-
// --- Tools ---
|
|
9
|
-
server.tool('run_query', 'Execute a SurrealQL query against the database', {
|
|
10
|
-
query: z.string().describe('SurrealQL query to execute'),
|
|
11
|
-
}, async ({ query }) => {
|
|
12
|
-
const result = await surreal.query(query);
|
|
13
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
14
|
-
});
|
|
15
|
-
server.tool('list_tables', 'List all database tables', {}, async () => {
|
|
16
|
-
const result = await surreal.query('INFO FOR DB;');
|
|
17
|
-
const info = result;
|
|
18
|
-
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
19
|
-
const tableNames = Object.keys(tables);
|
|
20
|
-
return { content: [{ type: 'text', text: JSON.stringify(tableNames, null, 2) }] };
|
|
21
|
-
});
|
|
22
|
-
server.tool('get_table_data', 'Fetch records from a database table', {
|
|
23
|
-
tableName: z.string().describe('Name of the table'),
|
|
24
|
-
limit: z.number().optional().default(100).describe('Max number of records to return'),
|
|
25
|
-
}, async ({ tableName, limit }) => {
|
|
26
|
-
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT ${limit};`);
|
|
27
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
28
|
-
});
|
|
29
|
-
server.tool('update_table_row', 'Update a record in a database table', {
|
|
30
|
-
recordId: z.string().describe('Record ID to update (e.g. "users:abc123")'),
|
|
31
|
-
updates: z.record(z.unknown()).describe('Fields to update'),
|
|
32
|
-
}, async ({ recordId, updates }) => {
|
|
33
|
-
const result = await surreal.query(`UPDATE ${recordId} MERGE ${JSON.stringify(updates)};`);
|
|
34
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
35
|
-
});
|
|
36
|
-
server.tool('delete_table_row', 'Delete a record from a database table', {
|
|
37
|
-
recordId: z.string().describe('Record ID to delete (e.g. "users:abc123")'),
|
|
38
|
-
}, async ({ recordId }) => {
|
|
39
|
-
const result = await surreal.query(`DELETE ${recordId};`);
|
|
40
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
41
|
-
});
|
|
42
|
-
server.tool('get_active_queries', 'Get all active live queries registered with the SSP', {}, async () => {
|
|
43
|
-
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
44
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
45
|
-
});
|
|
46
|
-
server.tool('get_events', 'Get event history, optionally limited', {
|
|
47
|
-
limit: z.number().optional().default(50).describe('Max number of events to return'),
|
|
48
|
-
}, async ({ limit }) => {
|
|
49
|
-
const result = await surreal.query(`SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT ${limit};`);
|
|
50
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
51
|
-
});
|
|
52
|
-
// --- Resources ---
|
|
53
|
-
server.resource('tables', 'sp00ky://tables', { description: 'List of database tables' }, async (uri) => {
|
|
54
|
-
const result = await surreal.query('INFO FOR DB;');
|
|
55
|
-
const info = result;
|
|
56
|
-
const tables = info?.[0]?.result?.tables ?? info?.[0]?.tables ?? {};
|
|
57
|
-
const tableNames = Object.keys(tables);
|
|
58
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(tableNames, null, 2) }] };
|
|
59
|
-
});
|
|
60
|
-
server.resource('table-data', new ResourceTemplate('sp00ky://tables/{tableName}', { list: undefined }), { description: 'Contents of a specific database table' }, async (uri, variables) => {
|
|
61
|
-
const tableName = variables.tableName;
|
|
62
|
-
const result = await surreal.query(`SELECT * FROM \`${tableName}\` LIMIT 100;`);
|
|
63
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
64
|
-
});
|
|
65
|
-
server.resource('queries', 'sp00ky://queries', { description: 'Active live queries' }, async (uri) => {
|
|
66
|
-
const result = await surreal.query('SELECT * FROM _00_query;');
|
|
67
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
68
|
-
});
|
|
69
|
-
server.resource('events', 'sp00ky://events', { description: 'Event history' }, async (uri) => {
|
|
70
|
-
const result = await surreal.query('SELECT * FROM _00_events ORDER BY timestamp DESC LIMIT 50;');
|
|
71
|
-
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }] };
|
|
72
|
-
});
|
|
73
|
-
return server;
|
|
74
|
-
}
|
package/mcp-proxy/surreal.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface SurrealConfig {
|
|
2
|
-
url: string;
|
|
3
|
-
namespace: string;
|
|
4
|
-
database: string;
|
|
5
|
-
username: string;
|
|
6
|
-
password: string;
|
|
7
|
-
}
|
|
8
|
-
export declare class SurrealClient {
|
|
9
|
-
private config;
|
|
10
|
-
private authHeader;
|
|
11
|
-
constructor(config: SurrealConfig);
|
|
12
|
-
query(surql: string): Promise<unknown[]>;
|
|
13
|
-
}
|
package/mcp-proxy/surreal.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export class SurrealClient {
|
|
2
|
-
config;
|
|
3
|
-
authHeader;
|
|
4
|
-
constructor(config) {
|
|
5
|
-
this.config = config;
|
|
6
|
-
this.authHeader =
|
|
7
|
-
'Basic ' + Buffer.from(`${config.username}:${config.password}`).toString('base64');
|
|
8
|
-
}
|
|
9
|
-
async query(surql) {
|
|
10
|
-
const res = await fetch(`${this.config.url}/sql`, {
|
|
11
|
-
method: 'POST',
|
|
12
|
-
headers: {
|
|
13
|
-
'Content-Type': 'application/json',
|
|
14
|
-
Authorization: this.authHeader,
|
|
15
|
-
'surreal-ns': this.config.namespace,
|
|
16
|
-
'surreal-db': this.config.database,
|
|
17
|
-
Accept: 'application/json',
|
|
18
|
-
},
|
|
19
|
-
body: surql,
|
|
20
|
-
});
|
|
21
|
-
if (!res.ok) {
|
|
22
|
-
const text = await res.text();
|
|
23
|
-
throw new Error(`SurrealDB query failed (${res.status}): ${text}`);
|
|
24
|
-
}
|
|
25
|
-
return res.json();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|