@kineviz/graphxr-mcp 0.2.1 → 0.4.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/README.md +103 -11
- package/dist/index.js +544 -13
- package/dist/index.js.map +7 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ Create or edit `.cursor/mcp.json` in your project or home directory:
|
|
|
60
60
|
|
|
61
61
|
#### Claude Desktop
|
|
62
62
|
|
|
63
|
-
Add to your Claude Desktop configuration:
|
|
63
|
+
Add to your Claude Desktop configuration (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
64
64
|
|
|
65
65
|
```json
|
|
66
66
|
{
|
|
@@ -83,8 +83,8 @@ Add to your Claude Desktop configuration:
|
|
|
83
83
|
|----------|----------|---------|-------------|
|
|
84
84
|
| `GRAPHXR_API_KEY` | Yes | - | Your GraphXR API key |
|
|
85
85
|
| `GRAPHXR_URL` | No | `http://localhost:9000` | GraphXR server URL |
|
|
86
|
-
| `
|
|
87
|
-
| `DEBUG` | No | `false` | Enable debug logging |
|
|
86
|
+
| `GRAPHXR_HEADLESS` | No | `true` | Run browser in headless mode (set to `false` to see the browser) |
|
|
87
|
+
| `DEBUG` or `GRAPHXR_DEBUG` | No | `false` | Enable debug logging |
|
|
88
88
|
|
|
89
89
|
## Available Tools
|
|
90
90
|
|
|
@@ -95,9 +95,8 @@ Once configured, you can ask Claude/Cursor to:
|
|
|
95
95
|
- **open_project** - Open a project in the browser session
|
|
96
96
|
- **run_javascript** - Execute JavaScript code using the `gxr` API
|
|
97
97
|
- **screenshot** - Take a screenshot of the current graph
|
|
98
|
-
- **export_graph** - Export graph data as JSON
|
|
99
|
-
- **
|
|
100
|
-
- **close_browser** - Close the browser session
|
|
98
|
+
- **export_graph** - Export graph data as JSON, GraphXR, or CSV
|
|
99
|
+
- **close_project** - Close the browser session
|
|
101
100
|
|
|
102
101
|
## Example Usage
|
|
103
102
|
|
|
@@ -134,11 +133,13 @@ Enable debug logging to see detailed output:
|
|
|
134
133
|
```json
|
|
135
134
|
{
|
|
136
135
|
"env": {
|
|
137
|
-
"
|
|
136
|
+
"GRAPHXR_DEBUG": "true"
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
```
|
|
141
140
|
|
|
141
|
+
You can also use `DEBUG=true` which works the same way.
|
|
142
|
+
|
|
142
143
|
## GraphXR API Reference
|
|
143
144
|
|
|
144
145
|
The `run_javascript` tool executes code with access to the `gxr` global object. Here are common operations:
|
|
@@ -351,6 +352,73 @@ const projectName = gxr.getProjectName();
|
|
|
351
352
|
|
|
352
353
|
For full API documentation, see the [GraphXR API Reference](https://graphxr.kineviz.com/docs).
|
|
353
354
|
|
|
355
|
+
## Development
|
|
356
|
+
|
|
357
|
+
### Setup
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# Install dependencies
|
|
361
|
+
yarn install
|
|
362
|
+
|
|
363
|
+
# Build for development (with sourcemaps)
|
|
364
|
+
yarn build:dev
|
|
365
|
+
|
|
366
|
+
# Build for npm package
|
|
367
|
+
yarn build
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Testing Locally with MCP Inspector
|
|
371
|
+
|
|
372
|
+
You can test the MCP server locally using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), a visual debugging tool for MCP servers:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Run the inspector with your built server
|
|
376
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This will open a web interface where you can:
|
|
380
|
+
- View all available tools and their schemas
|
|
381
|
+
- Execute tools interactively and see responses
|
|
382
|
+
- Debug request/response payloads
|
|
383
|
+
|
|
384
|
+
Make sure to set the required environment variables before running:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Set environment variables and run inspector
|
|
388
|
+
GRAPHXR_API_KEY=your-api-key GRAPHXR_URL=https://graphxr.kineviz.com npx @modelcontextprotocol/inspector node dist/index.js
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Or export them first:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
export GRAPHXR_API_KEY=your-api-key
|
|
395
|
+
export GRAPHXR_URL=https://graphxr.kineviz.com
|
|
396
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
To see the browser during testing, also set:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
export GRAPHXR_HEADLESS=false
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Project Structure
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
graphxr-mcp/
|
|
409
|
+
├── src/
|
|
410
|
+
│ ├── index.ts # Main MCP server
|
|
411
|
+
│ ├── browser-session.ts # Playwright browser manager
|
|
412
|
+
│ └── graphxr-client.ts # REST API client
|
|
413
|
+
├── scripts/
|
|
414
|
+
│ ├── build.ts # Dev build script
|
|
415
|
+
│ └── build-package.js # npm package build
|
|
416
|
+
├── dist/ # Build output
|
|
417
|
+
├── package.json
|
|
418
|
+
├── tsconfig.json
|
|
419
|
+
└── README.md
|
|
420
|
+
```
|
|
421
|
+
|
|
354
422
|
## Publishing (for maintainers)
|
|
355
423
|
|
|
356
424
|
### Prerequisites
|
|
@@ -360,19 +428,43 @@ For full API documentation, see the [GraphXR API Reference](https://graphxr.kine
|
|
|
360
428
|
|
|
361
429
|
### First-time Setup
|
|
362
430
|
|
|
363
|
-
|
|
431
|
+
1. **Create an npm account** (if you don't have one): https://www.npmjs.com/signup
|
|
432
|
+
|
|
433
|
+
2. **Request access** to the `@kineviz` scope from a team administrator
|
|
434
|
+
|
|
435
|
+
3. **Log in to npm** with the @kineviz scope:
|
|
364
436
|
|
|
365
437
|
```bash
|
|
366
438
|
npm login --scope=@kineviz
|
|
367
439
|
```
|
|
368
440
|
|
|
369
|
-
|
|
441
|
+
This will prompt you for your npm username, password, and email. If you have 2FA enabled, you'll also need to enter a one-time password.
|
|
442
|
+
|
|
443
|
+
4. **Verify you're logged in**:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
npm whoami
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
This should display your npm username.
|
|
450
|
+
|
|
451
|
+
### Before Each Release
|
|
452
|
+
|
|
453
|
+
Verify your npm session is still active:
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
npm whoami
|
|
457
|
+
```
|
|
370
458
|
|
|
371
|
-
|
|
459
|
+
If you get an error, log in again:
|
|
372
460
|
|
|
373
461
|
```bash
|
|
374
|
-
|
|
462
|
+
npm login --scope=@kineviz
|
|
463
|
+
```
|
|
375
464
|
|
|
465
|
+
### Release Process
|
|
466
|
+
|
|
467
|
+
```bash
|
|
376
468
|
# Bump version and publish in one command
|
|
377
469
|
npm version patch && yarn release # 0.1.0 → 0.1.1 (bug fixes)
|
|
378
470
|
npm version minor && yarn release # 0.1.0 → 0.2.0 (new features)
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -27,17 +28,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
28
|
));
|
|
28
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
30
|
|
|
30
|
-
// index.ts
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var src_exports = {};
|
|
33
|
+
__export(src_exports, {
|
|
33
34
|
GraphXRMCPServer: () => GraphXRMCPServer
|
|
34
35
|
});
|
|
35
|
-
module.exports = __toCommonJS(
|
|
36
|
+
module.exports = __toCommonJS(src_exports);
|
|
36
37
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
37
38
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
38
39
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
39
40
|
|
|
40
|
-
// graphxr-client.ts
|
|
41
|
+
// src/graphxr-client.ts
|
|
41
42
|
var import_node_fetch = __toESM(require("node-fetch"));
|
|
42
43
|
var DEBUG = process.env.DEBUG === "true" || process.env.GRAPHXR_DEBUG === "true";
|
|
43
44
|
function debug(...args) {
|
|
@@ -45,7 +46,87 @@ function debug(...args) {
|
|
|
45
46
|
console.error("[GraphXR Client]", (/* @__PURE__ */ new Date()).toISOString(), ...args);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
function convertCodeModeToMarkdown(codeMode) {
|
|
50
|
+
switch (codeMode) {
|
|
51
|
+
case "javascript2":
|
|
52
|
+
return "js";
|
|
53
|
+
default:
|
|
54
|
+
return codeMode;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function convertMarkdownToCodeMode(langTag) {
|
|
58
|
+
switch (langTag) {
|
|
59
|
+
case "js":
|
|
60
|
+
return "javascript2";
|
|
61
|
+
default:
|
|
62
|
+
return langTag || "javascript2";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function generateUUID() {
|
|
66
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
67
|
+
const r = Math.random() * 16 | 0;
|
|
68
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
69
|
+
return v.toString(16);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function convertGroveToMarkdown(grove) {
|
|
73
|
+
const mdBlocks = grove.blocks.map((block) => {
|
|
74
|
+
if (block.type === "codeTool" && block.data.codeData) {
|
|
75
|
+
const { pinCode, dname, codeMode, hide, value } = block.data.codeData;
|
|
76
|
+
const cellOptions = {
|
|
77
|
+
pinCode,
|
|
78
|
+
dname,
|
|
79
|
+
codeMode,
|
|
80
|
+
hide
|
|
81
|
+
};
|
|
82
|
+
const cellOptionsStr = `<!--${JSON.stringify(cellOptions)}-->`;
|
|
83
|
+
const langTag = convertCodeModeToMarkdown(codeMode);
|
|
84
|
+
return `${cellOptionsStr}
|
|
85
|
+
\`\`\`${langTag}
|
|
86
|
+
${value}
|
|
87
|
+
\`\`\``;
|
|
88
|
+
}
|
|
89
|
+
return block.data.text || "";
|
|
90
|
+
});
|
|
91
|
+
return mdBlocks.join("\n\n");
|
|
92
|
+
}
|
|
93
|
+
function convertMarkdownToGrove(mdContent) {
|
|
94
|
+
const codeBlockRegex = /(?:<!--(.*)-->\n)?```(\w+)?\n([\s\S]*?)```/g;
|
|
95
|
+
const blocks = [];
|
|
96
|
+
let match;
|
|
97
|
+
while ((match = codeBlockRegex.exec(mdContent)) !== null) {
|
|
98
|
+
const cellOptionsStr = match[1];
|
|
99
|
+
const langTag = match[2];
|
|
100
|
+
const codeContent = match[3].trim();
|
|
101
|
+
let cellOptions = {};
|
|
102
|
+
if (cellOptionsStr) {
|
|
103
|
+
try {
|
|
104
|
+
cellOptions = JSON.parse(cellOptionsStr);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const block = {
|
|
109
|
+
type: "codeTool",
|
|
110
|
+
data: {
|
|
111
|
+
codeData: {
|
|
112
|
+
value: codeContent,
|
|
113
|
+
pinCode: cellOptions.pinCode ?? false,
|
|
114
|
+
dname: cellOptions.dname ?? generateUUID(),
|
|
115
|
+
codeMode: convertMarkdownToCodeMode(langTag) || cellOptions.codeMode || "javascript2",
|
|
116
|
+
hide: cellOptions.hide ?? false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
blocks.push(block);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
blocks,
|
|
124
|
+
version: "2.19.1"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
48
127
|
var GraphXRClient = class {
|
|
128
|
+
baseUrl;
|
|
129
|
+
apiKey;
|
|
49
130
|
constructor(config) {
|
|
50
131
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
51
132
|
this.apiKey = config.apiKey;
|
|
@@ -178,9 +259,164 @@ var GraphXRClient = class {
|
|
|
178
259
|
getApiKey() {
|
|
179
260
|
return this.apiKey;
|
|
180
261
|
}
|
|
262
|
+
// ==========================================================================
|
|
263
|
+
// Grove API Methods
|
|
264
|
+
// ==========================================================================
|
|
265
|
+
/**
|
|
266
|
+
* Load all files and folders in a project's grove directory.
|
|
267
|
+
*
|
|
268
|
+
* @param projectId - The project ID
|
|
269
|
+
* @returns Object containing file and folder metadata
|
|
270
|
+
*/
|
|
271
|
+
async groveLoad(projectId) {
|
|
272
|
+
debug("groveLoad: loading project", projectId);
|
|
273
|
+
const response = await this.request(
|
|
274
|
+
"POST",
|
|
275
|
+
"/api/grove/load",
|
|
276
|
+
{ projectId }
|
|
277
|
+
);
|
|
278
|
+
debug("groveLoad: loaded", Object.keys(response.content || {}).length, "items");
|
|
279
|
+
return response.content || {};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Download a grove file and convert it to markdown format.
|
|
283
|
+
*
|
|
284
|
+
* @param projectId - The project ID
|
|
285
|
+
* @param filePath - The file path within the project
|
|
286
|
+
* @returns The file content as markdown string
|
|
287
|
+
*/
|
|
288
|
+
async groveDownloadFile(projectId, filePath) {
|
|
289
|
+
debug("groveDownloadFile:", projectId, filePath);
|
|
290
|
+
const url = `${this.baseUrl}/tmp/observable/projects/${projectId}/${filePath}`;
|
|
291
|
+
const response = await (0, import_node_fetch.default)(url, {
|
|
292
|
+
headers: {
|
|
293
|
+
"x-api-key": this.apiKey
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
|
|
298
|
+
}
|
|
299
|
+
const groveContent = await response.json();
|
|
300
|
+
debug("groveDownloadFile: got", groveContent.blocks?.length || 0, "blocks");
|
|
301
|
+
return convertGroveToMarkdown(groveContent);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Upload markdown content as a grove file.
|
|
305
|
+
*
|
|
306
|
+
* @param projectId - The project ID
|
|
307
|
+
* @param fileName - The file name/path within the project
|
|
308
|
+
* @param markdownContent - The markdown content to convert and upload
|
|
309
|
+
* @returns Upload response
|
|
310
|
+
*/
|
|
311
|
+
async groveUploadFile(projectId, fileName, markdownContent) {
|
|
312
|
+
debug("groveUploadFile:", projectId, fileName);
|
|
313
|
+
const grovePayload = convertMarkdownToGrove(markdownContent);
|
|
314
|
+
debug("groveUploadFile: converted to", grovePayload.blocks.length, "blocks");
|
|
315
|
+
const boundary = "----FormBoundary" + Math.random().toString(36).substring(2);
|
|
316
|
+
const formParts = [];
|
|
317
|
+
formParts.push(`--${boundary}`);
|
|
318
|
+
formParts.push('Content-Disposition: form-data; name="fileName"');
|
|
319
|
+
formParts.push("");
|
|
320
|
+
formParts.push(fileName);
|
|
321
|
+
formParts.push(`--${boundary}`);
|
|
322
|
+
formParts.push('Content-Disposition: form-data; name="projectId"');
|
|
323
|
+
formParts.push("");
|
|
324
|
+
formParts.push(projectId);
|
|
325
|
+
formParts.push(`--${boundary}`);
|
|
326
|
+
formParts.push('Content-Disposition: form-data; name="data"; filename="data"');
|
|
327
|
+
formParts.push("Content-Type: text/plain");
|
|
328
|
+
formParts.push("");
|
|
329
|
+
formParts.push(JSON.stringify(grovePayload));
|
|
330
|
+
formParts.push(`--${boundary}--`);
|
|
331
|
+
const body = formParts.join("\r\n");
|
|
332
|
+
const response = await (0, import_node_fetch.default)(`${this.baseUrl}/api/grove/simpleUploadFile`, {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: {
|
|
335
|
+
"Accept": "application/json",
|
|
336
|
+
"x-api-key": this.apiKey,
|
|
337
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`
|
|
338
|
+
},
|
|
339
|
+
body
|
|
340
|
+
});
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
const errorText = await response.text();
|
|
343
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}: ${errorText}`);
|
|
344
|
+
}
|
|
345
|
+
const result = await response.json();
|
|
346
|
+
debug("groveUploadFile: success", result);
|
|
347
|
+
return { filePath: result.content?.filePath || "" };
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Delete a file from the grove directory.
|
|
351
|
+
*
|
|
352
|
+
* @param projectId - The project ID
|
|
353
|
+
* @param fileKey - The file path/key to delete
|
|
354
|
+
* @returns The deleted file key
|
|
355
|
+
*/
|
|
356
|
+
async groveDeleteFile(projectId, fileKey) {
|
|
357
|
+
debug("groveDeleteFile:", projectId, fileKey);
|
|
358
|
+
const response = await this.request(
|
|
359
|
+
"POST",
|
|
360
|
+
"/api/grove/removeFile",
|
|
361
|
+
{ projectId, fileKey }
|
|
362
|
+
);
|
|
363
|
+
debug("groveDeleteFile: deleted", response.content);
|
|
364
|
+
return response.content || fileKey;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Rename or move a file within the grove directory.
|
|
368
|
+
*
|
|
369
|
+
* @param projectId - The project ID
|
|
370
|
+
* @param oldFileKey - Current file path/key
|
|
371
|
+
* @param newFileKey - New file path/key
|
|
372
|
+
* @returns The updated file info
|
|
373
|
+
*/
|
|
374
|
+
async groveRenameFile(projectId, oldFileKey, newFileKey) {
|
|
375
|
+
debug("groveRenameFile:", projectId, oldFileKey, "->", newFileKey);
|
|
376
|
+
const response = await this.request(
|
|
377
|
+
"POST",
|
|
378
|
+
"/api/grove/renameFile",
|
|
379
|
+
{ projectId, oldFileKey, newFileKey }
|
|
380
|
+
);
|
|
381
|
+
debug("groveRenameFile: renamed to", response.content?.fileKey);
|
|
382
|
+
return response.content;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Create a new folder in the grove directory.
|
|
386
|
+
*
|
|
387
|
+
* @param projectId - The project ID
|
|
388
|
+
* @param folderKey - Full folder path (e.g., "parent/newfolder/")
|
|
389
|
+
* @param folderName - Folder name
|
|
390
|
+
* @returns The created folder info
|
|
391
|
+
*/
|
|
392
|
+
async groveCreateFolder(projectId, folderKey, folderName) {
|
|
393
|
+
debug("groveCreateFolder:", projectId, folderKey, folderName);
|
|
394
|
+
const response = await this.request(
|
|
395
|
+
"POST",
|
|
396
|
+
"/api/grove/uploadFolder",
|
|
397
|
+
{ projectId, folderKey, folderName }
|
|
398
|
+
);
|
|
399
|
+
debug("groveCreateFolder: created", response.content?.folderKey);
|
|
400
|
+
return response.content;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Delete a folder and all its contents from the grove directory.
|
|
404
|
+
*
|
|
405
|
+
* @param projectId - The project ID
|
|
406
|
+
* @param folderKey - Folder path to delete
|
|
407
|
+
*/
|
|
408
|
+
async groveDeleteFolder(projectId, folderKey) {
|
|
409
|
+
debug("groveDeleteFolder:", projectId, folderKey);
|
|
410
|
+
await this.request(
|
|
411
|
+
"POST",
|
|
412
|
+
"/api/grove/removeFolder",
|
|
413
|
+
{ projectId, folderKey, _ids: [] }
|
|
414
|
+
);
|
|
415
|
+
debug("groveDeleteFolder: deleted");
|
|
416
|
+
}
|
|
181
417
|
};
|
|
182
418
|
|
|
183
|
-
// browser-session.ts
|
|
419
|
+
// src/browser-session.ts
|
|
184
420
|
var import_playwright = require("playwright");
|
|
185
421
|
var DEBUG2 = process.env.DEBUG === "true" || process.env.GRAPHXR_DEBUG === "true";
|
|
186
422
|
function debug2(...args) {
|
|
@@ -189,11 +425,13 @@ function debug2(...args) {
|
|
|
189
425
|
}
|
|
190
426
|
}
|
|
191
427
|
var BrowserSession = class {
|
|
428
|
+
client;
|
|
429
|
+
config;
|
|
430
|
+
browser = null;
|
|
431
|
+
context = null;
|
|
432
|
+
page = null;
|
|
433
|
+
currentProject = null;
|
|
192
434
|
constructor(client, config = {}) {
|
|
193
|
-
this.browser = null;
|
|
194
|
-
this.context = null;
|
|
195
|
-
this.page = null;
|
|
196
|
-
this.currentProject = null;
|
|
197
435
|
this.client = client;
|
|
198
436
|
this.config = {
|
|
199
437
|
headless: config.headless ?? true,
|
|
@@ -449,7 +687,7 @@ var BrowserSession = class {
|
|
|
449
687
|
}
|
|
450
688
|
};
|
|
451
689
|
|
|
452
|
-
// index.ts
|
|
690
|
+
// src/index.ts
|
|
453
691
|
var GRAPHXR_API_KEY = process.env.GRAPHXR_API_KEY || "";
|
|
454
692
|
var GRAPHXR_URL = process.env.GRAPHXR_URL || "http://localhost:9000";
|
|
455
693
|
var HEADLESS = process.env.GRAPHXR_HEADLESS !== "false";
|
|
@@ -569,12 +807,152 @@ The code should return a value that can be serialized to JSON.`,
|
|
|
569
807
|
properties: {},
|
|
570
808
|
required: []
|
|
571
809
|
}
|
|
810
|
+
},
|
|
811
|
+
// Grove (Grovebook) tools
|
|
812
|
+
{
|
|
813
|
+
name: "grove_list",
|
|
814
|
+
description: "List all grovebook files and folders in a project. Returns file metadata including names, sizes, versions, and modification times.",
|
|
815
|
+
inputSchema: {
|
|
816
|
+
type: "object",
|
|
817
|
+
properties: {
|
|
818
|
+
projectId: {
|
|
819
|
+
type: "string",
|
|
820
|
+
description: "The project ID to list grovebooks from"
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
required: ["projectId"]
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
name: "grove_download",
|
|
828
|
+
description: `Download a grovebook file and return its contents as markdown for editing.
|
|
829
|
+
Code blocks are converted to fenced code blocks with metadata preserved in HTML comments.
|
|
830
|
+
This is the first step in the edit workflow: download -> edit markdown -> upload.`,
|
|
831
|
+
inputSchema: {
|
|
832
|
+
type: "object",
|
|
833
|
+
properties: {
|
|
834
|
+
projectId: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "The project ID containing the grovebook"
|
|
837
|
+
},
|
|
838
|
+
filePath: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: 'The grovebook file path (e.g., "my-notebook.grove")'
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
required: ["projectId", "filePath"]
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: "grove_upload",
|
|
848
|
+
description: `Upload markdown content as a grovebook file.
|
|
849
|
+
Converts markdown with fenced code blocks back to Grove JSON format.
|
|
850
|
+
This is the final step in the edit workflow: download -> edit markdown -> upload.`,
|
|
851
|
+
inputSchema: {
|
|
852
|
+
type: "object",
|
|
853
|
+
properties: {
|
|
854
|
+
projectId: {
|
|
855
|
+
type: "string",
|
|
856
|
+
description: "The project ID to upload the grovebook to"
|
|
857
|
+
},
|
|
858
|
+
fileName: {
|
|
859
|
+
type: "string",
|
|
860
|
+
description: 'The grovebook file name/path (e.g., "my-notebook.grove")'
|
|
861
|
+
},
|
|
862
|
+
content: {
|
|
863
|
+
type: "string",
|
|
864
|
+
description: "The markdown content to convert and upload as a grovebook"
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
required: ["projectId", "fileName", "content"]
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: "grove_delete_file",
|
|
872
|
+
description: "Delete a grovebook file from a project",
|
|
873
|
+
inputSchema: {
|
|
874
|
+
type: "object",
|
|
875
|
+
properties: {
|
|
876
|
+
projectId: {
|
|
877
|
+
type: "string",
|
|
878
|
+
description: "The project ID containing the file"
|
|
879
|
+
},
|
|
880
|
+
fileKey: {
|
|
881
|
+
type: "string",
|
|
882
|
+
description: 'The file path/key to delete (e.g., "my-notebook.grove")'
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
required: ["projectId", "fileKey"]
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: "grove_rename_file",
|
|
890
|
+
description: "Rename or move a grovebook file within a project",
|
|
891
|
+
inputSchema: {
|
|
892
|
+
type: "object",
|
|
893
|
+
properties: {
|
|
894
|
+
projectId: {
|
|
895
|
+
type: "string",
|
|
896
|
+
description: "The project ID containing the file"
|
|
897
|
+
},
|
|
898
|
+
oldFileKey: {
|
|
899
|
+
type: "string",
|
|
900
|
+
description: "Current file path/key"
|
|
901
|
+
},
|
|
902
|
+
newFileKey: {
|
|
903
|
+
type: "string",
|
|
904
|
+
description: "New file path/key"
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
required: ["projectId", "oldFileKey", "newFileKey"]
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
name: "grove_create_folder",
|
|
912
|
+
description: "Create a new folder in the project grove directory",
|
|
913
|
+
inputSchema: {
|
|
914
|
+
type: "object",
|
|
915
|
+
properties: {
|
|
916
|
+
projectId: {
|
|
917
|
+
type: "string",
|
|
918
|
+
description: "The project ID"
|
|
919
|
+
},
|
|
920
|
+
folderKey: {
|
|
921
|
+
type: "string",
|
|
922
|
+
description: 'Full folder path ending with "/" (e.g., "notebooks/" or "parent/child/")'
|
|
923
|
+
},
|
|
924
|
+
folderName: {
|
|
925
|
+
type: "string",
|
|
926
|
+
description: "The folder name (last segment of the path)"
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
required: ["projectId", "folderKey", "folderName"]
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: "grove_delete_folder",
|
|
934
|
+
description: "Delete a folder and all its contents from the project grove directory",
|
|
935
|
+
inputSchema: {
|
|
936
|
+
type: "object",
|
|
937
|
+
properties: {
|
|
938
|
+
projectId: {
|
|
939
|
+
type: "string",
|
|
940
|
+
description: "The project ID"
|
|
941
|
+
},
|
|
942
|
+
folderKey: {
|
|
943
|
+
type: "string",
|
|
944
|
+
description: 'Folder path to delete (e.g., "notebooks/")'
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
required: ["projectId", "folderKey"]
|
|
948
|
+
}
|
|
572
949
|
}
|
|
573
950
|
];
|
|
574
951
|
var GraphXRMCPServer = class {
|
|
952
|
+
server;
|
|
953
|
+
client = null;
|
|
954
|
+
browserSession = null;
|
|
575
955
|
constructor() {
|
|
576
|
-
this.client = null;
|
|
577
|
-
this.browserSession = null;
|
|
578
956
|
this.server = new import_server.Server(
|
|
579
957
|
{
|
|
580
958
|
name: "graphxr-mcp-server",
|
|
@@ -644,6 +1022,20 @@ var GraphXRMCPServer = class {
|
|
|
644
1022
|
return await this.handleExportGraph(args);
|
|
645
1023
|
case "close_project":
|
|
646
1024
|
return await this.handleCloseProject();
|
|
1025
|
+
case "grove_list":
|
|
1026
|
+
return await this.handleGroveList(args);
|
|
1027
|
+
case "grove_download":
|
|
1028
|
+
return await this.handleGroveDownload(args);
|
|
1029
|
+
case "grove_upload":
|
|
1030
|
+
return await this.handleGroveUpload(args);
|
|
1031
|
+
case "grove_delete_file":
|
|
1032
|
+
return await this.handleGroveDeleteFile(args);
|
|
1033
|
+
case "grove_rename_file":
|
|
1034
|
+
return await this.handleGroveRenameFile(args);
|
|
1035
|
+
case "grove_create_folder":
|
|
1036
|
+
return await this.handleGroveCreateFolder(args);
|
|
1037
|
+
case "grove_delete_folder":
|
|
1038
|
+
return await this.handleGroveDeleteFolder(args);
|
|
647
1039
|
default:
|
|
648
1040
|
throw new Error(`Unknown tool: ${name}`);
|
|
649
1041
|
}
|
|
@@ -825,6 +1217,145 @@ ${exportResult.data}`
|
|
|
825
1217
|
]
|
|
826
1218
|
};
|
|
827
1219
|
}
|
|
1220
|
+
// ==========================================================================
|
|
1221
|
+
// Grove Tool Handlers
|
|
1222
|
+
// ==========================================================================
|
|
1223
|
+
/**
|
|
1224
|
+
* Handle grove_list tool - list all grovebooks in a project
|
|
1225
|
+
*/
|
|
1226
|
+
async handleGroveList(args) {
|
|
1227
|
+
debug3("handleGroveList:", args.projectId);
|
|
1228
|
+
const client = this.initializeClient();
|
|
1229
|
+
const items = await client.groveLoad(args.projectId);
|
|
1230
|
+
const files = [];
|
|
1231
|
+
const folders = [];
|
|
1232
|
+
for (const [key, item] of Object.entries(items)) {
|
|
1233
|
+
if ("fileKey" in item) {
|
|
1234
|
+
files.push(item);
|
|
1235
|
+
} else if ("folderKey" in item) {
|
|
1236
|
+
folders.push(item);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const result = {
|
|
1240
|
+
projectId: args.projectId,
|
|
1241
|
+
folders: folders.map((f) => ({ path: f.folderKey, name: f.name })),
|
|
1242
|
+
files: files.map((f) => ({
|
|
1243
|
+
path: f.fileKey,
|
|
1244
|
+
name: f.name,
|
|
1245
|
+
type: f.type,
|
|
1246
|
+
size: f.size,
|
|
1247
|
+
version: f.version,
|
|
1248
|
+
modifiedAt: f.mtimeMs ? new Date(f.mtimeMs).toISOString() : void 0
|
|
1249
|
+
}))
|
|
1250
|
+
};
|
|
1251
|
+
return {
|
|
1252
|
+
content: [
|
|
1253
|
+
{
|
|
1254
|
+
type: "text",
|
|
1255
|
+
text: JSON.stringify(result, null, 2)
|
|
1256
|
+
}
|
|
1257
|
+
]
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Handle grove_download tool - download a grovebook as markdown
|
|
1262
|
+
*/
|
|
1263
|
+
async handleGroveDownload(args) {
|
|
1264
|
+
debug3("handleGroveDownload:", args.projectId, args.filePath);
|
|
1265
|
+
const client = this.initializeClient();
|
|
1266
|
+
const markdown = await client.groveDownloadFile(args.projectId, args.filePath);
|
|
1267
|
+
return {
|
|
1268
|
+
content: [
|
|
1269
|
+
{
|
|
1270
|
+
type: "text",
|
|
1271
|
+
text: markdown
|
|
1272
|
+
}
|
|
1273
|
+
]
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Handle grove_upload tool - upload markdown as a grovebook
|
|
1278
|
+
*/
|
|
1279
|
+
async handleGroveUpload(args) {
|
|
1280
|
+
debug3("handleGroveUpload:", args.projectId, args.fileName);
|
|
1281
|
+
const client = this.initializeClient();
|
|
1282
|
+
const result = await client.groveUploadFile(args.projectId, args.fileName, args.content);
|
|
1283
|
+
return {
|
|
1284
|
+
content: [
|
|
1285
|
+
{
|
|
1286
|
+
type: "text",
|
|
1287
|
+
text: `Successfully uploaded grovebook: ${args.fileName}
|
|
1288
|
+
Path: ${result.filePath}`
|
|
1289
|
+
}
|
|
1290
|
+
]
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Handle grove_delete_file tool
|
|
1295
|
+
*/
|
|
1296
|
+
async handleGroveDeleteFile(args) {
|
|
1297
|
+
debug3("handleGroveDeleteFile:", args.projectId, args.fileKey);
|
|
1298
|
+
const client = this.initializeClient();
|
|
1299
|
+
const deletedKey = await client.groveDeleteFile(args.projectId, args.fileKey);
|
|
1300
|
+
return {
|
|
1301
|
+
content: [
|
|
1302
|
+
{
|
|
1303
|
+
type: "text",
|
|
1304
|
+
text: `Successfully deleted file: ${deletedKey}`
|
|
1305
|
+
}
|
|
1306
|
+
]
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Handle grove_rename_file tool
|
|
1311
|
+
*/
|
|
1312
|
+
async handleGroveRenameFile(args) {
|
|
1313
|
+
debug3("handleGroveRenameFile:", args.projectId, args.oldFileKey, "->", args.newFileKey);
|
|
1314
|
+
const client = this.initializeClient();
|
|
1315
|
+
const result = await client.groveRenameFile(args.projectId, args.oldFileKey, args.newFileKey);
|
|
1316
|
+
return {
|
|
1317
|
+
content: [
|
|
1318
|
+
{
|
|
1319
|
+
type: "text",
|
|
1320
|
+
text: `Successfully renamed file:
|
|
1321
|
+
From: ${args.oldFileKey}
|
|
1322
|
+
To: ${result.fileKey}`
|
|
1323
|
+
}
|
|
1324
|
+
]
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Handle grove_create_folder tool
|
|
1329
|
+
*/
|
|
1330
|
+
async handleGroveCreateFolder(args) {
|
|
1331
|
+
debug3("handleGroveCreateFolder:", args.projectId, args.folderKey, args.folderName);
|
|
1332
|
+
const client = this.initializeClient();
|
|
1333
|
+
const result = await client.groveCreateFolder(args.projectId, args.folderKey, args.folderName);
|
|
1334
|
+
return {
|
|
1335
|
+
content: [
|
|
1336
|
+
{
|
|
1337
|
+
type: "text",
|
|
1338
|
+
text: `Successfully created folder: ${result.folderKey}`
|
|
1339
|
+
}
|
|
1340
|
+
]
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Handle grove_delete_folder tool
|
|
1345
|
+
*/
|
|
1346
|
+
async handleGroveDeleteFolder(args) {
|
|
1347
|
+
debug3("handleGroveDeleteFolder:", args.projectId, args.folderKey);
|
|
1348
|
+
const client = this.initializeClient();
|
|
1349
|
+
await client.groveDeleteFolder(args.projectId, args.folderKey);
|
|
1350
|
+
return {
|
|
1351
|
+
content: [
|
|
1352
|
+
{
|
|
1353
|
+
type: "text",
|
|
1354
|
+
text: `Successfully deleted folder: ${args.folderKey}`
|
|
1355
|
+
}
|
|
1356
|
+
]
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
828
1359
|
/**
|
|
829
1360
|
* Start the MCP server
|
|
830
1361
|
*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/graphxr-client.ts", "../src/browser-session.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n/**\n * GraphXR MCP Server\n * \n * Model Context Protocol server that allows Claude/Cursor to interact with\n * GraphXR by authenticating with a per-user API key.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n Tool,\n TextContent,\n ImageContent,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { \n GraphXRClient, \n GraphXRProject,\n GroveFileInfo,\n GroveFolderInfo,\n GroveLoadResponse,\n} from './graphxr-client';\nimport { BrowserSession, ScreenshotOptions } from './browser-session';\n\n// Configuration from environment variables\nconst GRAPHXR_API_KEY = process.env.GRAPHXR_API_KEY || '';\nconst GRAPHXR_URL = process.env.GRAPHXR_URL || 'http://localhost:9000';\nconst HEADLESS = process.env.GRAPHXR_HEADLESS !== 'false';\nconst DEBUG = process.env.DEBUG === 'true' || process.env.GRAPHXR_DEBUG === 'true';\n\n// Debug logger (uses stderr so it doesn't interfere with MCP stdout)\nfunction debug(...args: unknown[]) {\n if (DEBUG) {\n console.error('[MCP DEBUG]', new Date().toISOString(), ...args);\n }\n}\n\n// Tool definitions\nconst tools: Tool[] = [\n {\n name: 'list_projects',\n description: 'List all GraphXR projects accessible to the authenticated user',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n },\n },\n {\n name: 'create_project',\n description: 'Create a new GraphXR project',\n inputSchema: {\n type: 'object',\n properties: {\n projectName: {\n type: 'string',\n description: 'Name for the new project',\n },\n databaseType: {\n type: 'string',\n description: 'Database type (default: kuzu)',\n enum: ['kuzu', 'neo4j'],\n },\n },\n required: ['projectName'],\n },\n },\n {\n name: 'open_project',\n description: 'Open a GraphXR project in a headless browser session for interaction',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID to open',\n },\n },\n required: ['projectId'],\n },\n },\n {\n name: 'run_javascript',\n description: `Execute JavaScript code in the GraphXR canvas context using the gxr API.\nThe code has access to the 'gxr' object which provides methods for:\n- Graph manipulation: gxr.addNode(), gxr.addEdge(), gxr.nodes(), gxr.edges(), gxr.clear()\n- Layout: gxr.forceLayout(), gxr.circle(), gxr.grid(), gxr.ego(), gxr.parametric()\n- Styling: gxr.colorNodesByProperty(), gxr.sizeNodesByProperty(), gxr.setCategoryColor()\n- Camera: gxr.flyToCenter(), gxr.zoomIn(), gxr.zoomOut()\n- Transform: gxr.extract(), gxr.link(), gxr.merge(), gxr.aggregate()\n- Query: gxr.query() for Cypher queries\nThe code should return a value that can be serialized to JSON.`,\n inputSchema: {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n description: 'JavaScript code to execute. Has access to gxr API. Must return a value.',\n },\n },\n required: ['code'],\n },\n },\n {\n name: 'screenshot',\n description: 'Take a screenshot of the GraphXR canvas',\n inputSchema: {\n type: 'object',\n properties: {\n frameNodes: {\n type: 'boolean',\n description: 'Whether to frame all nodes in view before capturing (default: true)',\n },\n hidePanels: {\n type: 'boolean',\n description: 'Whether to hide UI panels (default: true)',\n },\n includeLegends: {\n type: 'boolean',\n description: 'Whether to include legend overlays (default: false)',\n },\n },\n required: [],\n },\n },\n {\n name: 'export_graph',\n description: 'Export the current graph data',\n inputSchema: {\n type: 'object',\n properties: {\n format: {\n type: 'string',\n description: 'Export format',\n enum: ['json', 'graphxr', 'csv'],\n },\n },\n required: ['format'],\n },\n },\n {\n name: 'close_project',\n description: 'Close the current browser session',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n },\n },\n // Grove (Grovebook) tools\n {\n name: 'grove_list',\n description: 'List all grovebook files and folders in a project. Returns file metadata including names, sizes, versions, and modification times.',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID to list grovebooks from',\n },\n },\n required: ['projectId'],\n },\n },\n {\n name: 'grove_download',\n description: `Download a grovebook file and return its contents as markdown for editing.\nCode blocks are converted to fenced code blocks with metadata preserved in HTML comments.\nThis is the first step in the edit workflow: download -> edit markdown -> upload.`,\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID containing the grovebook',\n },\n filePath: {\n type: 'string',\n description: 'The grovebook file path (e.g., \"my-notebook.grove\")',\n },\n },\n required: ['projectId', 'filePath'],\n },\n },\n {\n name: 'grove_upload',\n description: `Upload markdown content as a grovebook file.\nConverts markdown with fenced code blocks back to Grove JSON format.\nThis is the final step in the edit workflow: download -> edit markdown -> upload.`,\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID to upload the grovebook to',\n },\n fileName: {\n type: 'string',\n description: 'The grovebook file name/path (e.g., \"my-notebook.grove\")',\n },\n content: {\n type: 'string',\n description: 'The markdown content to convert and upload as a grovebook',\n },\n },\n required: ['projectId', 'fileName', 'content'],\n },\n },\n {\n name: 'grove_delete_file',\n description: 'Delete a grovebook file from a project',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID containing the file',\n },\n fileKey: {\n type: 'string',\n description: 'The file path/key to delete (e.g., \"my-notebook.grove\")',\n },\n },\n required: ['projectId', 'fileKey'],\n },\n },\n {\n name: 'grove_rename_file',\n description: 'Rename or move a grovebook file within a project',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID containing the file',\n },\n oldFileKey: {\n type: 'string',\n description: 'Current file path/key',\n },\n newFileKey: {\n type: 'string',\n description: 'New file path/key',\n },\n },\n required: ['projectId', 'oldFileKey', 'newFileKey'],\n },\n },\n {\n name: 'grove_create_folder',\n description: 'Create a new folder in the project grove directory',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID',\n },\n folderKey: {\n type: 'string',\n description: 'Full folder path ending with \"/\" (e.g., \"notebooks/\" or \"parent/child/\")',\n },\n folderName: {\n type: 'string',\n description: 'The folder name (last segment of the path)',\n },\n },\n required: ['projectId', 'folderKey', 'folderName'],\n },\n },\n {\n name: 'grove_delete_folder',\n description: 'Delete a folder and all its contents from the project grove directory',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: {\n type: 'string',\n description: 'The project ID',\n },\n folderKey: {\n type: 'string',\n description: 'Folder path to delete (e.g., \"notebooks/\")',\n },\n },\n required: ['projectId', 'folderKey'],\n },\n },\n];\n\n/**\n * Main MCP Server class for GraphXR\n */\nclass GraphXRMCPServer {\n private server: Server;\n private client: GraphXRClient | null = null;\n private browserSession: BrowserSession | null = null;\n\n constructor() {\n this.server = new Server(\n {\n name: 'graphxr-mcp-server',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n this.setupHandlers();\n }\n\n /**\n * Initialize the GraphXR client with API key\n */\n private initializeClient(): GraphXRClient {\n if (!GRAPHXR_API_KEY) {\n throw new Error(\n 'GRAPHXR_API_KEY environment variable is required. ' +\n 'Generate an API key in GraphXR Settings > API Access.'\n );\n }\n\n if (!this.client) {\n this.client = new GraphXRClient({\n baseUrl: GRAPHXR_URL,\n apiKey: GRAPHXR_API_KEY,\n });\n }\n\n return this.client;\n }\n\n /**\n * Get or create the browser session\n */\n private getBrowserSession(): BrowserSession {\n const client = this.initializeClient();\n\n if (!this.browserSession) {\n this.browserSession = new BrowserSession(client, {\n headless: HEADLESS,\n });\n }\n\n return this.browserSession;\n }\n\n /**\n * Setup MCP request handlers\n */\n private setupHandlers(): void {\n // List available tools\n this.server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools };\n });\n\n // Handle tool calls\n this.server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n \n debug('Tool call received:', name);\n debug('Tool arguments:', JSON.stringify(args));\n\n try {\n switch (name) {\n case 'list_projects':\n return await this.handleListProjects();\n\n case 'create_project':\n return await this.handleCreateProject(args as { projectName: string; databaseType?: string });\n\n case 'open_project':\n return await this.handleOpenProject(args as { projectId: string });\n\n case 'run_javascript':\n return await this.handleRunJavaScript(args as { code: string });\n\n case 'screenshot':\n return await this.handleScreenshot(args as ScreenshotOptions);\n\n case 'export_graph':\n return await this.handleExportGraph(args as { format: string });\n\n case 'close_project':\n return await this.handleCloseProject();\n\n // Grove tools\n case 'grove_list':\n return await this.handleGroveList(args as { projectId: string });\n\n case 'grove_download':\n return await this.handleGroveDownload(args as { projectId: string; filePath: string });\n\n case 'grove_upload':\n return await this.handleGroveUpload(args as { projectId: string; fileName: string; content: string });\n\n case 'grove_delete_file':\n return await this.handleGroveDeleteFile(args as { projectId: string; fileKey: string });\n\n case 'grove_rename_file':\n return await this.handleGroveRenameFile(args as { projectId: string; oldFileKey: string; newFileKey: string });\n\n case 'grove_create_folder':\n return await this.handleGroveCreateFolder(args as { projectId: string; folderKey: string; folderName: string });\n\n case 'grove_delete_folder':\n return await this.handleGroveDeleteFolder(args as { projectId: string; folderKey: string });\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n debug('Tool error:', errorMessage);\n if (errorStack) {\n debug('Stack trace:', errorStack);\n }\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`,\n } as TextContent,\n ],\n isError: true,\n };\n }\n });\n }\n\n /**\n * Handle list_projects tool\n */\n private async handleListProjects() {\n debug('handleListProjects: starting');\n debug('handleListProjects: GRAPHXR_URL =', GRAPHXR_URL);\n debug('handleListProjects: API key length =', GRAPHXR_API_KEY.length);\n \n const client = this.initializeClient();\n debug('handleListProjects: client initialized');\n \n debug('handleListProjects: calling listProjects API...');\n const projects = await client.listProjects();\n debug('handleListProjects: received', projects.length, 'projects');\n\n const projectList = projects.map((p) => ({\n id: p._id,\n name: p.projectName,\n databaseType: p.databaseType,\n isShare: p.isShare,\n description: p.projectSettings?.description || '',\n }));\n\n debug('handleListProjects: returning project list');\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(projectList, null, 2),\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle create_project tool\n */\n private async handleCreateProject(args: { projectName: string; databaseType?: string }) {\n const client = this.initializeClient();\n const project = await client.createProject({\n projectName: args.projectName,\n databaseType: args.databaseType || 'kuzu',\n });\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n id: project._id,\n name: project.projectName,\n databaseType: project.databaseType,\n url: client.getProjectUrl(project),\n },\n null,\n 2\n ),\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle open_project tool\n */\n private async handleOpenProject(args: { projectId: string }) {\n const client = this.initializeClient();\n const project = await client.getProject(args.projectId);\n debug('handleOpenProject: got project', project?._id, project?.projectName);\n\n if (!project) {\n throw new Error(`Project not found: ${args.projectId}`);\n }\n\n const session = this.getBrowserSession();\n debug('handleOpenProject: opening project in browser');\n await session.openProject(project);\n\n return {\n content: [\n {\n type: 'text',\n text: `Opened project \"${project.projectName}\" (${project._id}). You can now run JavaScript code using the run_javascript tool.`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle run_javascript tool\n */\n private async handleRunJavaScript(args: { code: string }) {\n const session = this.getBrowserSession();\n\n if (!session.isActive()) {\n throw new Error('No project is currently open. Use open_project first.');\n }\n\n const result = await session.executeJavaScript(args.code);\n\n if (!result.success) {\n throw new Error(result.error || 'JavaScript execution failed');\n }\n\n return {\n content: [\n {\n type: 'text',\n text:\n result.result !== undefined\n ? JSON.stringify(result.result, null, 2)\n : 'Code executed successfully (no return value)',\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle screenshot tool\n */\n private async handleScreenshot(args: ScreenshotOptions) {\n const session = this.getBrowserSession();\n\n if (!session.isActive()) {\n throw new Error('No project is currently open. Use open_project first.');\n }\n\n const imageBuffer = await session.screenshot(args);\n const base64Image = imageBuffer.toString('base64');\n\n return {\n content: [\n {\n type: 'image',\n data: base64Image,\n mimeType: args.format === 'jpeg' ? 'image/jpeg' : 'image/png',\n } as ImageContent,\n ],\n };\n }\n\n /**\n * Handle export_graph tool\n */\n private async handleExportGraph(args: { format: string }) {\n const session = this.getBrowserSession();\n\n if (!session.isActive()) {\n throw new Error('No project is currently open. Use open_project first.');\n }\n\n const exportResult = await session.exportGraph({\n format: args.format as 'json' | 'graphxr' | 'csv',\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `Export complete. Filename: ${exportResult.filename}\\n\\nData:\\n${exportResult.data}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle close_project tool\n */\n private async handleCloseProject() {\n if (this.browserSession) {\n await this.browserSession.close();\n this.browserSession = null;\n }\n\n return {\n content: [\n {\n type: 'text',\n text: 'Browser session closed.',\n } as TextContent,\n ],\n };\n }\n\n // ==========================================================================\n // Grove Tool Handlers\n // ==========================================================================\n\n /**\n * Handle grove_list tool - list all grovebooks in a project\n */\n private async handleGroveList(args: { projectId: string }) {\n debug('handleGroveList:', args.projectId);\n const client = this.initializeClient();\n const items = await client.groveLoad(args.projectId);\n\n // Separate files and folders for cleaner output\n const files: GroveFileInfo[] = [];\n const folders: GroveFolderInfo[] = [];\n\n for (const [key, item] of Object.entries(items)) {\n if ('fileKey' in item) {\n files.push(item as GroveFileInfo);\n } else if ('folderKey' in item) {\n folders.push(item as GroveFolderInfo);\n }\n }\n\n const result = {\n projectId: args.projectId,\n folders: folders.map(f => ({ path: f.folderKey, name: f.name })),\n files: files.map(f => ({\n path: f.fileKey,\n name: f.name,\n type: f.type,\n size: f.size,\n version: f.version,\n modifiedAt: f.mtimeMs ? new Date(f.mtimeMs).toISOString() : undefined,\n })),\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_download tool - download a grovebook as markdown\n */\n private async handleGroveDownload(args: { projectId: string; filePath: string }) {\n debug('handleGroveDownload:', args.projectId, args.filePath);\n const client = this.initializeClient();\n const markdown = await client.groveDownloadFile(args.projectId, args.filePath);\n\n return {\n content: [\n {\n type: 'text',\n text: markdown,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_upload tool - upload markdown as a grovebook\n */\n private async handleGroveUpload(args: { projectId: string; fileName: string; content: string }) {\n debug('handleGroveUpload:', args.projectId, args.fileName);\n const client = this.initializeClient();\n const result = await client.groveUploadFile(args.projectId, args.fileName, args.content);\n\n return {\n content: [\n {\n type: 'text',\n text: `Successfully uploaded grovebook: ${args.fileName}\\nPath: ${result.filePath}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_delete_file tool\n */\n private async handleGroveDeleteFile(args: { projectId: string; fileKey: string }) {\n debug('handleGroveDeleteFile:', args.projectId, args.fileKey);\n const client = this.initializeClient();\n const deletedKey = await client.groveDeleteFile(args.projectId, args.fileKey);\n\n return {\n content: [\n {\n type: 'text',\n text: `Successfully deleted file: ${deletedKey}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_rename_file tool\n */\n private async handleGroveRenameFile(args: { projectId: string; oldFileKey: string; newFileKey: string }) {\n debug('handleGroveRenameFile:', args.projectId, args.oldFileKey, '->', args.newFileKey);\n const client = this.initializeClient();\n const result = await client.groveRenameFile(args.projectId, args.oldFileKey, args.newFileKey);\n\n return {\n content: [\n {\n type: 'text',\n text: `Successfully renamed file:\\n From: ${args.oldFileKey}\\n To: ${result.fileKey}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_create_folder tool\n */\n private async handleGroveCreateFolder(args: { projectId: string; folderKey: string; folderName: string }) {\n debug('handleGroveCreateFolder:', args.projectId, args.folderKey, args.folderName);\n const client = this.initializeClient();\n const result = await client.groveCreateFolder(args.projectId, args.folderKey, args.folderName);\n\n return {\n content: [\n {\n type: 'text',\n text: `Successfully created folder: ${result.folderKey}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Handle grove_delete_folder tool\n */\n private async handleGroveDeleteFolder(args: { projectId: string; folderKey: string }) {\n debug('handleGroveDeleteFolder:', args.projectId, args.folderKey);\n const client = this.initializeClient();\n await client.groveDeleteFolder(args.projectId, args.folderKey);\n\n return {\n content: [\n {\n type: 'text',\n text: `Successfully deleted folder: ${args.folderKey}`,\n } as TextContent,\n ],\n };\n }\n\n /**\n * Start the MCP server\n */\n async start(): Promise<void> {\n const transport = new StdioServerTransport();\n await this.server.connect(transport);\n\n // Handle cleanup on exit\n process.on('SIGINT', async () => {\n await this.cleanup();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n await this.cleanup();\n process.exit(0);\n });\n }\n\n /**\n * Cleanup resources\n */\n private async cleanup(): Promise<void> {\n if (this.browserSession) {\n await this.browserSession.close();\n }\n }\n}\n\n// Main entry point\nasync function main() {\n debug('Starting GraphXR MCP Server');\n debug('Configuration:');\n debug(' GRAPHXR_URL:', GRAPHXR_URL);\n debug(' GRAPHXR_API_KEY:', GRAPHXR_API_KEY ? `(${GRAPHXR_API_KEY.length} chars)` : '(not set)');\n debug(' HEADLESS:', HEADLESS);\n debug(' DEBUG:', DEBUG);\n \n const server = new GraphXRMCPServer();\n await server.start();\n debug('MCP Server started and listening on stdio');\n}\n\nmain().catch((error) => {\n console.error('Failed to start GraphXR MCP Server:', error);\n process.exit(1);\n});\n\nexport { GraphXRMCPServer };\n", "/**\n * GraphXR REST API Client\n * \n * Handles authentication and communication with the GraphXR backend API\n * using per-user API keys.\n */\n\nimport fetch from 'node-fetch';\n\nconst DEBUG = process.env.DEBUG === 'true' || process.env.GRAPHXR_DEBUG === 'true';\n\nfunction debug(...args: unknown[]) {\n if (DEBUG) {\n console.error('[GraphXR Client]', new Date().toISOString(), ...args);\n }\n}\n\nexport interface GraphXRProject {\n _id: string;\n projectName: string;\n databaseType: string;\n hostname?: string;\n isShare?: boolean;\n isDemo?: boolean;\n createTime?: string;\n lastActiveTime?: number;\n projectSettings?: {\n theme?: string;\n mode?: string;\n description?: string;\n tags?: string[];\n };\n}\n\nexport interface CreateProjectOptions {\n projectName: string;\n databaseType?: string;\n isShare?: boolean;\n}\n\nexport interface GraphXRClientConfig {\n baseUrl: string;\n apiKey: string;\n}\n\nexport interface APIResponse<T> {\n status: number;\n message: string;\n content?: T;\n}\n\n// ============================================================================\n// Grove Types\n// ============================================================================\n\n/**\n * Grove code block data structure\n */\nexport interface GroveCodeData {\n value: string;\n pinCode: boolean;\n dname: string;\n codeMode: string;\n hide: boolean;\n}\n\n/**\n * Grove block - can be a code block or text block\n */\nexport interface GroveBlock {\n type: 'codeTool' | 'text';\n data: {\n codeData?: GroveCodeData;\n text?: string;\n };\n}\n\n/**\n * Grove file structure (the JSON format of a .grove file)\n */\nexport interface GroveFile {\n blocks: GroveBlock[];\n version: string;\n}\n\n/**\n * Grove file metadata from the load API\n */\nexport interface GroveFileInfo {\n fileKey: string;\n name: string;\n type?: string;\n size?: number;\n mtimeMs?: number;\n version?: number;\n final?: number;\n versionMtimeMs?: number;\n previewImageUpload?: boolean;\n messages?: Record<string, string>;\n shareData?: unknown;\n}\n\n/**\n * Grove folder metadata from the load API\n */\nexport interface GroveFolderInfo {\n folderKey: string;\n name: string;\n}\n\n/**\n * Response from grove/load endpoint\n */\nexport interface GroveLoadResponse {\n [key: string]: GroveFileInfo | GroveFolderInfo;\n}\n\n// ============================================================================\n// Grove Format Conversion\n// ============================================================================\n\n/**\n * Convert Grove code mode to markdown language tag for syntax highlighting.\n */\nfunction convertCodeModeToMarkdown(codeMode: string): string {\n switch (codeMode) {\n case 'javascript2':\n return 'js';\n default:\n return codeMode;\n }\n}\n\n/**\n * Convert markdown language tag back to Grove code mode.\n */\nfunction convertMarkdownToCodeMode(langTag: string | undefined): string {\n switch (langTag) {\n case 'js':\n return 'javascript2';\n default:\n return langTag || 'javascript2';\n }\n}\n\n/**\n * Generate a UUID for new code blocks.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Converts Grove JSON format to Markdown for editing.\n * \n * Code blocks are converted to fenced code blocks with metadata in HTML comments.\n * Text blocks are preserved as-is.\n * \n * @param grove - The grove file content with blocks array\n * @returns Markdown string representation\n */\nexport function convertGroveToMarkdown(grove: GroveFile): string {\n const mdBlocks = grove.blocks.map((block) => {\n if (block.type === 'codeTool' && block.data.codeData) {\n const { pinCode, dname, codeMode, hide, value } = block.data.codeData;\n const cellOptions = {\n pinCode,\n dname,\n codeMode,\n hide,\n };\n const cellOptionsStr = `<!--${JSON.stringify(cellOptions)}-->`;\n const langTag = convertCodeModeToMarkdown(codeMode);\n return `${cellOptionsStr}\\n\\`\\`\\`${langTag}\\n${value}\\n\\`\\`\\``;\n }\n return block.data.text || '';\n });\n return mdBlocks.join('\\n\\n');\n}\n\n/**\n * Converts Markdown content back to Grove JSON format for uploading.\n * \n * Parses fenced code blocks with optional HTML comment metadata.\n * Each code block becomes a codeTool block in the Grove format.\n * \n * @param mdContent - The markdown content\n * @returns Grove format object with blocks array and version\n */\nexport function convertMarkdownToGrove(mdContent: string): GroveFile {\n const codeBlockRegex = /(?:<!--(.*)-->\\n)?```(\\w+)?\\n([\\s\\S]*?)```/g;\n const blocks: GroveBlock[] = [];\n let match;\n\n while ((match = codeBlockRegex.exec(mdContent)) !== null) {\n const cellOptionsStr = match[1];\n const langTag = match[2];\n const codeContent = match[3].trim();\n let cellOptions: Partial<GroveCodeData> = {};\n\n if (cellOptionsStr) {\n try {\n cellOptions = JSON.parse(cellOptionsStr);\n } catch {\n // Ignore parse errors for cell options\n }\n }\n\n const block: GroveBlock = {\n type: 'codeTool',\n data: {\n codeData: {\n value: codeContent,\n pinCode: cellOptions.pinCode ?? false,\n dname: cellOptions.dname ?? generateUUID(),\n codeMode: convertMarkdownToCodeMode(langTag) || cellOptions.codeMode || 'javascript2',\n hide: cellOptions.hide ?? false,\n },\n },\n };\n blocks.push(block);\n }\n\n return {\n blocks,\n version: '2.19.1',\n };\n}\n\n/**\n * GraphXR REST API Client\n * \n * Provides methods to interact with GraphXR backend for project management.\n */\nexport class GraphXRClient {\n private baseUrl: string;\n private apiKey: string;\n\n constructor(config: GraphXRClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.apiKey = config.apiKey;\n }\n\n /**\n * Make an authenticated request to the GraphXR API\n */\n private async request<T>(\n method: string,\n endpoint: string,\n body?: Record<string, unknown>\n ): Promise<APIResponse<T>> {\n const url = `${this.baseUrl}${endpoint}`;\n \n debug('request:', method, url);\n debug('request: body =', body ? JSON.stringify(body) : '(none)');\n \n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n };\n\n const options: RequestInit = {\n method,\n headers,\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n debug('request: sending...');\n const response = await fetch(url, options as any);\n debug('request: response status =', response.status, response.statusText);\n \n if (!response.ok) {\n const errorText = await response.text();\n debug('request: error response body =', errorText);\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json() as APIResponse<T>;\n debug('request: response data.status =', data.status, 'message =', data.message);\n \n if (data.status !== 0) {\n throw new Error(`API error: ${data.message}`);\n }\n\n return data;\n }\n\n /**\n * Verify the API key is valid by making a test request\n */\n async verifyApiKey(): Promise<boolean> {\n try {\n await this.listProjects();\n return true;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * List all projects accessible to the authenticated user\n */\n async listProjects(): Promise<GraphXRProject[]> {\n debug('listProjects: calling API');\n const response = await this.request<GraphXRProject[]>(\n 'POST',\n '/api/graph/neo4j/project/list',\n {}\n );\n debug('listProjects: got', response.content?.length ?? 0, 'projects');\n return response.content || [];\n }\n\n /**\n * Get detailed information about a specific project\n */\n async getProject(projectId: string): Promise<GraphXRProject | null> {\n try {\n const response = await this.request<GraphXRProject>(\n 'GET',\n `/api/graph/neo4j/project/getInfo?id=${encodeURIComponent(projectId)}`,\n );\n return response.content || null;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Create a new project\n */\n async createProject(options: CreateProjectOptions): Promise<GraphXRProject> {\n const dbinfo = {\n projectName: options.projectName,\n databaseType: options.databaseType || 'kuzu',\n isShare: options.isShare ?? true,\n hostname: '',\n isLocal: false,\n };\n\n const response = await this.request<GraphXRProject>(\n 'POST',\n '/api/graph/neo4j/project/add',\n { dbinfo }\n );\n\n if (!response.content) {\n throw new Error('Failed to create project: no content returned');\n }\n\n return response.content;\n }\n\n /**\n * Delete a project\n */\n async deleteProject(projectId: string): Promise<void> {\n await this.request(\n 'POST',\n '/api/graph/neo4j/project/delete',\n { id: projectId }\n );\n }\n\n /**\n * Get the URL to access a project in the browser\n */\n getProjectUrl(project: GraphXRProject): string {\n const encodedName = encodeURIComponent(project.projectName);\n return `${this.baseUrl}/p/${project._id}/${encodedName}`;\n }\n\n /**\n * Get the URL to access a project with API key authentication\n */\n getProjectUrlWithAuth(project: GraphXRProject): string {\n const baseProjectUrl = this.getProjectUrl(project);\n return `${baseProjectUrl}?apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n\n /**\n * Get the base URL of the GraphXR instance\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n\n /**\n * Get the API key (for passing to browser sessions)\n */\n getApiKey(): string {\n return this.apiKey;\n }\n\n // ==========================================================================\n // Grove API Methods\n // ==========================================================================\n\n /**\n * Load all files and folders in a project's grove directory.\n * \n * @param projectId - The project ID\n * @returns Object containing file and folder metadata\n */\n async groveLoad(projectId: string): Promise<GroveLoadResponse> {\n debug('groveLoad: loading project', projectId);\n const response = await this.request<GroveLoadResponse>(\n 'POST',\n '/api/grove/load',\n { projectId }\n );\n debug('groveLoad: loaded', Object.keys(response.content || {}).length, 'items');\n return response.content || {};\n }\n\n /**\n * Download a grove file and convert it to markdown format.\n * \n * @param projectId - The project ID\n * @param filePath - The file path within the project\n * @returns The file content as markdown string\n */\n async groveDownloadFile(projectId: string, filePath: string): Promise<string> {\n debug('groveDownloadFile:', projectId, filePath);\n \n const url = `${this.baseUrl}/tmp/observable/projects/${projectId}/${filePath}`;\n const response = await fetch(url, {\n headers: {\n 'x-api-key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);\n }\n\n const groveContent = await response.json() as GroveFile;\n debug('groveDownloadFile: got', groveContent.blocks?.length || 0, 'blocks');\n \n return convertGroveToMarkdown(groveContent);\n }\n\n /**\n * Upload markdown content as a grove file.\n * \n * @param projectId - The project ID\n * @param fileName - The file name/path within the project\n * @param markdownContent - The markdown content to convert and upload\n * @returns Upload response\n */\n async groveUploadFile(\n projectId: string,\n fileName: string,\n markdownContent: string\n ): Promise<{ filePath: string }> {\n debug('groveUploadFile:', projectId, fileName);\n\n // Convert markdown to Grove JSON format\n const grovePayload = convertMarkdownToGrove(markdownContent);\n debug('groveUploadFile: converted to', grovePayload.blocks.length, 'blocks');\n\n // Create form data - using the same approach as the VSCode extension\n const boundary = '----FormBoundary' + Math.random().toString(36).substring(2);\n const formParts: string[] = [];\n\n // Add fileName field\n formParts.push(`--${boundary}`);\n formParts.push('Content-Disposition: form-data; name=\"fileName\"');\n formParts.push('');\n formParts.push(fileName);\n\n // Add projectId field\n formParts.push(`--${boundary}`);\n formParts.push('Content-Disposition: form-data; name=\"projectId\"');\n formParts.push('');\n formParts.push(projectId);\n\n // Add data field (the grove JSON as a file)\n formParts.push(`--${boundary}`);\n formParts.push('Content-Disposition: form-data; name=\"data\"; filename=\"data\"');\n formParts.push('Content-Type: text/plain');\n formParts.push('');\n formParts.push(JSON.stringify(grovePayload));\n\n formParts.push(`--${boundary}--`);\n\n const body = formParts.join('\\r\\n');\n\n const response = await fetch(`${this.baseUrl}/api/grove/simpleUploadFile`, {\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'x-api-key': this.apiKey,\n 'Content-Type': `multipart/form-data; boundary=${boundary}`,\n },\n body,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Upload failed: ${response.status} ${response.statusText}: ${errorText}`);\n }\n\n const result = await response.json() as APIResponse<{ filePath: string }>;\n debug('groveUploadFile: success', result);\n \n return { filePath: result.content?.filePath || '' };\n }\n\n /**\n * Delete a file from the grove directory.\n * \n * @param projectId - The project ID\n * @param fileKey - The file path/key to delete\n * @returns The deleted file key\n */\n async groveDeleteFile(projectId: string, fileKey: string): Promise<string> {\n debug('groveDeleteFile:', projectId, fileKey);\n const response = await this.request<string>(\n 'POST',\n '/api/grove/removeFile',\n { projectId, fileKey }\n );\n debug('groveDeleteFile: deleted', response.content);\n return response.content || fileKey;\n }\n\n /**\n * Rename or move a file within the grove directory.\n * \n * @param projectId - The project ID\n * @param oldFileKey - Current file path/key\n * @param newFileKey - New file path/key\n * @returns The updated file info\n */\n async groveRenameFile(\n projectId: string,\n oldFileKey: string,\n newFileKey: string\n ): Promise<GroveFileInfo> {\n debug('groveRenameFile:', projectId, oldFileKey, '->', newFileKey);\n const response = await this.request<GroveFileInfo>(\n 'POST',\n '/api/grove/renameFile',\n { projectId, oldFileKey, newFileKey }\n );\n debug('groveRenameFile: renamed to', response.content?.fileKey);\n return response.content!;\n }\n\n /**\n * Create a new folder in the grove directory.\n * \n * @param projectId - The project ID\n * @param folderKey - Full folder path (e.g., \"parent/newfolder/\")\n * @param folderName - Folder name\n * @returns The created folder info\n */\n async groveCreateFolder(\n projectId: string,\n folderKey: string,\n folderName: string\n ): Promise<GroveFolderInfo> {\n debug('groveCreateFolder:', projectId, folderKey, folderName);\n const response = await this.request<GroveFolderInfo>(\n 'POST',\n '/api/grove/uploadFolder',\n { projectId, folderKey, folderName }\n );\n debug('groveCreateFolder: created', response.content?.folderKey);\n return response.content!;\n }\n\n /**\n * Delete a folder and all its contents from the grove directory.\n * \n * @param projectId - The project ID\n * @param folderKey - Folder path to delete\n */\n async groveDeleteFolder(projectId: string, folderKey: string): Promise<void> {\n debug('groveDeleteFolder:', projectId, folderKey);\n await this.request(\n 'POST',\n '/api/grove/removeFolder',\n { projectId, folderKey, _ids: [] }\n );\n debug('groveDeleteFolder: deleted');\n }\n}\n\nexport default GraphXRClient;\n", "/**\n * Browser Session Manager\n * \n * Manages a headless Playwright browser session for executing JavaScript\n * in the GraphXR canvas context and capturing screenshots/exports.\n */\n\nimport { chromium, Browser, Page, BrowserContext } from 'playwright';\nimport { GraphXRClient, GraphXRProject } from './graphxr-client';\n\nconst DEBUG = process.env.DEBUG === 'true' || process.env.GRAPHXR_DEBUG === 'true';\n\nfunction debug(...args: unknown[]) {\n if (DEBUG) {\n console.error('[Browser Session]', new Date().toISOString(), ...args);\n }\n}\n\nexport interface BrowserSessionConfig {\n headless?: boolean;\n timeout?: number;\n}\n\nexport interface ExecuteJavaScriptResult {\n success: boolean;\n result?: unknown;\n error?: string;\n}\n\nexport interface ScreenshotOptions {\n frameNodes?: boolean;\n hidePanels?: boolean;\n includeLegends?: boolean;\n format?: 'png' | 'jpeg';\n quality?: number;\n}\n\nexport interface ExportOptions {\n format: 'graphxr' | 'csv' | 'json';\n filename?: string;\n}\n\n/**\n * Manages a browser session for interacting with GraphXR\n */\nexport class BrowserSession {\n private client: GraphXRClient;\n private config: BrowserSessionConfig;\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n private currentProject: GraphXRProject | null = null;\n\n constructor(client: GraphXRClient, config: BrowserSessionConfig = {}) {\n this.client = client;\n this.config = {\n headless: config.headless ?? true,\n timeout: config.timeout ?? 60000,\n };\n }\n\n /**\n * Check if the browser session is active\n */\n isActive(): boolean {\n return this.browser !== null && this.page !== null;\n }\n\n /**\n * Get the currently open project\n */\n getCurrentProject(): GraphXRProject | null {\n return this.currentProject;\n }\n\n /**\n * Launch the browser and open a project\n */\n async openProject(project: GraphXRProject): Promise<void> {\n // Close any existing session\n if (this.isActive()) {\n await this.close();\n }\n\n debug('Launching browser, headless:', this.config.headless);\n\n // Launch browser\n this.browser = await chromium.launch({\n headless: this.config.headless,\n });\n\n this.context = await this.browser.newContext({\n viewport: { width: 1920, height: 1080 },\n ignoreHTTPSErrors: true,\n });\n\n this.page = await this.context.newPage();\n\n // Set up route interception to inject API key for same-origin requests\n await this.setupApiKeyInjection();\n\n // Navigate to the project (no need for apiKey in URL since route interception handles it)\n const projectUrl = this.client.getProjectUrl(project);\n debug('Navigating to project:', projectUrl);\n \n await this.page.goto(projectUrl, {\n waitUntil: 'networkidle',\n timeout: this.config.timeout,\n });\n\n // Wait for GraphXR to fully load\n await this.waitForGraphXRReady();\n\n this.currentProject = project;\n debug('Project opened successfully:', project.projectName);\n }\n\n /**\n * Set up route interception to automatically inject API key for same-origin requests.\n * This ensures all requests to the GraphXR server are authenticated without\n * leaking the API key to external services.\n */\n private async setupApiKeyInjection(): Promise<void> {\n if (!this.page) {\n throw new Error('No active page');\n }\n\n const graphxrOrigin = new URL(this.client.getBaseUrl()).origin;\n const apiKey = this.client.getApiKey();\n\n debug('Setting up API key injection for origin:', graphxrOrigin);\n\n await this.page.route('**/*', async (route) => {\n const request = route.request();\n const requestUrl = request.url();\n \n let requestOrigin: string;\n try {\n requestOrigin = new URL(requestUrl).origin;\n } catch {\n // If URL parsing fails, pass through unchanged\n debug('Could not parse URL, passing through:', requestUrl);\n await route.continue();\n return;\n }\n\n // Only inject API key for same-origin requests (requests to GraphXR server)\n if (requestOrigin === graphxrOrigin) {\n const existingHeaders = request.headers();\n \n // Don't overwrite if x-api-key is already set\n if (!existingHeaders['x-api-key']) {\n debug('Injecting API key for:', requestUrl);\n await route.continue({\n headers: {\n ...existingHeaders,\n 'x-api-key': apiKey,\n },\n });\n } else {\n debug('API key already present, passing through:', requestUrl);\n await route.continue();\n }\n } else {\n // External request - pass through unchanged\n debug('External request, passing through:', requestUrl);\n await route.continue();\n }\n });\n }\n\n /**\n * Wait for GraphXR to be fully loaded and ready\n */\n private async waitForGraphXRReady(): Promise<void> {\n if (!this.page) {\n throw new Error('No active page');\n }\n\n // Wait for the gxr object to be available\n await this.page.waitForFunction(\n () => {\n const win = window as any;\n return win.gxr && typeof win.gxr === 'function';\n },\n { timeout: this.config.timeout }\n );\n\n // Wait for canvas to be loaded\n await this.page.waitForFunction(\n () => {\n const win = window as any;\n return win.gxr && win.gxr.isCanvasLoaded && win.gxr.isCanvasLoaded();\n },\n { timeout: this.config.timeout }\n );\n\n // Give a little extra time for everything to stabilize\n await this.page.waitForTimeout(1000);\n }\n\n /**\n * Execute JavaScript code in the GraphXR context\n */\n async executeJavaScript(code: string): Promise<ExecuteJavaScriptResult> {\n if (!this.page) {\n return {\n success: false,\n error: 'No active browser session. Call openProject first.',\n };\n }\n\n try {\n const result = await this.page.evaluate(async (jsCode: string) => {\n const win = window as any;\n const gxr = win.gxr;\n\n if (!gxr) {\n throw new Error('GraphXR API (gxr) not available');\n }\n\n // Create an async function from the code\n const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;\n const fn = new AsyncFunction('gxr', jsCode);\n\n // Execute the code with gxr as the parameter\n return await fn(gxr);\n }, code);\n\n return {\n success: true,\n result: result,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n /**\n * Take a screenshot of the GraphXR canvas\n */\n async screenshot(options: ScreenshotOptions = {}): Promise<Buffer> {\n if (!this.page) {\n throw new Error('No active browser session. Call openProject first.');\n }\n\n const {\n frameNodes = true,\n hidePanels = true,\n includeLegends = false,\n format = 'png',\n quality = 1.0,\n } = options;\n\n // Use gxr.screenshot() to get a proper canvas screenshot\n const base64Data = await this.page.evaluate(\n async (opts: ScreenshotOptions) => {\n const win = window as any;\n const gxr = win.gxr;\n\n if (!gxr || !gxr.screenshot) {\n throw new Error('GraphXR screenshot API not available');\n }\n\n const blob = await gxr.screenshot({\n frameNodes: opts.frameNodes,\n hidePanels: opts.hidePanels,\n includeLegends: opts.includeLegends,\n format: opts.format,\n quality: opts.quality,\n });\n\n // Convert blob to base64\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const result = reader.result as string;\n // Remove the data URL prefix\n const base64 = result.split(',')[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n },\n { frameNodes, hidePanels, includeLegends, format, quality }\n );\n\n return Buffer.from(base64Data, 'base64');\n }\n\n /**\n * Export the graph data in the specified format\n */\n async exportGraph(options: ExportOptions): Promise<{ data: string; filename: string }> {\n if (!this.page) {\n throw new Error('No active browser session. Call openProject first.');\n }\n\n const { format, filename } = options;\n\n // For JSON export, we can use gxr.snapshot()\n if (format === 'json') {\n const result = await this.executeJavaScript(`\n const snapshot = gxr.snapshot();\n return JSON.stringify(snapshot, null, 2);\n `);\n\n if (!result.success) {\n throw new Error(`Export failed: ${result.error}`);\n }\n\n return {\n data: result.result as string,\n filename: filename || `graph-export-${Date.now()}.json`,\n };\n }\n\n // For CSV and GXRF formats, we need to trigger the export and capture the download\n // This is more complex and requires intercepting downloads\n const exportResult = await this.page.evaluate(\n async (exportFormat: string) => {\n const win = window as any;\n const controller = win._app?.controller;\n\n if (!controller || !controller.exportGraphXR) {\n throw new Error('GraphXR export API not available');\n }\n\n // For now, return the snapshot data for all formats\n // Full export implementation would require download interception\n const gxr = win.gxr;\n const snapshot = gxr.snapshot();\n return JSON.stringify(snapshot, null, 2);\n },\n format\n );\n\n return {\n data: exportResult,\n filename: filename || `graph-export-${Date.now()}.${format === 'graphxr' ? 'gxrf' : format}`,\n };\n }\n\n /**\n * Clear the graph\n */\n async clearGraph(): Promise<void> {\n const result = await this.executeJavaScript('gxr.clear(); return true;');\n if (!result.success) {\n throw new Error(`Failed to clear graph: ${result.error}`);\n }\n }\n\n /**\n * Close the browser session\n */\n async close(): Promise<void> {\n if (this.page) {\n await this.page.close();\n this.page = null;\n }\n\n if (this.context) {\n await this.context.close();\n this.context = null;\n }\n\n if (this.browser) {\n await this.browser.close();\n this.browser = null;\n }\n\n this.currentProject = null;\n }\n}\n\nexport default BrowserSession;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["fetch", "DEBUG", "debug", "DEBUG", "debug"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kineviz/graphxr-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for GraphXR - enables Claude/Cursor to create graph visualizations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"graphxr-mcp": "./dist/index.js"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"README.md"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "node build-package.js",
|
|
13
|
+
"build": "node scripts/build-package.js",
|
|
14
|
+
"build:dev": "tsx scripts/build.ts",
|
|
14
15
|
"prepublishOnly": "yarn build",
|
|
15
16
|
"release": "yarn build && npm publish --access public"
|
|
16
17
|
},
|
|
@@ -19,6 +20,12 @@
|
|
|
19
20
|
"playwright": "^1.53.1",
|
|
20
21
|
"node-fetch": "^2.7.0"
|
|
21
22
|
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"esbuild": "^0.20.0",
|
|
26
|
+
"tsx": "^4.7.0",
|
|
27
|
+
"typescript": "^5.3.0"
|
|
28
|
+
},
|
|
22
29
|
"engines": {
|
|
23
30
|
"node": ">=18.0.0"
|
|
24
31
|
},
|
|
@@ -27,7 +34,7 @@
|
|
|
27
34
|
},
|
|
28
35
|
"repository": {
|
|
29
36
|
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/kineviz/graphxr.git"
|
|
37
|
+
"url": "git+https://github.com/kineviz/graphxr-mcp.git"
|
|
31
38
|
},
|
|
32
39
|
"keywords": [
|
|
33
40
|
"mcp",
|