@kineviz/graphxr-mcp 0.1.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 +168 -0
- package/dist/index.js +870 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @kineviz/graphxr-mcp
|
|
2
|
+
|
|
3
|
+
> **ALPHA SOFTWARE - EXPERIMENTAL**
|
|
4
|
+
>
|
|
5
|
+
> This MCP server is in early alpha stage. APIs may change without notice. Do not use in production environments. Use at your own risk.
|
|
6
|
+
|
|
7
|
+
MCP (Model Context Protocol) server for [GraphXR](https://graphxr.kineviz.com) - enables Claude and Cursor to create graph visualizations programmatically.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Create and manage GraphXR projects
|
|
12
|
+
- Execute JavaScript using the GraphXR API (`gxr`)
|
|
13
|
+
- Take screenshots of graph visualizations
|
|
14
|
+
- Export graph data
|
|
15
|
+
- Clear and manipulate graph canvas
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- Node.js 18 or later
|
|
20
|
+
- A GraphXR account with an API key
|
|
21
|
+
- Playwright Chromium browser (installed automatically or manually)
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Get Your API Key
|
|
26
|
+
|
|
27
|
+
1. Log in to [GraphXR](https://graphxr.kineviz.com)
|
|
28
|
+
2. Click your profile icon in the top right
|
|
29
|
+
3. Select "API Key" from the dropdown
|
|
30
|
+
4. Copy your API key
|
|
31
|
+
|
|
32
|
+
### 2. Install Playwright Browser
|
|
33
|
+
|
|
34
|
+
The MCP server uses Playwright to interact with GraphXR. You need to install the Chromium browser:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx playwright install chromium
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Configure Your MCP Client
|
|
41
|
+
|
|
42
|
+
#### Cursor IDE
|
|
43
|
+
|
|
44
|
+
Create or edit `.cursor/mcp.json` in your project or home directory:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"graphxr": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "@kineviz/graphxr-mcp"],
|
|
52
|
+
"env": {
|
|
53
|
+
"GRAPHXR_API_KEY": "your-api-key-here",
|
|
54
|
+
"GRAPHXR_URL": "https://graphxr.kineviz.com"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Claude Desktop
|
|
62
|
+
|
|
63
|
+
Add to your Claude Desktop configuration:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"graphxr": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "@kineviz/graphxr-mcp"],
|
|
71
|
+
"env": {
|
|
72
|
+
"GRAPHXR_API_KEY": "your-api-key-here",
|
|
73
|
+
"GRAPHXR_URL": "https://graphxr.kineviz.com"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Environment Variables
|
|
81
|
+
|
|
82
|
+
| Variable | Required | Default | Description |
|
|
83
|
+
|----------|----------|---------|-------------|
|
|
84
|
+
| `GRAPHXR_API_KEY` | Yes | - | Your GraphXR API key |
|
|
85
|
+
| `GRAPHXR_URL` | No | `https://graphxr.kineviz.com` | GraphXR server URL |
|
|
86
|
+
| `HEADLESS` | No | `true` | Run browser in headless mode |
|
|
87
|
+
| `DEBUG` | No | `false` | Enable debug logging |
|
|
88
|
+
|
|
89
|
+
## Available Tools
|
|
90
|
+
|
|
91
|
+
Once configured, you can ask Claude/Cursor to:
|
|
92
|
+
|
|
93
|
+
- **list_projects** - List all your GraphXR projects
|
|
94
|
+
- **create_project** - Create a new project
|
|
95
|
+
- **open_project** - Open a project in the browser session
|
|
96
|
+
- **run_javascript** - Execute JavaScript code using the `gxr` API
|
|
97
|
+
- **screenshot** - Take a screenshot of the current graph
|
|
98
|
+
- **export_graph** - Export graph data as JSON
|
|
99
|
+
- **clear_graph** - Clear all nodes and edges from the graph
|
|
100
|
+
- **close_browser** - Close the browser session
|
|
101
|
+
|
|
102
|
+
## Example Usage
|
|
103
|
+
|
|
104
|
+
Ask Claude or Cursor:
|
|
105
|
+
|
|
106
|
+
> "Create a new GraphXR project called 'My Network' and add some sample nodes"
|
|
107
|
+
|
|
108
|
+
> "Take a screenshot of the current graph"
|
|
109
|
+
|
|
110
|
+
> "Run this JavaScript to create 5 connected nodes"
|
|
111
|
+
|
|
112
|
+
## Troubleshooting
|
|
113
|
+
|
|
114
|
+
### "Playwright browser not found"
|
|
115
|
+
|
|
116
|
+
Run `npx playwright install chromium` to install the required browser.
|
|
117
|
+
|
|
118
|
+
### "API key invalid"
|
|
119
|
+
|
|
120
|
+
1. Verify your API key is correct
|
|
121
|
+
2. Check that your GraphXR account is active
|
|
122
|
+
3. Ensure the `GRAPHXR_URL` matches your GraphXR server
|
|
123
|
+
|
|
124
|
+
### "Connection refused"
|
|
125
|
+
|
|
126
|
+
1. Check your internet connection
|
|
127
|
+
2. Verify the `GRAPHXR_URL` is accessible
|
|
128
|
+
3. Check if GraphXR is experiencing downtime
|
|
129
|
+
|
|
130
|
+
### Debug Mode
|
|
131
|
+
|
|
132
|
+
Enable debug logging to see detailed output:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"env": {
|
|
137
|
+
"DEBUG": "true"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## GraphXR API Reference
|
|
143
|
+
|
|
144
|
+
The `run_javascript` tool executes code with access to the `gxr` global object. Common operations:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// Add a node
|
|
148
|
+
gxr.add({ nodes: [{ category: 'Person', properties: { name: 'Alice' } }] });
|
|
149
|
+
|
|
150
|
+
// Get all nodes
|
|
151
|
+
const nodes = gxr.getNodes();
|
|
152
|
+
|
|
153
|
+
// Run a layout
|
|
154
|
+
gxr.layout.spring();
|
|
155
|
+
|
|
156
|
+
// Take a screenshot (returns base64)
|
|
157
|
+
const screenshot = await gxr.screenshot();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
For full API documentation, see the [GraphXR API Reference](https://graphxr.kineviz.com/docs).
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
165
|
+
|
|
166
|
+
## Support
|
|
167
|
+
|
|
168
|
+
For issues and feature requests, contact [Kineviz](https://kineviz.com).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
GraphXRMCPServer: () => GraphXRMCPServer
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
37
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
38
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
39
|
+
|
|
40
|
+
// graphxr-client.ts
|
|
41
|
+
var import_node_fetch = __toESM(require("node-fetch"));
|
|
42
|
+
var DEBUG = process.env.DEBUG === "true" || process.env.GRAPHXR_DEBUG === "true";
|
|
43
|
+
function debug(...args) {
|
|
44
|
+
if (DEBUG) {
|
|
45
|
+
console.error("[GraphXR Client]", (/* @__PURE__ */ new Date()).toISOString(), ...args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var GraphXRClient = class {
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
51
|
+
this.apiKey = config.apiKey;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Make an authenticated request to the GraphXR API
|
|
55
|
+
*/
|
|
56
|
+
async request(method, endpoint, body) {
|
|
57
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
58
|
+
debug("request:", method, url);
|
|
59
|
+
debug("request: body =", body ? JSON.stringify(body) : "(none)");
|
|
60
|
+
const headers = {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"x-api-key": this.apiKey
|
|
63
|
+
};
|
|
64
|
+
const options = {
|
|
65
|
+
method,
|
|
66
|
+
headers
|
|
67
|
+
};
|
|
68
|
+
if (body) {
|
|
69
|
+
options.body = JSON.stringify(body);
|
|
70
|
+
}
|
|
71
|
+
debug("request: sending...");
|
|
72
|
+
const response = await (0, import_node_fetch.default)(url, options);
|
|
73
|
+
debug("request: response status =", response.status, response.statusText);
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const errorText = await response.text();
|
|
76
|
+
debug("request: error response body =", errorText);
|
|
77
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
debug("request: response data.status =", data.status, "message =", data.message);
|
|
81
|
+
if (data.status !== 0) {
|
|
82
|
+
throw new Error(`API error: ${data.message}`);
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Verify the API key is valid by making a test request
|
|
88
|
+
*/
|
|
89
|
+
async verifyApiKey() {
|
|
90
|
+
try {
|
|
91
|
+
await this.listProjects();
|
|
92
|
+
return true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* List all projects accessible to the authenticated user
|
|
99
|
+
*/
|
|
100
|
+
async listProjects() {
|
|
101
|
+
debug("listProjects: calling API");
|
|
102
|
+
const response = await this.request(
|
|
103
|
+
"POST",
|
|
104
|
+
"/api/graph/neo4j/project/list",
|
|
105
|
+
{}
|
|
106
|
+
);
|
|
107
|
+
debug("listProjects: got", response.content?.length ?? 0, "projects");
|
|
108
|
+
return response.content || [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get detailed information about a specific project
|
|
112
|
+
*/
|
|
113
|
+
async getProject(projectId) {
|
|
114
|
+
try {
|
|
115
|
+
const response = await this.request(
|
|
116
|
+
"GET",
|
|
117
|
+
`/api/graph/neo4j/project/getInfo?id=${encodeURIComponent(projectId)}`
|
|
118
|
+
);
|
|
119
|
+
return response.content || null;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a new project
|
|
126
|
+
*/
|
|
127
|
+
async createProject(options) {
|
|
128
|
+
const dbinfo = {
|
|
129
|
+
projectName: options.projectName,
|
|
130
|
+
databaseType: options.databaseType || "kuzu",
|
|
131
|
+
isShare: options.isShare ?? true,
|
|
132
|
+
hostname: "",
|
|
133
|
+
isLocal: false
|
|
134
|
+
};
|
|
135
|
+
const response = await this.request(
|
|
136
|
+
"POST",
|
|
137
|
+
"/api/graph/neo4j/project/add",
|
|
138
|
+
{ dbinfo }
|
|
139
|
+
);
|
|
140
|
+
if (!response.content) {
|
|
141
|
+
throw new Error("Failed to create project: no content returned");
|
|
142
|
+
}
|
|
143
|
+
return response.content;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Delete a project
|
|
147
|
+
*/
|
|
148
|
+
async deleteProject(projectId) {
|
|
149
|
+
await this.request(
|
|
150
|
+
"POST",
|
|
151
|
+
"/api/graph/neo4j/project/delete",
|
|
152
|
+
{ id: projectId }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the URL to access a project in the browser
|
|
157
|
+
*/
|
|
158
|
+
getProjectUrl(project) {
|
|
159
|
+
const encodedName = encodeURIComponent(project.projectName);
|
|
160
|
+
return `${this.baseUrl}/p/${project._id}/${encodedName}`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get the URL to access a project with API key authentication
|
|
164
|
+
*/
|
|
165
|
+
getProjectUrlWithAuth(project) {
|
|
166
|
+
const baseProjectUrl = this.getProjectUrl(project);
|
|
167
|
+
return `${baseProjectUrl}?apiKey=${encodeURIComponent(this.apiKey)}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get the base URL of the GraphXR instance
|
|
171
|
+
*/
|
|
172
|
+
getBaseUrl() {
|
|
173
|
+
return this.baseUrl;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the API key (for passing to browser sessions)
|
|
177
|
+
*/
|
|
178
|
+
getApiKey() {
|
|
179
|
+
return this.apiKey;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// browser-session.ts
|
|
184
|
+
var import_playwright = require("playwright");
|
|
185
|
+
var DEBUG2 = process.env.DEBUG === "true" || process.env.GRAPHXR_DEBUG === "true";
|
|
186
|
+
function debug2(...args) {
|
|
187
|
+
if (DEBUG2) {
|
|
188
|
+
console.error("[Browser Session]", (/* @__PURE__ */ new Date()).toISOString(), ...args);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
var BrowserSession = class {
|
|
192
|
+
constructor(client, config = {}) {
|
|
193
|
+
this.browser = null;
|
|
194
|
+
this.context = null;
|
|
195
|
+
this.page = null;
|
|
196
|
+
this.currentProject = null;
|
|
197
|
+
this.client = client;
|
|
198
|
+
this.config = {
|
|
199
|
+
headless: config.headless ?? true,
|
|
200
|
+
timeout: config.timeout ?? 6e4
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Check if the browser session is active
|
|
205
|
+
*/
|
|
206
|
+
isActive() {
|
|
207
|
+
return this.browser !== null && this.page !== null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get the currently open project
|
|
211
|
+
*/
|
|
212
|
+
getCurrentProject() {
|
|
213
|
+
return this.currentProject;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Launch the browser and open a project
|
|
217
|
+
*/
|
|
218
|
+
async openProject(project) {
|
|
219
|
+
if (this.isActive()) {
|
|
220
|
+
await this.close();
|
|
221
|
+
}
|
|
222
|
+
debug2("Launching browser, headless:", this.config.headless);
|
|
223
|
+
this.browser = await import_playwright.chromium.launch({
|
|
224
|
+
headless: this.config.headless
|
|
225
|
+
});
|
|
226
|
+
this.context = await this.browser.newContext({
|
|
227
|
+
viewport: { width: 1920, height: 1080 },
|
|
228
|
+
ignoreHTTPSErrors: true
|
|
229
|
+
});
|
|
230
|
+
this.page = await this.context.newPage();
|
|
231
|
+
await this.setupApiKeyInjection();
|
|
232
|
+
const projectUrl = this.client.getProjectUrl(project);
|
|
233
|
+
debug2("Navigating to project:", projectUrl);
|
|
234
|
+
await this.page.goto(projectUrl, {
|
|
235
|
+
waitUntil: "networkidle",
|
|
236
|
+
timeout: this.config.timeout
|
|
237
|
+
});
|
|
238
|
+
await this.waitForGraphXRReady();
|
|
239
|
+
this.currentProject = project;
|
|
240
|
+
debug2("Project opened successfully:", project.projectName);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Set up route interception to automatically inject API key for same-origin requests.
|
|
244
|
+
* This ensures all requests to the GraphXR server are authenticated without
|
|
245
|
+
* leaking the API key to external services.
|
|
246
|
+
*/
|
|
247
|
+
async setupApiKeyInjection() {
|
|
248
|
+
if (!this.page) {
|
|
249
|
+
throw new Error("No active page");
|
|
250
|
+
}
|
|
251
|
+
const graphxrOrigin = new URL(this.client.getBaseUrl()).origin;
|
|
252
|
+
const apiKey = this.client.getApiKey();
|
|
253
|
+
debug2("Setting up API key injection for origin:", graphxrOrigin);
|
|
254
|
+
await this.page.route("**/*", async (route) => {
|
|
255
|
+
const request = route.request();
|
|
256
|
+
const requestUrl = request.url();
|
|
257
|
+
let requestOrigin;
|
|
258
|
+
try {
|
|
259
|
+
requestOrigin = new URL(requestUrl).origin;
|
|
260
|
+
} catch {
|
|
261
|
+
debug2("Could not parse URL, passing through:", requestUrl);
|
|
262
|
+
await route.continue();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (requestOrigin === graphxrOrigin) {
|
|
266
|
+
const existingHeaders = request.headers();
|
|
267
|
+
if (!existingHeaders["x-api-key"]) {
|
|
268
|
+
debug2("Injecting API key for:", requestUrl);
|
|
269
|
+
await route.continue({
|
|
270
|
+
headers: {
|
|
271
|
+
...existingHeaders,
|
|
272
|
+
"x-api-key": apiKey
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
debug2("API key already present, passing through:", requestUrl);
|
|
277
|
+
await route.continue();
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
debug2("External request, passing through:", requestUrl);
|
|
281
|
+
await route.continue();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Wait for GraphXR to be fully loaded and ready
|
|
287
|
+
*/
|
|
288
|
+
async waitForGraphXRReady() {
|
|
289
|
+
if (!this.page) {
|
|
290
|
+
throw new Error("No active page");
|
|
291
|
+
}
|
|
292
|
+
await this.page.waitForFunction(
|
|
293
|
+
() => {
|
|
294
|
+
const win = window;
|
|
295
|
+
return win.gxr && typeof win.gxr === "function";
|
|
296
|
+
},
|
|
297
|
+
{ timeout: this.config.timeout }
|
|
298
|
+
);
|
|
299
|
+
await this.page.waitForFunction(
|
|
300
|
+
() => {
|
|
301
|
+
const win = window;
|
|
302
|
+
return win.gxr && win.gxr.isCanvasLoaded && win.gxr.isCanvasLoaded();
|
|
303
|
+
},
|
|
304
|
+
{ timeout: this.config.timeout }
|
|
305
|
+
);
|
|
306
|
+
await this.page.waitForTimeout(1e3);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Execute JavaScript code in the GraphXR context
|
|
310
|
+
*/
|
|
311
|
+
async executeJavaScript(code) {
|
|
312
|
+
if (!this.page) {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: "No active browser session. Call openProject first."
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const result = await this.page.evaluate(async (jsCode) => {
|
|
320
|
+
const win = window;
|
|
321
|
+
const gxr = win.gxr;
|
|
322
|
+
if (!gxr) {
|
|
323
|
+
throw new Error("GraphXR API (gxr) not available");
|
|
324
|
+
}
|
|
325
|
+
const AsyncFunction = Object.getPrototypeOf(async function() {
|
|
326
|
+
}).constructor;
|
|
327
|
+
const fn = new AsyncFunction("gxr", jsCode);
|
|
328
|
+
return await fn(gxr);
|
|
329
|
+
}, code);
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
result
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: error instanceof Error ? error.message : String(error)
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Take a screenshot of the GraphXR canvas
|
|
343
|
+
*/
|
|
344
|
+
async screenshot(options = {}) {
|
|
345
|
+
if (!this.page) {
|
|
346
|
+
throw new Error("No active browser session. Call openProject first.");
|
|
347
|
+
}
|
|
348
|
+
const {
|
|
349
|
+
frameNodes = true,
|
|
350
|
+
hidePanels = true,
|
|
351
|
+
includeLegends = false,
|
|
352
|
+
format = "png",
|
|
353
|
+
quality = 1
|
|
354
|
+
} = options;
|
|
355
|
+
const base64Data = await this.page.evaluate(
|
|
356
|
+
async (opts) => {
|
|
357
|
+
const win = window;
|
|
358
|
+
const gxr = win.gxr;
|
|
359
|
+
if (!gxr || !gxr.screenshot) {
|
|
360
|
+
throw new Error("GraphXR screenshot API not available");
|
|
361
|
+
}
|
|
362
|
+
const blob = await gxr.screenshot({
|
|
363
|
+
frameNodes: opts.frameNodes,
|
|
364
|
+
hidePanels: opts.hidePanels,
|
|
365
|
+
includeLegends: opts.includeLegends,
|
|
366
|
+
format: opts.format,
|
|
367
|
+
quality: opts.quality
|
|
368
|
+
});
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
const reader = new FileReader();
|
|
371
|
+
reader.onloadend = () => {
|
|
372
|
+
const result = reader.result;
|
|
373
|
+
const base64 = result.split(",")[1];
|
|
374
|
+
resolve(base64);
|
|
375
|
+
};
|
|
376
|
+
reader.onerror = reject;
|
|
377
|
+
reader.readAsDataURL(blob);
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
{ frameNodes, hidePanels, includeLegends, format, quality }
|
|
381
|
+
);
|
|
382
|
+
return Buffer.from(base64Data, "base64");
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Export the graph data in the specified format
|
|
386
|
+
*/
|
|
387
|
+
async exportGraph(options) {
|
|
388
|
+
if (!this.page) {
|
|
389
|
+
throw new Error("No active browser session. Call openProject first.");
|
|
390
|
+
}
|
|
391
|
+
const { format, filename } = options;
|
|
392
|
+
if (format === "json") {
|
|
393
|
+
const result = await this.executeJavaScript(`
|
|
394
|
+
const snapshot = gxr.snapshot();
|
|
395
|
+
return JSON.stringify(snapshot, null, 2);
|
|
396
|
+
`);
|
|
397
|
+
if (!result.success) {
|
|
398
|
+
throw new Error(`Export failed: ${result.error}`);
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
data: result.result,
|
|
402
|
+
filename: filename || `graph-export-${Date.now()}.json`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const exportResult = await this.page.evaluate(
|
|
406
|
+
async (exportFormat) => {
|
|
407
|
+
const win = window;
|
|
408
|
+
const controller = win._app?.controller;
|
|
409
|
+
if (!controller || !controller.exportGraphXR) {
|
|
410
|
+
throw new Error("GraphXR export API not available");
|
|
411
|
+
}
|
|
412
|
+
const gxr = win.gxr;
|
|
413
|
+
const snapshot = gxr.snapshot();
|
|
414
|
+
return JSON.stringify(snapshot, null, 2);
|
|
415
|
+
},
|
|
416
|
+
format
|
|
417
|
+
);
|
|
418
|
+
return {
|
|
419
|
+
data: exportResult,
|
|
420
|
+
filename: filename || `graph-export-${Date.now()}.${format === "graphxr" ? "gxrf" : format}`
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Clear the graph
|
|
425
|
+
*/
|
|
426
|
+
async clearGraph() {
|
|
427
|
+
const result = await this.executeJavaScript("gxr.clear(); return true;");
|
|
428
|
+
if (!result.success) {
|
|
429
|
+
throw new Error(`Failed to clear graph: ${result.error}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Close the browser session
|
|
434
|
+
*/
|
|
435
|
+
async close() {
|
|
436
|
+
if (this.page) {
|
|
437
|
+
await this.page.close();
|
|
438
|
+
this.page = null;
|
|
439
|
+
}
|
|
440
|
+
if (this.context) {
|
|
441
|
+
await this.context.close();
|
|
442
|
+
this.context = null;
|
|
443
|
+
}
|
|
444
|
+
if (this.browser) {
|
|
445
|
+
await this.browser.close();
|
|
446
|
+
this.browser = null;
|
|
447
|
+
}
|
|
448
|
+
this.currentProject = null;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// index.ts
|
|
453
|
+
var GRAPHXR_API_KEY = process.env.GRAPHXR_API_KEY || "";
|
|
454
|
+
var GRAPHXR_URL = process.env.GRAPHXR_URL || "http://localhost:9000";
|
|
455
|
+
var HEADLESS = process.env.GRAPHXR_HEADLESS !== "false";
|
|
456
|
+
var DEBUG3 = process.env.DEBUG === "true" || process.env.GRAPHXR_DEBUG === "true";
|
|
457
|
+
function debug3(...args) {
|
|
458
|
+
if (DEBUG3) {
|
|
459
|
+
console.error("[MCP DEBUG]", (/* @__PURE__ */ new Date()).toISOString(), ...args);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
var tools = [
|
|
463
|
+
{
|
|
464
|
+
name: "list_projects",
|
|
465
|
+
description: "List all GraphXR projects accessible to the authenticated user",
|
|
466
|
+
inputSchema: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {},
|
|
469
|
+
required: []
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "create_project",
|
|
474
|
+
description: "Create a new GraphXR project",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {
|
|
478
|
+
projectName: {
|
|
479
|
+
type: "string",
|
|
480
|
+
description: "Name for the new project"
|
|
481
|
+
},
|
|
482
|
+
databaseType: {
|
|
483
|
+
type: "string",
|
|
484
|
+
description: "Database type (default: kuzu)",
|
|
485
|
+
enum: ["kuzu", "neo4j"]
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
required: ["projectName"]
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "open_project",
|
|
493
|
+
description: "Open a GraphXR project in a headless browser session for interaction",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
projectId: {
|
|
498
|
+
type: "string",
|
|
499
|
+
description: "The project ID to open"
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
required: ["projectId"]
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "run_javascript",
|
|
507
|
+
description: `Execute JavaScript code in the GraphXR canvas context using the gxr API.
|
|
508
|
+
The code has access to the 'gxr' object which provides methods for:
|
|
509
|
+
- Graph manipulation: gxr.addNode(), gxr.addEdge(), gxr.nodes(), gxr.edges(), gxr.clear()
|
|
510
|
+
- Layout: gxr.forceLayout(), gxr.circle(), gxr.grid(), gxr.ego(), gxr.parametric()
|
|
511
|
+
- Styling: gxr.colorNodesByProperty(), gxr.sizeNodesByProperty(), gxr.setCategoryColor()
|
|
512
|
+
- Camera: gxr.flyToCenter(), gxr.zoomIn(), gxr.zoomOut()
|
|
513
|
+
- Transform: gxr.extract(), gxr.link(), gxr.merge(), gxr.aggregate()
|
|
514
|
+
- Query: gxr.query() for Cypher queries
|
|
515
|
+
The code should return a value that can be serialized to JSON.`,
|
|
516
|
+
inputSchema: {
|
|
517
|
+
type: "object",
|
|
518
|
+
properties: {
|
|
519
|
+
code: {
|
|
520
|
+
type: "string",
|
|
521
|
+
description: "JavaScript code to execute. Has access to gxr API. Must return a value."
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
required: ["code"]
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "screenshot",
|
|
529
|
+
description: "Take a screenshot of the GraphXR canvas",
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
frameNodes: {
|
|
534
|
+
type: "boolean",
|
|
535
|
+
description: "Whether to frame all nodes in view before capturing (default: true)"
|
|
536
|
+
},
|
|
537
|
+
hidePanels: {
|
|
538
|
+
type: "boolean",
|
|
539
|
+
description: "Whether to hide UI panels (default: true)"
|
|
540
|
+
},
|
|
541
|
+
includeLegends: {
|
|
542
|
+
type: "boolean",
|
|
543
|
+
description: "Whether to include legend overlays (default: false)"
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
required: []
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: "export_graph",
|
|
551
|
+
description: "Export the current graph data",
|
|
552
|
+
inputSchema: {
|
|
553
|
+
type: "object",
|
|
554
|
+
properties: {
|
|
555
|
+
format: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: "Export format",
|
|
558
|
+
enum: ["json", "graphxr", "csv"]
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
required: ["format"]
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "close_project",
|
|
566
|
+
description: "Close the current browser session",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: "object",
|
|
569
|
+
properties: {},
|
|
570
|
+
required: []
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
];
|
|
574
|
+
var GraphXRMCPServer = class {
|
|
575
|
+
constructor() {
|
|
576
|
+
this.client = null;
|
|
577
|
+
this.browserSession = null;
|
|
578
|
+
this.server = new import_server.Server(
|
|
579
|
+
{
|
|
580
|
+
name: "graphxr-mcp-server",
|
|
581
|
+
version: "1.0.0"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
capabilities: {
|
|
585
|
+
tools: {}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
this.setupHandlers();
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Initialize the GraphXR client with API key
|
|
593
|
+
*/
|
|
594
|
+
initializeClient() {
|
|
595
|
+
if (!GRAPHXR_API_KEY) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
"GRAPHXR_API_KEY environment variable is required. Generate an API key in GraphXR Settings > API Access."
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (!this.client) {
|
|
601
|
+
this.client = new GraphXRClient({
|
|
602
|
+
baseUrl: GRAPHXR_URL,
|
|
603
|
+
apiKey: GRAPHXR_API_KEY
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return this.client;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get or create the browser session
|
|
610
|
+
*/
|
|
611
|
+
getBrowserSession() {
|
|
612
|
+
const client = this.initializeClient();
|
|
613
|
+
if (!this.browserSession) {
|
|
614
|
+
this.browserSession = new BrowserSession(client, {
|
|
615
|
+
headless: HEADLESS
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return this.browserSession;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Setup MCP request handlers
|
|
622
|
+
*/
|
|
623
|
+
setupHandlers() {
|
|
624
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
625
|
+
return { tools };
|
|
626
|
+
});
|
|
627
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
628
|
+
const { name, arguments: args } = request.params;
|
|
629
|
+
debug3("Tool call received:", name);
|
|
630
|
+
debug3("Tool arguments:", JSON.stringify(args));
|
|
631
|
+
try {
|
|
632
|
+
switch (name) {
|
|
633
|
+
case "list_projects":
|
|
634
|
+
return await this.handleListProjects();
|
|
635
|
+
case "create_project":
|
|
636
|
+
return await this.handleCreateProject(args);
|
|
637
|
+
case "open_project":
|
|
638
|
+
return await this.handleOpenProject(args);
|
|
639
|
+
case "run_javascript":
|
|
640
|
+
return await this.handleRunJavaScript(args);
|
|
641
|
+
case "screenshot":
|
|
642
|
+
return await this.handleScreenshot(args);
|
|
643
|
+
case "export_graph":
|
|
644
|
+
return await this.handleExportGraph(args);
|
|
645
|
+
case "close_project":
|
|
646
|
+
return await this.handleCloseProject();
|
|
647
|
+
default:
|
|
648
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
652
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
653
|
+
debug3("Tool error:", errorMessage);
|
|
654
|
+
if (errorStack) {
|
|
655
|
+
debug3("Stack trace:", errorStack);
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
content: [
|
|
659
|
+
{
|
|
660
|
+
type: "text",
|
|
661
|
+
text: `Error: ${errorMessage}`
|
|
662
|
+
}
|
|
663
|
+
],
|
|
664
|
+
isError: true
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Handle list_projects tool
|
|
671
|
+
*/
|
|
672
|
+
async handleListProjects() {
|
|
673
|
+
debug3("handleListProjects: starting");
|
|
674
|
+
debug3("handleListProjects: GRAPHXR_URL =", GRAPHXR_URL);
|
|
675
|
+
debug3("handleListProjects: API key length =", GRAPHXR_API_KEY.length);
|
|
676
|
+
const client = this.initializeClient();
|
|
677
|
+
debug3("handleListProjects: client initialized");
|
|
678
|
+
debug3("handleListProjects: calling listProjects API...");
|
|
679
|
+
const projects = await client.listProjects();
|
|
680
|
+
debug3("handleListProjects: received", projects.length, "projects");
|
|
681
|
+
const projectList = projects.map((p) => ({
|
|
682
|
+
id: p._id,
|
|
683
|
+
name: p.projectName,
|
|
684
|
+
databaseType: p.databaseType,
|
|
685
|
+
isShare: p.isShare,
|
|
686
|
+
description: p.projectSettings?.description || ""
|
|
687
|
+
}));
|
|
688
|
+
debug3("handleListProjects: returning project list");
|
|
689
|
+
return {
|
|
690
|
+
content: [
|
|
691
|
+
{
|
|
692
|
+
type: "text",
|
|
693
|
+
text: JSON.stringify(projectList, null, 2)
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Handle create_project tool
|
|
700
|
+
*/
|
|
701
|
+
async handleCreateProject(args) {
|
|
702
|
+
const client = this.initializeClient();
|
|
703
|
+
const project = await client.createProject({
|
|
704
|
+
projectName: args.projectName,
|
|
705
|
+
databaseType: args.databaseType || "kuzu"
|
|
706
|
+
});
|
|
707
|
+
return {
|
|
708
|
+
content: [
|
|
709
|
+
{
|
|
710
|
+
type: "text",
|
|
711
|
+
text: JSON.stringify(
|
|
712
|
+
{
|
|
713
|
+
id: project._id,
|
|
714
|
+
name: project.projectName,
|
|
715
|
+
databaseType: project.databaseType,
|
|
716
|
+
url: client.getProjectUrl(project)
|
|
717
|
+
},
|
|
718
|
+
null,
|
|
719
|
+
2
|
|
720
|
+
)
|
|
721
|
+
}
|
|
722
|
+
]
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Handle open_project tool
|
|
727
|
+
*/
|
|
728
|
+
async handleOpenProject(args) {
|
|
729
|
+
const client = this.initializeClient();
|
|
730
|
+
const project = await client.getProject(args.projectId);
|
|
731
|
+
debug3("handleOpenProject: got project", project?._id, project?.projectName);
|
|
732
|
+
if (!project) {
|
|
733
|
+
throw new Error(`Project not found: ${args.projectId}`);
|
|
734
|
+
}
|
|
735
|
+
const session = this.getBrowserSession();
|
|
736
|
+
debug3("handleOpenProject: opening project in browser");
|
|
737
|
+
await session.openProject(project);
|
|
738
|
+
return {
|
|
739
|
+
content: [
|
|
740
|
+
{
|
|
741
|
+
type: "text",
|
|
742
|
+
text: `Opened project "${project.projectName}" (${project._id}). You can now run JavaScript code using the run_javascript tool.`
|
|
743
|
+
}
|
|
744
|
+
]
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Handle run_javascript tool
|
|
749
|
+
*/
|
|
750
|
+
async handleRunJavaScript(args) {
|
|
751
|
+
const session = this.getBrowserSession();
|
|
752
|
+
if (!session.isActive()) {
|
|
753
|
+
throw new Error("No project is currently open. Use open_project first.");
|
|
754
|
+
}
|
|
755
|
+
const result = await session.executeJavaScript(args.code);
|
|
756
|
+
if (!result.success) {
|
|
757
|
+
throw new Error(result.error || "JavaScript execution failed");
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
content: [
|
|
761
|
+
{
|
|
762
|
+
type: "text",
|
|
763
|
+
text: result.result !== void 0 ? JSON.stringify(result.result, null, 2) : "Code executed successfully (no return value)"
|
|
764
|
+
}
|
|
765
|
+
]
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Handle screenshot tool
|
|
770
|
+
*/
|
|
771
|
+
async handleScreenshot(args) {
|
|
772
|
+
const session = this.getBrowserSession();
|
|
773
|
+
if (!session.isActive()) {
|
|
774
|
+
throw new Error("No project is currently open. Use open_project first.");
|
|
775
|
+
}
|
|
776
|
+
const imageBuffer = await session.screenshot(args);
|
|
777
|
+
const base64Image = imageBuffer.toString("base64");
|
|
778
|
+
return {
|
|
779
|
+
content: [
|
|
780
|
+
{
|
|
781
|
+
type: "image",
|
|
782
|
+
data: base64Image,
|
|
783
|
+
mimeType: args.format === "jpeg" ? "image/jpeg" : "image/png"
|
|
784
|
+
}
|
|
785
|
+
]
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Handle export_graph tool
|
|
790
|
+
*/
|
|
791
|
+
async handleExportGraph(args) {
|
|
792
|
+
const session = this.getBrowserSession();
|
|
793
|
+
if (!session.isActive()) {
|
|
794
|
+
throw new Error("No project is currently open. Use open_project first.");
|
|
795
|
+
}
|
|
796
|
+
const exportResult = await session.exportGraph({
|
|
797
|
+
format: args.format
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
content: [
|
|
801
|
+
{
|
|
802
|
+
type: "text",
|
|
803
|
+
text: `Export complete. Filename: ${exportResult.filename}
|
|
804
|
+
|
|
805
|
+
Data:
|
|
806
|
+
${exportResult.data}`
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Handle close_project tool
|
|
813
|
+
*/
|
|
814
|
+
async handleCloseProject() {
|
|
815
|
+
if (this.browserSession) {
|
|
816
|
+
await this.browserSession.close();
|
|
817
|
+
this.browserSession = null;
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
content: [
|
|
821
|
+
{
|
|
822
|
+
type: "text",
|
|
823
|
+
text: "Browser session closed."
|
|
824
|
+
}
|
|
825
|
+
]
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Start the MCP server
|
|
830
|
+
*/
|
|
831
|
+
async start() {
|
|
832
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
833
|
+
await this.server.connect(transport);
|
|
834
|
+
process.on("SIGINT", async () => {
|
|
835
|
+
await this.cleanup();
|
|
836
|
+
process.exit(0);
|
|
837
|
+
});
|
|
838
|
+
process.on("SIGTERM", async () => {
|
|
839
|
+
await this.cleanup();
|
|
840
|
+
process.exit(0);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Cleanup resources
|
|
845
|
+
*/
|
|
846
|
+
async cleanup() {
|
|
847
|
+
if (this.browserSession) {
|
|
848
|
+
await this.browserSession.close();
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
async function main() {
|
|
853
|
+
debug3("Starting GraphXR MCP Server");
|
|
854
|
+
debug3("Configuration:");
|
|
855
|
+
debug3(" GRAPHXR_URL:", GRAPHXR_URL);
|
|
856
|
+
debug3(" GRAPHXR_API_KEY:", GRAPHXR_API_KEY ? `(${GRAPHXR_API_KEY.length} chars)` : "(not set)");
|
|
857
|
+
debug3(" HEADLESS:", HEADLESS);
|
|
858
|
+
debug3(" DEBUG:", DEBUG3);
|
|
859
|
+
const server = new GraphXRMCPServer();
|
|
860
|
+
await server.start();
|
|
861
|
+
debug3("MCP Server started and listening on stdio");
|
|
862
|
+
}
|
|
863
|
+
main().catch((error) => {
|
|
864
|
+
console.error("Failed to start GraphXR MCP Server:", error);
|
|
865
|
+
process.exit(1);
|
|
866
|
+
});
|
|
867
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
868
|
+
0 && (module.exports = {
|
|
869
|
+
GraphXRMCPServer
|
|
870
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kineviz/graphxr-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for GraphXR - enables Claude/Cursor to create graph visualizations",
|
|
5
|
+
"bin": {
|
|
6
|
+
"graphxr-mcp": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "node build-package.js",
|
|
14
|
+
"prepublishOnly": "yarn build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
18
|
+
"playwright": "^1.53.1",
|
|
19
|
+
"node-fetch": "^2.7.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/kineviz/graphxr.git"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"graphxr",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"claude",
|
|
36
|
+
"cursor",
|
|
37
|
+
"graph-visualization"
|
|
38
|
+
],
|
|
39
|
+
"author": "Kineviz",
|
|
40
|
+
"license": "MIT"
|
|
41
|
+
}
|