@norbix.ai/mcp 0.0.0-stage → 0.2.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 CHANGED
@@ -1,3 +1,284 @@
1
- # Temporary Holding Version
1
+ # Norbix MCP Server (`@norbix.ai/mcp`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Work with your Norbix backend from any AI tool that speaks MCP — Claude
4
+ Desktop, Claude Code, Cursor, VS Code, Windsurf, and others. Create projects,
5
+ manage integrations, query databases, manage users, read logs — everything
6
+ your API key is allowed to do.
7
+
8
+ Built on the official [`@norbix.ai/ts`](../norbix-js) SDK. Covers **every**
9
+ Norbix Hub and API endpoint (640+) through a small tool set.
10
+
11
+ ## What you need
12
+
13
+ 1. **A Norbix API key (service user).**
14
+ In the Norbix dashboard, open your project → *Membership* → *Service
15
+ users*. Create a service user, give it a role (for example *Project
16
+ Administrator* for full project access), and issue an API key.
17
+ The key's permissions decide what the MCP server can do.
18
+ 2. **Your Norbix Hub endpoint** (for example `https://hub.norbix.ai`, or
19
+ your own host on a self-hosted install). The server calls `/echo` on
20
+ startup to discover the rest (API URL, versions, regions).
21
+
22
+ ## Configuration (environment variables)
23
+
24
+ | Variable | Required | Meaning |
25
+ | --- | --- | --- |
26
+ | `NORBIX_API_KEY` | yes | Service-user API key (sent as `Authorization: Bearer`). |
27
+ | `NORBIX_HUB_URL` | yes | Norbix Hub endpoint. |
28
+ | `NORBIX_PROJECT_ID` | no | Default project to work in. You can also switch at any time with the `norbix_set_project` tool. |
29
+ | `NORBIX_API_URL` | no | API endpoint. Discovered from `/echo` when not set. |
30
+ | `NORBIX_ENV` | no | Project environment (e.g. `DEV`). Default is `PROD`. |
31
+ | `NORBIX_REGION` | no | Region code (e.g. `nb-eu-germany`). |
32
+
33
+ These are the same variables the `@norbix.ai/ts` SDK uses.
34
+
35
+ ## Install
36
+
37
+ The server is published on npm as
38
+ [`@norbix.ai/mcp`](https://www.npmjs.com/package/@norbix.ai/mcp). Nothing to
39
+ install by hand — MCP clients start it with `npx` (Node.js 18 or newer):
40
+
41
+ ```bash
42
+ npx -y @norbix.ai/mcp --help # usage and env variables
43
+ npx -y @norbix.ai/mcp --version
44
+ ```
45
+
46
+ Or install it globally and use the `norbix-mcp` command:
47
+
48
+ ```bash
49
+ npm install -g @norbix.ai/mcp
50
+ norbix-mcp --help
51
+ ```
52
+
53
+ ### Build from source
54
+
55
+ ```bash
56
+ git clone git@github.com:norbix-code/mcp.git && cd mcp
57
+ npm ci
58
+ npm run build
59
+ node dist/index.js --help
60
+ ```
61
+
62
+ ## IDE / client setup
63
+
64
+ ### Claude Desktop
65
+
66
+ `claude_desktop_config.json` → `mcpServers`:
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "norbix": {
72
+ "command": "npx",
73
+ "args": ["-y", "@norbix.ai/mcp"],
74
+ "env": {
75
+ "NORBIX_API_KEY": "your-api-key",
76
+ "NORBIX_HUB_URL": "https://hub.norbix.ai",
77
+ "NORBIX_PROJECT_ID": "your-project-id"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Claude Code
85
+
86
+ ```bash
87
+ claude mcp add norbix \
88
+ -e NORBIX_API_KEY=your-api-key \
89
+ -e NORBIX_HUB_URL=https://hub.norbix.ai \
90
+ -e NORBIX_PROJECT_ID=your-project-id \
91
+ -- npx -y @norbix.ai/mcp
92
+ ```
93
+
94
+ ### Cursor
95
+
96
+ `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "norbix": {
102
+ "command": "npx",
103
+ "args": ["-y", "@norbix.ai/mcp"],
104
+ "env": {
105
+ "NORBIX_API_KEY": "your-api-key",
106
+ "NORBIX_HUB_URL": "https://hub.norbix.ai",
107
+ "NORBIX_PROJECT_ID": "your-project-id"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### VS Code (GitHub Copilot agent mode)
115
+
116
+ `.vscode/mcp.json`:
117
+
118
+ ```json
119
+ {
120
+ "servers": {
121
+ "norbix": {
122
+ "type": "stdio",
123
+ "command": "npx",
124
+ "args": ["-y", "@norbix.ai/mcp"],
125
+ "env": {
126
+ "NORBIX_API_KEY": "your-api-key",
127
+ "NORBIX_HUB_URL": "https://hub.norbix.ai"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Windsurf
135
+
136
+ `~/.codeium/windsurf/mcp_config.json` — same JSON shape as Cursor.
137
+
138
+ > Until the package is published, replace `"command": "npx", "args": [...]`
139
+ > with `"command": "node", "args": ["/path/to/sdks/norbix-mcp/dist/index.js"]`.
140
+
141
+ ## Remote mode (Streamable HTTP)
142
+
143
+ For a hosted, shared MCP endpoint:
144
+
145
+ ```bash
146
+ NORBIX_HUB_URL=https://hub.norbix.ai norbix-mcp --http --port 3010
147
+ ```
148
+
149
+ - Endpoint: `POST http://host:3010/mcp` (stateless Streamable HTTP).
150
+ - Each request may carry its own key: `Authorization: Bearer <norbix-api-key>`.
151
+ This overrides `NORBIX_API_KEY`, so one instance can serve many users.
152
+ - Health check: `GET /healthz`.
153
+
154
+ ## Tools
155
+
156
+ **Discovery + generic call — reaches every endpoint:**
157
+
158
+ - `norbix_search_endpoints` — find endpoints by keyword (`"push template"`, `"invite user"`, …)
159
+ - `norbix_describe_endpoint` — full request/response field shapes, nested types, enums, and the known subtypes of a polymorphic field
160
+ - `norbix_call_endpoint` — execute any endpoint by name with a JSON body
161
+ - `norbix_set_project` — set/switch the active project
162
+
163
+ **Curated shortcuts:** `norbix_echo`, `norbix_get_projects`, `norbix_get_project`,
164
+ `norbix_create_project`, `norbix_get_project_environments`,
165
+ `norbix_get_integrations`, `norbix_get_integration`, `norbix_save_integration`,
166
+ `norbix_test_integration`, `norbix_get_marketplace_integrations`,
167
+ `norbix_save_marketplace_integration`, `norbix_get_database_schemas`,
168
+ `norbix_find_documents`, `norbix_insert_document`, `norbix_update_document`,
169
+ `norbix_get_taxonomy_terms`, `norbix_get_roles`, `norbix_get_policies`,
170
+ `norbix_get_logs`, `norbix_list_service_api_keys`.
171
+
172
+ Typical flow the model follows for anything not curated:
173
+ `norbix_search_endpoints("archive email template")` →
174
+ `norbix_describe_endpoint("archiveEmailTemplate")` →
175
+ `norbix_call_endpoint({ name: "archiveEmailTemplate", body: { id: "…" } })`.
176
+
177
+ A polymorphic body lists its subtypes. Example — a scheduled task (only
178
+ `EmailCampaign` runs today; `cron` is 5 fields in UTC; `initiatorUserId` is
179
+ you or a project service user): `norbix_describe_endpoint("saveSchedulerTask")`
180
+ shows `types.SchedulerTaskRequest.subtypes.EmailCampaignSchedulerTaskRequest`
181
+ (`type`, `campaign?`, `databaseIntegrationId?`), then
182
+
183
+ ```json
184
+ { "name": "saveSchedulerTask", "body": {
185
+ "name": "Weekly digest", "cron": "0 9 * * 1", "initiatorUserId": "usr_…",
186
+ "isEnabled": true, "stopOnError": false,
187
+ "task": { "type": "EmailCampaign",
188
+ "campaign": { "source": "AllUsers", "templateId": "tmpl_…" } } } }
189
+ ```
190
+
191
+ The scheduler module switch (`enableScheduler` / `disableScheduler`) is `PUT`.
192
+
193
+ ## Database tools
194
+
195
+ Five shortcuts cover the everyday database work. Everything else in the
196
+ Database module (95 endpoints: schemas and drafts, records, taxonomies and
197
+ terms, triggers, aggregates, database integrations) is reached with the
198
+ generic trio.
199
+
200
+ | Tool | SDK method | Route | Input |
201
+ | --- | --- | --- | --- |
202
+ | `norbix_get_database_schemas` | `hub.database.getDatabaseSchemas` | `GET /{version}/database/schemas` | `body` (paging) |
203
+ | `norbix_find_documents` | `api.database.find` | `GET /{version}/database/collections/{collectionName}` | `body`: `collectionName`, `filter` (JSON string), `sortBy`, `pageSize`, … |
204
+ | `norbix_insert_document` | `api.database.insertOne` | `POST /{version}/database/collections/{collectionName}` | `collectionName`, `document`, `databaseIntegrationId?` |
205
+ | `norbix_update_document` | `api.database.updateOne` | `PUT /{version}/database/collections/{collectionName}/{id}` | `collectionName`, `id`, `update`, `databaseIntegrationId?` |
206
+ | `norbix_get_taxonomy_terms` | `api.database.findTerms` | `GET /{version}/database/taxonomies/{taxonomyName}/terms` | `taxonomyName`, `filter?`, `pageSize?`, `sortDescending?`, `databaseIntegrationId?` |
207
+
208
+ The Api record endpoints expect the document, the update and the filter as a
209
+ JSON **string**. The three newer shortcuts accept an object or a string and
210
+ send the string, so this works in one call:
211
+
212
+ ```json
213
+ { "name": "norbix_insert_document", "arguments": {
214
+ "collectionName": "products", "document": { "title": "Lamp", "price": 12 } } }
215
+ ```
216
+
217
+ ```json
218
+ { "name": "norbix_update_document", "arguments": {
219
+ "collectionName": "products", "id": "…", "update": { "price": 10 } } }
220
+ ```
221
+
222
+ `update` is the plain field document — the gateway applies it with `$set`.
223
+ A `$` operator in it (`$set`, `$inc`, …) is refused with
224
+ `CM-ERRORS-DATABASE-035`. An update-many or delete-many call with an empty
225
+ filter `{}` is refused (`CM-ERRORS-DATABASE-037`) unless the body also has
226
+ `allRecords: true`.
227
+
228
+ For anything else, search the `database` group:
229
+
230
+ - `norbix_search_endpoints({ query: "taxonomy term", group: "database" })`
231
+ - `norbix_describe_endpoint({ name: "saveSchemaTrigger" })`
232
+ - `norbix_call_endpoint({ name: "getDatabaseSchemas", surface: "api" })` —
233
+ a few names exist on both the Hub (admin plane) and the Api (data plane);
234
+ pass `surface` for those.
235
+
236
+ Which tools work depends on the API key's role: the Hub tools need a
237
+ project role with database permissions, the Api tools need permissions on the
238
+ collection. An error answer carries `status`, `code`, `errors` (per field)
239
+ and `payload` (the gateway's body).
240
+
241
+ ## Keeping the endpoint manifest in sync
242
+
243
+ The tool catalog is generated from the `norbix-js` SDK source and committed
244
+ under `src/manifest/`. After endpoints change (backend regenerates DTOs →
245
+ `norbix-js` runs `npm run generate-endpoints`):
246
+
247
+ ```bash
248
+ npm run generate-manifest && npm run build && npm test
249
+ ```
250
+
251
+ `generate-manifest` reads `../norbix-js`; from a worktree, point it at a
252
+ checkout of the released SDK: `NORBIX_JS_DIR=/path/to/norbix-js npm run generate-manifest`.
253
+ Keep `@norbix.ai/ts` in `package.json` at the version the manifest was made
254
+ from (a test checks every curated tool is in the manifest).
255
+
256
+ ## Tests
257
+
258
+ ```bash
259
+ npm test
260
+ ```
261
+
262
+ Vitest, no network: the server runs over the MCP SDK's in-memory transport
263
+ with a fake Norbix client (or the real `@norbix.ai/ts` client with a fake
264
+ `fetch`).
265
+
266
+ ## Versioning and releases
267
+
268
+ Every merge to `main` is released to npm by
269
+ [semantic-release](https://semantic-release.gitbook.io/) from the
270
+ conventional-commit messages: `feat:` → minor, `fix:` / `perf:` /
271
+ `refactor:` → patch; `ci:`, `build:`, `test:`, `chore:` do not release.
272
+ **The major version is frozen until the public launch**: a breaking change
273
+ is written as `feat(...)` with a `Breaking: <what>` line in the body (a
274
+ `!:` title or a `BREAKING CHANGE:` footer fails the PR check). The git tag
275
+ is the source of truth for the version; release notes are on the GitHub
276
+ Releases page.
277
+
278
+ ## Security notes
279
+
280
+ - The server can do exactly what the API key can do — nothing more. Scope the
281
+ service user's role to what you need (e.g. read-only roles for analysis).
282
+ - Never commit API keys. Use env vars in the MCP client config.
283
+ - In `--http` mode, put the server behind TLS and network controls; the
284
+ `Authorization` header carries the Norbix API key.
package/dist/client.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Norbix client factory. Configuration comes from NORBIX_* env vars
3
+ * (NORBIX_API_KEY, NORBIX_HUB_URL, NORBIX_PROJECT_ID, ...) — the same
4
+ * variables the @norbix.ai/ts SDK reads. On startup we call the Hub /echo
5
+ * endpoint to discover the rest of the deployment (API URL, regions,
6
+ * versions) so users only need an API key and the Hub endpoint.
7
+ */
8
+ import { Norbix } from '@norbix.ai/ts';
9
+ export function requireConfig() {
10
+ const missing = ['NORBIX_API_KEY', 'NORBIX_HUB_URL'].filter((k) => !process.env[k]);
11
+ if (missing.length > 0) {
12
+ throw new Error(`Missing required environment variable(s): ${missing.join(', ')}. ` +
13
+ 'Create a service-user API key in the Norbix dashboard ' +
14
+ '(Membership → Service users) and set NORBIX_HUB_URL to your Hub endpoint.');
15
+ }
16
+ }
17
+ /**
18
+ * The SDK constructor requires `projectId`, but account-level work (e.g.
19
+ * creating the very first project) has none. The transport only sends the
20
+ * project headers when `cfg.projectId` is set, so when no NORBIX_PROJECT_ID
21
+ * is configured we construct with a placeholder and clear it. Use the
22
+ * `norbix_set_project` tool (client.setScope) to pick a project later.
23
+ */
24
+ function newNorbix(config = {}) {
25
+ const hasProject = Boolean(process.env.NORBIX_PROJECT_ID);
26
+ const client = new Norbix({
27
+ ...config,
28
+ ...(hasProject ? {} : { projectId: 'unscoped' }),
29
+ });
30
+ if (!hasProject) {
31
+ client.cfg.projectId =
32
+ undefined;
33
+ }
34
+ return client;
35
+ }
36
+ export async function createClient(overrides = {}) {
37
+ const base = newNorbix(overrides.apiKey ? { apiKey: overrides.apiKey } : {});
38
+ let echo;
39
+ try {
40
+ echo = (await base.hub.echo.echo({}));
41
+ }
42
+ catch {
43
+ // /echo is best-effort discovery; tools still work against the Hub.
44
+ }
45
+ // If the API base URL was not configured explicitly, adopt the one the
46
+ // deployment reports. A user-supplied NORBIX_API_URL always wins.
47
+ const discoveredApiUrl = !process.env.NORBIX_API_URL && typeof echo?.apiUrl === 'string'
48
+ ? echo.apiUrl
49
+ : undefined;
50
+ const client = discoveredApiUrl
51
+ ? newNorbix({
52
+ ...(overrides.apiKey ? { apiKey: overrides.apiKey } : {}),
53
+ apiUrl: discoveredApiUrl,
54
+ })
55
+ : base;
56
+ return { client, echo };
57
+ }
58
+ /**
59
+ * Dispatch a call to a generated SDK endpoint method:
60
+ * client.hub.<group>.<method>(body) / client.api.<group>.<method>(body).
61
+ */
62
+ export async function invoke(client, surface, group, method, body) {
63
+ const ns = client[surface];
64
+ const mod = ns?.[group];
65
+ const fn = mod?.[method];
66
+ if (typeof fn !== 'function') {
67
+ throw new Error(`Unknown endpoint: ${surface}.${group}.${method}`);
68
+ }
69
+ return fn(body ?? {});
70
+ }
package/dist/http.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Streamable HTTP transport (remote MCP). Stateless: every POST /mcp gets a
3
+ * fresh server + transport, so the endpoint scales horizontally and needs no
4
+ * session store.
5
+ *
6
+ * Auth: `Authorization: Bearer <norbix-api-key>` on each request wins over
7
+ * the NORBIX_API_KEY env var. This lets one hosted instance serve many users,
8
+ * each with their own service-user key.
9
+ */
10
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
11
+ import express from 'express';
12
+ import { createClient } from './client.js';
13
+ import { buildServer } from './server.js';
14
+ export async function startHttp(port) {
15
+ const app = express();
16
+ app.use(express.json({ limit: '4mb' }));
17
+ app.get('/healthz', (_req, res) => {
18
+ res.json({ ok: true, name: 'norbix-mcp' });
19
+ });
20
+ app.post('/mcp', async (req, res) => {
21
+ try {
22
+ const auth = req.headers.authorization;
23
+ const apiKey = auth?.replace(/^Bearer\s+/i, '').trim() || undefined;
24
+ const handle = await createClient(apiKey ? { apiKey } : {});
25
+ const server = buildServer(handle);
26
+ const transport = new StreamableHTTPServerTransport({
27
+ sessionIdGenerator: undefined,
28
+ });
29
+ res.on('close', () => {
30
+ void transport.close();
31
+ void server.close();
32
+ });
33
+ await server.connect(transport);
34
+ await transport.handleRequest(req, res, req.body);
35
+ }
36
+ catch (e) {
37
+ if (!res.headersSent) {
38
+ res.status(500).json({
39
+ jsonrpc: '2.0',
40
+ error: { code: -32603, message: e.message },
41
+ id: null,
42
+ });
43
+ }
44
+ }
45
+ });
46
+ // Stateless mode: no server-initiated streams, no sessions to delete.
47
+ const reject = (_req, res) => {
48
+ res.status(405).json({
49
+ jsonrpc: '2.0',
50
+ error: { code: -32000, message: 'Method not allowed (stateless server).' },
51
+ id: null,
52
+ });
53
+ };
54
+ app.get('/mcp', reject);
55
+ app.delete('/mcp', reject);
56
+ await new Promise((resolve) => {
57
+ app.listen(port, () => {
58
+ console.error(`norbix-mcp listening on http://localhost:${port}/mcp`);
59
+ resolve();
60
+ });
61
+ });
62
+ }
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Norbix MCP server.
4
+ *
5
+ * norbix-mcp stdio transport (Claude, Cursor, VS Code, …)
6
+ * norbix-mcp --http Streamable HTTP on port 3010 (remote MCP)
7
+ * norbix-mcp --http --port N Streamable HTTP on port N
8
+ *
9
+ * Required env: NORBIX_API_KEY, NORBIX_HUB_URL.
10
+ * Optional env: NORBIX_PROJECT_ID, NORBIX_ACCOUNT_ID, NORBIX_API_URL,
11
+ * NORBIX_ENV, NORBIX_REGION (same vars as the @norbix.ai/ts SDK).
12
+ */
13
+ import { createRequire } from 'node:module';
14
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
15
+ import { createClient, requireConfig } from './client.js';
16
+ import { startHttp } from './http.js';
17
+ import { buildServer } from './server.js';
18
+ const HELP = `Norbix MCP server
19
+
20
+ Usage:
21
+ norbix-mcp stdio transport (Claude, Cursor, VS Code, ...)
22
+ norbix-mcp --http [--port N] Streamable HTTP (default port 3010)
23
+ norbix-mcp --help show this text
24
+ norbix-mcp --version show the version
25
+
26
+ Required env: NORBIX_API_KEY, NORBIX_HUB_URL
27
+ Optional env: NORBIX_PROJECT_ID, NORBIX_ACCOUNT_ID, NORBIX_API_URL,
28
+ NORBIX_ENV, NORBIX_REGION
29
+
30
+ Docs: https://github.com/norbix-code/mcp#readme`;
31
+ function packageVersion() {
32
+ // dist/index.js -> ../package.json (npm always ships package.json).
33
+ const pkg = createRequire(import.meta.url)('../package.json');
34
+ return pkg.version ?? 'unknown';
35
+ }
36
+ async function main() {
37
+ const args = process.argv.slice(2);
38
+ if (args.includes('--help') || args.includes('-h')) {
39
+ console.log(HELP);
40
+ return;
41
+ }
42
+ if (args.includes('--version') || args.includes('-v')) {
43
+ console.log(packageVersion());
44
+ return;
45
+ }
46
+ const http = args.includes('--http');
47
+ const portIdx = args.indexOf('--port');
48
+ const port = portIdx >= 0 ? Number(args[portIdx + 1]) : 3010;
49
+ if (http) {
50
+ // In HTTP mode the key may arrive per request; env config is a fallback.
51
+ await startHttp(Number.isFinite(port) ? port : 3010);
52
+ return;
53
+ }
54
+ requireConfig();
55
+ const handle = await createClient();
56
+ if (handle.echo) {
57
+ console.error(`norbix-mcp: connected to ${process.env.NORBIX_HUB_URL} ` +
58
+ `(hub ${String(handle.echo.hubVersion ?? '?')}, api ${String(handle.echo.apiVersion ?? '?')})`);
59
+ }
60
+ else {
61
+ console.error('norbix-mcp: warning — /echo discovery failed; check NORBIX_HUB_URL and network. Tools will still try to call the Hub.');
62
+ }
63
+ const server = buildServer(handle);
64
+ await server.connect(new StdioServerTransport());
65
+ console.error('norbix-mcp: ready (stdio)');
66
+ }
67
+ main().catch((e) => {
68
+ console.error(`norbix-mcp: fatal — ${e.message}`);
69
+ process.exit(1);
70
+ });