ultipa-mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Ultipa
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # Ultipa MCP
2
+
3
+ Model Context Protocol server for [Ultipa Cloud](https://dbaas.ultipa.com) and any self-managed Ultipa GQLDB instance. Lets MCP clients provision and operate instances, run GQL queries, manage backups, view metrics, and more, all through natural language.
4
+
5
+ ## Auth
6
+
7
+ Each MCP server entry in your client's config points at one Ultipa target via one of two paths:
8
+
9
+ | Path | Use it if | Env vars |
10
+ |---|---|---|
11
+ | **Ultipa Cloud** | You manage instances via [Ultipa Cloud](https://dbaas.ultipa.com). | `ULTIPA_CLOUD_API_KEY` (create at [Ultipa Cloud](https://dbaas.ultipa.com) → Settings → API Keys) |
12
+ | **Direct instance** | You have admin credentials to a single GQLDB instance. | `ULTIPA_HOST` + `ULTIPA_USERNAME` + `ULTIPA_PASSWORD`, optional `ULTIPA_GRAPH` |
13
+
14
+ Need both, or multiple direct instances? Add more entries (see [Multiple targets](#multiple-targets)).
15
+
16
+ **Ultipa Cloud API key permissions** depend on which tools you'll use:
17
+
18
+ | Permission | What it unlocks |
19
+ |---|---|
20
+ | `instances:read` | All read tools (list, get, metrics, …) |
21
+ | `instances:write` | State changes (create, pause, restart, upgrade, set log level, schedule backups, …) |
22
+ | `instances:delete` | `delete_instance`, `delete_backup` |
23
+ | `instances:credentials` | `get_instance_credentials`. Also required by the data-plane tools under Ultipa Cloud account mode, because they fetch credentials per call. |
24
+ | `billing:read`, `billing:write` | The billing tools |
25
+
26
+ ## Install
27
+
28
+ ### One target via install-mcp
29
+
30
+ Replace `claude` with your client: `cursor`, `windsurf`, `vscode`, `cline`, etc.
31
+
32
+ For an Ultipa Cloud account:
33
+
34
+ ```bash
35
+ npx -y install-mcp@latest ultipa-mcp --client claude \
36
+ --env ULTIPA_CLOUD_API_KEY=uc_...
37
+ ```
38
+
39
+ For a direct instance:
40
+
41
+ ```bash
42
+ npx -y install-mcp@latest ultipa-mcp --client claude \
43
+ --env ULTIPA_HOST=<host>:<port> \
44
+ --env ULTIPA_USERNAME=admin \
45
+ --env ULTIPA_PASSWORD=<password> \
46
+ --env ULTIPA_GRAPH=default
47
+ ```
48
+
49
+ ### Multiple targets
50
+
51
+ Each MCP server entry in your client's config points at one Ultipa target. Add as many entries as you need, with descriptive names. Claude (or any agent) sees each entry as its own toolset and picks based on what you ask (e.g. "query staging" routes to the `ultipa-staging` entry).
52
+
53
+ The same JSON shape works in any stdio MCP client; only the file path differs (Claude Desktop: `claude_desktop_config.json` via Settings → Developer → Edit Config; Cursor: `~/.cursor/mcp.json`; Windsurf, VS Code MCP extensions: see their docs).
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "ultipa-cloud": {
59
+ "command": "npx",
60
+ "args": ["-y", "ultipa-mcp"],
61
+ "env": {
62
+ "ULTIPA_CLOUD_API_KEY": "uc_..."
63
+ }
64
+ },
65
+ "ultipa-staging": {
66
+ "command": "npx",
67
+ "args": ["-y", "ultipa-mcp"],
68
+ "env": {
69
+ "ULTIPA_HOST": "staging.internal:60061",
70
+ "ULTIPA_USERNAME": "admin",
71
+ "ULTIPA_PASSWORD": "..."
72
+ }
73
+ },
74
+ "ultipa-prod": {
75
+ "command": "npx",
76
+ "args": ["-y", "ultipa-mcp"],
77
+ "env": {
78
+ "ULTIPA_HOST": "prod.internal:60061",
79
+ "ULTIPA_USERNAME": "admin",
80
+ "ULTIPA_PASSWORD": "..."
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ Restart your client after editing.
88
+
89
+ ## Tools
90
+
91
+ ### Account
92
+
93
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
94
+
95
+ | Tool | What it does |
96
+ |---|---|
97
+ | `get_account` | Authenticated account profile (email, name, balance flags). |
98
+
99
+ ### Instance lifecycle
100
+
101
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
102
+
103
+ | Tool | What it does |
104
+ |---|---|
105
+ | `list_instances` | List all instances on the account. |
106
+ | `get_instance` | Fetch one instance by ID. |
107
+ | `list_deleted_instances` | List deleted instances (not returned by `list_instances`). |
108
+ | `create_instance` | Provision a new instance (name, region, sizeId). |
109
+ | `rename_instance` | Change an instance's display name. |
110
+ | `pause_instance` | Pause a running instance. |
111
+ | `resume_instance` | Resume a paused instance. |
112
+ | `restart_instance` | Restart the instance. |
113
+ | `upgrade_version` | Upgrade to the latest GQLDB version. |
114
+ | `delete_instance` | Delete an instance. Requires the instance name as confirmation. |
115
+ | `get_instance_credentials` | Fetch admin username and password of the instance. |
116
+ | `reset_admin_password` | Rotate the admin DB password. Breaks existing connections. |
117
+ | `list_regions` | List supported regions and their Manager URLs. |
118
+ | `list_instance_sizes` | List available sizes and pricing. |
119
+ | `get_latest_version` | Latest available GQLDB version. |
120
+ | `get_trial_status` | Free-trial eligibility. Pre-check before creating a free-trial instance. |
121
+ | `get_enterprise_status` | Enterprise-tier eligibility. Pre-check before creating an enterprise instance. |
122
+ | `get_operations_lock` | Whether instance ops are globally locked (maintenance / freeze). |
123
+ | `wait_for_instance_status` | Explicit polling helper. Rarely needed. |
124
+
125
+ ### Metrics, Logs & Alerts
126
+
127
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
128
+
129
+ | Tool | What it does |
130
+ |---|---|
131
+ | `get_live_metrics` | Current CPU / memory / disk / network snapshot. |
132
+ | `get_metrics_history` | Historical metrics over the last N minutes (default 60, max 14 days). |
133
+ | `get_instance_logs` | Recent container logs (default 100 lines, max 1000). |
134
+ | `set_log_level` | Set GQLDB log level (debug / info / warn / error). |
135
+ | `list_alerts` | All alerts across the account's instances. |
136
+ | `list_instance_alerts` | Alerts for a single instance. |
137
+
138
+ ### Firewall
139
+
140
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
141
+
142
+ | Tool | What it does |
143
+ |---|---|
144
+ | `get_my_ip` | Public IP of the machine running Ultipa MCP (pair with `add_firewall_rule` to allow `${ip}/32`). |
145
+ | `list_firewall_rules` | IP-allowlist rules for an instance. |
146
+ | `add_firewall_rule` | Add a CIDR to the allowlist. |
147
+ | `remove_firewall_rule` | Remove a rule by its CIDR. |
148
+
149
+ ### Backups
150
+
151
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
152
+
153
+ | Tool | What it does |
154
+ |---|---|
155
+ | `list_backups` | List backups for an instance. |
156
+ | `create_backup` | Trigger an on-demand backup (default 10-min timeout). |
157
+ | `restore_backup` | Restore from a completed backup. **Destructive: overwrites current data.** |
158
+ | `delete_backup` | Permanently delete a backup snapshot. |
159
+ | `set_backup_schedule` | Set/update an automated backup schedule. |
160
+ | `clear_backup_schedule` | Remove the schedule (existing backups kept). |
161
+
162
+ ### Billing
163
+
164
+ Requires Ultipa Cloud (`ULTIPA_CLOUD_API_KEY`).
165
+
166
+ | Tool | What it does |
167
+ |---|---|
168
+ | `get_balance` | Current account balance and billing flags. |
169
+ | `list_transactions` | Balance transactions (top-ups, charges, refunds). |
170
+ | `get_usage` | Monthly usage-based billing summary. |
171
+ | `get_payment_method` | Saved card info. |
172
+ | `get_auto_reload` | Current auto-reload settings. |
173
+ | `set_auto_reload` | Update auto-reload settings. |
174
+ | `topup_balance` | Top up balance with a saved card. |
175
+ | `start_payment_method_setup` | Stripe Checkout URL for adding/changing the saved card. |
176
+
177
+ ### Data plane
178
+
179
+ Works with either Ultipa Cloud or a Direct instance.
180
+
181
+ | Tool | What it does |
182
+ |---|---|
183
+ | `test_connection` | Quick health check on the target GQLDB instance. |
184
+ | `run_gql_query` | Execute a GQL query and return results. |
185
+ | `explain_query` | Return the execution plan without running the query. |
186
+ | `run_algo` | Run a built-in graph algorithm. Centrality, community detection, similarity, pathfinding, graph embeddings, etc. Same execution as `run_gql_query`; separate so the agent surfaces the algorithm catalog for analytical questions. |
187
+ | `list_graphs` | List all graphs on the instance. |
188
+ | `describe_schema` | Detect graph mode (OPEN / CLOSED / ONTOLOGY) and run schema introspection. |
189
+ | `create_graph` | Create a new graph (OPEN / CLOSED / ONTOLOGY). |
190
+ | `delete_graph` | Drop a graph. |
191
+ | `write_data` | Run a GQL DML statement the agent composes by hand. For files on the user's machine, use `import_data` instead. |
192
+ | `import_data` | Bulk-write structured nodes / edges via the driver's gRPC bulk-insert path. Highly recommend to provide filepath to the CSV, JSON, or JSONL files. |
193
+ | `write_procedure` | Create a stored procedure. |
194
+ | `get_db_version` | Live GQLDB version reported by the instance. |
195
+ | `get_db_license` | GQLDB Edition + license info. |
196
+ | `reload_db_stats` | Rebuild the instance's stored statistics. |
197
+
198
+ ### Docs
199
+
200
+ Works with either Ultipa Cloud or a Direct instance.
201
+
202
+ | Tool | What it does |
203
+ |---|---|
204
+ | `lookup_docs` | Fetch Ultipa documentation pages by topic. Useful for the agent to ground GQLDB features and GQL composition in authoritative reference. |
205
+
206
+ ## Troubleshooting
207
+
208
+ | Symptom | Likely cause / fix |
209
+ |---|---|
210
+ | `ENOENT: no such file or directory` from `import_data` with `filePath` | The path is on your agent's sandbox, not the MCP host. Drag the file into your terminal to copy its real host path, or paste the file content via `csv` mode. |
211
+ | Edge insert rejected with "EDGE_ID disabled" or custom `_id` not allowed | The graph has edge `_id` disabled. Run `ALTER GRAPH <name> SET EDGE_ID ENABLED` via `run_gql_query`, or drop the `_id` from your edge data. |
212
+ | `instances:credentials` permission required (data plane fails on a Cloud target) | Your `ULTIPA_CLOUD_API_KEY` doesn't have `instances:credentials`. Regenerate the key with that scope at https://dbaas.ultipa.com → Settings → API Keys. |
213
+ | `list_instances` shows nothing even though you have an instance | You're targeting a Direct instance (env vars only), not a Cloud account. `list_instances` only sees Cloud instances. Call `test_connection` (omit `id`) to confirm the Direct instance is reachable. |
214
+ | `create_instance` succeeded but `adminPassword` is missing in the response | The Cloud REST `POST /v1/instances` returns the password exactly once. If you missed it, call `get_instance_credentials` (requires the `instances:credentials` scope). |
215
+ | MCP launches but exits immediately with "needs at least one auth mode" | None of `ULTIPA_CLOUD_API_KEY` or the Direct trio (`ULTIPA_HOST` + `ULTIPA_USERNAME` + `ULTIPA_PASSWORD`) are set. Add them to your MCP client's `env` block. |
216
+ | MCP launches but exits with "Direct instance config is incomplete" | You set one or two of the Direct env vars; all three (`ULTIPA_HOST`, `ULTIPA_USERNAME`, `ULTIPA_PASSWORD`) are required together. |
217
+ | `import_data` very slow / truncated in `csv` or arrays mode | The agent's output rate is the bottleneck. Provide a host file path so the MCP uses `filePath` mode (constant tokens), or fall back to Ultipa Manager → Data Integration for very large imports. |
218
+
219
+ For agent-side trace debugging, set `ULTIPA_MCP_DEBUG=1` in the MCP env to log every tool call name + latency to stderr.
220
+
221
+ ## License
222
+
223
+ ISC
@@ -0,0 +1,9 @@
1
+ export declare function api(path: string, init?: Omit<RequestInit, "body"> & {
2
+ body?: unknown;
3
+ }): Promise<any>;
4
+ export declare const json: (data: unknown) => {
5
+ content: {
6
+ type: "text";
7
+ text: string;
8
+ }[];
9
+ };
@@ -0,0 +1,25 @@
1
+ import { API_KEY, BASE_URL } from "./env.js";
2
+ export async function api(path, init = {}) {
3
+ if (!API_KEY) {
4
+ throw new Error("This tool needs Ultipa Cloud (ULTIPA_CLOUD_API_KEY) but only a Direct instance is configured.");
5
+ }
6
+ const { body, ...rest } = init;
7
+ const res = await fetch(`${BASE_URL}${path}`, {
8
+ ...rest,
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ "X-API-Key": API_KEY,
12
+ ...(rest.headers ?? {}),
13
+ },
14
+ body: body === undefined ? undefined : JSON.stringify(body),
15
+ });
16
+ if (!res.ok) {
17
+ throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
18
+ }
19
+ if (res.status === 204)
20
+ return null;
21
+ return res.json();
22
+ }
23
+ export const json = (data) => ({
24
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
25
+ });
@@ -0,0 +1,5 @@
1
+ import { GqldbClient } from "@ultipa-graph/ultipa-driver";
2
+ export declare function getDataPlaneClient(target: string): Promise<GqldbClient>;
3
+ export declare function resolveDataPlaneTarget(id: string | undefined): string;
4
+ export declare function serializeResponse(r: any): any;
5
+ export declare function closeAllDataPlaneClients(): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import { GqldbClient, ConfigBuilder } from "@ultipa-graph/ultipa-driver";
2
+ import { api } from "./api.js";
3
+ import { INSTANCE_HOST, INSTANCE_USER, INSTANCE_PASSWORD, hasModeA, hasModeB, } from "./env.js";
4
+ // Data-plane client cache. Keys: "direct" (env-configured single instance) or an instance ID
5
+ // (resolved via Ultipa Cloud). Values are lazily-opened, logged-in GqldbClient promises.
6
+ const clientCache = new Map();
7
+ async function buildDataPlaneClient(target) {
8
+ let host;
9
+ let user;
10
+ let password;
11
+ if (target === "direct") {
12
+ host = INSTANCE_HOST;
13
+ user = INSTANCE_USER;
14
+ password = INSTANCE_PASSWORD;
15
+ }
16
+ else {
17
+ // Ultipa Cloud: target is an instance ID. Resolve host + credentials via the Cloud control plane.
18
+ const inst = (await api(`/v1/instances/${target}`));
19
+ if (inst.status !== "running") {
20
+ throw new Error(`Instance ${target} is not running (status: "${inst.status}"). Data-plane calls require a running instance.`);
21
+ }
22
+ if (!inst.host || !inst.port) {
23
+ throw new Error(`Instance ${target} has no reachable host/port (host="${inst.host}", port=${inst.port}).`);
24
+ }
25
+ const creds = (await api(`/v1/instances/${target}/credentials`));
26
+ host = `${inst.host}:${inst.port}`;
27
+ user = creds.adminUser;
28
+ password = creds.adminPassword;
29
+ }
30
+ const cfg = new ConfigBuilder()
31
+ .hosts(host)
32
+ .username(user)
33
+ .password(password)
34
+ .timeoutSeconds(120)
35
+ .build();
36
+ const client = new GqldbClient(cfg);
37
+ try {
38
+ await client.login(user, password);
39
+ }
40
+ catch (e) {
41
+ // Don't leak an un-logged-in client — gRPC channel + driver state.
42
+ await client.close().catch(() => { });
43
+ throw e;
44
+ }
45
+ return client;
46
+ }
47
+ export async function getDataPlaneClient(target) {
48
+ let pending = clientCache.get(target);
49
+ if (!pending) {
50
+ pending = buildDataPlaneClient(target);
51
+ clientCache.set(target, pending);
52
+ // On failure, drop the cache entry so the next call retries with a fresh build.
53
+ pending.catch(() => clientCache.delete(target));
54
+ }
55
+ return pending;
56
+ }
57
+ export function resolveDataPlaneTarget(id) {
58
+ if (id) {
59
+ if (!hasModeA) {
60
+ throw new Error("This call passed an `id`, which routes through Ultipa Cloud, but ULTIPA_CLOUD_API_KEY is not configured. Either set it, or omit `id` to use the Direct instance.");
61
+ }
62
+ return id;
63
+ }
64
+ if (!hasModeB) {
65
+ throw new Error("No `id` provided and Direct instance (ULTIPA_HOST + ULTIPA_USERNAME + ULTIPA_PASSWORD) is not configured. Pass `id` to target an instance via Ultipa Cloud, or set the Direct instance env vars to designate a default.");
66
+ }
67
+ return "direct";
68
+ }
69
+ export function serializeResponse(r) {
70
+ // Convert the driver's Response into a clean JSON shape: column names, rows as objects
71
+ // keyed by column name, plus row count. `toJSON()` on the SDK is typed as `string`, so
72
+ // calling it would force the agent to parse a string out of the tool result — instead we
73
+ // use `toObjects()` to project rows into a plain object array.
74
+ if (r && typeof r === "object" && typeof r.toObjects === "function") {
75
+ return {
76
+ columns: r.columns,
77
+ rows: r.toObjects(),
78
+ rowCount: r.rowCount,
79
+ rowsAffected: r.rowsAffected,
80
+ };
81
+ }
82
+ return r;
83
+ }
84
+ export async function closeAllDataPlaneClients() {
85
+ const pending = [...clientCache.values()];
86
+ clientCache.clear();
87
+ await Promise.allSettled(pending.map((p) => p.then((c) => c.close()).catch(() => { })));
88
+ }
@@ -0,0 +1,9 @@
1
+ export declare const API_KEY: string | undefined;
2
+ export declare const BASE_URL: string;
3
+ export declare const INSTANCE_HOST: string | undefined;
4
+ export declare const INSTANCE_USER: string | undefined;
5
+ export declare const INSTANCE_PASSWORD: string | undefined;
6
+ export declare const DEFAULT_GRAPH: string | undefined;
7
+ export declare const hasModeA: boolean;
8
+ export declare const hasModeB: boolean;
9
+ export declare const DEBUG: boolean;
@@ -0,0 +1,19 @@
1
+ // Centralized env-var reading + auth-mode flags.
2
+ // Both modes are independent; either or both can be configured.
3
+ // Ultipa Cloud — control plane + data-plane access against any account instance via `id`
4
+ export const API_KEY = process.env.ULTIPA_CLOUD_API_KEY;
5
+ export const BASE_URL = process.env.ULTIPA_CLOUD_BASE_URL ?? "https://dbaas.ultipa.com";
6
+ // Direct instance — data plane only, against the env-configured GQLDB instance
7
+ export const INSTANCE_HOST = process.env.ULTIPA_HOST;
8
+ export const INSTANCE_USER = process.env.ULTIPA_USERNAME;
9
+ export const INSTANCE_PASSWORD = process.env.ULTIPA_PASSWORD;
10
+ export const DEFAULT_GRAPH = process.env.ULTIPA_GRAPH;
11
+ export const hasModeA = !!API_KEY;
12
+ export const hasModeB = !!(INSTANCE_HOST && INSTANCE_USER && INSTANCE_PASSWORD);
13
+ // Debug logging — when ULTIPA_MCP_DEBUG is truthy ("1", "true", etc.), every
14
+ // tool call's name + latency + error (if any) goes to stderr. Useful for
15
+ // agent-trace debugging without slowing down normal operation.
16
+ export const DEBUG = (() => {
17
+ const v = process.env.ULTIPA_MCP_DEBUG?.toLowerCase().trim();
18
+ return v === "1" || v === "true" || v === "yes" || v === "on";
19
+ })();
@@ -0,0 +1,45 @@
1
+ import type { EdgeData, NodeData } from "@ultipa-graph/ultipa-driver";
2
+ export declare function parseCsv(text: string, opts?: {
3
+ delimiter?: string;
4
+ quote?: string;
5
+ }): string[][];
6
+ export declare function coerceCell(value: string, type?: string): any;
7
+ export declare function csvToCanonical(content: string, opts: {
8
+ label: string;
9
+ idColumn?: string;
10
+ fromColumn?: string;
11
+ toColumn?: string;
12
+ properties?: Array<{
13
+ property: string;
14
+ column: string;
15
+ type?: string;
16
+ }>;
17
+ delimiter?: string;
18
+ quote?: string;
19
+ }): {
20
+ nodes?: NodeData[];
21
+ edges?: EdgeData[];
22
+ rowCount: number;
23
+ };
24
+ export declare function jsonToCanonical(content: string, isJsonl: boolean): {
25
+ nodes?: NodeData[];
26
+ edges?: EdgeData[];
27
+ };
28
+ export declare function loadFilePath(path: string, csvOpts: {
29
+ label?: string;
30
+ idColumn?: string;
31
+ fromColumn?: string;
32
+ toColumn?: string;
33
+ properties?: Array<{
34
+ property: string;
35
+ column: string;
36
+ type?: string;
37
+ }>;
38
+ delimiter?: string;
39
+ quote?: string;
40
+ }): Promise<{
41
+ nodes?: NodeData[];
42
+ edges?: EdgeData[];
43
+ format: "csv" | "json" | "jsonl";
44
+ rowCount?: number;
45
+ }>;