@norbix.ai/mcp 0.0.0-stage → 0.2.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/README.md +283 -2
- package/dist/client.js +70 -0
- package/dist/http.js +62 -0
- package/dist/index.js +70 -0
- package/dist/manifest/dtos.api.json +4624 -0
- package/dist/manifest/dtos.hub.json +28142 -0
- package/dist/manifest/endpoints.json +7783 -0
- package/dist/manifest.js +94 -0
- package/dist/server.js +281 -0
- package/package.json +65 -4
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the committed endpoint manifest + DTO catalogs generated by
|
|
3
|
+
* scripts/generate-manifest.mjs from the norbix-js SDK source.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
const dir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'manifest');
|
|
9
|
+
function load(file) {
|
|
10
|
+
return JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
11
|
+
}
|
|
12
|
+
export const endpoints = load('endpoints.json');
|
|
13
|
+
export const dtos = {
|
|
14
|
+
hub: load('dtos.hub.json'),
|
|
15
|
+
api: load('dtos.api.json'),
|
|
16
|
+
};
|
|
17
|
+
/** Find an endpoint by SDK method name or request DTO name (case-insensitive). */
|
|
18
|
+
export function resolveEndpoint(name, surface) {
|
|
19
|
+
const n = name.toLowerCase();
|
|
20
|
+
return endpoints.filter((e) => (!surface || e.surface === surface) &&
|
|
21
|
+
(e.method.toLowerCase() === n || e.requestDto.toLowerCase() === n));
|
|
22
|
+
}
|
|
23
|
+
/** Collect a DTO's own + inherited fields (base-class chain). */
|
|
24
|
+
export function collectFields(surface, dtoName) {
|
|
25
|
+
const catalog = dtos[surface];
|
|
26
|
+
const fields = [];
|
|
27
|
+
const chain = [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
let cur = dtoName;
|
|
30
|
+
while (cur && !seen.has(cur)) {
|
|
31
|
+
seen.add(cur);
|
|
32
|
+
const shape = catalog[cur];
|
|
33
|
+
if (!shape || ('enum' in shape && shape.enum))
|
|
34
|
+
break;
|
|
35
|
+
chain.push(cur);
|
|
36
|
+
for (const f of shape.fields) {
|
|
37
|
+
if (!fields.some((x) => x.name === f.name))
|
|
38
|
+
fields.push(f);
|
|
39
|
+
}
|
|
40
|
+
cur = shape.base;
|
|
41
|
+
}
|
|
42
|
+
return { fields, chain };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The known subtypes of a polymorphic base type ("extends" in the contract),
|
|
46
|
+
* e.g. SchedulerTaskRequest → EmailCampaignSchedulerTaskRequest.
|
|
47
|
+
*/
|
|
48
|
+
export function subtypesOf(surface, base) {
|
|
49
|
+
return Object.entries(dtos[surface])
|
|
50
|
+
.filter(([, s]) => !('enum' in s && s.enum) && s.base === base)
|
|
51
|
+
.map(([name]) => name)
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
const fieldText = (x) => `${x.name}${x.optional ? '?' : ''}: ${x.type}`;
|
|
55
|
+
/**
|
|
56
|
+
* Shallow expansion of complex field types (one level), for describe output.
|
|
57
|
+
* A base type with known subtypes also lists each subtype's own fields under
|
|
58
|
+
* `subtypes` — send one of them, with its `type` discriminator — and the
|
|
59
|
+
* types of those subtype fields are expanded too (one more level), so a
|
|
60
|
+
* polymorphic body such as SaveSchedulerTaskRequest.task is fully visible.
|
|
61
|
+
*/
|
|
62
|
+
export function expandTypes(surface, fields) {
|
|
63
|
+
const catalog = dtos[surface];
|
|
64
|
+
const out = {};
|
|
65
|
+
const expand = (list) => {
|
|
66
|
+
const reached = [];
|
|
67
|
+
for (const f of list) {
|
|
68
|
+
const bare = f.type.replace(/\[\]$/, '').trim();
|
|
69
|
+
const shape = catalog[bare];
|
|
70
|
+
if (!shape || bare in out)
|
|
71
|
+
continue;
|
|
72
|
+
if ('enum' in shape && shape.enum) {
|
|
73
|
+
out[bare] = { enum: shape.values };
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const entry = {
|
|
77
|
+
fields: shape.fields.map(fieldText),
|
|
78
|
+
};
|
|
79
|
+
const subs = subtypesOf(surface, bare);
|
|
80
|
+
if (subs.length > 0) {
|
|
81
|
+
entry.subtypes = {};
|
|
82
|
+
for (const sub of subs) {
|
|
83
|
+
const own = catalog[sub].fields;
|
|
84
|
+
entry.subtypes[sub] = own.map(fieldText);
|
|
85
|
+
reached.push(...own);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
out[bare] = entry;
|
|
89
|
+
}
|
|
90
|
+
return reached;
|
|
91
|
+
};
|
|
92
|
+
expand(expand(fields));
|
|
93
|
+
return out;
|
|
94
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Norbix MCP server: curated tools for the most common Hub/API
|
|
3
|
+
* work, plus a generic search → describe → call trio that reaches every
|
|
4
|
+
* one of the Norbix endpoints in the manifest (src/manifest/endpoints.json).
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { invoke } from './client.js';
|
|
9
|
+
import { collectFields, endpoints, expandTypes, resolveEndpoint, } from './manifest.js';
|
|
10
|
+
const MAX_RESULT_CHARS = 60_000;
|
|
11
|
+
function json(data) {
|
|
12
|
+
let text = JSON.stringify(data, null, 2) ?? 'null';
|
|
13
|
+
if (text.length > MAX_RESULT_CHARS) {
|
|
14
|
+
text =
|
|
15
|
+
text.slice(0, MAX_RESULT_CHARS) +
|
|
16
|
+
`\n… truncated (${text.length} chars total). Narrow the request (pageSize, filter) to see more.`;
|
|
17
|
+
}
|
|
18
|
+
return { content: [{ type: 'text', text }] };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Turn a thrown error into an MCP tool error. A NorbixError (@norbix.ai/ts)
|
|
22
|
+
* carries status, code, the field errors and the raw body; `payload` is the
|
|
23
|
+
* older name of the body and is kept for clients that read it.
|
|
24
|
+
*/
|
|
25
|
+
export function err(e) {
|
|
26
|
+
const anyE = e;
|
|
27
|
+
const errors = Array.isArray(anyE?.fieldErrors) && anyE.fieldErrors.length > 0
|
|
28
|
+
? anyE.fieldErrors
|
|
29
|
+
: undefined;
|
|
30
|
+
return {
|
|
31
|
+
isError: true,
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify({
|
|
36
|
+
error: anyE?.message ?? String(e),
|
|
37
|
+
status: anyE?.status,
|
|
38
|
+
code: anyE?.code,
|
|
39
|
+
errors,
|
|
40
|
+
payload: anyE?.raw ?? anyE?.payload,
|
|
41
|
+
}, null, 2),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Human-readable request-field summary for a tool description. */
|
|
47
|
+
function fieldSummary(ep, max = 20) {
|
|
48
|
+
const { fields } = collectFields(ep.surface, ep.requestDto);
|
|
49
|
+
const shown = fields
|
|
50
|
+
.slice(0, max)
|
|
51
|
+
.map((f) => `${f.name}${f.optional ? '?' : ''}: ${f.type}`)
|
|
52
|
+
.join(', ');
|
|
53
|
+
const more = fields.length > max ? `, … +${fields.length - max} more` : '';
|
|
54
|
+
return shown ? ` Body fields: ${shown}${more}.` : '';
|
|
55
|
+
}
|
|
56
|
+
const bodySchema = {
|
|
57
|
+
body: z
|
|
58
|
+
.record(z.unknown())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Request body (JSON object matching the request DTO fields).'),
|
|
61
|
+
};
|
|
62
|
+
export const curated = [
|
|
63
|
+
{ name: 'norbix_echo', surface: 'hub', group: 'echo', method: 'echo', description: 'Get Norbix deployment info: release, Hub/API URLs and versions, regions, license.' },
|
|
64
|
+
{ name: 'norbix_get_projects', surface: 'hub', group: 'account', method: 'getProjects', description: 'List all projects in the account.' },
|
|
65
|
+
{ name: 'norbix_get_project', surface: 'hub', group: 'account', method: 'getProject', description: 'Get one project by id.' },
|
|
66
|
+
{ name: 'norbix_create_project', surface: 'hub', group: 'account', method: 'createProject', description: 'Create a new project.' },
|
|
67
|
+
{ name: 'norbix_get_project_environments', surface: 'hub', group: 'account', method: 'getProjectEnvironments', description: 'List the environments of a project.' },
|
|
68
|
+
{ name: 'norbix_get_integrations', surface: 'hub', group: 'code', method: 'getCodeIntegrations', description: 'List code integrations of the project.' },
|
|
69
|
+
{ name: 'norbix_get_integration', surface: 'hub', group: 'code', method: 'getCodeIntegration', description: 'Get one code integration.' },
|
|
70
|
+
{ name: 'norbix_save_integration', surface: 'hub', group: 'code', method: 'saveCodeIntegration', description: 'Create or update a code integration.' },
|
|
71
|
+
{ name: 'norbix_test_integration', surface: 'hub', group: 'code', method: 'testCodeIntegration', description: 'Test a code integration.' },
|
|
72
|
+
{ name: 'norbix_get_marketplace_integrations', surface: 'hub', group: 'code', method: 'getMarketplaceIntegrations', description: 'List marketplace integrations of the project.' },
|
|
73
|
+
{ name: 'norbix_save_marketplace_integration', surface: 'hub', group: 'code', method: 'saveMarketplaceIntegration', description: 'Create or update a marketplace integration.' },
|
|
74
|
+
{ name: 'norbix_get_database_schemas', surface: 'hub', group: 'database', method: 'getDatabaseSchemas', description: 'List database collection schemas of the project.' },
|
|
75
|
+
{ name: 'norbix_find_documents', surface: 'api', group: 'database', method: 'find', description: 'Query documents in a database collection (filter, sort, projection, paging).' },
|
|
76
|
+
{ name: 'norbix_get_roles', surface: 'hub', group: 'membership', method: 'getRoles', description: 'List membership roles of the project.' },
|
|
77
|
+
{ name: 'norbix_get_policies', surface: 'hub', group: 'membership', method: 'getPolicies', description: 'List membership policies of the project.' },
|
|
78
|
+
{ name: 'norbix_get_logs', surface: 'hub', group: 'logs', method: 'getLogs', description: 'Query project logs.' },
|
|
79
|
+
{ name: 'norbix_list_service_api_keys', surface: 'hub', group: 'membership', method: 'listServiceUserApiKeys', description: 'List API keys of a service user.' },
|
|
80
|
+
];
|
|
81
|
+
export function buildServer(handle) {
|
|
82
|
+
const server = new McpServer({ name: 'norbix', version: '0.1.0' });
|
|
83
|
+
// --- Curated tools -------------------------------------------------------
|
|
84
|
+
for (const t of curated) {
|
|
85
|
+
const ep = endpoints.find((e) => e.surface === t.surface && e.group === t.group && e.method === t.method);
|
|
86
|
+
const desc = t.description +
|
|
87
|
+
(ep ? ` [${ep.verb} ${ep.path}]` : '') +
|
|
88
|
+
(ep ? fieldSummary(ep) : '');
|
|
89
|
+
server.registerTool(t.name, { title: t.name, description: desc, inputSchema: bodySchema }, async ({ body }) => {
|
|
90
|
+
try {
|
|
91
|
+
return json(await invoke(handle.client, t.surface, t.group, t.method, body ?? {}));
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return err(e);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// --- Database shortcuts with typed input ---------------------------------
|
|
99
|
+
// The Api record endpoints take the document / update as a JSON *string*;
|
|
100
|
+
// a model naturally sends an object. These shortcuts accept either and
|
|
101
|
+
// send the string, so the most common data tasks work in one call instead
|
|
102
|
+
// of search → describe → call (and a failed first try).
|
|
103
|
+
const asJson = (v) => (typeof v === 'string' ? v : JSON.stringify(v));
|
|
104
|
+
const jsonValue = z.union([z.string(), z.record(z.unknown()), z.array(z.unknown())]);
|
|
105
|
+
const integrationId = z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Database integration id; omit for the project default database.');
|
|
109
|
+
server.registerTool('norbix_insert_document', {
|
|
110
|
+
title: 'norbix_insert_document',
|
|
111
|
+
description: 'Insert one document into a database collection. Returns the new id. [POST /{version}/database/collections/{collectionName}]',
|
|
112
|
+
inputSchema: {
|
|
113
|
+
collectionName: z.string().describe('Collection (schema) name, e.g. "products".'),
|
|
114
|
+
document: jsonValue.describe('The document: a JSON object (or a JSON string).'),
|
|
115
|
+
databaseIntegrationId: integrationId,
|
|
116
|
+
},
|
|
117
|
+
}, async ({ collectionName, document, databaseIntegrationId }) => {
|
|
118
|
+
try {
|
|
119
|
+
return json(await invoke(handle.client, 'api', 'database', 'insertOne', {
|
|
120
|
+
collectionName,
|
|
121
|
+
document: asJson(document),
|
|
122
|
+
...(databaseIntegrationId ? { databaseIntegrationId } : {}),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
return err(e);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
server.registerTool('norbix_update_document', {
|
|
130
|
+
title: 'norbix_update_document',
|
|
131
|
+
description: 'Update one document by id. Send only the fields to change as a plain document, e.g. { "price": 10 }; the gateway applies it with $set. Update operators ($set, $inc, $unset, …) are refused (CM-ERRORS-DATABASE-035). [PUT /{version}/database/collections/{collectionName}/{id}]',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
collectionName: z.string().describe('Collection (schema) name.'),
|
|
134
|
+
id: z.string().describe('Document id.'),
|
|
135
|
+
update: jsonValue.describe('The fields to change as a plain document, e.g. { "field": "value" } (object or JSON string). No $ operators.'),
|
|
136
|
+
databaseIntegrationId: integrationId,
|
|
137
|
+
},
|
|
138
|
+
}, async ({ collectionName, id, update, databaseIntegrationId }) => {
|
|
139
|
+
try {
|
|
140
|
+
return json(await invoke(handle.client, 'api', 'database', 'updateOne', {
|
|
141
|
+
collectionName,
|
|
142
|
+
id,
|
|
143
|
+
update: asJson(update),
|
|
144
|
+
...(databaseIntegrationId ? { databaseIntegrationId } : {}),
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
return err(e);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
server.registerTool('norbix_get_taxonomy_terms', {
|
|
152
|
+
title: 'norbix_get_taxonomy_terms',
|
|
153
|
+
description: 'List the terms of a taxonomy (categories, tags), with an optional MongoDB filter and paging. [GET /{version}/database/taxonomies/{taxonomyName}/terms]',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
taxonomyName: z.string().describe('Taxonomy name, e.g. "categories".'),
|
|
156
|
+
filter: jsonValue.optional().describe('MongoDB filter on the terms (object or JSON string).'),
|
|
157
|
+
pageSize: z.number().int().min(1).max(1000).optional(),
|
|
158
|
+
sortDescending: z.boolean().optional(),
|
|
159
|
+
databaseIntegrationId: integrationId,
|
|
160
|
+
},
|
|
161
|
+
}, async ({ taxonomyName, filter, pageSize, sortDescending, databaseIntegrationId }) => {
|
|
162
|
+
try {
|
|
163
|
+
return json(await invoke(handle.client, 'api', 'database', 'findTerms', {
|
|
164
|
+
taxonomyName,
|
|
165
|
+
sortDescending: sortDescending ?? false,
|
|
166
|
+
...(filter !== undefined ? { filter: asJson(filter) } : {}),
|
|
167
|
+
...(pageSize !== undefined ? { pageSize } : {}),
|
|
168
|
+
...(databaseIntegrationId ? { databaseIntegrationId } : {}),
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return err(e);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// --- Project scope --------------------------------------------------------
|
|
176
|
+
server.registerTool('norbix_set_project', {
|
|
177
|
+
title: 'Set active project',
|
|
178
|
+
description: 'Set (or switch) the project that later calls target. Sent as the project header on every request. Use norbix_get_projects to find project ids.',
|
|
179
|
+
inputSchema: {
|
|
180
|
+
projectId: z.string().describe('The project id to work in.'),
|
|
181
|
+
accountId: z.string().optional(),
|
|
182
|
+
},
|
|
183
|
+
}, async ({ projectId, accountId }) => {
|
|
184
|
+
handle.client.setScope({ projectId, ...(accountId ? { accountId } : {}) });
|
|
185
|
+
return json({ ok: true, projectId });
|
|
186
|
+
});
|
|
187
|
+
// --- Generic trio: search → describe → call ------------------------------
|
|
188
|
+
server.registerTool('norbix_search_endpoints', {
|
|
189
|
+
title: 'Search Norbix endpoints',
|
|
190
|
+
description: `Search all Norbix Hub/API endpoints (${endpoints.length}) by keyword.` +
|
|
191
|
+
' Returns method names to use with norbix_describe_endpoint and norbix_call_endpoint. Groups: ' +
|
|
192
|
+
[...new Set(endpoints.map((e) => e.group))].sort().join(', ') +
|
|
193
|
+
'.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
query: z.string().describe('Keyword(s), e.g. "push template", "invite user", "webhook".'),
|
|
196
|
+
surface: z.enum(['hub', 'api']).optional().describe('hub = admin/dashboard plane, api = data plane.'),
|
|
197
|
+
group: z.string().optional().describe('Endpoint group, e.g. account, code, database, membership, notifications.'),
|
|
198
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
199
|
+
},
|
|
200
|
+
}, async ({ query, surface, group, limit }) => {
|
|
201
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
202
|
+
const scored = endpoints
|
|
203
|
+
.filter((e) => (!surface || e.surface === surface) && (!group || e.group === group))
|
|
204
|
+
.map((e) => {
|
|
205
|
+
const hay = `${e.method} ${e.requestDto} ${e.path} ${e.group}`.toLowerCase();
|
|
206
|
+
const score = words.reduce((s, w) => s + (hay.includes(w) ? 1 : 0), 0);
|
|
207
|
+
return { e, score };
|
|
208
|
+
})
|
|
209
|
+
.filter((x) => x.score === words.length || x.score > 0)
|
|
210
|
+
.sort((a, b) => b.score - a.score)
|
|
211
|
+
.slice(0, limit ?? 25)
|
|
212
|
+
.map(({ e }) => ({
|
|
213
|
+
surface: e.surface,
|
|
214
|
+
group: e.group,
|
|
215
|
+
method: e.method,
|
|
216
|
+
requestDto: e.requestDto,
|
|
217
|
+
route: `${e.verb} ${e.path}`,
|
|
218
|
+
}));
|
|
219
|
+
return json(scored);
|
|
220
|
+
});
|
|
221
|
+
server.registerTool('norbix_describe_endpoint', {
|
|
222
|
+
title: 'Describe a Norbix endpoint',
|
|
223
|
+
description: 'Show the full request/response shape of an endpoint (all body fields, inherited ones included, plus nested types and enums). A polymorphic type lists its known subtypes under types.<Type>.subtypes — send one of them with its type discriminator (e.g. task: { type: "EmailCampaign", campaign: {...} }). Use before norbix_call_endpoint.',
|
|
224
|
+
inputSchema: {
|
|
225
|
+
name: z.string().describe('SDK method name (e.g. createProject) or request DTO name (e.g. CreateProjectRequest).'),
|
|
226
|
+
surface: z.enum(['hub', 'api']).optional(),
|
|
227
|
+
},
|
|
228
|
+
}, async ({ name, surface }) => {
|
|
229
|
+
const matches = resolveEndpoint(name, surface);
|
|
230
|
+
if (matches.length === 0) {
|
|
231
|
+
return err(new Error(`No endpoint named "${name}". Use norbix_search_endpoints first.`));
|
|
232
|
+
}
|
|
233
|
+
return json(matches.map((ep) => {
|
|
234
|
+
const req = collectFields(ep.surface, ep.requestDto);
|
|
235
|
+
const res = collectFields(ep.surface, ep.responseDto);
|
|
236
|
+
return {
|
|
237
|
+
surface: ep.surface,
|
|
238
|
+
group: ep.group,
|
|
239
|
+
method: ep.method,
|
|
240
|
+
route: `${ep.verb} ${ep.path}`,
|
|
241
|
+
scope: ep.scope,
|
|
242
|
+
request: {
|
|
243
|
+
dto: ep.requestDto,
|
|
244
|
+
fields: req.fields.map((f) => `${f.name}${f.optional ? '?' : ''}: ${f.type}`),
|
|
245
|
+
},
|
|
246
|
+
response: {
|
|
247
|
+
dto: ep.responseDto,
|
|
248
|
+
fields: res.fields.map((f) => `${f.name}${f.optional ? '?' : ''}: ${f.type}`),
|
|
249
|
+
},
|
|
250
|
+
types: expandTypes(ep.surface, [...req.fields, ...res.fields]),
|
|
251
|
+
};
|
|
252
|
+
}));
|
|
253
|
+
});
|
|
254
|
+
server.registerTool('norbix_call_endpoint', {
|
|
255
|
+
title: 'Call any Norbix endpoint',
|
|
256
|
+
description: 'Execute any Norbix Hub/API endpoint by SDK method name or request DTO name. The API key’s permissions decide what is allowed. Use norbix_search_endpoints + norbix_describe_endpoint to find the right name and body shape.',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
name: z.string().describe('SDK method name (e.g. sendInviteToTeamMember) or request DTO name.'),
|
|
259
|
+
surface: z.enum(['hub', 'api']).optional().describe('Required only when the name exists on both surfaces.'),
|
|
260
|
+
body: z.record(z.unknown()).optional().describe('Request body matching the request DTO fields.'),
|
|
261
|
+
},
|
|
262
|
+
}, async ({ name, surface, body }) => {
|
|
263
|
+
const matches = resolveEndpoint(name, surface);
|
|
264
|
+
if (matches.length === 0) {
|
|
265
|
+
return err(new Error(`No endpoint named "${name}". Use norbix_search_endpoints first.`));
|
|
266
|
+
}
|
|
267
|
+
if (matches.length > 1) {
|
|
268
|
+
return err(new Error(`Ambiguous name "${name}" — matches: ` +
|
|
269
|
+
matches.map((m) => `${m.surface}.${m.group}.${m.method}`).join(', ') +
|
|
270
|
+
'. Pass surface to disambiguate.'));
|
|
271
|
+
}
|
|
272
|
+
const ep = matches[0];
|
|
273
|
+
try {
|
|
274
|
+
return json(await invoke(handle.client, ep.surface, ep.group, ep.method, body ?? {}));
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
return err(e);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return server;
|
|
281
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norbix.ai/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Norbix MCP server — manage projects, integrations, databases, membership, notifications and every other Norbix Hub/API endpoint from Claude, Cursor, VS Code, Windsurf or any MCP client.",
|
|
5
|
+
"author": "UAB Isidos",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://norbix.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/norbix-code/mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/norbix-code/mcp/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"norbix",
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"baas",
|
|
20
|
+
"ai",
|
|
21
|
+
"claude",
|
|
22
|
+
"cursor"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"norbix-mcp": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"generate-manifest": "node scripts/generate-manifest.mjs",
|
|
38
|
+
"build": "tsc && node -e \"const fs=require('fs');fs.rmSync('dist/manifest',{recursive:true,force:true});fs.cpSync('src/manifest','dist/manifest',{recursive:true});fs.chmodSync('dist/index.js',0o755)\"",
|
|
39
|
+
"prepublishOnly": "npm run build",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"start:http": "node dist/index.js --http",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"release:dry": "semantic-release --dry-run"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
48
|
+
"@norbix.ai/ts": "^4.11.0",
|
|
49
|
+
"express": "^4.21.2",
|
|
50
|
+
"zod": "^3.24.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
54
|
+
"@semantic-release/github": "^12.0.10",
|
|
55
|
+
"@semantic-release/npm": "^13.2.0",
|
|
56
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
57
|
+
"@types/express": "^4.17.21",
|
|
58
|
+
"@types/node": "^22.0.0",
|
|
59
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
60
|
+
"semantic-release": "^25.0.9",
|
|
61
|
+
"typescript": "^5.7.0",
|
|
62
|
+
"vitest": "^5.0.3"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
}
|
|
67
|
+
}
|