@vectros-ai/mcp-server 0.11.0 → 0.13.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/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/server.ts
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
 
4
- // ../../node_modules/@vectros-ai/sdk/dist/index.mjs
4
+ // node_modules/@vectros-ai/sdk/dist/index.mjs
5
5
  var __defProp = Object.defineProperty;
6
6
  var __export = (target, all) => {
7
7
  for (var name in all)
@@ -2430,7 +2430,7 @@ var AuthClient = class {
2430
2430
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
2431
2431
  }
2432
2432
  /**
2433
- * Lists all of your scoped API keys (`ssk_*`) across both your live and test environments. Revoked keys are excluded. Requires the `keys:r` scope.
2433
+ * Lists your scoped API keys (`ssk_*`) in your credential's own environment — a live key lists live keys, a test key lists test keys. Revoked keys are excluded. Requires the `keys:r` scope.
2434
2434
  *
2435
2435
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
2436
2436
  *
@@ -2813,7 +2813,7 @@ var AuthClient = class {
2813
2813
  );
2814
2814
  }
2815
2815
  /**
2816
- * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2816
+ * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope, and applies the same `identityOverrides` bounds the update endpoint documents — a scoped credential may not repoint or clear an identity value it does not itself hold). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2817
2817
  *
2818
2818
  * @param {Vectros.CreateAccessProfileRequest} request
2819
2819
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3231,7 +3231,7 @@ var AuthClient = class {
3231
3231
  );
3232
3232
  }
3233
3233
  /**
3234
- * Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:u` scope.
3234
+ * Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own, and its `identityOverrides` are bounded twice: you may only set a value your own identity holds, and you may only change or clear a value the profile already holds if that value is yours as well. Repointing or clearing another principal's established identity therefore returns 403. A root API key (`sk_`) is exempt. Requires the `profiles:u` scope.
3235
3235
  *
3236
3236
  * @param {Vectros.UpdateAccessProfileRequest} request
3237
3237
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3304,7 +3304,7 @@ var AuthClient = class {
3304
3304
  );
3305
3305
  }
3306
3306
  /**
3307
- * Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. Requires the `profiles:d` scope.
3307
+ * Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. If you use a scoped credential and the profile carries `identityOverrides`, you may only delete it when you hold those values yourself — deleting a profile removes its identity, so the same bound applies as when clearing it. A profile with no `identityOverrides` is unaffected, and a root API key (`sk_`) is exempt. Requires the `profiles:d` scope.
3308
3308
  *
3309
3309
  * @param {Vectros.DeleteAccessProfileRequest} request
3310
3310
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4158,7 +4158,7 @@ var AuthClient = class {
4158
4158
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/auth/token");
4159
4159
  }
4160
4160
  /**
4161
- * Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `admin:users` scope.
4161
+ * Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate — this requires the `users:r` and `users:u` scopes in addition to `users:c`, because resending rotates a credential on an existing invitation and invalidates any link already sent. Without them the collision returns 409 instead, with no invitation details and no change to the outstanding invitation. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `users:c` scope.
4162
4162
  *
4163
4163
  * @param {Vectros.CreateInviteRequest} request
4164
4164
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4229,7 +4229,7 @@ var AuthClient = class {
4229
4229
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/users/invite");
4230
4230
  }
4231
4231
  /**
4232
- * Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged. Requires the `admin:users` scope.
4232
+ * Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged. Because this rotates a credential on an existing invitation, it requires the `users:c`, `users:r` and `users:u` scopes.
4233
4233
  *
4234
4234
  * @param {Vectros.CreateInviteRequest} request
4235
4235
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4563,7 +4563,7 @@ var DocumentsClient = class {
4563
4563
  * userId: "550e8400-e29b-41d4-a716-446655440000",
4564
4564
  * scope: "group:eng-team",
4565
4565
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4566
- * startFrom: "doc_prev123"
4566
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
4567
4567
  * })
4568
4568
  */
4569
4569
  listDocuments(request = {}, requestOptions) {
@@ -4611,7 +4611,7 @@ var DocumentsClient = class {
4611
4611
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
4612
4612
  }
4613
4613
  /**
4614
- * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope.
4614
+ * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope to create. Being returned the existing document on a collision is a read of that document's data and additionally requires the `documents:r` scope — a credential holding `documents:c` alone receives a `400` ("already exists") on collision instead of the document.
4615
4615
  *
4616
4616
  * @param {Vectros.IngestDocumentRequest} request
4617
4617
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4873,6 +4873,7 @@ var DocumentsClient = class {
4873
4873
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4874
4874
  *
4875
4875
  * @throws {@link Vectros.BadRequestError}
4876
+ * @throws {@link Vectros.ForbiddenError}
4876
4877
  * @throws {@link Vectros.NotFoundError}
4877
4878
  * @throws {@link Vectros.ConflictError}
4878
4879
  * @throws {@link Vectros.TooManyRequestsError}
@@ -4920,6 +4921,8 @@ var DocumentsClient = class {
4920
4921
  switch (_response.error.statusCode) {
4921
4922
  case 400:
4922
4923
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4924
+ case 403:
4925
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4923
4926
  case 404:
4924
4927
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4925
4928
  case 409:
@@ -4937,25 +4940,29 @@ var DocumentsClient = class {
4937
4940
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
4938
4941
  }
4939
4942
  /**
4940
- * Finds documents of a given type by field value. Supported fields: `externalId` (the document's first-class external identifier — no schema declaration required) and any field declared as a lookup field on the bound schema. A lookup on a sensitive field is rejected here because the value would appear in the URL query string; use POST /v1/documents/lookup (the request-body variant) for a sensitive field instead. Results are paginated: set `limit` for the page size and feed the returned `nextCursor` back as `startFrom` to fetch the next page. The response is a `{data, nextCursor}` envelope. Requires the `documents:r` scope.
4943
+ * Finds documents of a given type by field value. Supported fields: `externalId` (the document's first-class external identifier — no schema declaration required) and any field declared as a lookup field on the bound schema. A lookup on a sensitive field is rejected here because the value would appear in the URL query string; use POST /v1/documents/lookup (the request-body variant) for a sensitive field instead. `type`'s schema resolves with basedOn-aware shadowing: your own `userId`- or `scope`-owned variant if you have one, otherwise the shared base — for a scoped credential the owner is always your own token identity; `userId`/`scope` here only apply as an explicit owner selector for a root API key. Results are paginated: set `limit` for the page size and feed the returned `nextCursor` back as `startFrom` to fetch the next page. The response is a `{data, nextCursor}` envelope. Requires the `documents:r` scope.
4941
4944
  *
4942
4945
  * @param {Vectros.LookupDocumentsRequest} request
4943
4946
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4944
4947
  *
4948
+ * @throws {@link Vectros.BadRequestError}
4949
+ *
4945
4950
  * @example
4946
4951
  * await client.documents.lookupDocuments({
4947
4952
  * type: "invoice",
4948
4953
  * field: "po_number",
4949
4954
  * value: "PO-1001",
4950
4955
  * prefix: "PO-2024",
4951
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
4956
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
4957
+ * userId: "550e8400-e29b-41d4-a716-446655440000",
4958
+ * scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
4952
4959
  * })
4953
4960
  */
4954
4961
  lookupDocuments(request, requestOptions) {
4955
4962
  return HttpResponsePromise.fromPromise(this.__lookupDocuments(request, requestOptions));
4956
4963
  }
4957
4964
  async __lookupDocuments(request, requestOptions) {
4958
- const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, order } = request;
4965
+ const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, order, userId, scope } = request;
4959
4966
  const _queryParams = {
4960
4967
  type: type_,
4961
4968
  field,
@@ -4965,7 +4972,9 @@ var DocumentsClient = class {
4965
4972
  prefix,
4966
4973
  startFrom,
4967
4974
  limit,
4968
- order: order != null ? order : void 0
4975
+ order: order != null ? order : void 0,
4976
+ userId,
4977
+ scope
4969
4978
  };
4970
4979
  const _authRequest = await this._options.authProvider.getAuthRequest();
4971
4980
  const _headers = mergeHeaders(
@@ -4991,11 +5000,16 @@ var DocumentsClient = class {
4991
5000
  return { data: _response.body, rawResponse: _response.rawResponse };
4992
5001
  }
4993
5002
  if (_response.error.reason === "status-code") {
4994
- throw new VectrosError({
4995
- statusCode: _response.error.statusCode,
4996
- body: _response.error.body,
4997
- rawResponse: _response.rawResponse
4998
- });
5003
+ switch (_response.error.statusCode) {
5004
+ case 400:
5005
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
5006
+ default:
5007
+ throw new VectrosError({
5008
+ statusCode: _response.error.statusCode,
5009
+ body: _response.error.body,
5010
+ rawResponse: _response.rawResponse
5011
+ });
5012
+ }
4999
5013
  }
5000
5014
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/lookup");
5001
5015
  }
@@ -5315,7 +5329,7 @@ var FoldersClient = class {
5315
5329
  * parentFolderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
5316
5330
  * userId: "550e8400-e29b-41d4-a716-446655440000",
5317
5331
  * scope: "group:eng-team",
5318
- * startFrom: "fld_prev123"
5332
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
5319
5333
  * })
5320
5334
  */
5321
5335
  listFolders(request = {}, requestOptions) {
@@ -5363,7 +5377,7 @@ var FoldersClient = class {
5363
5377
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
5364
5378
  }
5365
5379
  /**
5366
- * Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Folder creation is idempotent by (slug + parent): if a folder with the same slug already exists under the same parent, that existing folder is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing folder was returned) tells the two apart. To overwrite an existing folder's mutable fields instead of returning it unchanged, set `?upsert=true` (this also requires the `folders:u` scope). Requires the `folders:c` scope.
5380
+ * Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Folder creation is idempotent by (slug + parent): if a folder with the same slug already exists under the same parent, that existing folder is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing folder was returned) tells the two apart. To overwrite an existing folder's mutable fields instead of returning it unchanged, set `?upsert=true` (this also requires the `folders:u` scope). Requires the `folders:c` scope to create. Being returned the existing folder on a collision is a read of that folder's data and additionally requires the `folders:r` scope — a credential holding `folders:c` alone receives a `400` ("already exists") on collision instead of the folder.
5367
5381
  *
5368
5382
  * @param {Vectros.CreateFolderRequest} request
5369
5383
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5492,6 +5506,7 @@ var FoldersClient = class {
5492
5506
  * @param {Vectros.UpdateFolderRequest} request
5493
5507
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5494
5508
  *
5509
+ * @throws {@link Vectros.ForbiddenError}
5495
5510
  * @throws {@link Vectros.NotFoundError}
5496
5511
  * @throws {@link Vectros.TooManyRequestsError}
5497
5512
  *
@@ -5536,6 +5551,8 @@ var FoldersClient = class {
5536
5551
  }
5537
5552
  if (_response.error.reason === "status-code") {
5538
5553
  switch (_response.error.statusCode) {
5554
+ case 403:
5555
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5539
5556
  case 404:
5540
5557
  throw new NotFoundError(_response.error.body, _response.rawResponse);
5541
5558
  case 429:
@@ -5618,6 +5635,7 @@ var FoldersClient = class {
5618
5635
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5619
5636
  *
5620
5637
  * @throws {@link Vectros.BadRequestError}
5638
+ * @throws {@link Vectros.ForbiddenError}
5621
5639
  * @throws {@link Vectros.NotFoundError}
5622
5640
  * @throws {@link Vectros.ConflictError}
5623
5641
  * @throws {@link Vectros.TooManyRequestsError}
@@ -5665,6 +5683,8 @@ var FoldersClient = class {
5665
5683
  switch (_response.error.statusCode) {
5666
5684
  case 400:
5667
5685
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5686
+ case 403:
5687
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5668
5688
  case 404:
5669
5689
  throw new NotFoundError(_response.error.body, _response.rawResponse);
5670
5690
  case 409:
@@ -5752,7 +5772,8 @@ var IdentityClient = class {
5752
5772
  *
5753
5773
  * @example
5754
5774
  * await client.identity.listEntities({
5755
- * namespace: "team"
5775
+ * namespace: "team",
5776
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
5756
5777
  * })
5757
5778
  */
5758
5779
  listEntities(request, requestOptions) {
@@ -5821,7 +5842,7 @@ var IdentityClient = class {
5821
5842
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/entities/{namespace}");
5822
5843
  }
5823
5844
  /**
5824
- * Creates a new entity in the given namespace. This call is idempotent on `externalId` within the namespace: if an entity with the same `externalId` already exists, the existing record is returned instead of creating a duplicate (`created: false`, HTTP 200). To overwrite an existing entity's content instead of returning it unchanged, set `?upsert=true` (also requires the `entities:u:<namespace>` scope). The namespace must be entity-backed (`org`/`client`, or registered via `POST /v1/namespaces`). Requires the `entities:c:<namespace>` scope.
5845
+ * Creates a new entity in the given namespace. This call is idempotent on `externalId` within the namespace: if an entity with the same `externalId` already exists, the existing record is returned instead of creating a duplicate (`created: false`, HTTP 200). To overwrite an existing entity's content instead of returning it unchanged, set `?upsert=true` (also requires the `entities:u:<namespace>` scope). The namespace must be entity-backed (`org`/`client`, or registered via `POST /v1/namespaces`). Requires the `entities:c:<namespace>` scope to create. Being returned the existing entity on a collision is a read of that entity's data and additionally requires the `entities:r:<namespace>` scope — a credential holding `entities:c:<namespace>` alone receives a `400` ("already in use") on collision instead of the entity.
5825
5846
  *
5826
5847
  * @param {Vectros.CreateEntityRequest} request
5827
5848
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6265,7 +6286,7 @@ var IdentityClient = class {
6265
6286
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/namespaces/{namespace}");
6266
6287
  }
6267
6288
  /**
6268
- * Updates the mutable fields (`entityBacked`, `defaultSchemaId`) of a registered namespace. The namespace name itself is immutable. Requires a root API key. The reserved built-ins cannot be updated.
6289
+ * Updates the mutable fields (`entityBacked`, `defaultSchemaId`, `specificityRank`) of a registered namespace. The namespace name itself is immutable. Requires a root API key. The reserved built-ins cannot be updated.
6269
6290
  *
6270
6291
  * @param {Vectros.UpdateNamespaceRequest} request
6271
6292
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6278,7 +6299,8 @@ var IdentityClient = class {
6278
6299
  * await client.identity.updateNamespace({
6279
6300
  * namespace: "team",
6280
6301
  * body: {
6281
- * namespace: "team"
6302
+ * namespace: "team",
6303
+ * specificityRank: 1500
6282
6304
  * }
6283
6305
  * })
6284
6306
  */
@@ -6443,7 +6465,7 @@ var IdentityClient = class {
6443
6465
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/namespaces");
6444
6466
  }
6445
6467
  /**
6446
- * Registers a new scope namespace and declares whether its values resolve to identity entities (`entityBacked`). Requires a root API key. The reserved names `org` and `client` are built in and cannot be registered.
6468
+ * Registers a new scope namespace and declares whether its values resolve to identity entities (`entityBacked`). Also requires `specificityRank`, an explicit, account-unique position in the specificity order used to break recordType schema-resolution ties. Requires a root API key. The reserved names `org` and `client` are built in and cannot be registered.
6447
6469
  *
6448
6470
  * @param {Vectros.NamespaceRequest} request
6449
6471
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6454,7 +6476,8 @@ var IdentityClient = class {
6454
6476
  *
6455
6477
  * @example
6456
6478
  * await client.identity.registerNamespace({
6457
- * namespace: "team"
6479
+ * namespace: "team",
6480
+ * specificityRank: 1500
6458
6481
  * })
6459
6482
  */
6460
6483
  registerNamespace(request, requestOptions) {
@@ -6514,7 +6537,7 @@ var IdentityClient = class {
6514
6537
  * @example
6515
6538
  * await client.identity.listUsers({
6516
6539
  * externalId: "usr_12345",
6517
- * startFrom: "550e8400-e29b-41d4-a716-446655440000",
6540
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
6518
6541
  * type: "person_v1",
6519
6542
  * field: "team",
6520
6543
  * value: "engineering",
@@ -6571,7 +6594,7 @@ var IdentityClient = class {
6571
6594
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
6572
6595
  }
6573
6596
  /**
6574
- * Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing user was returned) tells the two apart. To overwrite an existing user's mutable fields (email, status, payload, schema binding) instead of returning it unchanged, set `?upsert=true` (this also requires the `users:u` scope). Requires the `users:c` scope.
6597
+ * Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing user was returned) tells the two apart. To overwrite an existing user's mutable fields (email, status, payload, schema binding) instead of returning it unchanged, set `?upsert=true` (this also requires the `users:u` scope). Requires the `users:c` scope to create. Being returned the existing user on a collision is a read of that user's data and additionally requires the `users:r` scope — a credential holding `users:c` alone receives a `400` ("already exists") on collision instead of the user.
6575
6598
  *
6576
6599
  * @param {Vectros.CreateUserRequest} request
6577
6600
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6695,11 +6718,12 @@ var IdentityClient = class {
6695
6718
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/{id}");
6696
6719
  }
6697
6720
  /**
6698
- * Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
6721
+ * Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation, and `email` cannot be changed while an invitation to that user is still outstanding — revoke the invitation, or invite the new address instead. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
6699
6722
  *
6700
6723
  * @param {Vectros.UpdateUserRequest} request
6701
6724
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6702
6725
  *
6726
+ * @throws {@link Vectros.BadRequestError}
6703
6727
  * @throws {@link Vectros.NotFoundError}
6704
6728
  * @throws {@link Vectros.TooManyRequestsError}
6705
6729
  *
@@ -6744,6 +6768,8 @@ var IdentityClient = class {
6744
6768
  }
6745
6769
  if (_response.error.reason === "status-code") {
6746
6770
  switch (_response.error.statusCode) {
6771
+ case 400:
6772
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
6747
6773
  case 404:
6748
6774
  throw new NotFoundError(_response.error.body, _response.rawResponse);
6749
6775
  case 429:
@@ -7391,7 +7417,7 @@ var RecordsClient = class {
7391
7417
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7392
7418
  * userId: "550e8400-e29b-41d4-a716-446655440000",
7393
7419
  * scope: "group:eng-team",
7394
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
7420
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
7395
7421
  * })
7396
7422
  */
7397
7423
  listRecords(request = {}, requestOptions) {
@@ -7442,7 +7468,7 @@ var RecordsClient = class {
7442
7468
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
7443
7469
  }
7444
7470
  /**
7445
- * Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing record was returned) tells the two apart. To overwrite an existing record's content instead of returning it unchanged, set `?upsert=true` (this also requires the `records:u:<type>` scope). Requires the `records:c:<type>` scope.
7471
+ * Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing record was returned) tells the two apart. To overwrite an existing record's content instead of returning it unchanged, set `?upsert=true` (this also requires the `records:u:<type>` scope). Requires the `records:c:<type>` scope to create. Being returned the existing record on a collision is a read of that record's data and additionally requires the `records:r:<type>` scope — a credential holding `records:c:<type>` alone receives a `400` ("already exists") on collision instead of the record.
7446
7472
  *
7447
7473
  * @param {Vectros.CreateRecordRequest} request
7448
7474
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7579,6 +7605,7 @@ var RecordsClient = class {
7579
7605
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7580
7606
  *
7581
7607
  * @throws {@link Vectros.BadRequestError}
7608
+ * @throws {@link Vectros.ForbiddenError}
7582
7609
  * @throws {@link Vectros.NotFoundError}
7583
7610
  * @throws {@link Vectros.ConflictError}
7584
7611
  * @throws {@link Vectros.TooManyRequestsError}
@@ -7627,6 +7654,8 @@ var RecordsClient = class {
7627
7654
  switch (_response.error.statusCode) {
7628
7655
  case 400:
7629
7656
  throw new BadRequestError(_response.error.body, _response.rawResponse);
7657
+ case 403:
7658
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
7630
7659
  case 404:
7631
7660
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7632
7661
  case 409:
@@ -7708,6 +7737,7 @@ var RecordsClient = class {
7708
7737
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7709
7738
  *
7710
7739
  * @throws {@link Vectros.BadRequestError}
7740
+ * @throws {@link Vectros.ForbiddenError}
7711
7741
  * @throws {@link Vectros.NotFoundError}
7712
7742
  * @throws {@link Vectros.ConflictError}
7713
7743
  * @throws {@link Vectros.TooManyRequestsError}
@@ -7758,6 +7788,8 @@ var RecordsClient = class {
7758
7788
  switch (_response.error.statusCode) {
7759
7789
  case 400:
7760
7790
  throw new BadRequestError(_response.error.body, _response.rawResponse);
7791
+ case 403:
7792
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
7761
7793
  case 404:
7762
7794
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7763
7795
  case 409:
@@ -7775,7 +7807,15 @@ var RecordsClient = class {
7775
7807
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/records/{id}");
7776
7808
  }
7777
7809
  /**
7778
- * Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields. Results are paginated: set `limit` for the page size and pass the returned `nextCursor` back as `startFrom` for the next page. Requires the `records:r:<type>` scope.
7810
+ * Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields.
7811
+ *
7812
+ * A `value` lookup can additionally be narrowed to a window of the lookup field's **sort key** using `sortFrom` and/or `sortTo` (inclusive) — for example, one session's records created since a timestamp. The sort key is whatever the schema declares as that lookup's `sortBy` (`createdAt` by default, `lastUpdated`, or another field), and bounds are given in that field's own units — epoch milliseconds for the two timestamp options. **Records that have no value for the sorted field are never included in a bounded window** — they are ordered ahead of every record that does have one, and a `sortFrom`/`sortTo` window only ever selects from records carrying a value. The sorted field does not have to be `required`.
7813
+ *
7814
+ * Narrowing is available on any lookup field your schema declares for fast equality lookup, and on `externalId` — which is always ordered by creation time, so its bounds are epoch milliseconds whatever the schema says. It is rejected (`400`) for a field declared with `rangeEnabled`, for a field declared beyond the schema's fast-lookup budget, for a lookup whose `sortBy` names a sensitive field (a sensitive value is stored as a blind index and has no order), and for a window whose start is after its end. Ownership fields are not lookup fields on this endpoint at all — see the `field` parameter.
7815
+ *
7816
+ * While paging a narrowed lookup, keep every other parameter identical. The cursor is valid only for the exact query that returned it — changing `order`, `sortFrom`, `sortTo`, or dropping them altogether, is rejected rather than silently resumed at a position that means something different in the new query.
7817
+ *
7818
+ * Results are paginated: set `limit` for the page size and pass the returned `nextCursor` back as `startFrom` for the next page. **Keep paging until `nextCursor` is null** — a page can come back empty or shorter than `limit` while more results remain, so an empty page is not the end of the results. Requires the `records:r:<type>` scope.
7779
7819
  *
7780
7820
  * @param {Vectros.LookupRecordsRequest} request
7781
7821
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7787,22 +7827,40 @@ var RecordsClient = class {
7787
7827
  * type: "intake_form",
7788
7828
  * field: "email",
7789
7829
  * value: "jane@example.com",
7830
+ * values: ["open"],
7790
7831
  * prefix: "jane",
7791
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
7832
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
7792
7833
  * })
7793
7834
  */
7794
7835
  lookupRecords(request, requestOptions) {
7795
7836
  return HttpResponsePromise.fromPromise(this.__lookupRecords(request, requestOptions));
7796
7837
  }
7797
7838
  async __lookupRecords(request, requestOptions) {
7798
- const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, includePayload, order } = request;
7839
+ const {
7840
+ type: type_,
7841
+ field,
7842
+ value,
7843
+ values,
7844
+ from: from_,
7845
+ to,
7846
+ prefix,
7847
+ sortFrom,
7848
+ sortTo,
7849
+ startFrom,
7850
+ limit,
7851
+ includePayload,
7852
+ order
7853
+ } = request;
7799
7854
  const _queryParams = {
7800
7855
  type: type_,
7801
7856
  field,
7802
7857
  value,
7858
+ values,
7803
7859
  from: from_,
7804
7860
  to,
7805
7861
  prefix,
7862
+ sortFrom,
7863
+ sortTo,
7806
7864
  startFrom,
7807
7865
  limit,
7808
7866
  includePayload,
@@ -7846,7 +7904,7 @@ var RecordsClient = class {
7846
7904
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records/lookup");
7847
7905
  }
7848
7906
  /**
7849
- * Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant. Requires the `records:r:<type>` scope.
7907
+ * Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here, as does narrowing a `value` lookup by the field's sort key with `sortFrom`/`sortTo` — see the GET variant for what the sort key is and when narrowing by it is available. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant; keep paging until `nextCursor` is null rather than stopping on an empty page. Requires the `records:r:<type>` scope.
7850
7908
  *
7851
7909
  * @param {Vectros.RecordLookupRequest} request
7852
7910
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8033,13 +8091,15 @@ var SchemasClient = class {
8033
8091
  * @param {Vectros.ListSchemasRequest} request
8034
8092
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8035
8093
  *
8094
+ * @throws {@link Vectros.BadRequestError}
8095
+ *
8036
8096
  * @example
8037
8097
  * await client.schemas.listSchemas({
8038
8098
  * userId: "550e8400-e29b-41d4-a716-446655440000",
8039
8099
  * scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8",
8040
8100
  * surface: "document",
8041
8101
  * recordType: "intake_form",
8042
- * startFrom: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
8102
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
8043
8103
  * })
8044
8104
  */
8045
8105
  listSchemas(request = {}, requestOptions) {
@@ -8079,16 +8139,21 @@ var SchemasClient = class {
8079
8139
  return { data: _response.body, rawResponse: _response.rawResponse };
8080
8140
  }
8081
8141
  if (_response.error.reason === "status-code") {
8082
- throw new VectrosError({
8083
- statusCode: _response.error.statusCode,
8084
- body: _response.error.body,
8085
- rawResponse: _response.rawResponse
8086
- });
8142
+ switch (_response.error.statusCode) {
8143
+ case 400:
8144
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
8145
+ default:
8146
+ throw new VectrosError({
8147
+ statusCode: _response.error.statusCode,
8148
+ body: _response.error.body,
8149
+ rawResponse: _response.rawResponse
8150
+ });
8151
+ }
8087
8152
  }
8088
8153
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
8089
8154
  }
8090
8155
  /**
8091
- * Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing schema was returned) tells the two apart. To reconcile an existing schema to the submitted shape instead of returning it unchanged, set `?upsert=true` (this also requires the `schemas:w` scope; only legal schema changes are applied — migration-locked changes are rejected). Requires the `schemas:w` scope.
8156
+ * Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing schema was returned) tells the two apart. To reconcile an existing schema to the submitted shape instead of returning it unchanged, set `?upsert=true` (this also requires the `schemas:u` scope; only legal schema changes are applied — migration-locked changes are rejected). Requires the `schemas:c` scope to create. Being returned the existing schema on a collision is a read of that schema's data and additionally requires the `schemas:r` scope — a credential holding `schemas:c` alone receives a `400` ("already in use") on collision instead of the schema.
8092
8157
  *
8093
8158
  * @param {Vectros.CreateSchemaRequest} request
8094
8159
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8232,12 +8297,13 @@ var SchemasClient = class {
8232
8297
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}");
8233
8298
  }
8234
8299
  /**
8235
- * Updates a record schema. Fields you omit are preserved; `typeName` is immutable and cannot be changed. Collection fields (`fields`, `lookupFields`, `renderHints`, `capabilities`) are replaced in full when supplied. Requires the `schemas:w` scope.
8300
+ * Updates a record schema. Fields you omit are preserved; `typeName` is immutable and cannot be changed. Collection fields (`fields`, `lookupFields`, `renderHints`, `capabilities`) are replaced in full when supplied. Requires the `schemas:u` scope.
8236
8301
  *
8237
8302
  * @param {Vectros.UpdateSchemaRequest} request
8238
8303
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8239
8304
  *
8240
8305
  * @throws {@link Vectros.BadRequestError}
8306
+ * @throws {@link Vectros.ForbiddenError}
8241
8307
  * @throws {@link Vectros.NotFoundError}
8242
8308
  * @throws {@link Vectros.TooManyRequestsError}
8243
8309
  *
@@ -8286,6 +8352,8 @@ var SchemasClient = class {
8286
8352
  switch (_response.error.statusCode) {
8287
8353
  case 400:
8288
8354
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8355
+ case 403:
8356
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
8289
8357
  case 404:
8290
8358
  throw new NotFoundError(_response.error.body, _response.rawResponse);
8291
8359
  case 429:
@@ -8301,7 +8369,7 @@ var SchemasClient = class {
8301
8369
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
8302
8370
  }
8303
8371
  /**
8304
- * Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. Requires the `schemas:w` scope.
8372
+ * Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. Requires the `schemas:d` scope.
8305
8373
  *
8306
8374
  * @param {Vectros.DeleteSchemaRequest} request
8307
8375
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8603,8 +8671,8 @@ function warnOnSuboptimalKey(key, log) {
8603
8671
 
8604
8672
  // src/build-info.ts
8605
8673
  var BUILD_INFO = {
8606
- mcpServer: true ? "0.11.0" : "dev",
8607
- sdk: true ? "0.36.0" : "dev"
8674
+ mcpServer: true ? "0.13.0" : "dev",
8675
+ sdk: true ? "0.38.0" : "dev"
8608
8676
  };
8609
8677
 
8610
8678
  // src/log.ts
@@ -8644,7 +8712,9 @@ async function resolveIdentity({
8644
8712
  const derived = {
8645
8713
  status: "ok",
8646
8714
  environment: deriveEnvironment(environment),
8647
- principalType: derivePrincipalType(apiKey)
8715
+ principalType: derivePrincipalType(apiKey),
8716
+ mcpServerVersion: BUILD_INFO.mcpServer,
8717
+ sdkVersion: BUILD_INFO.sdk
8648
8718
  };
8649
8719
  if (!apiKey || !environment) {
8650
8720
  log.debug({ component: "identity", mode: "derived-only" }, "identity derived (no fetch)");
@@ -8672,7 +8742,13 @@ async function resolveIdentity({
8672
8742
  );
8673
8743
  }
8674
8744
  }
8675
- const merged = { ...derived, ...extended, status: "ok" };
8745
+ const merged = {
8746
+ ...derived,
8747
+ ...extended,
8748
+ status: "ok",
8749
+ mcpServerVersion: derived.mcpServerVersion,
8750
+ sdkVersion: derived.sdkVersion
8751
+ };
8676
8752
  log.debug(
8677
8753
  {
8678
8754
  component: "identity",
@@ -8994,14 +9070,23 @@ import { z as z2 } from "zod";
8994
9070
  function pageItems(page) {
8995
9071
  return page.data ?? [];
8996
9072
  }
9073
+ var MAX_PAGES = 100;
9074
+ var MAX_ROWS = 1e6;
8997
9075
  async function drainPages(fetchPage) {
8998
9076
  const all = [];
8999
9077
  let startFrom;
9078
+ let pages = 0;
9000
9079
  for (; ; ) {
9001
9080
  const page = await fetchPage(startFrom);
9002
- if (page.data?.length) all.push(...page.data);
9081
+ pages++;
9082
+ if (page.data?.length) for (const item of page.data) all.push(item);
9003
9083
  const next = page.nextCursor;
9004
9084
  if (!next) break;
9085
+ if (pages > MAX_PAGES || all.length > MAX_ROWS) {
9086
+ throw new Error(
9087
+ `drainPages: exceeded the runaway guard (${all.length} rows over ${pages} pages, limits ${MAX_ROWS}/${MAX_PAGES}) without the cursor going null. Refusing to return a partial result \u2014 the backend may be returning empty pages with a live cursor.`
9088
+ );
9089
+ }
9005
9090
  startFrom = next;
9006
9091
  }
9007
9092
  return all;
@@ -9012,14 +9097,23 @@ var MCP_DEFAULT_LIMIT2 = 3;
9012
9097
  var MCP_MAX_LIMIT2 = 100;
9013
9098
  var inputSchema2 = {
9014
9099
  type: z2.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9015
- // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9100
+ // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | values | from+to | prefix.
9016
9101
  field: z2.string().optional().describe(
9017
- 'Lookup mode: name of the lookup-indexed field to query by. `externalId` is always queryable \u2014 e.g. `{type:"control", field:"externalId", value:"ctrl-scoped-key"}` finds the record you created under that externalId. (record_get / record_update also accept externalId directly.)'
9102
+ 'Lookup mode: name of the lookup-indexed field to query by. `externalId` is always queryable \u2014 e.g. `{type:"control", field:"externalId", value:"ctrl-scoped-key"}` finds the record you created under that externalId. (record_get / record_update also accept externalId directly.) For a SCHEMA-declared composite lookup \u2014 check list_schemas\' lookupFields for a fieldNames entry, not any two fields you pick \u2014 give those field names comma-joined in the declared order (e.g. "status,area"). Use `values` to match on several of those fields at once, or `value` to match on just the FIRST field named (equivalent to a one-element `values`); range/prefix are not valid on a composite field.'
9103
+ ),
9104
+ value: z2.string().optional().describe(
9105
+ "Lookup mode (equality): exact-match value for `field`. Also valid when `field` names a SCHEMA-declared composite lookup \u2014 matches just the first named field, grouping by the rest (equivalent to `values` with one entry)."
9106
+ ),
9107
+ values: z2.array(z2.string()).min(1).max(3).optional().describe(
9108
+ 'Lookup mode (composite equality): only valid against a lookup the SCHEMA declares over several fields together (a fieldNames entry in list_schemas \u2014 you cannot combine two ordinary single-field lookups this way). One value per field, in the order the schema declares them (e.g. field="status,area", values=["open","billing"]). Mutually exclusive with `value`; a single-element array is exactly `value` written uniformly. Supplying FEWER values than the lookup declares is allowed \u2014 as long as they\'re a leading run of its fields \u2014 and returns records grouped by the fields left unspecified; `order` then sorts within each group, not across them. Record lookups only (not documents/users/entities).'
9018
9109
  ),
9019
- value: z2.string().optional().describe("Lookup mode (equality): exact-match value for `field`."),
9020
9110
  from: z2.string().optional().describe("Lookup mode (range): inclusive lower bound; requires `to`. Non-sensitive fields only."),
9021
9111
  to: z2.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
9022
9112
  prefix: z2.string().optional().describe("Lookup mode (prefix): match values starting with this. String, non-sensitive fields only."),
9113
+ sortFrom: z2.string().optional().describe(
9114
+ "Lookup mode (equality/composite only): inclusive lower bound on the lookup field's sort key, narrowing a `value`/`values` match to a window instead of paging the whole match. Give the bound in the sort field's own units (epoch milliseconds for `createdAt`/`lastUpdated`). With `values`, requires every value be supplied (no grouping) for one continuous ordering. Not valid with `from`/`to`/`prefix`."
9115
+ ),
9116
+ sortTo: z2.string().optional().describe("Lookup mode (equality/composite only): inclusive upper bound on the sort key; pairs with `sortFrom`."),
9023
9117
  order: z2.enum(["asc", "desc"]).optional().describe(
9024
9118
  "Lookup mode: sort direction by the looked-up field. `asc` (default) or `desc` \u2014 use `desc` with a range/prefix lookup to get the most-recent / highest values first (e.g. latest-N). Ignored in list mode."
9025
9119
  ),
@@ -9036,54 +9130,106 @@ var inputSchema2 = {
9036
9130
  var recordQuery = ({ client, log }) => ({
9037
9131
  name: "record_query",
9038
9132
  title: "Record query (list or lookup)",
9039
- description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields too.\n \u2022 List by type: omit `field`; optionally filter by `userId` or `scope` (`namespace:value`).\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` records (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. Mode is auto-detected from the arguments present.",
9133
+ description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `values` (exact, composite \u2014 one value per field; a single-element array is the same as `value`), `from`+`to` (range), or `prefix`. `values` with more than one entry, or a comma-joined `field`, ONLY works for a lookup the schema itself declared over several fields together \u2014 check list_schemas' lookupFields for a fieldNames entry, then pass those field names comma-joined as `field`; `from`/`to`/`prefix` are not valid there. Works on sensitive fields too. Optionally narrow an exact match to a window with `sortFrom`/`sortTo`.\n \u2022 List by type: omit `field`; optionally filter by `userId` or `scope` (`namespace:value`).\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` records (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. A composite lookup given fewer than its full field count returns records grouped by the unspecified fields, ordered within each group. Mode is auto-detected from the arguments present.",
9040
9134
  inputSchema: inputSchema2,
9041
9135
  handler: async (args) => {
9042
9136
  const limit = args.limit ?? MCP_DEFAULT_LIMIT2;
9043
9137
  const type = args.type;
9044
9138
  const field = args.field;
9045
9139
  const value = args.value;
9140
+ const values = args.values;
9046
9141
  const from = args.from;
9047
9142
  const to = args.to;
9048
9143
  const prefix = args.prefix;
9144
+ const sortFrom = args.sortFrom;
9145
+ const sortTo = args.sortTo;
9049
9146
  const startFrom = args.startFrom;
9147
+ if (!field && (value !== void 0 || values !== void 0 || from !== void 0 || to !== void 0 || prefix !== void 0 || sortFrom !== void 0 || sortTo !== void 0)) {
9148
+ return toolError(
9149
+ "record_query",
9150
+ new Error(
9151
+ "'value'/'values'/'from'/'to'/'prefix'/'sortFrom'/'sortTo' require 'field' \u2014 without it, list mode runs and silently ignores them. Pass 'field' to run a lookup, or drop these to list by type."
9152
+ )
9153
+ );
9154
+ }
9050
9155
  try {
9051
9156
  let page;
9052
9157
  if (field) {
9158
+ const fieldNames = field.split(",").map((f) => f.trim()).filter(Boolean);
9159
+ const normalizedField = fieldNames.join(",");
9160
+ const isCompositeField = fieldNames.length > 1;
9053
9161
  const hasEquality = value !== void 0;
9162
+ const hasComposite = values !== void 0;
9054
9163
  const hasRange = from !== void 0 || to !== void 0;
9055
9164
  const hasPrefix = prefix !== void 0;
9056
- const modes = Number(hasEquality) + Number(hasRange) + Number(hasPrefix);
9165
+ const modes = Number(hasEquality) + Number(hasComposite) + Number(hasRange) + Number(hasPrefix);
9057
9166
  if (modes === 0) {
9058
9167
  return toolError(
9059
9168
  "record_query",
9060
9169
  new Error(
9061
- `lookup on field '${field}' needs one of: 'value' (exact), 'from'+'to' (range), or 'prefix'.`
9170
+ `lookup on field '${field}' needs one of: 'value' (exact), 'values' (composite exact), 'from'+'to' (range), or 'prefix'.`
9062
9171
  )
9063
9172
  );
9064
9173
  }
9065
9174
  if (modes > 1) {
9066
9175
  return toolError(
9067
9176
  "record_query",
9068
- new Error("'value', 'from'/'to', and 'prefix' are mutually exclusive \u2014 provide exactly one.")
9177
+ new Error(
9178
+ "'value', 'values', 'from'/'to', and 'prefix' are mutually exclusive \u2014 provide exactly one."
9179
+ )
9069
9180
  );
9070
9181
  }
9071
9182
  if (hasRange && !(from !== void 0 && to !== void 0)) {
9072
9183
  return toolError("record_query", new Error("range lookup requires both 'from' and 'to'."));
9073
9184
  }
9185
+ if (isCompositeField && (hasRange || hasPrefix)) {
9186
+ return toolError(
9187
+ "record_query",
9188
+ new Error(
9189
+ `'field' names ${fieldNames.length} fields ('${normalizedField}') \u2014 a composite lookup is exact-match only ('value' or 'values'), not 'from'/'to'/'prefix'.`
9190
+ )
9191
+ );
9192
+ }
9193
+ if (hasComposite && values.length > fieldNames.length) {
9194
+ return toolError(
9195
+ "record_query",
9196
+ new Error(
9197
+ `'values' has ${values.length} entries but 'field' names only ${fieldNames.length} field(s) ('${normalizedField}') \u2014 supply at most one value per field, in order (fewer only as a leading run).`
9198
+ )
9199
+ );
9200
+ }
9201
+ if (sortFrom !== void 0 || sortTo !== void 0) {
9202
+ if (!(hasEquality || hasComposite)) {
9203
+ return toolError(
9204
+ "record_query",
9205
+ new Error("'sortFrom'/'sortTo' narrow a 'value' or 'values' match only \u2014 not 'from'/'to'/'prefix'.")
9206
+ );
9207
+ }
9208
+ if (hasComposite && values.length < fieldNames.length) {
9209
+ return toolError(
9210
+ "record_query",
9211
+ new Error(
9212
+ `'sortFrom'/'sortTo' with 'values' requires a value for every field named in 'field' (got ${values.length} of ${fieldNames.length}) \u2014 a partial/grouped match has no single continuous ordering to window.`
9213
+ )
9214
+ );
9215
+ }
9216
+ }
9074
9217
  page = await client.records.lookupRecordsByBody({
9075
9218
  type,
9076
- field,
9219
+ field: normalizedField,
9077
9220
  value,
9221
+ values,
9078
9222
  from,
9079
9223
  to,
9080
9224
  prefix,
9225
+ sortFrom,
9226
+ sortTo,
9081
9227
  order: args.order,
9082
9228
  startFrom,
9083
9229
  limit
9084
9230
  });
9085
9231
  log.debug(
9086
- { tool: "record_query", mode: "lookup", type, field, returned: pageItems(page).length },
9232
+ { tool: "record_query", mode: "lookup", type, field: normalizedField, returned: pageItems(page).length },
9087
9233
  "record_query lookup ok"
9088
9234
  );
9089
9235
  } else {
@@ -9116,14 +9262,14 @@ var record_query_default = recordQuery;
9116
9262
  import { z as z3 } from "zod";
9117
9263
 
9118
9264
  // src/tools/resolve-external-id.ts
9119
- function validateSelector(sel, entity) {
9265
+ function validateSelector(sel, entity, fieldName) {
9120
9266
  const hasId = sel.id !== void 0 && sel.id !== "";
9121
9267
  const hasExt = sel.externalId !== void 0 && sel.externalId !== "";
9122
9268
  if (hasId && hasExt) {
9123
- return `Provide either id or externalId (not both) to identify the ${entity}.`;
9269
+ return `Provide either ${fieldName} or externalId (not both) to identify the ${entity}.`;
9124
9270
  }
9125
9271
  if (!hasId && !hasExt) {
9126
- return `Provide id, or externalId + type, to identify the ${entity}.`;
9272
+ return `Provide ${fieldName}, or externalId + type, to identify the ${entity}.`;
9127
9273
  }
9128
9274
  if (hasExt && (sel.type === void 0 || sel.type === "")) {
9129
9275
  return "type is required when selecting by externalId (externalId is unique within a type).";
@@ -9131,7 +9277,7 @@ function validateSelector(sel, entity) {
9131
9277
  return void 0;
9132
9278
  }
9133
9279
  async function resolveRecordIdByExternalId(client, sel) {
9134
- const err = validateSelector(sel, "record");
9280
+ const err = validateSelector(sel, "record", "id");
9135
9281
  if (err) return { error: err };
9136
9282
  if (sel.id) return { id: sel.id };
9137
9283
  const page = await client.records.lookupRecordsByBody({
@@ -9147,7 +9293,7 @@ async function resolveRecordIdByExternalId(client, sel) {
9147
9293
  return { id: items[0].id };
9148
9294
  }
9149
9295
  async function resolveDocumentIdByExternalId(client, sel) {
9150
- const err = validateSelector(sel, "document");
9296
+ const err = validateSelector(sel, "document", "documentId");
9151
9297
  if (err) return { error: err };
9152
9298
  if (sel.id) return { id: sel.id };
9153
9299
  const page = await client.documents.lookupDocumentsByBody({
@@ -10022,7 +10168,7 @@ var inputSchema18 = {
10022
10168
  var currentIdentity = ({ log, apiKey, environment }) => ({
10023
10169
  name: "current_identity",
10024
10170
  title: "Current identity (tenant + principal scope)",
10025
- description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, principalLabel, and (for scoped credentials) allowedActions plus dataScope \u2014 the credential's reach as { userId, scopes[] }, where each scope is a `namespace:value` string (e.g. \"org:<uuid>\"). Use this when the user asks 'what can you do here?' or 'what tenant am I in?'. Calls GET /v1/ping under the hood.",
10171
+ description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, principalLabel, and (for scoped credentials) allowedActions plus dataScope \u2014 the credential's reach as { userId, scopes[] }, where each scope is a `namespace:value` string (e.g. \"org:<uuid>\"). Also reports mcpServerVersion (this server's own package version) and sdkVersion (the bundled @vectros-ai/sdk version) so a caller can tell what build it's talking to. Use this when the user asks 'what can you do here?', 'what tenant am I in?', or 'what version is this?'. Calls GET /v1/ping under the hood.",
10026
10172
  inputSchema: inputSchema18,
10027
10173
  handler: async () => {
10028
10174
  try {
@@ -10500,7 +10646,7 @@ var identityResource = ({ log, apiKey, environment }) => ({
10500
10646
  name: "identity",
10501
10647
  uri: "vectros://identity",
10502
10648
  title: "Current identity (tenant + principal scope)",
10503
- description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, tenantId, environment, principalType, principalKeyId, principalLabel, and (for scoped credentials) allowedActions + dataScope ({ userId, scopes[] }). The /v1/ping backing call ships this extended shape today.",
10649
+ description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, tenantId, environment, principalType, principalKeyId, principalLabel, (for scoped credentials) allowedActions + dataScope ({ userId, scopes[] }), plus mcpServerVersion and sdkVersion (this build's own version and the bundled @vectros-ai/sdk version). The /v1/ping backing call ships the extended shape today; the version fields are always client-derived.",
10504
10650
  mimeType: "application/json",
10505
10651
  read: async () => {
10506
10652
  const identity = await resolveIdentity({ log, apiKey, environment });