@vectros-ai/mcp-server 0.10.0 → 0.12.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.js CHANGED
@@ -77,6 +77,7 @@ __export2(api_exports, {
77
77
  FileUploadRequest: () => FileUploadRequest,
78
78
  ForbiddenError: () => ForbiddenError,
79
79
  IdentityLookupRequest: () => IdentityLookupRequest,
80
+ IndexFailure: () => IndexFailure,
80
81
  InternalServerError: () => InternalServerError,
81
82
  ListEntitiesRequestOrder: () => ListEntitiesRequestOrder,
82
83
  ListUsersRequestOrder: () => ListUsersRequestOrder,
@@ -598,6 +599,17 @@ var IdentityLookupRequest;
598
599
  Desc: "desc"
599
600
  };
600
601
  })(IdentityLookupRequest || (IdentityLookupRequest = {}));
602
+ var IndexFailure;
603
+ ((IndexFailure2) => {
604
+ IndexFailure2.Code = {
605
+ SourceUnavailable: "SOURCE_UNAVAILABLE",
606
+ TextIndexFailed: "TEXT_INDEX_FAILED",
607
+ EmbeddingFailed: "EMBEDDING_FAILED",
608
+ IndexingFailed: "INDEXING_FAILED",
609
+ VectorLimitExceeded: "VECTOR_LIMIT_EXCEEDED",
610
+ Internal: "INTERNAL"
611
+ };
612
+ })(IndexFailure || (IndexFailure = {}));
601
613
  var ModelDataVersionResponse;
602
614
  ((ModelDataVersionResponse2) => {
603
615
  ModelDataVersionResponse2.ChangeType = {
@@ -4114,7 +4126,7 @@ var AuthClient = class {
4114
4126
  );
4115
4127
  }
4116
4128
  /**
4117
- * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user, organization, or client. Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
4129
+ * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user or identity entity (in any namespace). Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
4118
4130
  *
4119
4131
  * @param {Vectros.TokenRequest} request
4120
4132
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4331,7 +4343,7 @@ var ComplianceClient = class {
4331
4343
  this._options = normalizeClientOptionsWithAuth(options);
4332
4344
  }
4333
4345
  /**
4334
- * Submits a right-to-erasure request for a single end-subject (a user, client, or organization). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
4346
+ * Submits a right-to-erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
4335
4347
  *
4336
4348
  * @param {Vectros.ErasureRequest} request
4337
4349
  * @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4640,7 +4652,7 @@ var DocumentsClient = class {
4640
4652
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
4641
4653
  }
4642
4654
  /**
4643
- * 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.
4655
+ * 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.
4644
4656
  *
4645
4657
  * @param {Vectros.IngestDocumentRequest} request
4646
4658
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4966,25 +4978,29 @@ var DocumentsClient = class {
4966
4978
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
4967
4979
  }
4968
4980
  /**
4969
- * 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.
4981
+ * 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.
4970
4982
  *
4971
4983
  * @param {Vectros.LookupDocumentsRequest} request
4972
4984
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4973
4985
  *
4986
+ * @throws {@link Vectros.BadRequestError}
4987
+ *
4974
4988
  * @example
4975
4989
  * await client.documents.lookupDocuments({
4976
4990
  * type: "invoice",
4977
4991
  * field: "po_number",
4978
4992
  * value: "PO-1001",
4979
4993
  * prefix: "PO-2024",
4980
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
4994
+ * startFrom: "550e8400-e29b-41d4-a716-446655440000",
4995
+ * userId: "550e8400-e29b-41d4-a716-446655440000",
4996
+ * scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
4981
4997
  * })
4982
4998
  */
4983
4999
  lookupDocuments(request, requestOptions) {
4984
5000
  return HttpResponsePromise.fromPromise(this.__lookupDocuments(request, requestOptions));
4985
5001
  }
4986
5002
  async __lookupDocuments(request, requestOptions) {
4987
- const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, order } = request;
5003
+ const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, order, userId, scope } = request;
4988
5004
  const _queryParams = {
4989
5005
  type: type_,
4990
5006
  field,
@@ -4994,7 +5010,9 @@ var DocumentsClient = class {
4994
5010
  prefix,
4995
5011
  startFrom,
4996
5012
  limit,
4997
- order: order != null ? order : void 0
5013
+ order: order != null ? order : void 0,
5014
+ userId,
5015
+ scope
4998
5016
  };
4999
5017
  const _authRequest = await this._options.authProvider.getAuthRequest();
5000
5018
  const _headers = mergeHeaders(
@@ -5020,11 +5038,16 @@ var DocumentsClient = class {
5020
5038
  return { data: _response.body, rawResponse: _response.rawResponse };
5021
5039
  }
5022
5040
  if (_response.error.reason === "status-code") {
5023
- throw new VectrosError({
5024
- statusCode: _response.error.statusCode,
5025
- body: _response.error.body,
5026
- rawResponse: _response.rawResponse
5027
- });
5041
+ switch (_response.error.statusCode) {
5042
+ case 400:
5043
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
5044
+ default:
5045
+ throw new VectrosError({
5046
+ statusCode: _response.error.statusCode,
5047
+ body: _response.error.body,
5048
+ rawResponse: _response.rawResponse
5049
+ });
5050
+ }
5028
5051
  }
5029
5052
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/lookup");
5030
5053
  }
@@ -5392,7 +5415,7 @@ var FoldersClient = class {
5392
5415
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
5393
5416
  }
5394
5417
  /**
5395
- * 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.
5418
+ * 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.
5396
5419
  *
5397
5420
  * @param {Vectros.CreateFolderRequest} request
5398
5421
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5850,7 +5873,7 @@ var IdentityClient = class {
5850
5873
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/entities/{namespace}");
5851
5874
  }
5852
5875
  /**
5853
- * 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.
5876
+ * 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.
5854
5877
  *
5855
5878
  * @param {Vectros.CreateEntityRequest} request
5856
5879
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6294,7 +6317,7 @@ var IdentityClient = class {
6294
6317
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/namespaces/{namespace}");
6295
6318
  }
6296
6319
  /**
6297
- * 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.
6320
+ * 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.
6298
6321
  *
6299
6322
  * @param {Vectros.UpdateNamespaceRequest} request
6300
6323
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6307,7 +6330,8 @@ var IdentityClient = class {
6307
6330
  * await client.identity.updateNamespace({
6308
6331
  * namespace: "team",
6309
6332
  * body: {
6310
- * namespace: "team"
6333
+ * namespace: "team",
6334
+ * specificityRank: 1500
6311
6335
  * }
6312
6336
  * })
6313
6337
  */
@@ -6472,7 +6496,7 @@ var IdentityClient = class {
6472
6496
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/namespaces");
6473
6497
  }
6474
6498
  /**
6475
- * 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.
6499
+ * 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.
6476
6500
  *
6477
6501
  * @param {Vectros.NamespaceRequest} request
6478
6502
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6483,7 +6507,8 @@ var IdentityClient = class {
6483
6507
  *
6484
6508
  * @example
6485
6509
  * await client.identity.registerNamespace({
6486
- * namespace: "team"
6510
+ * namespace: "team",
6511
+ * specificityRank: 1500
6487
6512
  * })
6488
6513
  */
6489
6514
  registerNamespace(request, requestOptions) {
@@ -6600,7 +6625,7 @@ var IdentityClient = class {
6600
6625
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
6601
6626
  }
6602
6627
  /**
6603
- * 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.
6628
+ * 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.
6604
6629
  *
6605
6630
  * @param {Vectros.CreateUserRequest} request
6606
6631
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7471,7 +7496,7 @@ var RecordsClient = class {
7471
7496
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
7472
7497
  }
7473
7498
  /**
7474
- * 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.
7499
+ * 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.
7475
7500
  *
7476
7501
  * @param {Vectros.CreateRecordRequest} request
7477
7502
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8062,6 +8087,8 @@ var SchemasClient = class {
8062
8087
  * @param {Vectros.ListSchemasRequest} request
8063
8088
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8064
8089
  *
8090
+ * @throws {@link Vectros.BadRequestError}
8091
+ *
8065
8092
  * @example
8066
8093
  * await client.schemas.listSchemas({
8067
8094
  * userId: "550e8400-e29b-41d4-a716-446655440000",
@@ -8108,16 +8135,21 @@ var SchemasClient = class {
8108
8135
  return { data: _response.body, rawResponse: _response.rawResponse };
8109
8136
  }
8110
8137
  if (_response.error.reason === "status-code") {
8111
- throw new VectrosError({
8112
- statusCode: _response.error.statusCode,
8113
- body: _response.error.body,
8114
- rawResponse: _response.rawResponse
8115
- });
8138
+ switch (_response.error.statusCode) {
8139
+ case 400:
8140
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
8141
+ default:
8142
+ throw new VectrosError({
8143
+ statusCode: _response.error.statusCode,
8144
+ body: _response.error.body,
8145
+ rawResponse: _response.rawResponse
8146
+ });
8147
+ }
8116
8148
  }
8117
8149
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
8118
8150
  }
8119
8151
  /**
8120
- * 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.
8152
+ * 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.
8121
8153
  *
8122
8154
  * @param {Vectros.CreateSchemaRequest} request
8123
8155
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8261,7 +8293,7 @@ var SchemasClient = class {
8261
8293
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}");
8262
8294
  }
8263
8295
  /**
8264
- * 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.
8296
+ * 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.
8265
8297
  *
8266
8298
  * @param {Vectros.UpdateSchemaRequest} request
8267
8299
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8330,7 +8362,7 @@ var SchemasClient = class {
8330
8362
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
8331
8363
  }
8332
8364
  /**
8333
- * 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.
8365
+ * 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.
8334
8366
  *
8335
8367
  * @param {Vectros.DeleteSchemaRequest} request
8336
8368
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8627,8 +8659,8 @@ function warnOnSuboptimalKey(key, log) {
8627
8659
 
8628
8660
  // src/build-info.ts
8629
8661
  var BUILD_INFO = {
8630
- mcpServer: true ? "0.10.0" : "dev",
8631
- sdk: true ? "0.35.0" : "dev"
8662
+ mcpServer: true ? "0.12.0" : "dev",
8663
+ sdk: true ? "0.37.0" : "dev"
8632
8664
  };
8633
8665
 
8634
8666
  // src/log.ts
@@ -8832,31 +8864,45 @@ async function consumeStream(stream, onProgress) {
8832
8864
  }
8833
8865
 
8834
8866
  // src/tools/errors.ts
8867
+ function asErrorBody(body) {
8868
+ if (body && typeof body === "object") return body;
8869
+ return void 0;
8870
+ }
8835
8871
  function toolError(toolName, err, docPointer) {
8836
8872
  let summary = `${toolName} failed`;
8837
8873
  let detail = String(err);
8874
+ let errorCode;
8875
+ let requestId;
8838
8876
  if (err instanceof StreamError) {
8839
8877
  summary = `${toolName} stream error`;
8840
8878
  detail = err.message;
8841
8879
  } else if (err instanceof Error) {
8842
8880
  const e = err;
8843
8881
  detail = e.message;
8882
+ const body = asErrorBody(e.body);
8883
+ if (body) {
8884
+ if (typeof body.message === "string" && body.message.trim().length > 0) detail = body.message;
8885
+ if (typeof body.errorCode === "string" && body.errorCode.length > 0) errorCode = body.errorCode;
8886
+ if (typeof body.requestId === "string" && body.requestId.length > 0) requestId = body.requestId;
8887
+ }
8844
8888
  if (e.statusCode) {
8845
- summary = `${toolName} failed: HTTP ${e.statusCode}`;
8889
+ summary = `${toolName} failed: HTTP ${e.statusCode}${errorCode ? ` (${errorCode})` : ""}`;
8846
8890
  } else if (e.name && e.name !== "Error") {
8847
8891
  summary = `${toolName} failed: ${e.name}`;
8848
8892
  }
8849
8893
  }
8850
- const pointer = docPointer ? `
8894
+ const trailer = (errorCode ? `
8895
+ errorCode: ${errorCode}` : "") + (requestId ? `
8896
+ requestId: ${requestId}` : "") + (docPointer ? `
8851
8897
 
8852
- See: ${docPointer}` : "";
8898
+ See: ${docPointer}` : "");
8853
8899
  return {
8854
8900
  isError: true,
8855
8901
  content: [
8856
8902
  {
8857
8903
  type: "text",
8858
8904
  text: `${summary}
8859
- ${detail}${pointer}`
8905
+ ${detail}${trailer}`
8860
8906
  }
8861
8907
  ]
8862
8908
  };
@@ -8933,7 +8979,7 @@ var inputSchema = {
8933
8979
  var hybridSearch = ({ client, log }) => ({
8934
8980
  name: "hybrid_search",
8935
8981
  title: "Hybrid search",
8936
- description: "Search your tenant's indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to `limit` results (default 3, max 50 \u2014 raise it in one call when you can accept the larger result) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/scope), folder scope, a created date window, keyword precision (textMode OR/AND/PHRASE), relevance floors (minSimilarity/minTextRelevance), uniqueDocuments, and requireComplete (fail closed on a degraded backend). Tenant-isolated; the caller's scoped key fully constrains which content is visible. ARCHIVED (soft-retracted) documents never appear in results \u2014 fetch them by id via document_get, or restore them via document_update. To stay within the agent context window each hit returns its matched chunkText + a short snippet by default; set includeContext:true for the broader surrounding passage. If the whole result would be too large it is truncated to the top hits (truncated:true). totalResults is the full matching pool and hasMore:true means results remain past this page \u2014 raise limit or advance offset to fetch them. textLegEmpty:true flags that the keyword leg matched nothing (all textScores 0) \u2014 usually a too-long PHRASE query; retry with textMode:\"OR\".",
8982
+ description: 'Search your tenant\'s indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to `limit` results (default 3, max 50 \u2014 raise it in one call when you can accept the larger result) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/scope), folder scope, a created date window, keyword precision (textMode OR/AND/PHRASE), relevance floors (minSimilarity/minTextRelevance), uniqueDocuments, and requireComplete (fail closed on a degraded backend). Tenant-isolated; the caller\'s scoped key fully constrains which content is visible. ARCHIVED (soft-retracted) documents never appear in results \u2014 fetch them by id via document_get, or restore them via document_update. To stay within the agent context window each hit returns its matched chunkText + a short snippet by default; set includeContext:true for the broader surrounding passage. If the whole result would be too large it is truncated to the top hits (truncated:true). totalResults is the full matching pool and hasMore:true means results remain past this page \u2014 raise limit or advance offset to fetch them. textLegEmpty:true flags that the keyword (BM25) leg matched nothing (all textScores 0) \u2014 typically the default PHRASE mode finding no contiguous match for a long natural-language query. Loosen the keyword leg: shorten the query to its key terms, or set textMode "AND" (all terms, higher precision) or "OR" (any term, broad recall).',
8937
8983
  inputSchema,
8938
8984
  handler: async (args) => {
8939
8985
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9126,14 +9172,14 @@ var record_query_default = recordQuery;
9126
9172
  var import_zod3 = require("zod");
9127
9173
 
9128
9174
  // src/tools/resolve-external-id.ts
9129
- function validateSelector(sel, entity) {
9175
+ function validateSelector(sel, entity, fieldName) {
9130
9176
  const hasId = sel.id !== void 0 && sel.id !== "";
9131
9177
  const hasExt = sel.externalId !== void 0 && sel.externalId !== "";
9132
9178
  if (hasId && hasExt) {
9133
- return `Provide either id or externalId (not both) to identify the ${entity}.`;
9179
+ return `Provide either ${fieldName} or externalId (not both) to identify the ${entity}.`;
9134
9180
  }
9135
9181
  if (!hasId && !hasExt) {
9136
- return `Provide id, or externalId + type, to identify the ${entity}.`;
9182
+ return `Provide ${fieldName}, or externalId + type, to identify the ${entity}.`;
9137
9183
  }
9138
9184
  if (hasExt && (sel.type === void 0 || sel.type === "")) {
9139
9185
  return "type is required when selecting by externalId (externalId is unique within a type).";
@@ -9141,7 +9187,7 @@ function validateSelector(sel, entity) {
9141
9187
  return void 0;
9142
9188
  }
9143
9189
  async function resolveRecordIdByExternalId(client, sel) {
9144
- const err = validateSelector(sel, "record");
9190
+ const err = validateSelector(sel, "record", "id");
9145
9191
  if (err) return { error: err };
9146
9192
  if (sel.id) return { id: sel.id };
9147
9193
  const page = await client.records.lookupRecordsByBody({
@@ -9157,7 +9203,7 @@ async function resolveRecordIdByExternalId(client, sel) {
9157
9203
  return { id: items[0].id };
9158
9204
  }
9159
9205
  async function resolveDocumentIdByExternalId(client, sel) {
9160
- const err = validateSelector(sel, "document");
9206
+ const err = validateSelector(sel, "document", "documentId");
9161
9207
  if (err) return { error: err };
9162
9208
  if (sel.id) return { id: sel.id };
9163
9209
  const page = await client.documents.lookupDocumentsByBody({
@@ -9185,7 +9231,7 @@ var inputSchema3 = {
9185
9231
  var recordGet = ({ client, log }) => ({
9186
9232
  name: "record_get",
9187
9233
  title: "Get a record by id or externalId",
9188
- description: "Fetch a single structured record, including the full payload. Select it EITHER by its Vectros `id` (e.g. from record_query / hybrid_search) OR by your own `externalId` + `type` \u2014 the same key you passed to record_create, resolved for you (no manual record_query lookup needed). Very large payloads are truncated to protect the agent context window.",
9234
+ description: "Fetch a single structured record, including the full payload. Select it EITHER by its Vectros `id` (e.g. from record_query / hybrid_search) OR by your own `externalId` + `type` \u2014 the same key you passed to record_create, resolved for you (no manual record_query lookup needed). Very large payloads are truncated to protect the agent context window. The response carries the record's processing `indexStatus`; when it is FAILED an `indexFailure.code` explains whether the content is still searchable (e.g. VECTOR_LIMIT_EXCEEDED keeps keyword search vs INDEXING_FAILED = not findable at all) \u2014 branch on the `code`, not the message.",
9189
9235
  inputSchema: inputSchema3,
9190
9236
  handler: async (args) => {
9191
9237
  const id = args.id;
@@ -9223,7 +9269,7 @@ var import_zod4 = require("zod");
9223
9269
  var inputSchema4 = {
9224
9270
  type: import_zod4.z.string().min(1, "type is required").describe('Record type / schema name (e.g. "task"). Must match an existing schema \u2014 see list_schemas.'),
9225
9271
  fields: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).describe(
9226
- "The record payload \u2014 a JSON object of field\u2192value, validated against the type schema. Call list_schemas to learn required fields, types, and enum values."
9272
+ "The record payload \u2014 a JSON object of field\u2192value, validated against the type schema. Call list_schemas to learn required fields, types, and enum values. Every number must fall within the signed 64-bit range (a value outside it, or with >38 significant digits, is refused with a 400 naming the field) \u2014 send a larger whole number (e.g. a big external id) as a STRING, which stores it exactly and supports exact-match lookup."
9227
9273
  ),
9228
9274
  externalId: import_zod4.z.string().optional().describe(
9229
9275
  "Stable caller-supplied id. Immutable; unique per type. Re-creating with the same externalId returns the existing record (idempotent) \u2014 use it to make creates safely retryable."
@@ -9241,7 +9287,7 @@ var inputSchema4 = {
9241
9287
  var recordCreate = ({ client, log }) => ({
9242
9288
  name: "record_create",
9243
9289
  title: "Create a record",
9244
- description: "Create a structured record of a given type. Provide `type` and `fields` (the payload, validated against the type schema \u2014 call list_schemas first to learn required fields and enums). Idempotent by `externalId`. Requires the key to allow records:c.",
9290
+ description: "Create a structured record of a given type. Provide `type` and `fields` (the payload, validated against the type schema \u2014 call list_schemas first to learn required fields and enums). Idempotent by `externalId`. Requires the key to allow records:c (and records:r to receive the existing record on a collision).",
9245
9291
  inputSchema: inputSchema4,
9246
9292
  handler: async (args) => {
9247
9293
  const type = args.type;
@@ -9599,7 +9645,7 @@ function isNotAvailable(err) {
9599
9645
  var documentGet = ({ client, log }) => ({
9600
9646
  name: "document_get",
9601
9647
  title: "Get document metadata (and optionally text)",
9602
- description: "Fetch metadata for a single document \u2014 select it by its Vectros `documentId` OR by your own `externalId` + `type` (resolved for you). Pass `includeText: true` to also fetch the text body (capped at ~8K tokens; `truncated: true` flag if cut). Text is available for every text-ingested document and for file-ingested documents unless they were uploaded with `storeText: false` (extracted text discarded after indexing) \u2014 then `textAvailable: false` is set; the original file is still retrievable via `includeDownloadUrl`. Pass `includeDownloadUrl: true` to also get a short-lived presigned `downloadUrl` for the original file (file-backed documents only). Returns the full document metadata, including its lifecycle `status` (ACTIVE, or ARCHIVED when soft-retracted from search \u2014 restorable via document_update) and its processing `indexStatus`.",
9648
+ description: "Fetch metadata for a single document \u2014 select it by its Vectros `documentId` OR by your own `externalId` + `type` (resolved for you). Pass `includeText: true` to also fetch the text body (capped at ~8K tokens; `truncated: true` flag if cut). Text is available for every text-ingested document and for file-ingested documents unless they were uploaded with `storeText: false` (extracted text discarded after indexing) \u2014 then `textAvailable: false` is set; the original file is still retrievable via `includeDownloadUrl`. Pass `includeDownloadUrl: true` to also get a short-lived presigned `downloadUrl` for the original file (file-backed documents only). Returns the full document metadata, including its lifecycle `status` (ACTIVE, or ARCHIVED when soft-retracted from search \u2014 restorable via document_update) and its processing `indexStatus`. When `indexStatus` is FAILED the response also carries `indexFailure` with a machine-readable `code` telling you whether the content is still findable \u2014 e.g. TEXT_INDEX_FAILED / VECTOR_LIMIT_EXCEEDED (one leg still serves it) vs INDEXING_FAILED (not findable by search at all). Branch on `code`, not on the message.",
9603
9649
  inputSchema: inputSchema10,
9604
9650
  handler: async (args) => {
9605
9651
  const documentId = args.documentId;
@@ -9933,7 +9979,7 @@ var inputSchema15 = {
9933
9979
  var folderCreate = ({ client, log }) => ({
9934
9980
  name: "folder_create",
9935
9981
  title: "Create a folder",
9936
- description: "Create a folder to organize documents and records. Pass `parentId` to nest it (create-only \u2014 folders cannot be moved later). Idempotent by `slug` within the parent. Requires the key to allow folders:c.",
9982
+ description: "Create a folder to organize documents and records. Pass `parentId` to nest it (create-only \u2014 folders cannot be moved later). Idempotent by `slug` within the parent. Requires the key to allow folders:c (and folders:r to receive the existing folder on a collision).",
9937
9983
  inputSchema: inputSchema15,
9938
9984
  handler: async (args) => {
9939
9985
  try {
@@ -10032,7 +10078,7 @@ var inputSchema18 = {
10032
10078
  var currentIdentity = ({ log, apiKey, environment }) => ({
10033
10079
  name: "current_identity",
10034
10080
  title: "Current identity (tenant + principal scope)",
10035
- description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, and (for scoped keys) allowedActions + dataScope. Use this when the user asks 'what can you do here?' or 'what tenant am I in?'. Calls GET /v1/ping under the hood. Backend may still be rolling out the extended /v1/ping response shape \u2014 until then this tool returns a minimal shape; richer fields appear automatically as backend ships.",
10081
+ 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.",
10036
10082
  inputSchema: inputSchema18,
10037
10083
  handler: async () => {
10038
10084
  try {
@@ -10130,7 +10176,7 @@ var inputSchema19 = {
10130
10176
  "Index strategy. HYBRID (BM25 + dense, default; best for general retrieval), SEMANTIC (dense only; concept-driven queries), TEXT (BM25 only; exact-match / keyword), NONE (store-only / archival \u2014 retrievable by id and structured-field lookup but never in search results). Omit to inherit the bound schema's default."
10131
10177
  ),
10132
10178
  externalId: import_zod18.z.string().optional().describe(
10133
- "Stable caller-supplied id. Immutable; unique per type within your context. Re-ingesting with the same externalId returns the existing document (idempotent) instead of creating a duplicate \u2014 use it to make ingests safely retryable and as the key other records reference. Mirrors record_create."
10179
+ 'Stable caller-supplied id. Immutable; unique per type within your context. Re-ingesting with the same externalId returns the existing document (idempotent) instead of creating a duplicate \u2014 use it to make ingests safely retryable and as the key other records reference. Mirrors record_create. Receiving the existing document back is a read of it and needs documents:r in addition to documents:c; a create-only key gets an "already exists" error instead of the document.'
10134
10180
  ),
10135
10181
  upsert: import_zod18.z.boolean().optional().describe(
10136
10182
  "When true, re-ingesting an existing externalId OVERWRITES that document and re-indexes the new body (text mode) / re-uploads and re-indexes the new file (file mode), instead of returning the existing one unchanged. This is the curation/re-sync primitive: edit a source, re-ingest with upsert:true, and the search index reflects the new content. Requires the documents:u scope. IMPORTANT: pass the SAME schemaId (and type) you used originally \u2014 externalId is unique WITHIN a type, so omitting schemaId resolves in the untyped namespace and mints a duplicate instead of updating. The response `created` flag confirms which happened (created:true = a new document was minted)."
@@ -10139,12 +10185,12 @@ var inputSchema19 = {
10139
10185
  "Bind this document to a record schema. When set, `payload` is validated against the schema and its lookup fields become directly queryable via document_query (records parity). Resolve a schema id from its type via list_schemas. Omit for an untyped document."
10140
10186
  ),
10141
10187
  payload: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.unknown()).optional().describe(
10142
- "The document's structured data \u2014 a flat key/value object. With `schemaId`, declared fields are validated and lookup fields indexed; undeclared keys pass through as free-form and are searchable via the hybrid_search `filters` param. Without a schema it is stored as free-form metadata."
10188
+ "The document's structured data \u2014 a flat key/value object. With `schemaId`, declared fields are validated and lookup fields indexed; undeclared keys pass through as free-form and are searchable via the hybrid_search `filters` param. Without a schema it is stored as free-form metadata. Every number must fall within the signed 64-bit range \u2014 send a larger whole number as a STRING (stored exactly, exact-match lookup), else the ingest is refused with a 400 naming the field."
10143
10189
  ),
10144
10190
  // Inline text mode (mutually exclusive with filePath):
10145
10191
  text: import_zod18.z.string().min(1).optional().describe("Text body to ingest inline. Mutually exclusive with filePath. Either text or filePath is required."),
10146
10192
  storeText: import_zod18.z.boolean().optional().describe(
10147
- "Whether the document's text is retained after indexing (default true). Applies to BOTH modes and is fixed at ingest time. Text mode: the ingested body is always retained (this flag is effectively always true \u2014 the body is the document). File mode: true keeps the extracted text retrievable via document_get(includeText:true) and usable by document_ask; false discards the extracted text once indexing completes \u2014 search still works and the original file remains downloadable, but includeText returns textAvailable:false and document_ask is unavailable."
10193
+ "FILE MODE ONLY. Whether the uploaded file's EXTRACTED text is retained after indexing (default true), fixed at ingest. true keeps the extracted text retrievable via document_get(includeText:true) and usable by document_ask; false discards it once indexing completes \u2014 search still works and the original file remains downloadable, but includeText returns textAvailable:false and document_ask is unavailable. NOT accepted with `text`: a text-ingested body is the document itself and is ALWAYS retained, so the backend has no storeText knob on the text path \u2014 passing it with `text` is rejected rather than silently ignored."
10148
10194
  ),
10149
10195
  // File upload mode (mutually exclusive with text; stdio-transport-only):
10150
10196
  filePath: import_zod18.z.string().min(1).optional().describe(
@@ -10161,7 +10207,7 @@ var inputSchema19 = {
10161
10207
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10162
10208
  name: "document_ingest",
10163
10209
  title: "Ingest a new document (text body or file upload)",
10164
- description: "Create a new document in your Vectros tenant. Two modes:\n \u2022 Text mode: pass `text` (string body). Single-call. Use for crawled content, notes, generated text.\n \u2022 File mode: pass `filePath` (local path on the MCP server's host machine). STDIO-TRANSPORT ONLY \u2014 rejected on HTTP transport. MCP server reads the bytes, requests a presigned upload URL, and PUTs them. Returns when the upload is accepted (indexStatus: PENDING_INDEX); poll with `document_get` until indexStatus is INDEXED.\nEither `text` or `filePath` must be present; both is an error. Idempotent by `externalId` (re-ingest returns the existing document, not a duplicate). To UPDATE an existing document instead \u2014 re-index an edited body \u2014 pass `upsert:true` with the same `externalId` (and the same `schemaId`); this is the re-sync primitive for keeping a knowledge base current. Pass `schemaId` + `payload` for a typed, lookup-queryable document (records parity). indexMode defaults to HYBRID for untyped documents (omit to inherit a bound schema's default). storeText defaults to true and is fixed at ingest: in file mode, false discards the extracted text after indexing (search + file download keep working; text retrieval and document_ask do not). Text-mode bodies are always retained.",
10210
+ description: "Create a new document in your Vectros tenant. Two modes:\n \u2022 Text mode: pass `text` (string body). Single-call. Use for crawled content, notes, generated text.\n \u2022 File mode: pass `filePath` (local path on the MCP server's host machine). STDIO-TRANSPORT ONLY \u2014 rejected on HTTP transport. MCP server reads the bytes, requests a presigned upload URL, and PUTs them. Returns when the upload is accepted (indexStatus: PENDING_INDEX); poll with `document_get` until indexStatus is INDEXED.\nEither `text` or `filePath` must be present; both is an error. Idempotent by `externalId` (re-ingest returns the existing document, not a duplicate \u2014 requires documents:r in addition to documents:c, since being handed the existing document is a read of it). To UPDATE an existing document instead \u2014 re-index an edited body \u2014 pass `upsert:true` with the same `externalId` (and the same `schemaId`); this is the re-sync primitive for keeping a knowledge base current. Pass `schemaId` + `payload` for a typed, lookup-queryable document (records parity). indexMode defaults to HYBRID for untyped documents (omit to inherit a bound schema's default). storeText is a FILE-MODE knob (default true): false discards the uploaded file's extracted text after indexing (search + file download keep working; text retrieval and document_ask do not). Text-mode bodies are always retained, so storeText is not accepted with `text`.",
10165
10211
  inputSchema: inputSchema19,
10166
10212
  handler: async (args) => {
10167
10213
  const title = args.title;
@@ -10198,6 +10244,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10198
10244
  )
10199
10245
  );
10200
10246
  }
10247
+ if (text && args.storeText !== void 0) {
10248
+ return toolError(
10249
+ "document_ingest",
10250
+ new Error(
10251
+ "`storeText` is not supported with `text` \u2014 a text-ingested body is the document itself and is always retained; the text path has no storeText control. Omit it (text mode), or use `filePath` mode where storeText:false discards the uploaded file's extracted text after indexing."
10252
+ )
10253
+ );
10254
+ }
10201
10255
  const common = {
10202
10256
  folderId: args.folderId,
10203
10257
  payload: args.payload,
@@ -10502,7 +10556,7 @@ var identityResource = ({ log, apiKey, environment }) => ({
10502
10556
  name: "identity",
10503
10557
  uri: "vectros://identity",
10504
10558
  title: "Current identity (tenant + principal scope)",
10505
- description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, environment, principalType, principalKeyId, and (for scoped keys) allowedActions + dataScope. Until backend ships the extended /v1/ping response, returns a minimal derived shape; richer fields appear automatically as backend rolls out.",
10559
+ 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.",
10506
10560
  mimeType: "application/json",
10507
10561
  read: async () => {
10508
10562
  const identity = await resolveIdentity({ log, apiKey, environment });