mc8yp 2.5.0 → 2.5.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +212 -39
  2. 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.5.0";
1548
+ var version = "2.5.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
@@ -1995,7 +1995,11 @@ const specs = Object.freeze([
1995
1995
  },
1996
1996
  "/alarm/alarms/upsert": { "post": {
1997
1997
  "operationId": "postAlarmUpsertResource",
1998
- "parameters": [{ "$ref": "#/components/parameters/acceptHeader" }, { "$ref": "#/components/parameters/processingModeHeader" }],
1998
+ "parameters": [
1999
+ { "$ref": "#/components/parameters/acceptHeader" },
2000
+ { "$ref": "#/components/parameters/processingModeHeader" },
2001
+ { "$ref": "#/components/parameters/queryParam_alarm_incrementCount" }
2002
+ ],
1999
2003
  "tags": ["Alarms"],
2000
2004
  "summary": "Create or update an alarm",
2001
2005
  "description": "Upserts an alarm based on the provided representation. If a non-cleared alarm with the same type and source already exists, it is updated; otherwise, a new alarm is created.\n\n<section><h5>Required roles</h5>\nROLE_ALARM_ADMIN <b>OR</b> owner of the source <b>OR</b> ALARM_ADMIN permission on the source\n</section>\n",
@@ -2016,11 +2020,11 @@ const specs = Object.freeze([
2016
2020
  "responses": {
2017
2021
  "200": {
2018
2022
  "description": "An existing alarm was updated.",
2019
- "content": { "application/vnd.com.nsn.cumulocity.alarm+json": { "schema": { "$ref": "#/components/schemas/alarm" } } }
2023
+ "content": { "application/vnd.com.nsn.cumulocity.alarmUpsertResponse+json": { "schema": { "$ref": "#/components/schemas/alarmUpsertResponse" } } }
2020
2024
  },
2021
2025
  "201": {
2022
2026
  "description": "An alarm was created.",
2023
- "content": { "application/vnd.com.nsn.cumulocity.alarm+json": { "schema": { "$ref": "#/components/schemas/alarm" } } }
2027
+ "content": { "application/vnd.com.nsn.cumulocity.alarmUpsertResponse+json": { "schema": { "$ref": "#/components/schemas/alarmUpsertResponse" } } }
2024
2028
  }
2025
2029
  }
2026
2030
  } },
@@ -5112,6 +5116,55 @@ const specs = Object.freeze([
5112
5116
  "responses": { "204": { "description": "A tenant was unsubscribed from an application." } }
5113
5117
  }
5114
5118
  },
5119
+ "/tenant/tenants/{tenantId}/applications/restricted-roles": {
5120
+ "parameters": [{ "$ref": "#/components/parameters/tenantId" }],
5121
+ "get": {
5122
+ "operationId": "getTenantApplicationRestrictedRolesResource",
5123
+ "tags": ["Tenant applications"],
5124
+ "summary": "Retrieve the microservice applications restricted roles list",
5125
+ "description": "Retrieve the list of roles that are restricted from being requested by microservices subscribed to or owned by the tenant.\n\n<section><h5>Required roles</h5>\n(ROLE_TENANT_MANAGEMENT_READ <b>OR</b> ROLE_TENANT_ADMIN) <b>AND</b> (the current tenant is its parent <b>OR</b> is the management tenant <b>OR</b> is the current tenant)\n</section>\n",
5126
+ "responses": { "200": { "$ref": "#/components/responses/tenantRestrictedRolesFound" } }
5127
+ },
5128
+ "put": {
5129
+ "operationId": "putTenantApplicationRestrictedRolesResource",
5130
+ "parameters": [{ "$ref": "#/components/parameters/acceptHeader" }],
5131
+ "tags": ["Tenant applications"],
5132
+ "summary": "Replace the microservice applications restricted roles list",
5133
+ "description": "Replace tenant's restricted roles list entirely. Microservices that are subscribed to or owned by the tenant cannot request any role on this list.\nOnly roles that actually exist in the system are accepted. Duplicate roles in the request body are silently deduplicated.\n\n<section><h5>Required roles</h5>\n(ROLE_TENANT_MANAGEMENT_ADMIN <b>OR</b> ROLE_TENANT_MANAGEMENT_UPDATE <b>OR</b> ROLE_TENANT_ADMIN) <b>AND</b> (the current tenant is its parent <b>OR</b> is the management tenant <b>OR</b> is the current tenant)\n</section>\n",
5134
+ "requestBody": {
5135
+ "required": true,
5136
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RestrictedRoles" } } }
5137
+ },
5138
+ "responses": { "200": {
5139
+ "description": "The restricted roles list was replaced and the updated tenant is sent in the response.",
5140
+ "content": { "application/vnd.com.nsn.cumulocity.tenant+json": { "schema": { "$ref": "#/components/schemas/tenantWithRestrictedRoles" } } }
5141
+ } }
5142
+ }
5143
+ },
5144
+ "/tenant/tenants/{tenantId}/applications/restricted-roles/{roleId}": {
5145
+ "parameters": [{ "$ref": "#/components/parameters/tenantId" }, { "$ref": "#/components/parameters/roleId" }],
5146
+ "post": {
5147
+ "operationId": "postTenantApplicationRestrictedRoleResource",
5148
+ "parameters": [{ "$ref": "#/components/parameters/acceptHeader" }],
5149
+ "tags": ["Tenant applications"],
5150
+ "summary": "Add a role to the microservice restricted roles list",
5151
+ "description": "Add a single role to tenant's restricted roles list. Microservices that are subscribed to or owned by the tenant cannot request roles on this list.\nIf the role is already in the list, the request is accepted and the list remains unchanged.\n\n<section><h5>Required roles</h5>\n(ROLE_TENANT_MANAGEMENT_ADMIN <b>OR</b> ROLE_TENANT_MANAGEMENT_UPDATE <b>OR</b> ROLE_TENANT_ADMIN) <b>AND</b> (the current tenant is its parent <b>OR</b> is the management tenant <b>OR</b> is the current tenant)\n</section>\n",
5152
+ "responses": { "200": {
5153
+ "description": "The role was added and the updated tenant is sent in the response.",
5154
+ "content": { "application/vnd.com.nsn.cumulocity.tenant+json": { "schema": { "$ref": "#/components/schemas/tenantWithMoreRestrictedRoles" } } }
5155
+ } }
5156
+ },
5157
+ "delete": {
5158
+ "operationId": "deleteTenantApplicationRestrictedRoleResource",
5159
+ "tags": ["Tenant applications"],
5160
+ "summary": "Remove a role from the microservice restricted roles list",
5161
+ "description": "Remove a single role from tenant's restricted roles list. Microservices that are subscribed to or owned by the tenant cannot request any role on this list.\n\n<section><h5>Required roles</h5>\n(ROLE_TENANT_MANAGEMENT_ADMIN <b>OR</b> ROLE_TENANT_MANAGEMENT_UPDATE <b>OR</b> ROLE_TENANT_ADMIN) <b>AND</b> (the current tenant is its parent <b>OR</b> is the management tenant <b>OR</b> is the current tenant)\n</section>\n",
5162
+ "responses": { "200": {
5163
+ "description": "The role was removed and the updated tenant is sent in the response.",
5164
+ "content": { "application/vnd.com.nsn.cumulocity.tenant+json": { "schema": { "$ref": "#/components/schemas/tenantWithRestrictedRoles" } } }
5165
+ } }
5166
+ }
5167
+ },
5115
5168
  "/tenant/tenants/{tenantId}/trusted-certificates": {
5116
5169
  "parameters": [{ "$ref": "#/components/parameters/tenantId" }],
5117
5170
  "post": {
@@ -7010,6 +7063,16 @@ const specs = Object.freeze([
7010
7063
  "example": "2021-03-30"
7011
7064
  }
7012
7065
  },
7066
+ "queryParam_alarm_incrementCount": {
7067
+ "name": "incrementCount",
7068
+ "in": "query",
7069
+ "description": "When set to `true`, the `count` property of an existing alarm is incremented during upsert.",
7070
+ "schema": {
7071
+ "type": "boolean",
7072
+ "default": false,
7073
+ "example": true
7074
+ }
7075
+ },
7013
7076
  "queryParam_application_type": {
7014
7077
  "name": "type",
7015
7078
  "in": "query",
@@ -8363,6 +8426,10 @@ const specs = Object.freeze([
8363
8426
  }
8364
8427
  } }
8365
8428
  },
8429
+ "tenantRestrictedRolesFound": {
8430
+ "description": "The request has succeeded and the restricted roles list is sent in the response.",
8431
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RestrictedRoles" } } }
8432
+ },
8366
8433
  "tenantApplicationReferenceFound": {
8367
8434
  "description": "The request has succeeded and the tenant applications are sent in the response.",
8368
8435
  "content": { "application/vnd.com.nsn.cumulocity.applicationreferencecollection+json": { "schema": { "$ref": "#/components/schemas/ApplicationReferenceCollection" } } }
@@ -8725,6 +8792,57 @@ const specs = Object.freeze([
8725
8792
  },
8726
8793
  "additionalProperties": { "description": "It is possible to add an arbitrary number of additional properties as a list of key-value pairs, for example, `\"property1\": {}`, `\"property2\": \"value\"`. These properties are known as custom fragments and can be of any type, for example, object or string. Each custom fragment is identified by a unique name.\n\nReview [Getting started > Technical concepts > Cumulocity's domain model > Inventory > Fragments > Naming conventions of fragments](https://www.cumulocity.com/docs/concepts/domain-model/#naming-conventions-of-fragments) in the Cumulocity user documentation as there are characters that can not be used when naming custom fragments.\n" }
8727
8794
  },
8795
+ "alarmUpsertResponse": {
8796
+ "type": "object",
8797
+ "required": ["alarm", "previousState"],
8798
+ "properties": {
8799
+ "alarm": {
8800
+ "description": "The alarm as it exists after the upsert operation.",
8801
+ "allOf": [{ "$ref": "#/components/schemas/alarm" }]
8802
+ },
8803
+ "previousState": {
8804
+ "description": "A snapshot of the alarm as it existed immediately before the upsert. This field is `null` when the upsert\ncreated a new alarm, and a full alarm representation when an existing alarm was updated.\n",
8805
+ "nullable": true,
8806
+ "allOf": [{ "$ref": "#/components/schemas/alarm" }]
8807
+ }
8808
+ },
8809
+ "example": {
8810
+ "alarm": {
8811
+ "count": 1,
8812
+ "creationTime": "2020-03-19T12:16:31.586Z",
8813
+ "lastUpdated": "2020-03-20T13:41:39.678Z",
8814
+ "id": "20200301",
8815
+ "self": "https://<TENANT_DOMAIN>/alarm/alarms/20200301",
8816
+ "severity": "MAJOR",
8817
+ "source": {
8818
+ "id": "251982",
8819
+ "name": "My tracking device",
8820
+ "self": "https://<TENANT_DOMAIN>/inventory/managedObjects/251982"
8821
+ },
8822
+ "status": "ACTIVE",
8823
+ "text": "No data received from the device within the required interval.",
8824
+ "time": "2020-03-19T00:00:00.000Z",
8825
+ "type": "c8y_UnavailabilityAlarm"
8826
+ },
8827
+ "previousState": {
8828
+ "count": 1,
8829
+ "creationTime": "2020-03-19T12:16:31.586Z",
8830
+ "lastUpdated": "2020-03-19T12:16:31.586Z",
8831
+ "id": "20200301",
8832
+ "self": "https://<TENANT_DOMAIN>/alarm/alarms/20200301",
8833
+ "severity": "MINOR",
8834
+ "source": {
8835
+ "id": "251982",
8836
+ "name": "My tracking device",
8837
+ "self": "https://<TENANT_DOMAIN>/inventory/managedObjects/251982"
8838
+ },
8839
+ "status": "ACKNOWLEDGED",
8840
+ "text": "No data received from the device within the required interval.",
8841
+ "time": "2020-03-19T00:00:00.000Z",
8842
+ "type": "c8y_UnavailabilityAlarm"
8843
+ }
8844
+ }
8845
+ },
8728
8846
  "auditApiResource": {
8729
8847
  "type": "object",
8730
8848
  "properties": {
@@ -11814,6 +11932,71 @@ const specs = Object.freeze([
11814
11932
  "status": "ACTIVE"
11815
11933
  }
11816
11934
  },
11935
+ "tenantWithRestrictedRoles": {
11936
+ "allOf": [{ "$ref": "#/components/schemas/tenant" }],
11937
+ "example": {
11938
+ "id": "t07007007",
11939
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007",
11940
+ "adminEmail": "john@doe.com",
11941
+ "adminName": "johndoe",
11942
+ "allowCreateTenants": false,
11943
+ "applications": {
11944
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007/applications",
11945
+ "references": []
11946
+ },
11947
+ "company": "ACME AG",
11948
+ "contactName": "John Doe",
11949
+ "contactPhone": "+52 333 567 1234",
11950
+ "creationTime": "2020-05-02T20:00:29.907Z",
11951
+ "customProperties": { "msForbiddenRoles": ["ROLE_TENANT_ADMIN", "ROLE_TENANT_MANAGEMENT_ADMIN"] },
11952
+ "domain": "mytenant.cumulocity.com",
11953
+ "ownedApplications": {
11954
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007/applications",
11955
+ "references": []
11956
+ },
11957
+ "parent": "t1511681",
11958
+ "status": "ACTIVE"
11959
+ }
11960
+ },
11961
+ "tenantWithMoreRestrictedRoles": {
11962
+ "allOf": [{ "$ref": "#/components/schemas/tenant" }],
11963
+ "example": {
11964
+ "id": "t07007007",
11965
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007",
11966
+ "adminEmail": "john@doe.com",
11967
+ "adminName": "johndoe",
11968
+ "allowCreateTenants": false,
11969
+ "applications": {
11970
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007/applications",
11971
+ "references": []
11972
+ },
11973
+ "company": "ACME AG",
11974
+ "contactName": "John Doe",
11975
+ "contactPhone": "+52 333 567 1234",
11976
+ "creationTime": "2020-05-02T20:00:29.907Z",
11977
+ "customProperties": { "msForbiddenRoles": [
11978
+ "ROLE_TENANT_ADMIN",
11979
+ "ROLE_TENANT_MANAGEMENT_ADMIN",
11980
+ "ROLE_TENANT_ALARM_ADMIN"
11981
+ ] },
11982
+ "domain": "mytenant.cumulocity.com",
11983
+ "ownedApplications": {
11984
+ "self": "https://<TENANT_DOMAIN>/tenant/tenants/t07007007/applications",
11985
+ "references": []
11986
+ },
11987
+ "parent": "t1511681",
11988
+ "status": "ACTIVE"
11989
+ }
11990
+ },
11991
+ "RestrictedRoles": {
11992
+ "description": "An array of role names that are restricted from being requested by microservices in the tenant.",
11993
+ "type": "array",
11994
+ "items": {
11995
+ "type": "string",
11996
+ "minLength": 1
11997
+ },
11998
+ "example": ["ROLE_TENANT_ADMIN", "ROLE_TENANT_MANAGEMENT_ADMIN"]
11999
+ },
11817
12000
  "option": {
11818
12001
  "description": "A tuple storing tenant configuration.",
11819
12002
  "type": "object",
@@ -57857,14 +58040,6 @@ async () => {
57857
58040
  }
57858
58041
  \`\`\`
57859
58042
 
57860
- ## Efficient Querying
57861
-
57862
- Parameters with \`schema.format === 'c8y:query'\` accept Cumulocity Query Language. Use \`queryBuilder()\` — injected into every execute sandbox — to construct the query string. Always prefer server-side filtering over fetching all pages: max \`pageSize\` is 2000 and tenants can have millions of objects.
57863
-
57864
- Key operators: \`eq\`, \`gt\`, \`ge\`, \`lt\`, \`le\`, \`and\`, \`or\`, \`not\`, \`has()\`, \`hasany()\`, \`bygroupid()\`, \`isinhierarchyof()\`. **There is no \`like\` or \`in\`** — use \`eq\` with \`*\` wildcards for substring matches (\`name eq '*pattern*'\`, case-insensitive), and \`or\` chains for multi-value ID matching. When the ID list is large, fetch all and join in memory instead.
57865
-
57866
- For the full syntax reference, run the query tool: \`() => coreSpec.tags.find(t => t.name === 'Query language')?.description\`
57867
-
57868
58043
  ${getRuntimeSection()}
57869
58044
 
57870
58045
  ${getOpenApiSection()}
@@ -131836,22 +132011,6 @@ function createCumulocitySafeFetch(tenantUrl, authHeaders, restrictions = [], al
131836
132011
  allowCompressedResponses: false
131837
132012
  });
131838
132013
  }
131839
- const QUERY_BUILDER_SOURCE = `\
131840
- /**
131841
- * Builds a Cumulocity query string for parameters of name "query" or with schema.format "c8y:query".
131842
- * Use to filter server-side. This is always the preferred method if query parameters are present (max pageSize 2000).
131843
- * No like/in operators — use eq with * wildcards (name eq '*pat*') or or-chains for multi-value.
131844
- * To get query syntax documentation run query tool → () => coreSpec.tags.find(t => t.name === 'Query language')?.description
131845
- * Use the path exactly as it appears in serviceSpecs — never prepend /service/<key>.
131846
- * @example /inventory/managedObjects?\${queryBuilder({ filter: "type eq 'c8y_Firmware'" })}
131847
- * @example <path>?\${queryBuilder({ filter: "name eq '*Ctrl*'", orderby: "name asc" })}
131848
- */
131849
- function queryBuilder({ filter, orderby, pageSize = 2000 } = {}) {
131850
- const parts = []
131851
- if (filter) parts.push('$filter=' + filter)
131852
- if (orderby) parts.push('$orderby=' + orderby)
131853
- return (parts.length ? 'query=' + parts.join(' ') + '&' : '') + 'pageSize=' + pageSize
131854
- }`;
131855
132014
  function normalizeCode(functionCode) {
131856
132015
  return functionCode.trim().replace(/^```(?:js|javascript|ts|typescript)?\s*/i, "").replace(/\s*```$/, "").trim();
131857
132016
  }
@@ -131943,7 +132102,6 @@ async function execute(functionCode) {
131943
132102
  const restrictions = c8yMcpServer.ctx.custom?.restrictions ?? [];
131944
132103
  const allowRules = c8yMcpServer.ctx.custom?.allowRules ?? [];
131945
132104
  const code = [
131946
- QUERY_BUILDER_SOURCE,
131947
132105
  `const __mc8ypExecute = (${normalizeCode(functionCode)});`,
131948
132106
  "if (typeof __mc8ypExecute !== \"function\") { throw new TypeError(\"Execute code must evaluate to a function.\") }",
131949
132107
  "export default await __mc8ypExecute();"
@@ -137407,16 +137565,21 @@ function refreshApiSpecs(tenantId, client) {
137407
137565
  function getCachedDiscovery(tenantId) {
137408
137566
  return cache.get(tenantId);
137409
137567
  }
137568
+ const APPLICATIONS_PAGE_SIZE = 100;
137569
+ const APPLICATIONS_MAX_PAGES = 50;
137410
137570
  /**
137411
137571
  * Fetch and return discovered specs for the given tenant.
137412
137572
  * Throws on fatal errors (applications listing); individual spec download
137413
137573
  * failures are skipped.
137414
137574
  *
137415
- * Uses `applicationsByTenant/{tenantId}` (via `listByTenant`) rather than
137416
- * the user-scoped `applicationsByUser` endpoint so the call works with
137417
- * service-user credentials — service users cannot call /user/currentUser,
137418
- * which the user-scoped endpoint depends on. The tenantId is always known
137419
- * at the call site (it is the discovery cache key).
137575
+ * Uses `/application/applications?tenant=<id>&type=MICROSERVICE`, paginated.
137576
+ * The `tenant` filter covers apps the tenant owns or is subscribed to, and
137577
+ * `type=MICROSERVICE` drops HOSTED/EXTERNAL apps — they can never contribute
137578
+ * a spec anyway (spec download goes through `/service/<contextPath>/`, which
137579
+ * only exists for microservices) and their large manifests were what made the
137580
+ * unpaginated response exceed gateway limits. The endpoint needs only
137581
+ * ROLE_APPLICATION_MANAGEMENT_READ and does not depend on /user/currentUser,
137582
+ * so it works with service-user credentials.
137420
137583
  *
137421
137584
  * All Cumulocity API calls go through the provided \@c8y/client. This module
137422
137585
  * never touches `fetch` directly so auth strategy choice (Basic, Bearer,
@@ -137425,9 +137588,19 @@ function getCachedDiscovery(tenantId) {
137425
137588
  * @param tenantId - Cumulocity tenant ID to list applications for
137426
137589
  */
137427
137590
  async function discoverApiSpecs(client, tenantId) {
137428
- let apps;
137591
+ const apps = [];
137429
137592
  try {
137430
- apps = (await client.application.listByTenant(tenantId, { pageSize: 2e3 })).data ?? [];
137593
+ for (let currentPage = 1; currentPage <= APPLICATIONS_MAX_PAGES; currentPage++) {
137594
+ const page = (await client.application.list({
137595
+ tenant: tenantId,
137596
+ type: "MICROSERVICE",
137597
+ pageSize: APPLICATIONS_PAGE_SIZE,
137598
+ currentPage,
137599
+ withTotalPages: false
137600
+ })).data ?? [];
137601
+ apps.push(...page);
137602
+ if (page.length < APPLICATIONS_PAGE_SIZE) break;
137603
+ }
137431
137604
  } catch (err) {
137432
137605
  throw new Error(`Failed to fetch applications: ${c8yErrorSummary(err)}`);
137433
137606
  }
@@ -140310,7 +140483,8 @@ const BUNDLED_SERVICE_SPECS = Object.freeze([{
140310
140483
  * deliberately browse all bundled snapshots regardless of installation,
140311
140484
  * use {@link getBundledOnlySpecs} (the CLI's no-tenant fallback).
140312
140485
  * @param discoveredSpecs Result of live API discovery for the tenant.
140313
- * @param installedContextPaths Subscribed app context paths on the tenant.
140486
+ * @param installedContextPaths Context paths of microservices the tenant owns
140487
+ * or is subscribed to (discovery filters to type=MICROSERVICE).
140314
140488
  */
140315
140489
  function resolveSpecs(discoveredSpecs, installedContextPaths) {
140316
140490
  const specs = {};
@@ -140609,8 +140783,7 @@ async function getStoredC8yAuth() {
140609
140783
  }
140610
140784
  async function getCredentialsByTenantUrl(tenantUrl) {
140611
140785
  const cleanedUrl = cleanTenantUrl(tenantUrl);
140612
- let entry = (await findCredentialsAsync(name, cleanedUrl))[0];
140613
- if (!entry) entry = (await findCredentialsAsync(name)).find((e) => cleanTenantUrl(e.account) === cleanedUrl);
140786
+ const entry = (await findCredentialsAsync(name)).find((e) => cleanTenantUrl(e.account) === cleanedUrl);
140614
140787
  if (!entry) throw new Error(`No stored credentials found for tenant URL: ${cleanedUrl}`);
140615
140788
  return parseStoredUserC8yAuth(entry.password, cleanedUrl);
140616
140789
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mc8yp",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "type": "module",
5
5
  "description": "Cumulocity IoT MCP Server - Model Context Protocol integration for IoT device management",
6
6
  "keywords": [