@vectros-ai/mcp-server 0.5.2 → 0.6.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
@@ -520,10 +520,18 @@ var DocumentRequest;
520
520
  Text: "TEXT",
521
521
  None: "NONE"
522
522
  };
523
+ DocumentRequest2.Status = {
524
+ Active: "ACTIVE",
525
+ Archived: "ARCHIVED"
526
+ };
523
527
  })(DocumentRequest || (DocumentRequest = {}));
524
528
  var DocumentResponse;
525
529
  ((DocumentResponse2) => {
526
530
  DocumentResponse2.Status = {
531
+ Active: "ACTIVE",
532
+ Archived: "ARCHIVED"
533
+ };
534
+ DocumentResponse2.IndexStatus = {
527
535
  PendingUpload: "PENDING_UPLOAD",
528
536
  Uploaded: "UPLOADED",
529
537
  Extracting: "EXTRACTING",
@@ -2877,7 +2885,7 @@ var AuthClient = class {
2877
2885
  );
2878
2886
  }
2879
2887
  /**
2880
- * 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. 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` may set only `orgId` and `clientId`; 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.
2888
+ * 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` may set only `orgId` and `clientId`; 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.
2881
2889
  *
2882
2890
  * @param {Vectros.CreateAccessProfileRequest} request
2883
2891
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -2899,7 +2907,10 @@ var AuthClient = class {
2899
2907
  return HttpResponsePromise.fromPromise(this.__createAccessProfile(request, requestOptions));
2900
2908
  }
2901
2909
  async __createAccessProfile(request, requestOptions) {
2902
- const { contextId, body: _body } = request;
2910
+ const { contextId, upsert, body: _body } = request;
2911
+ const _queryParams = {
2912
+ upsert
2913
+ };
2903
2914
  const _authRequest = await this._options.authProvider.getAuthRequest();
2904
2915
  const _headers = mergeHeaders(
2905
2916
  _authRequest.headers,
@@ -2914,7 +2925,7 @@ var AuthClient = class {
2914
2925
  method: "POST",
2915
2926
  headers: _headers,
2916
2927
  contentType: "application/json",
2917
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
2928
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
2918
2929
  requestType: "json",
2919
2930
  body: _body,
2920
2931
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -3009,9 +3020,9 @@ var AuthClient = class {
3009
3020
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
3010
3021
  }
3011
3022
  /**
3012
- * Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context is returned (with status 200) instead of creating a duplicate. The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
3023
+ * Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context 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 context was returned) tells the two apart. To overwrite an existing context's `name`/`description` instead of returning it unchanged, set `?upsert=true` (this also requires the `app-contexts:u` scope). The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
3013
3024
  *
3014
- * @param {Vectros.AppContextRequest} request
3025
+ * @param {Vectros.CreateAppContextRequest} request
3015
3026
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
3016
3027
  *
3017
3028
  * @throws {@link Vectros.BadRequestError}
@@ -3020,14 +3031,20 @@ var AuthClient = class {
3020
3031
  *
3021
3032
  * @example
3022
3033
  * await client.auth.createAppContext({
3023
- * contextId: "myapp",
3024
- * name: "My Internal App"
3034
+ * body: {
3035
+ * contextId: "myapp",
3036
+ * name: "My Internal App"
3037
+ * }
3025
3038
  * })
3026
3039
  */
3027
3040
  createAppContext(request, requestOptions) {
3028
3041
  return HttpResponsePromise.fromPromise(this.__createAppContext(request, requestOptions));
3029
3042
  }
3030
3043
  async __createAppContext(request, requestOptions) {
3044
+ const { upsert, body: _body } = request;
3045
+ const _queryParams = {
3046
+ upsert
3047
+ };
3031
3048
  const _authRequest = await this._options.authProvider.getAuthRequest();
3032
3049
  const _headers = mergeHeaders(
3033
3050
  _authRequest.headers,
@@ -3042,9 +3059,9 @@ var AuthClient = class {
3042
3059
  method: "POST",
3043
3060
  headers: _headers,
3044
3061
  contentType: "application/json",
3045
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3062
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3046
3063
  requestType: "json",
3047
- body: request,
3064
+ body: _body,
3048
3065
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
3049
3066
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
3050
3067
  abortSignal: requestOptions?.abortSignal,
@@ -3140,7 +3157,7 @@ var AuthClient = class {
3140
3157
  );
3141
3158
  }
3142
3159
  /**
3143
- * Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role is returned (with status 200) instead of creating a duplicate. If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
3160
+ * Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role 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 role was returned) tells the two apart. To overwrite an existing role's `name`/`description`/`scopes` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
3144
3161
  *
3145
3162
  * @param {Vectros.CreateRoleRequest} request
3146
3163
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3166,7 +3183,10 @@ var AuthClient = class {
3166
3183
  return HttpResponsePromise.fromPromise(this.__createRole(request, requestOptions));
3167
3184
  }
3168
3185
  async __createRole(request, requestOptions) {
3169
- const { contextId, body: _body } = request;
3186
+ const { contextId, upsert, body: _body } = request;
3187
+ const _queryParams = {
3188
+ upsert
3189
+ };
3170
3190
  const _authRequest = await this._options.authProvider.getAuthRequest();
3171
3191
  const _headers = mergeHeaders(
3172
3192
  _authRequest.headers,
@@ -3181,7 +3201,7 @@ var AuthClient = class {
3181
3201
  method: "POST",
3182
3202
  headers: _headers,
3183
3203
  contentType: "application/json",
3184
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3204
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3185
3205
  requestType: "json",
3186
3206
  body: _body,
3187
3207
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4665,23 +4685,30 @@ var DocumentsClient = class {
4665
4685
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
4666
4686
  }
4667
4687
  /**
4668
- * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Requires the `documents:c` scope.
4688
+ * 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.
4669
4689
  *
4670
- * @param {Vectros.DocumentRequest} request
4690
+ * @param {Vectros.IngestDocumentRequest} request
4671
4691
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4672
4692
  *
4673
4693
  * @throws {@link Vectros.BadRequestError}
4694
+ * @throws {@link Vectros.ForbiddenError}
4674
4695
  * @throws {@link Vectros.TooManyRequestsError}
4675
4696
  *
4676
4697
  * @example
4677
4698
  * await client.documents.ingestDocument({
4678
- * title: "Patient Intake Form \u2014 Jane Doe"
4699
+ * body: {
4700
+ * title: "Patient Intake Form \u2014 Jane Doe"
4701
+ * }
4679
4702
  * })
4680
4703
  */
4681
4704
  ingestDocument(request, requestOptions) {
4682
4705
  return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
4683
4706
  }
4684
4707
  async __ingestDocument(request, requestOptions) {
4708
+ const { upsert, body: _body } = request;
4709
+ const _queryParams = {
4710
+ upsert
4711
+ };
4685
4712
  const _authRequest = await this._options.authProvider.getAuthRequest();
4686
4713
  const _headers = mergeHeaders(
4687
4714
  _authRequest.headers,
@@ -4696,9 +4723,9 @@ var DocumentsClient = class {
4696
4723
  method: "POST",
4697
4724
  headers: _headers,
4698
4725
  contentType: "application/json",
4699
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4726
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4700
4727
  requestType: "json",
4701
- body: request,
4728
+ body: _body,
4702
4729
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4703
4730
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4704
4731
  abortSignal: requestOptions?.abortSignal,
@@ -4712,6 +4739,8 @@ var DocumentsClient = class {
4712
4739
  switch (_response.error.statusCode) {
4713
4740
  case 400:
4714
4741
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4742
+ case 403:
4743
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4715
4744
  case 429:
4716
4745
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4717
4746
  default:
@@ -4978,7 +5007,7 @@ var DocumentsClient = class {
4978
5007
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
4979
5008
  }
4980
5009
  /**
4981
- * Finds documents of a given type by a schema-declared lookup field. The document must be bound to a schema (via `schemaId`) that declares the field as a lookup field. 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.
5010
+ * 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.
4982
5011
  *
4983
5012
  * @param {Vectros.LookupDocumentsRequest} request
4984
5013
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5273,12 +5302,13 @@ var DocumentsClient = class {
5273
5302
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
5274
5303
  }
5275
5304
  /**
5276
- * Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Requires the `documents:c` scope.
5305
+ * Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL to the SAME existing document/object (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body). Creating a NEW document requires the `documents:c` scope. Re-uploading over an EXISTING document overwrites (and re-indexes) its body, so it is an update: it requires the `documents:u` scope (as does `?upsert=true` for the metadata).
5277
5306
  *
5278
5307
  * @param {Vectros.FileUploadRequest} request
5279
5308
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
5280
5309
  *
5281
5310
  * @throws {@link Vectros.BadRequestError}
5311
+ * @throws {@link Vectros.ForbiddenError}
5282
5312
  * @throws {@link Vectros.TooManyRequestsError}
5283
5313
  *
5284
5314
  * @example
@@ -5291,6 +5321,10 @@ var DocumentsClient = class {
5291
5321
  return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
5292
5322
  }
5293
5323
  async __uploadDocument(request, requestOptions) {
5324
+ const { upsert, ..._body } = request;
5325
+ const _queryParams = {
5326
+ upsert
5327
+ };
5294
5328
  const _authRequest = await this._options.authProvider.getAuthRequest();
5295
5329
  const _headers = mergeHeaders(
5296
5330
  _authRequest.headers,
@@ -5305,9 +5339,9 @@ var DocumentsClient = class {
5305
5339
  method: "POST",
5306
5340
  headers: _headers,
5307
5341
  contentType: "application/json",
5308
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5342
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5309
5343
  requestType: "json",
5310
- body: request,
5344
+ body: _body,
5311
5345
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5312
5346
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5313
5347
  abortSignal: requestOptions?.abortSignal,
@@ -5321,6 +5355,8 @@ var DocumentsClient = class {
5321
5355
  switch (_response.error.statusCode) {
5322
5356
  case 400:
5323
5357
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5358
+ case 403:
5359
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5324
5360
  case 429:
5325
5361
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5326
5362
  default:
@@ -5399,23 +5435,30 @@ var FoldersClient = class {
5399
5435
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
5400
5436
  }
5401
5437
  /**
5402
- * Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Requires the `folders:c` scope.
5438
+ * 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.
5403
5439
  *
5404
- * @param {Vectros.FolderRequest} request
5440
+ * @param {Vectros.CreateFolderRequest} request
5405
5441
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5406
5442
  *
5407
5443
  * @throws {@link Vectros.BadRequestError}
5444
+ * @throws {@link Vectros.ForbiddenError}
5408
5445
  * @throws {@link Vectros.TooManyRequestsError}
5409
5446
  *
5410
5447
  * @example
5411
5448
  * await client.folders.createFolder({
5412
- * name: "Patient Records 2024"
5449
+ * body: {
5450
+ * name: "Patient Records 2024"
5451
+ * }
5413
5452
  * })
5414
5453
  */
5415
5454
  createFolder(request, requestOptions) {
5416
5455
  return HttpResponsePromise.fromPromise(this.__createFolder(request, requestOptions));
5417
5456
  }
5418
5457
  async __createFolder(request, requestOptions) {
5458
+ const { upsert, body: _body } = request;
5459
+ const _queryParams = {
5460
+ upsert
5461
+ };
5419
5462
  const _authRequest = await this._options.authProvider.getAuthRequest();
5420
5463
  const _headers = mergeHeaders(
5421
5464
  _authRequest.headers,
@@ -5430,9 +5473,9 @@ var FoldersClient = class {
5430
5473
  method: "POST",
5431
5474
  headers: _headers,
5432
5475
  contentType: "application/json",
5433
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5476
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5434
5477
  requestType: "json",
5435
- body: request,
5478
+ body: _body,
5436
5479
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5437
5480
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5438
5481
  abortSignal: requestOptions?.abortSignal,
@@ -5446,6 +5489,8 @@ var FoldersClient = class {
5446
5489
  switch (_response.error.statusCode) {
5447
5490
  case 400:
5448
5491
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5492
+ case 403:
5493
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5449
5494
  case 429:
5450
5495
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5451
5496
  default:
@@ -5854,23 +5899,30 @@ var IdentityClient = class {
5854
5899
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/clients");
5855
5900
  }
5856
5901
  /**
5857
- * Creates a new client identity in your account. This call is idempotent on `externalId`: if a client with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `clients:c` scope.
5902
+ * Creates a new client identity in your account. This call is idempotent on `externalId`: if a client 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 client was returned) tells the two apart. To overwrite an existing client's content instead of returning it unchanged, set `?upsert=true` (this also requires the `clients:u` scope). Requires the `clients:c` scope.
5858
5903
  *
5859
- * @param {Vectros.ClientRequest} request
5904
+ * @param {Vectros.CreateClientRequest} request
5860
5905
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
5861
5906
  *
5862
5907
  * @throws {@link Vectros.BadRequestError}
5908
+ * @throws {@link Vectros.ForbiddenError}
5863
5909
  * @throws {@link Vectros.TooManyRequestsError}
5864
5910
  *
5865
5911
  * @example
5866
5912
  * await client.identity.createClient({
5867
- * externalId: "patient_789"
5913
+ * body: {
5914
+ * externalId: "patient_789"
5915
+ * }
5868
5916
  * })
5869
5917
  */
5870
5918
  createClient(request, requestOptions) {
5871
5919
  return HttpResponsePromise.fromPromise(this.__createClient(request, requestOptions));
5872
5920
  }
5873
5921
  async __createClient(request, requestOptions) {
5922
+ const { upsert, body: _body } = request;
5923
+ const _queryParams = {
5924
+ upsert
5925
+ };
5874
5926
  const _authRequest = await this._options.authProvider.getAuthRequest();
5875
5927
  const _headers = mergeHeaders(
5876
5928
  _authRequest.headers,
@@ -5885,9 +5937,9 @@ var IdentityClient = class {
5885
5937
  method: "POST",
5886
5938
  headers: _headers,
5887
5939
  contentType: "application/json",
5888
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5940
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5889
5941
  requestType: "json",
5890
- body: request,
5942
+ body: _body,
5891
5943
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5892
5944
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5893
5945
  abortSignal: requestOptions?.abortSignal,
@@ -5901,6 +5953,8 @@ var IdentityClient = class {
5901
5953
  switch (_response.error.statusCode) {
5902
5954
  case 400:
5903
5955
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5956
+ case 403:
5957
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5904
5958
  case 429:
5905
5959
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5906
5960
  default:
@@ -6289,23 +6343,30 @@ var IdentityClient = class {
6289
6343
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/orgs");
6290
6344
  }
6291
6345
  /**
6292
- * Creates a new organization in your account. This call is idempotent on `externalId`: if an organization with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `orgs:c` scope.
6346
+ * Creates a new organization in your account. This call is idempotent on `externalId`: if an organization 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 organization was returned) tells the two apart. To overwrite an existing organization's content instead of returning it unchanged, set `?upsert=true` (this also requires the `orgs:u` scope). Requires the `orgs:c` scope.
6293
6347
  *
6294
- * @param {Vectros.OrgRequest} request
6348
+ * @param {Vectros.CreateOrgRequest} request
6295
6349
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6296
6350
  *
6297
6351
  * @throws {@link Vectros.BadRequestError}
6352
+ * @throws {@link Vectros.ForbiddenError}
6298
6353
  * @throws {@link Vectros.TooManyRequestsError}
6299
6354
  *
6300
6355
  * @example
6301
6356
  * await client.identity.createOrg({
6302
- * externalId: "clinic_001"
6357
+ * body: {
6358
+ * externalId: "clinic_001"
6359
+ * }
6303
6360
  * })
6304
6361
  */
6305
6362
  createOrg(request, requestOptions) {
6306
6363
  return HttpResponsePromise.fromPromise(this.__createOrg(request, requestOptions));
6307
6364
  }
6308
6365
  async __createOrg(request, requestOptions) {
6366
+ const { upsert, body: _body } = request;
6367
+ const _queryParams = {
6368
+ upsert
6369
+ };
6309
6370
  const _authRequest = await this._options.authProvider.getAuthRequest();
6310
6371
  const _headers = mergeHeaders(
6311
6372
  _authRequest.headers,
@@ -6320,9 +6381,9 @@ var IdentityClient = class {
6320
6381
  method: "POST",
6321
6382
  headers: _headers,
6322
6383
  contentType: "application/json",
6323
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6384
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6324
6385
  requestType: "json",
6325
- body: request,
6386
+ body: _body,
6326
6387
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6327
6388
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6328
6389
  abortSignal: requestOptions?.abortSignal,
@@ -6336,6 +6397,8 @@ var IdentityClient = class {
6336
6397
  switch (_response.error.statusCode) {
6337
6398
  case 400:
6338
6399
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6400
+ case 403:
6401
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6339
6402
  case 429:
6340
6403
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6341
6404
  default:
@@ -6710,23 +6773,30 @@ var IdentityClient = class {
6710
6773
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
6711
6774
  }
6712
6775
  /**
6713
- * 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. Requires the `users:c` scope.
6776
+ * 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.
6714
6777
  *
6715
- * @param {Vectros.UserRequest} request
6778
+ * @param {Vectros.CreateUserRequest} request
6716
6779
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6717
6780
  *
6718
6781
  * @throws {@link Vectros.BadRequestError}
6782
+ * @throws {@link Vectros.ForbiddenError}
6719
6783
  * @throws {@link Vectros.TooManyRequestsError}
6720
6784
  *
6721
6785
  * @example
6722
6786
  * await client.identity.createUser({
6723
- * externalId: "usr_12345"
6787
+ * body: {
6788
+ * externalId: "usr_12345"
6789
+ * }
6724
6790
  * })
6725
6791
  */
6726
6792
  createUser(request, requestOptions) {
6727
6793
  return HttpResponsePromise.fromPromise(this.__createUser(request, requestOptions));
6728
6794
  }
6729
6795
  async __createUser(request, requestOptions) {
6796
+ const { upsert, body: _body } = request;
6797
+ const _queryParams = {
6798
+ upsert
6799
+ };
6730
6800
  const _authRequest = await this._options.authProvider.getAuthRequest();
6731
6801
  const _headers = mergeHeaders(
6732
6802
  _authRequest.headers,
@@ -6741,9 +6811,9 @@ var IdentityClient = class {
6741
6811
  method: "POST",
6742
6812
  headers: _headers,
6743
6813
  contentType: "application/json",
6744
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6814
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6745
6815
  requestType: "json",
6746
- body: request,
6816
+ body: _body,
6747
6817
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6748
6818
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6749
6819
  abortSignal: requestOptions?.abortSignal,
@@ -6757,6 +6827,8 @@ var IdentityClient = class {
6757
6827
  switch (_response.error.statusCode) {
6758
6828
  case 400:
6759
6829
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6830
+ case 403:
6831
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6760
6832
  case 429:
6761
6833
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6762
6834
  default:
@@ -7574,9 +7646,9 @@ var RecordsClient = class {
7574
7646
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
7575
7647
  }
7576
7648
  /**
7577
- * 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. Requires the `records:c:<type>` scope.
7649
+ * 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.
7578
7650
  *
7579
- * @param {Vectros.RecordRequest} request
7651
+ * @param {Vectros.CreateRecordRequest} request
7580
7652
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7581
7653
  *
7582
7654
  * @throws {@link Vectros.BadRequestError}
@@ -7585,19 +7657,25 @@ var RecordsClient = class {
7585
7657
  *
7586
7658
  * @example
7587
7659
  * await client.records.createRecord({
7588
- * typeName: "intake_form",
7589
- * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7590
- * payload: {
7591
- * "first_name": "Jane",
7592
- * "email": "jane@example.com"
7593
- * },
7594
- * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7660
+ * body: {
7661
+ * typeName: "intake_form",
7662
+ * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7663
+ * payload: {
7664
+ * "first_name": "Jane",
7665
+ * "email": "jane@example.com"
7666
+ * },
7667
+ * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7668
+ * }
7595
7669
  * })
7596
7670
  */
7597
7671
  createRecord(request, requestOptions) {
7598
7672
  return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
7599
7673
  }
7600
7674
  async __createRecord(request, requestOptions) {
7675
+ const { upsert, body: _body } = request;
7676
+ const _queryParams = {
7677
+ upsert
7678
+ };
7601
7679
  const _authRequest = await this._options.authProvider.getAuthRequest();
7602
7680
  const _headers = mergeHeaders(
7603
7681
  _authRequest.headers,
@@ -7612,9 +7690,9 @@ var RecordsClient = class {
7612
7690
  method: "POST",
7613
7691
  headers: _headers,
7614
7692
  contentType: "application/json",
7615
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
7693
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7616
7694
  requestType: "json",
7617
- body: request,
7695
+ body: _body,
7618
7696
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7619
7697
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7620
7698
  abortSignal: requestOptions?.abortSignal,
@@ -8210,43 +8288,50 @@ var SchemasClient = class {
8210
8288
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
8211
8289
  }
8212
8290
  /**
8213
- * 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. Requires the `schemas:w` scope.
8291
+ * 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.
8214
8292
  *
8215
- * @param {Vectros.SchemaRequest} request
8293
+ * @param {Vectros.CreateSchemaRequest} request
8216
8294
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8217
8295
  *
8218
8296
  * @throws {@link Vectros.BadRequestError}
8297
+ * @throws {@link Vectros.ForbiddenError}
8219
8298
  * @throws {@link Vectros.TooManyRequestsError}
8220
8299
  *
8221
8300
  * @example
8222
8301
  * await client.schemas.createSchema({
8223
- * typeName: "intake_form",
8224
- * displayName: "Client Intake Form",
8225
- * description: "Captures initial client information",
8226
- * fields: [{
8227
- * fieldId: "first_name",
8228
- * fieldType: "string",
8229
- * required: true,
8230
- * searchable: true
8231
- * }, {
8232
- * fieldId: "email",
8233
- * fieldType: "string",
8234
- * required: true
8235
- * }],
8236
- * lookupFields: [{
8237
- * fieldName: "email",
8238
- * unique: true
8239
- * }],
8240
- * capabilities: {
8241
- * "auditHistory": true
8242
- * },
8243
- * allowedSurfaces: ["record"]
8302
+ * body: {
8303
+ * typeName: "intake_form",
8304
+ * displayName: "Client Intake Form",
8305
+ * description: "Captures initial client information",
8306
+ * fields: [{
8307
+ * fieldId: "first_name",
8308
+ * fieldType: "string",
8309
+ * required: true,
8310
+ * searchable: true
8311
+ * }, {
8312
+ * fieldId: "email",
8313
+ * fieldType: "string",
8314
+ * required: true
8315
+ * }],
8316
+ * lookupFields: [{
8317
+ * fieldName: "email",
8318
+ * unique: true
8319
+ * }],
8320
+ * capabilities: {
8321
+ * "auditHistory": true
8322
+ * },
8323
+ * allowedSurfaces: ["record"]
8324
+ * }
8244
8325
  * })
8245
8326
  */
8246
8327
  createSchema(request, requestOptions) {
8247
8328
  return HttpResponsePromise.fromPromise(this.__createSchema(request, requestOptions));
8248
8329
  }
8249
8330
  async __createSchema(request, requestOptions) {
8331
+ const { upsert, body: _body } = request;
8332
+ const _queryParams = {
8333
+ upsert
8334
+ };
8250
8335
  const _authRequest = await this._options.authProvider.getAuthRequest();
8251
8336
  const _headers = mergeHeaders(
8252
8337
  _authRequest.headers,
@@ -8261,9 +8346,9 @@ var SchemasClient = class {
8261
8346
  method: "POST",
8262
8347
  headers: _headers,
8263
8348
  contentType: "application/json",
8264
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
8349
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
8265
8350
  requestType: "json",
8266
- body: request,
8351
+ body: _body,
8267
8352
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
8268
8353
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
8269
8354
  abortSignal: requestOptions?.abortSignal,
@@ -8277,6 +8362,8 @@ var SchemasClient = class {
8277
8362
  switch (_response.error.statusCode) {
8278
8363
  case 400:
8279
8364
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8365
+ case 403:
8366
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
8280
8367
  case 429:
8281
8368
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
8282
8369
  default:
@@ -8711,8 +8798,8 @@ function warnOnSuboptimalKey(key, log) {
8711
8798
 
8712
8799
  // src/build-info.ts
8713
8800
  var BUILD_INFO = {
8714
- mcpServer: true ? "0.5.2" : "dev",
8715
- sdk: true ? "0.30.0" : "dev"
8801
+ mcpServer: true ? "0.6.0" : "dev",
8802
+ sdk: true ? "0.32.0" : "dev"
8716
8803
  };
8717
8804
 
8718
8805
  // src/log.ts
@@ -8949,6 +9036,32 @@ ${detail}${pointer}`
8949
9036
  // src/tools/hybrid_search.ts
8950
9037
  var MCP_DEFAULT_LIMIT = 3;
8951
9038
  var MCP_MAX_LIMIT = 10;
9039
+ var MCP_RESPONSE_CHAR_BUDGET = 24e3;
9040
+ var SNIPPET_MAX_CHARS = 160;
9041
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["tenantId", "owner_id", "model_type", "rootFolderId", "folderId"]);
9042
+ function trimMetadata(md) {
9043
+ if (!md || typeof md !== "object" || Array.isArray(md)) return md;
9044
+ const out = {};
9045
+ for (const [k, v] of Object.entries(md)) {
9046
+ if (!INTERNAL_METADATA_KEYS.has(k)) out[k] = v;
9047
+ }
9048
+ return out;
9049
+ }
9050
+ function reshapeHit(hit, includeContext) {
9051
+ const out = { ...hit };
9052
+ if ("metadata" in out) out.metadata = trimMetadata(out.metadata);
9053
+ const chunk = typeof hit.chunkText === "string" ? hit.chunkText : void 0;
9054
+ const ctx = typeof hit.contextText === "string" ? hit.contextText : void 0;
9055
+ if (includeContext) {
9056
+ if (chunk && ctx && ctx.includes(chunk)) delete out.chunkText;
9057
+ } else {
9058
+ delete out.contextText;
9059
+ }
9060
+ if ((out.snippet == null || out.snippet === "") && chunk) {
9061
+ out.snippet = chunk.length > SNIPPET_MAX_CHARS ? chunk.slice(0, SNIPPET_MAX_CHARS) + "\u2026" : chunk;
9062
+ }
9063
+ return out;
9064
+ }
8952
9065
  var inputSchema = {
8953
9066
  query: import_zod.z.string().min(1, "query must be non-empty").describe("Natural-language or keyword query."),
8954
9067
  mode: import_zod.z.enum(["HYBRID", "TEXT", "SEMANTIC"]).optional().describe("HYBRID (BM25 + dense, default), TEXT (BM25 only), or SEMANTIC (dense only)."),
@@ -8963,7 +9076,9 @@ var inputSchema = {
8963
9076
  rootFolderId: import_zod.z.string().optional().describe("Scope to content under this folder AND all its descendants (subtree-root folder id)."),
8964
9077
  // Content narrowing.
8965
9078
  contentTypes: import_zod.z.array(import_zod.z.enum(["documents", "records"])).optional().describe('Narrow to content types. ["documents"] or ["records"]; omit for unified (both).'),
8966
- typeName: import_zod.z.string().optional().describe('Restrict record hits to this schema type (e.g. "patient"). No-op for documents.'),
9079
+ typeName: import_zod.z.string().optional().describe(
9080
+ 'Restrict hits to this schema type (e.g. "patient", "runbook"). Applies to BOTH documents and records \u2014 combine with contentTypes to scope within one content type. Untyped content never matches.'
9081
+ ),
8967
9082
  filters: import_zod.z.record(import_zod.z.unknown()).optional().describe(
8968
9083
  'Field-level metadata filters (AND-combined). Value = scalar (equality), array (OR-set), or operator map: $eq/$ne/$gt/$gte/$lt/$lte (scalar) or $in/$nin (array). e.g. {"status":"open"} or {"price":{"$gte":100,"$lte":500}}.'
8969
9084
  ),
@@ -8978,6 +9093,9 @@ var inputSchema = {
8978
9093
  minSimilarity: import_zod.z.number().min(0).max(1).optional().describe("Minimum semantic similarity (0.0\u20131.0); results below are excluded."),
8979
9094
  minTextRelevance: import_zod.z.number().min(0).max(1).optional().describe("Minimum relative keyword (BM25) relevance (0.0\u20131.0); the keyword-leg analogue of minSimilarity."),
8980
9095
  uniqueDocuments: import_zod.z.boolean().optional().describe("When true, return at most one chunk per source document."),
9096
+ includeContext: import_zod.z.boolean().optional().describe(
9097
+ "Include the broader passage (contextText) around each matched chunk. Off by default to keep the result compact \u2014 each hit returns its matched chunkText plus a short snippet. Turn this on only when you need more surrounding context inline; for a full deep read, fetch the source with document_get."
9098
+ ),
8981
9099
  requireComplete: import_zod.z.boolean().optional().describe(
8982
9100
  "When true, fail closed (HTTP 503) if a search backend is unavailable rather than silently returning partial/degraded results. Default false (degrade to the surviving engine; the result still flags degraded)."
8983
9101
  )
@@ -8985,7 +9103,7 @@ var inputSchema = {
8985
9103
  var hybridSearch = ({ client, log }) => ({
8986
9104
  name: "hybrid_search",
8987
9105
  title: "Hybrid search",
8988
- description: "Search the partner tenant's indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to 10 results (default 3) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/orgId/clientId), 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.",
9106
+ description: `Search the partner tenant's indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to 10 results (default 3) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/orgId/clientId), 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). textLegEmpty:true flags that the keyword leg matched nothing (all textScores 0) \u2014 usually a too-long PHRASE query; retry with textMode:"OR".`,
8989
9107
  inputSchema,
8990
9108
  handler: async (args) => {
8991
9109
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9012,13 +9130,30 @@ var hybridSearch = ({ client, log }) => ({
9012
9130
  uniqueDocuments: args.uniqueDocuments,
9013
9131
  requireComplete: args.requireComplete
9014
9132
  });
9133
+ const hits = Array.isArray(
9134
+ result.results
9135
+ ) ? result.results : [];
9136
+ const includeContext = args.includeContext ?? false;
9137
+ let shaped = hits.map((h) => reshapeHit(h, includeContext));
9138
+ const textLegEmpty = mode !== "SEMANTIC" && hits.length > 0 && hits.every((h) => !(Number(h.textScore) > 0));
9139
+ const envelope = {
9140
+ ...result,
9141
+ results: shaped
9142
+ };
9143
+ if (textLegEmpty) envelope.textLegEmpty = true;
9144
+ let text = JSON.stringify(envelope, null, 2);
9145
+ while (shaped.length > 1 && text.length > MCP_RESPONSE_CHAR_BUDGET) {
9146
+ shaped = shaped.slice(0, -1);
9147
+ envelope.results = shaped;
9148
+ envelope.truncated = true;
9149
+ envelope.returnedResults = shaped.length;
9150
+ text = JSON.stringify(envelope, null, 2);
9151
+ }
9015
9152
  log.debug(
9016
- { tool: "hybrid_search", mode, limit, returned: result.results?.length },
9153
+ { tool: "hybrid_search", mode, limit, returned: shaped.length, textLegEmpty },
9017
9154
  "hybrid_search ok"
9018
9155
  );
9019
- return {
9020
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
9021
- };
9156
+ return { content: [{ type: "text", text }] };
9022
9157
  } catch (err) {
9023
9158
  log.warn({ tool: "hybrid_search", err: String(err) }, "hybrid_search failed");
9024
9159
  return toolError("hybrid_search", err);
@@ -9053,7 +9188,9 @@ var MCP_MAX_LIMIT2 = 10;
9053
9188
  var inputSchema2 = {
9054
9189
  type: import_zod2.z.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9055
9190
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9056
- field: import_zod2.z.string().optional().describe("Lookup mode: name of the lookup-indexed field to query by."),
9191
+ field: import_zod2.z.string().optional().describe(
9192
+ '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.)'
9193
+ ),
9057
9194
  value: import_zod2.z.string().optional().describe("Lookup mode (equality): exact-match value for `field`."),
9058
9195
  from: import_zod2.z.string().optional().describe("Lookup mode (range): inclusive lower bound; requires `to`. Non-sensitive fields only."),
9059
9196
  to: import_zod2.z.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
@@ -9148,19 +9285,77 @@ var record_query_default = recordQuery;
9148
9285
 
9149
9286
  // src/tools/record_get.ts
9150
9287
  var import_zod3 = require("zod");
9288
+
9289
+ // src/tools/resolve-external-id.ts
9290
+ function validateSelector(sel, entity) {
9291
+ const hasId = sel.id !== void 0 && sel.id !== "";
9292
+ const hasExt = sel.externalId !== void 0 && sel.externalId !== "";
9293
+ if (hasId && hasExt) {
9294
+ return `Provide either id or externalId (not both) to identify the ${entity}.`;
9295
+ }
9296
+ if (!hasId && !hasExt) {
9297
+ return `Provide id, or externalId + type, to identify the ${entity}.`;
9298
+ }
9299
+ if (hasExt && (sel.type === void 0 || sel.type === "")) {
9300
+ return "type is required when selecting by externalId (externalId is unique within a type).";
9301
+ }
9302
+ return void 0;
9303
+ }
9304
+ async function resolveRecordIdByExternalId(client, sel) {
9305
+ const err = validateSelector(sel, "record");
9306
+ if (err) return { error: err };
9307
+ if (sel.id) return { id: sel.id };
9308
+ const page = await client.records.lookupRecordsByBody({
9309
+ type: sel.type,
9310
+ field: "externalId",
9311
+ value: sel.externalId,
9312
+ limit: 1
9313
+ });
9314
+ const items = pageItems(page);
9315
+ if (items.length === 0) {
9316
+ return { error: `No record of type '${sel.type}' with externalId '${sel.externalId}'.` };
9317
+ }
9318
+ return { id: items[0].id };
9319
+ }
9320
+ async function resolveDocumentIdByExternalId(client, sel) {
9321
+ const err = validateSelector(sel, "document");
9322
+ if (err) return { error: err };
9323
+ if (sel.id) return { id: sel.id };
9324
+ const page = await client.documents.lookupDocumentsByBody({
9325
+ type: sel.type,
9326
+ field: "externalId",
9327
+ value: sel.externalId,
9328
+ limit: 1
9329
+ });
9330
+ const items = pageItems(page);
9331
+ if (items.length === 0) {
9332
+ return { error: `No document of type '${sel.type}' with externalId '${sel.externalId}'.` };
9333
+ }
9334
+ return { id: items[0].id };
9335
+ }
9336
+
9337
+ // src/tools/record_get.ts
9151
9338
  var MAX_PAYLOAD_CHARS = 32e3;
9152
9339
  var inputSchema3 = {
9153
- id: import_zod3.z.string().min(1, "id is required").describe("The Vectros record id (e.g. from record_query or hybrid_search).")
9340
+ id: import_zod3.z.string().optional().describe("The Vectros record id (e.g. from record_query or hybrid_search). Provide this OR externalId+type."),
9341
+ externalId: import_zod3.z.string().optional().describe(
9342
+ "Your caller-owned externalId \u2014 an alternative selector to `id`. Requires `type` (externalId is unique within a type). Resolves to the record without a manual record_query lookup first."
9343
+ ),
9344
+ type: import_zod3.z.string().optional().describe("Record type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type).")
9154
9345
  };
9155
9346
  var recordGet = ({ client, log }) => ({
9156
9347
  name: "record_get",
9157
- title: "Get a record by id",
9158
- description: "Fetch a single structured record by its Vectros id, including the full payload. Use after record_query / hybrid_search surfaces an id and you need the complete record. Very large payloads are truncated to protect the agent context window.",
9348
+ title: "Get a record by id or externalId",
9349
+ 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.",
9159
9350
  inputSchema: inputSchema3,
9160
9351
  handler: async (args) => {
9161
9352
  const id = args.id;
9353
+ const externalId = args.externalId;
9354
+ const type = args.type;
9162
9355
  try {
9163
- const record = await client.records.getRecord({ id });
9356
+ const resolved = await resolveRecordIdByExternalId(client, { id, externalId, type });
9357
+ if ("error" in resolved) return toolError("record_get", new Error(resolved.error));
9358
+ const record = await client.records.getRecord({ id: resolved.id });
9164
9359
  const out = { ...record };
9165
9360
  if (record.payload !== void 0) {
9166
9361
  const json = JSON.stringify(record.payload);
@@ -9172,12 +9367,12 @@ var recordGet = ({ client, log }) => ({
9172
9367
  }
9173
9368
  }
9174
9369
  log.debug(
9175
- { tool: "record_get", id, truncated: out.payloadTruncated === true },
9370
+ { tool: "record_get", id: resolved.id, externalId, truncated: out.payloadTruncated === true },
9176
9371
  "record_get ok"
9177
9372
  );
9178
9373
  return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
9179
9374
  } catch (err) {
9180
- log.warn({ tool: "record_get", id, err: String(err) }, "record_get failed");
9375
+ log.warn({ tool: "record_get", id, externalId, type, err: String(err) }, "record_get failed");
9181
9376
  return toolError("record_get", err);
9182
9377
  }
9183
9378
  }
@@ -9212,15 +9407,17 @@ var recordCreate = ({ client, log }) => ({
9212
9407
  const type = args.type;
9213
9408
  try {
9214
9409
  const created = await client.records.createRecord({
9215
- typeName: type,
9216
- payload: args.fields,
9217
- externalId: args.externalId,
9218
- indexMode: args.indexMode,
9219
- status: args.status,
9220
- folderId: args.folderId,
9221
- userId: args.userId,
9222
- orgId: args.orgId,
9223
- clientId: args.clientId
9410
+ body: {
9411
+ typeName: type,
9412
+ payload: args.fields,
9413
+ externalId: args.externalId,
9414
+ indexMode: args.indexMode,
9415
+ status: args.status,
9416
+ folderId: args.folderId,
9417
+ userId: args.userId,
9418
+ orgId: args.orgId,
9419
+ clientId: args.clientId
9420
+ }
9224
9421
  });
9225
9422
  log.debug({ tool: "record_create", type, id: created.id }, "record_create ok");
9226
9423
  return { content: [{ type: "text", text: JSON.stringify(created, null, 2) }] };
@@ -9235,7 +9432,11 @@ var record_create_default = recordCreate;
9235
9432
  // src/tools/record_update.ts
9236
9433
  var import_zod5 = require("zod");
9237
9434
  var inputSchema5 = {
9238
- id: import_zod5.z.string().min(1, "id is required").describe("The Vectros record id to update."),
9435
+ id: import_zod5.z.string().optional().describe("The Vectros record id to update. Provide this OR externalId+type."),
9436
+ externalId: import_zod5.z.string().optional().describe(
9437
+ "Your caller-owned externalId \u2014 an alternative selector to `id` (requires `type`). Updates the record you created under that externalId without a manual record_query lookup first."
9438
+ ),
9439
+ type: import_zod5.z.string().optional().describe("Record type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9239
9440
  fields: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown()).describe(
9240
9441
  "Field\u2192value changes DEEP-MERGED into the existing payload: keys you send overwrite (recursing into nested objects), a key set to `null` is deleted, and unspecified fields are preserved."
9241
9442
  ),
@@ -9246,23 +9447,27 @@ var inputSchema5 = {
9246
9447
  };
9247
9448
  var recordUpdate = ({ client, log }) => ({
9248
9449
  name: "record_update",
9249
- title: "Update a record",
9250
- description: "Update a record by id. The provided `fields` are DEEP-MERGED into the existing payload (unspecified fields are preserved; a field set to `null` is deleted). Pass `expectedVersion` (the version you last read) for safe concurrent edits \u2014 a stale update is refused (409). Use `status` to archive without deleting. Requires the key to allow records:u.",
9450
+ title: "Update a record by id or externalId",
9451
+ description: "Update a record \u2014 select it by its Vectros `id` OR by your own `externalId` + `type` (the same key you passed to record_create, resolved for you). The provided `fields` are DEEP-MERGED into the existing payload (unspecified fields are preserved; a field set to `null` is deleted). Pass `expectedVersion` (the version you last read) for safe concurrent edits \u2014 a stale update is refused (409). Use `status` to archive without deleting. Requires the key to allow records:u.",
9251
9452
  inputSchema: inputSchema5,
9252
9453
  handler: async (args) => {
9253
9454
  const id = args.id;
9455
+ const externalId = args.externalId;
9456
+ const type = args.type;
9254
9457
  const fields = args.fields;
9255
9458
  const status = args.status;
9256
9459
  const expectedVersion = args.expectedVersion;
9257
9460
  try {
9461
+ const resolved = await resolveRecordIdByExternalId(client, { id, externalId, type });
9462
+ if ("error" in resolved) return toolError("record_update", new Error(resolved.error));
9258
9463
  const body = { payload: fields };
9259
9464
  if (status !== void 0) body.status = status;
9260
9465
  if (expectedVersion !== void 0) body.expectedVersion = expectedVersion;
9261
- const updated = await client.records.patchRecord({ id, body });
9262
- log.debug({ tool: "record_update", id }, "record_update ok");
9466
+ const updated = await client.records.patchRecord({ id: resolved.id, body });
9467
+ log.debug({ tool: "record_update", id: resolved.id, externalId }, "record_update ok");
9263
9468
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
9264
9469
  } catch (err) {
9265
- log.warn({ tool: "record_update", id, err: String(err) }, "record_update failed");
9470
+ log.warn({ tool: "record_update", id, externalId, type, err: String(err) }, "record_update failed");
9266
9471
  return toolError("record_update", err);
9267
9472
  }
9268
9473
  }
@@ -9529,7 +9734,11 @@ var list_schemas_default = listSchemas;
9529
9734
  var import_zod10 = require("zod");
9530
9735
  var MAX_TEXT_CHARS = 32e3;
9531
9736
  var inputSchema10 = {
9532
- documentId: import_zod10.z.string().min(1, "documentId is required").describe("The document ID returned by document_ingest or hybrid_search."),
9737
+ documentId: import_zod10.z.string().optional().describe("The document ID returned by document_ingest or hybrid_search. Provide this OR externalId+type."),
9738
+ externalId: import_zod10.z.string().optional().describe(
9739
+ "Your caller-owned externalId \u2014 an alternative selector to `documentId` (requires `type`). Fetches the document you ingested under that externalId without a manual lookup first."
9740
+ ),
9741
+ type: import_zod10.z.string().optional().describe("Document type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9533
9742
  includeText: import_zod10.z.boolean().optional().describe(
9534
9743
  "If true, fetch and include the document text (capped at ~8K tokens, truncated: true flag if cut). Default false \u2014 metadata only. For larger documents, prefer `document_ask` over dumping text into context."
9535
9744
  ),
@@ -9549,13 +9758,18 @@ function isNotAvailable(err) {
9549
9758
  var documentGet = ({ client, log }) => ({
9550
9759
  name: "document_get",
9551
9760
  title: "Get document metadata (and optionally text)",
9552
- description: "Fetch metadata for a single document by id. Pass `includeText: true` to also fetch the text body (capped at ~8K tokens; `truncated: true` flag if cut). If the document was ingested without `storeText: true`, text is unavailable \u2014 `textAvailable: false` flag is set and the agent should use `document_ask` for question-driven extraction instead. Pass `includeDownloadUrl: true` to also get a short-lived presigned `downloadUrl` for the original file (file-backed documents only). Returns full DocumentResponse from the SDK.",
9761
+ 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). If the document was ingested without `storeText: true`, text is unavailable \u2014 `textAvailable: false` flag is set and the agent should use `document_ask` for question-driven extraction instead. 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`.",
9553
9762
  inputSchema: inputSchema10,
9554
9763
  handler: async (args) => {
9555
- const id = args.documentId;
9764
+ const documentId = args.documentId;
9765
+ const externalId = args.externalId;
9766
+ const type = args.type;
9556
9767
  const includeText = args.includeText ?? false;
9557
9768
  const includeDownloadUrl = args.includeDownloadUrl ?? false;
9558
9769
  try {
9770
+ const resolved = await resolveDocumentIdByExternalId(client, { id: documentId, externalId, type });
9771
+ if ("error" in resolved) return toolError("document_get", new Error(resolved.error));
9772
+ const id = resolved.id;
9559
9773
  const doc = await client.documents.getDocument({ id });
9560
9774
  let extra = {};
9561
9775
  if (includeText) {
@@ -9602,7 +9816,7 @@ var documentGet = ({ client, log }) => ({
9602
9816
  ]
9603
9817
  };
9604
9818
  } catch (err) {
9605
- log.warn({ tool: "document_get", id, err: String(err) }, "document_get failed");
9819
+ log.warn({ tool: "document_get", documentId, externalId, type, err: String(err) }, "document_get failed");
9606
9820
  return toolError("document_get", err);
9607
9821
  }
9608
9822
  }
@@ -9717,13 +9931,23 @@ var document_query_default = documentQuery;
9717
9931
  // src/tools/document_update.ts
9718
9932
  var import_zod12 = require("zod");
9719
9933
  var inputSchema12 = {
9720
- documentId: import_zod12.z.string().min(1, "documentId is required").describe("The Vectros document id to update."),
9934
+ documentId: import_zod12.z.string().optional().describe("The Vectros document id to update. Provide this OR externalId+type."),
9935
+ externalId: import_zod12.z.string().optional().describe(
9936
+ "Your caller-owned externalId \u2014 an alternative selector to `documentId` (requires `type`). Updates the document you ingested under that externalId without a manual lookup first."
9937
+ ),
9938
+ type: import_zod12.z.string().optional().describe("Document type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9721
9939
  title: import_zod12.z.string().optional().describe("New title. Omit to keep the current title."),
9722
9940
  fields: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown()).optional().describe(
9723
9941
  "Typed-payload field\u2192value changes DEEP-MERGED into the existing payload (a field set to `null` is deleted; unspecified fields preserved). Omit to leave the payload unchanged."
9724
9942
  ),
9943
+ text: import_zod12.z.string().optional().describe(
9944
+ "New raw text body. Supplying it re-ingests the document body and re-queues it for indexing, so the search index reflects the edit (text-ingested documents only \u2014 not file-backed ones). Omit to leave the stored text unchanged. This is the in-place alternative to re-ingesting with upsert:true."
9945
+ ),
9725
9946
  folderId: import_zod12.z.string().optional().describe("Move the document into this folder (cannot be cleared once set)."),
9726
9947
  storeText: import_zod12.z.boolean().optional().describe("Whether the raw text is stored + retrievable."),
9948
+ status: import_zod12.z.enum(["ACTIVE", "ARCHIVED"]).optional().describe(
9949
+ "Lifecycle status. `ARCHIVED` soft-retracts the document \u2014 it is pulled from search and recall but kept and recoverable; set it back to `ACTIVE` to re-index and restore it. The way to retire superseded content when the credential has no delete authority. Omit to leave unchanged."
9950
+ ),
9727
9951
  userId: import_zod12.z.string().optional().describe("Reassign the owning user (Vectros UUID)."),
9728
9952
  orgId: import_zod12.z.string().optional().describe("Reassign the owning org (Vectros UUID)."),
9729
9953
  clientId: import_zod12.z.string().optional().describe("Reassign the associated client (Vectros UUID)."),
@@ -9734,33 +9958,41 @@ var inputSchema12 = {
9734
9958
  var documentUpdate = ({ client, log }) => ({
9735
9959
  name: "document_update",
9736
9960
  title: "Update a document",
9737
- description: "Update a document by id. `fields` are DEEP-MERGED into the existing typed payload (unspecified fields preserved; a field set to `null` is deleted); title/folderId/ownership are updated when provided and preserved otherwise. Pass `expectedVersion` (the version you last read) for safe concurrent edits \u2014 a stale update is refused (409). Requires the key to allow documents:u.",
9961
+ description: 'Update a document \u2014 select it by its Vectros `documentId` OR by your own `externalId` + `type` (resolved for you). `fields` are DEEP-MERGED into the existing typed payload (unspecified fields preserved; a field set to `null` is deleted); title/folderId/status/ownership are updated when provided and preserved otherwise. Supply `text` to re-ingest and re-index the document body in place (text documents only). Set `status: "ARCHIVED"` to soft-retract a document (pulled from search, kept and recoverable) and `status: "ACTIVE"` to restore it. Pass `expectedVersion` (the version you last read) for safe concurrent edits \u2014 a stale update is refused (409). Requires the key to allow documents:u.',
9738
9962
  inputSchema: inputSchema12,
9739
9963
  handler: async (args) => {
9740
9964
  const documentId = args.documentId;
9965
+ const externalId = args.externalId;
9966
+ const type = args.type;
9741
9967
  const title = args.title;
9742
9968
  const fields = args.fields;
9969
+ const text = args.text;
9743
9970
  const folderId = args.folderId;
9744
9971
  const storeText = args.storeText;
9972
+ const status = args.status;
9745
9973
  const userId = args.userId;
9746
9974
  const orgId = args.orgId;
9747
9975
  const clientId = args.clientId;
9748
9976
  const expectedVersion = args.expectedVersion;
9749
9977
  try {
9978
+ const resolved = await resolveDocumentIdByExternalId(client, { id: documentId, externalId, type });
9979
+ if ("error" in resolved) return toolError("document_update", new Error(resolved.error));
9750
9980
  const body = {};
9751
9981
  if (title !== void 0) body.title = title;
9752
9982
  if (fields !== void 0) body.payload = fields;
9983
+ if (text !== void 0) body.text = text;
9753
9984
  if (folderId !== void 0) body.folderId = folderId;
9754
9985
  if (storeText !== void 0) body.storeText = storeText;
9986
+ if (status !== void 0) body.status = status;
9755
9987
  if (userId !== void 0) body.userId = userId;
9756
9988
  if (orgId !== void 0) body.orgId = orgId;
9757
9989
  if (clientId !== void 0) body.clientId = clientId;
9758
9990
  if (expectedVersion !== void 0) body.expectedVersion = expectedVersion;
9759
9991
  const updated = await client.documents.patchDocument({
9760
- id: documentId,
9992
+ id: resolved.id,
9761
9993
  body
9762
9994
  });
9763
- log.debug({ tool: "document_update", documentId }, "document_update ok");
9995
+ log.debug({ tool: "document_update", documentId: resolved.id, externalId }, "document_update ok");
9764
9996
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
9765
9997
  } catch (err) {
9766
9998
  log.warn({ tool: "document_update", documentId, err: String(err) }, "document_update failed");
@@ -9778,7 +10010,7 @@ var inputSchema13 = {
9778
10010
  var documentDelete = ({ client, log }) => ({
9779
10011
  name: "document_delete",
9780
10012
  title: "Delete a document",
9781
- description: "Permanently delete a document by id (removes it and its indexed content). Requires the key to allow documents:d \u2014 a key without it gets a permission error.",
10013
+ description: 'Permanently delete a document by id (removes it and its indexed content). Requires the key to allow documents:d \u2014 a key without it gets a permission error. To archive without deleting, use document_update to set status: "ARCHIVED" instead (recoverable, and needs no delete authority).',
9782
10014
  inputSchema: inputSchema13,
9783
10015
  handler: async (args) => {
9784
10016
  const documentId = args.documentId;
@@ -9862,13 +10094,15 @@ var folderCreate = ({ client, log }) => ({
9862
10094
  handler: async (args) => {
9863
10095
  try {
9864
10096
  const folder = await client.folders.createFolder({
9865
- name: args.name,
9866
- description: args.description,
9867
- parentFolderId: args.parentId,
9868
- slug: args.slug,
9869
- userId: args.userId,
9870
- orgId: args.orgId,
9871
- clientId: args.clientId
10097
+ body: {
10098
+ name: args.name,
10099
+ description: args.description,
10100
+ parentFolderId: args.parentId,
10101
+ slug: args.slug,
10102
+ userId: args.userId,
10103
+ orgId: args.orgId,
10104
+ clientId: args.clientId
10105
+ }
9872
10106
  });
9873
10107
  log.debug({ tool: "folder_create", id: folder.id, name: folder.name }, "folder_create ok");
9874
10108
  return { content: [{ type: "text", text: JSON.stringify(folder, null, 2) }] };
@@ -10056,6 +10290,9 @@ var inputSchema19 = {
10056
10290
  externalId: import_zod18.z.string().optional().describe(
10057
10291
  "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."
10058
10292
  ),
10293
+ upsert: import_zod18.z.boolean().optional().describe(
10294
+ "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)."
10295
+ ),
10059
10296
  schemaId: import_zod18.z.string().optional().describe(
10060
10297
  "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."
10061
10298
  ),
@@ -10081,13 +10318,14 @@ var inputSchema19 = {
10081
10318
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10082
10319
  name: "document_ingest",
10083
10320
  title: "Ingest a new document (text body or file upload)",
10084
- description: "Create a new document in the partner 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 (status: PENDING_INDEX); poll with `document_get` to confirm INDEXED.\nEither `text` or `filePath` must be present; both is an error. Idempotent by `externalId` (re-ingest returns the existing document, not a duplicate). 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 (set false to skip storing the text body).",
10321
+ 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 (set false to skip storing the text body).",
10085
10322
  inputSchema: inputSchema19,
10086
10323
  handler: async (args) => {
10087
10324
  const title = args.title;
10088
10325
  const text = args.text;
10089
10326
  const filePath = args.filePath;
10090
10327
  const schemaId = args.schemaId;
10328
+ const upsert = args.upsert;
10091
10329
  const indexMode = args.indexMode ?? (schemaId ? void 0 : "HYBRID");
10092
10330
  if (!text && !filePath) {
10093
10331
  return toolError(
@@ -10122,11 +10360,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10122
10360
  if (text) {
10123
10361
  const storeText = args.storeText ?? true;
10124
10362
  const result = await client.documents.ingestDocument({
10125
- title,
10126
- text,
10127
- indexMode,
10128
- storeText,
10129
- ...common
10363
+ upsert,
10364
+ body: {
10365
+ title,
10366
+ text,
10367
+ indexMode,
10368
+ storeText,
10369
+ ...common
10370
+ }
10130
10371
  });
10131
10372
  log.debug(
10132
10373
  { tool: "document_ingest", mode: "text", id: result?.id, indexMode },
@@ -10156,6 +10397,7 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10156
10397
  const fileName = (0, import_node_path.basename)(safePath);
10157
10398
  const fileType = args.fileType ?? inferMimeType(safePath);
10158
10399
  const upload = await client.documents.uploadDocument({
10400
+ upsert,
10159
10401
  fileName,
10160
10402
  fileType,
10161
10403
  indexMode,
@@ -10194,8 +10436,8 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10194
10436
  text: JSON.stringify(
10195
10437
  {
10196
10438
  ...upload,
10197
- status: "PENDING_INDEX",
10198
- _note: "File uploaded; indexing is asynchronous. Poll document_get(id) until status is INDEXED."
10439
+ indexStatus: "PENDING_INDEX",
10440
+ _note: "File uploaded; indexing is asynchronous. Poll document_get(id) until indexStatus is INDEXED."
10199
10441
  },
10200
10442
  null,
10201
10443
  2
@@ -10492,6 +10734,14 @@ function zodTypeToJsonSchema(schema) {
10492
10734
 
10493
10735
  // src/server.ts
10494
10736
  var DEFAULT_ENVIRONMENT = "https://api.vectros.ai";
10737
+ function formatArgError(tool, error) {
10738
+ const unknownKeys = error.issues.filter((i) => i.code === "unrecognized_keys").flatMap((i) => i.keys);
10739
+ if (unknownKeys.length > 0) {
10740
+ const valid = Object.keys(tool.inputSchema).join(", ");
10741
+ return `Unknown argument(s): ${unknownKeys.join(", ")}. Valid arguments for ${tool.name}: ${valid}. Pass only these \u2014 an unrecognized argument is rejected, not ignored.`;
10742
+ }
10743
+ return `Invalid arguments: ${error.message}`;
10744
+ }
10495
10745
  var VectrosMCPServer = class {
10496
10746
  server;
10497
10747
  log;
@@ -10581,7 +10831,11 @@ var VectrosMCPServer = class {
10581
10831
  name: t.name,
10582
10832
  title: t.title,
10583
10833
  description: t.description,
10584
- inputSchema: zodShapeToJsonSchema(t.inputSchema)
10834
+ // additionalProperties:false advertises strict argument validation to the
10835
+ // MCP client (a well-behaved client rejects an unknown key before the call).
10836
+ // The server enforces it too (z.object(...).strict() below) so a raw JSON-RPC
10837
+ // call that skips client validation is still rejected — belt and suspenders.
10838
+ inputSchema: { ...zodShapeToJsonSchema(t.inputSchema), additionalProperties: false }
10585
10839
  }))
10586
10840
  })
10587
10841
  );
@@ -10591,9 +10845,9 @@ var VectrosMCPServer = class {
10591
10845
  if (!tool) {
10592
10846
  result = toolError(req.params.name ?? "unknown", new Error("No such tool."));
10593
10847
  } else {
10594
- const parsed = import_zod22.z.object(tool.inputSchema).safeParse(req.params.arguments ?? {});
10848
+ const parsed = import_zod22.z.object(tool.inputSchema).strict().safeParse(req.params.arguments ?? {});
10595
10849
  if (!parsed.success) {
10596
- result = toolError(tool.name, new Error(`Invalid arguments: ${parsed.error.message}`));
10850
+ result = toolError(tool.name, new Error(formatArgError(tool, parsed.error)));
10597
10851
  } else {
10598
10852
  try {
10599
10853
  result = await tool.handler(