mc8yp 2.3.1 → 2.3.2
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/dist/cli.mjs +69 -71
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1545,7 +1545,7 @@ const consola = createConsola();
|
|
|
1545
1545
|
//#endregion
|
|
1546
1546
|
//#region package.json
|
|
1547
1547
|
var name = "mc8yp";
|
|
1548
|
-
var version = "2.3.
|
|
1548
|
+
var version = "2.3.2";
|
|
1549
1549
|
var description$1 = "Cumulocity IoT MCP Server - Model Context Protocol integration for IoT device management";
|
|
1550
1550
|
//#endregion
|
|
1551
1551
|
//#region \0virtual:core-openapi
|
|
@@ -165490,6 +165490,18 @@ var Client = class Client {
|
|
|
165490
165490
|
* Shared client authentication utility.
|
|
165491
165491
|
* Centralizes the logic for getting an authenticated C8y client.
|
|
165492
165492
|
*/
|
|
165493
|
+
/**
|
|
165494
|
+
* Map a \@c8y/client error (which is thrown as `{ res, data }` for HTTP
|
|
165495
|
+
* statuses >= 400) to a short human-readable string.
|
|
165496
|
+
* @param err - Error thrown by an \@c8y/client service call
|
|
165497
|
+
*/
|
|
165498
|
+
function c8yErrorSummary(err) {
|
|
165499
|
+
if (err && typeof err === "object" && "res" in err) {
|
|
165500
|
+
const r = err.res;
|
|
165501
|
+
if (r && typeof r.status === "number") return `${r.status} ${r.statusText ?? ""}`.trim();
|
|
165502
|
+
}
|
|
165503
|
+
return err instanceof Error ? err.message : String(err);
|
|
165504
|
+
}
|
|
165493
165505
|
async function resolveC8yAuth() {
|
|
165494
165506
|
const custom = c8yMcpServer.ctx.custom;
|
|
165495
165507
|
const auth = custom?.auth;
|
|
@@ -165898,7 +165910,9 @@ function normalizeCode(functionCode) {
|
|
|
165898
165910
|
}
|
|
165899
165911
|
function buildQueryScript(sourceCode) {
|
|
165900
165912
|
const functionExpression = normalizeCode(sourceCode);
|
|
165901
|
-
const
|
|
165913
|
+
const custom = c8yMcpServer.ctx.custom;
|
|
165914
|
+
const resolved = custom?.specs;
|
|
165915
|
+
if (!resolved) throw new Error(custom?.env === "cli" ? "No active tenant set. Call set-active-tenant first." : "No tenant specs available for this MCP connection. This usually means the request reached the server without a resolvable tenant context (e.g. a platform probe). Reconnect with valid tenant auth.");
|
|
165902
165916
|
return [
|
|
165903
165917
|
`const coreSpec = ${JSON.stringify(resolved.core)};`,
|
|
165904
165918
|
`const serviceSpecs = ${JSON.stringify(resolved.specs)};`,
|
|
@@ -166153,57 +166167,28 @@ function createTools(env) {
|
|
|
166153
166167
|
* description lambda in CLI mode) can read the last known result without
|
|
166154
166168
|
* awaiting.
|
|
166155
166169
|
*/
|
|
166156
|
-
const
|
|
166157
|
-
const refreshTimers = /* @__PURE__ */ new Map();
|
|
166158
|
-
const DISCOVERY_REFRESH_INTERVAL_MS = 1800 * 1e3;
|
|
166159
|
-
function normalizeTenantUrl(url) {
|
|
166160
|
-
try {
|
|
166161
|
-
return new URL(url).toString().replace(/\/$/, "");
|
|
166162
|
-
} catch {
|
|
166163
|
-
return url.replace(/\/$/, "");
|
|
166164
|
-
}
|
|
166165
|
-
}
|
|
166166
|
-
function clearRefreshTimer(key) {
|
|
166167
|
-
const t = refreshTimers.get(key);
|
|
166168
|
-
if (t) {
|
|
166169
|
-
clearTimeout(t);
|
|
166170
|
-
refreshTimers.delete(key);
|
|
166171
|
-
}
|
|
166172
|
-
}
|
|
166173
|
-
function scheduleRefresh(key, authHeaders) {
|
|
166174
|
-
clearRefreshTimer(key);
|
|
166175
|
-
const timer = setTimeout(() => {
|
|
166176
|
-
refreshTimers.delete(key);
|
|
166177
|
-
const empty = {
|
|
166178
|
-
specs: [],
|
|
166179
|
-
installedContextPaths: /* @__PURE__ */ new Set()
|
|
166180
|
-
};
|
|
166181
|
-
const promise = discoverApiSpecs(key, authHeaders).catch(() => empty);
|
|
166182
|
-
specPromises.set(key, promise);
|
|
166183
|
-
promise.then(() => scheduleRefresh(key, authHeaders)).catch(() => {});
|
|
166184
|
-
}, DISCOVERY_REFRESH_INTERVAL_MS);
|
|
166185
|
-
timer.unref?.();
|
|
166186
|
-
refreshTimers.set(key, timer);
|
|
166187
|
-
}
|
|
166170
|
+
const cache = /* @__PURE__ */ new Map();
|
|
166188
166171
|
/**
|
|
166189
166172
|
* Start discovery for the given tenant if not already started, or return the
|
|
166190
|
-
* existing in-flight promise. Idempotent — safe to call
|
|
166191
|
-
*
|
|
166192
|
-
*
|
|
166173
|
+
* existing in-flight / last-known-good promise. Idempotent — safe to call
|
|
166174
|
+
* on every request.
|
|
166175
|
+
*
|
|
166176
|
+
* Failure handling: if the discovery promise rejects and there is no
|
|
166177
|
+
* prior cached result, the in-flight entry is removed so the next caller
|
|
166178
|
+
* retries, and the rejection propagates to the current caller (the request
|
|
166179
|
+
* fails). A prior successful entry is never overwritten by a failure.
|
|
166180
|
+
* @param tenantId - Cumulocity tenant ID; used as the cache key
|
|
166181
|
+
* @param client - Configured Cumulocity client for all API reads
|
|
166193
166182
|
*/
|
|
166194
|
-
function startDiscovery(
|
|
166195
|
-
const
|
|
166196
|
-
if (
|
|
166197
|
-
const
|
|
166198
|
-
|
|
166199
|
-
|
|
166200
|
-
|
|
166201
|
-
|
|
166202
|
-
consola.warn(`API spec discovery failed for ${key}:`, err instanceof Error ? err.message : String(err));
|
|
166203
|
-
return empty;
|
|
166183
|
+
function startDiscovery(tenantId, client) {
|
|
166184
|
+
const existing = cache.get(tenantId);
|
|
166185
|
+
if (existing) return existing;
|
|
166186
|
+
const promise = discoverApiSpecs(client, tenantId);
|
|
166187
|
+
cache.set(tenantId, promise);
|
|
166188
|
+
promise.catch((err) => {
|
|
166189
|
+
consola.warn(`API spec discovery failed for tenant ${tenantId}:`, c8yErrorSummary(err));
|
|
166190
|
+
if (cache.get(tenantId) === promise) cache.delete(tenantId);
|
|
166204
166191
|
});
|
|
166205
|
-
promise.then(() => scheduleRefresh(key, authHeaders)).catch(() => {});
|
|
166206
|
-
specPromises.set(key, promise);
|
|
166207
166192
|
return promise;
|
|
166208
166193
|
}
|
|
166209
166194
|
function rewriteDiscoveredSpecPaths(spec, servicePrefix) {
|
|
@@ -166220,23 +166205,28 @@ function rewriteDiscoveredSpecPaths(spec, servicePrefix) {
|
|
|
166220
166205
|
}
|
|
166221
166206
|
/**
|
|
166222
166207
|
* Fetch and return discovered specs for the given tenant.
|
|
166223
|
-
* Throws on fatal errors; individual spec download
|
|
166224
|
-
*
|
|
166225
|
-
*
|
|
166208
|
+
* Throws on fatal errors (applications listing); individual spec download
|
|
166209
|
+
* failures are skipped.
|
|
166210
|
+
*
|
|
166211
|
+
* Uses `applicationsByTenant/{tenantId}` (via `listByTenant`) rather than
|
|
166212
|
+
* the user-scoped `applicationsByUser` endpoint so the call works with
|
|
166213
|
+
* service-user credentials — service users cannot call /user/currentUser,
|
|
166214
|
+
* which the user-scoped endpoint depends on. The tenantId is always known
|
|
166215
|
+
* at the call site (it is the discovery cache key).
|
|
166216
|
+
*
|
|
166217
|
+
* All Cumulocity API calls go through the provided \@c8y/client. This module
|
|
166218
|
+
* never touches `fetch` directly so auth strategy choice (Basic, Bearer,
|
|
166219
|
+
* cookie, service-user) stays in the client construction layer.
|
|
166220
|
+
* @param client - Configured Cumulocity client used for all API reads
|
|
166221
|
+
* @param tenantId - Cumulocity tenant ID to list applications for
|
|
166226
166222
|
*/
|
|
166227
|
-
async function discoverApiSpecs(
|
|
166228
|
-
|
|
166229
|
-
|
|
166230
|
-
|
|
166231
|
-
|
|
166232
|
-
|
|
166233
|
-
|
|
166234
|
-
if (!userRes.ok) throw new Error(`Failed to fetch current user: ${userRes.status} ${userRes.statusText}`);
|
|
166235
|
-
const currentUser = await userRes.json();
|
|
166236
|
-
if (!currentUser.id) throw new Error("Could not determine current user ID from /user/currentUser response");
|
|
166237
|
-
const appsRes = await fetch(`${baseUrl}/application/applicationsByUser/${encodeURIComponent(currentUser.id)}?pageSize=2000`, { headers });
|
|
166238
|
-
if (!appsRes.ok) throw new Error(`Failed to fetch applications: ${appsRes.status} ${appsRes.statusText}`);
|
|
166239
|
-
const apps = (await appsRes.json()).applications ?? [];
|
|
166223
|
+
async function discoverApiSpecs(client, tenantId) {
|
|
166224
|
+
let apps;
|
|
166225
|
+
try {
|
|
166226
|
+
apps = (await client.application.listByTenant(tenantId, { pageSize: 2e3 })).data ?? [];
|
|
166227
|
+
} catch (err) {
|
|
166228
|
+
throw new Error(`Failed to fetch applications: ${c8yErrorSummary(err)}`);
|
|
166229
|
+
}
|
|
166240
166230
|
const appsWithSpec = apps.filter((app) => typeof app.contextPath === "string" && app.contextPath.length > 0 && typeof app.name === "string" && app.name.length > 0 && app.manifest != null && app.manifest.openApiSpec != null);
|
|
166241
166231
|
const seenContextPaths = /* @__PURE__ */ new Map();
|
|
166242
166232
|
for (const app of appsWithSpec) {
|
|
@@ -166256,7 +166246,7 @@ async function discoverApiSpecs(tenantUrl, authHeaders) {
|
|
|
166256
166246
|
path: app.manifest.openApiSpec
|
|
166257
166247
|
}] : app.manifest.openApiSpec;
|
|
166258
166248
|
for (const entry of entries) try {
|
|
166259
|
-
const specRes = await fetch(`${
|
|
166249
|
+
const specRes = await client.core.fetch(`${servicePrefix}/${entry.path.replace(/^\//, "")}`);
|
|
166260
166250
|
if (!specRes.ok) continue;
|
|
166261
166251
|
specs.push({
|
|
166262
166252
|
contextPath: app.contextPath,
|
|
@@ -174139,8 +174129,14 @@ function clearCliTenantContext() {
|
|
|
174139
174129
|
* @param tenantUrl - Base URL of the Cumulocity tenant to activate
|
|
174140
174130
|
*/
|
|
174141
174131
|
async function setCliTenantContext(tenantUrl) {
|
|
174142
|
-
const
|
|
174143
|
-
const
|
|
174132
|
+
const creds = await globalThis._getCredentialsByTenantUrl(tenantUrl);
|
|
174133
|
+
const authHeaders = createC8yAuthHeaders(creds);
|
|
174134
|
+
const cliClient = new Client(new BasicAuth({
|
|
174135
|
+
tenant: creds.tenantId,
|
|
174136
|
+
user: creds.user,
|
|
174137
|
+
password: creds.password
|
|
174138
|
+
}), tenantUrl);
|
|
174139
|
+
const { specs: discovered, installedContextPaths } = await startDiscovery(creds.tenantId, cliClient);
|
|
174144
174140
|
_context = {
|
|
174145
174141
|
tenantUrl,
|
|
174146
174142
|
authorizationHeader: authHeaders.Authorization,
|
|
@@ -174408,8 +174404,10 @@ runMain(defineCommand({
|
|
|
174408
174404
|
const activeTenant = readActiveTenantUrl();
|
|
174409
174405
|
if (activeTenant) try {
|
|
174410
174406
|
const tenantCtx = await setCliTenantContext(activeTenant);
|
|
174411
|
-
const
|
|
174412
|
-
consola.info(`Active tenant: ${activeTenant}
|
|
174407
|
+
const serviceKeys = Object.keys(tenantCtx.specs.specs);
|
|
174408
|
+
consola.info(`Active tenant: ${activeTenant}`);
|
|
174409
|
+
consola.info(`Startup discovery complete: ${serviceKeys.length} microservice API spec(s) found${serviceKeys.length > 0 ? ` [${serviceKeys.join(", ")}]` : ""}`);
|
|
174410
|
+
consola.info(`Available specs: ${["core", ...serviceKeys].join(", ")}`);
|
|
174413
174411
|
} catch (err) {
|
|
174414
174412
|
const message = err instanceof Error ? err.message : String(err);
|
|
174415
174413
|
if (message.includes("No stored credentials found for tenant URL")) {
|
|
@@ -174417,7 +174415,7 @@ runMain(defineCommand({
|
|
|
174417
174415
|
consola.warn(`Active tenant ${activeTenant} was cleared because no credentials are stored for it. Call set-active-tenant with a known tenant URL or run 'creds add' first.`);
|
|
174418
174416
|
} else consola.warn(`Could not activate tenant ${activeTenant}:`, message);
|
|
174419
174417
|
}
|
|
174420
|
-
else consola.info("No active tenant set. Call set-active-tenant to connect before using query or execute.");
|
|
174418
|
+
else consola.info("No active tenant set. Call set-active-tenant to connect before using query or execute. Discovery will run once a tenant is activated.");
|
|
174421
174419
|
setupMcpServer("cli");
|
|
174422
174420
|
const transport = new StdioTransport(c8yMcpServer);
|
|
174423
174421
|
const active = getCliTenantContext();
|