@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/cli.mjs CHANGED
@@ -481,10 +481,18 @@ var DocumentRequest;
481
481
  Text: "TEXT",
482
482
  None: "NONE"
483
483
  };
484
+ DocumentRequest2.Status = {
485
+ Active: "ACTIVE",
486
+ Archived: "ARCHIVED"
487
+ };
484
488
  })(DocumentRequest || (DocumentRequest = {}));
485
489
  var DocumentResponse;
486
490
  ((DocumentResponse2) => {
487
491
  DocumentResponse2.Status = {
492
+ Active: "ACTIVE",
493
+ Archived: "ARCHIVED"
494
+ };
495
+ DocumentResponse2.IndexStatus = {
488
496
  PendingUpload: "PENDING_UPLOAD",
489
497
  Uploaded: "UPLOADED",
490
498
  Extracting: "EXTRACTING",
@@ -2838,7 +2846,7 @@ var AuthClient = class {
2838
2846
  );
2839
2847
  }
2840
2848
  /**
2841
- * 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.
2849
+ * 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.
2842
2850
  *
2843
2851
  * @param {Vectros.CreateAccessProfileRequest} request
2844
2852
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -2860,7 +2868,10 @@ var AuthClient = class {
2860
2868
  return HttpResponsePromise.fromPromise(this.__createAccessProfile(request, requestOptions));
2861
2869
  }
2862
2870
  async __createAccessProfile(request, requestOptions) {
2863
- const { contextId, body: _body } = request;
2871
+ const { contextId, upsert, body: _body } = request;
2872
+ const _queryParams = {
2873
+ upsert
2874
+ };
2864
2875
  const _authRequest = await this._options.authProvider.getAuthRequest();
2865
2876
  const _headers = mergeHeaders(
2866
2877
  _authRequest.headers,
@@ -2875,7 +2886,7 @@ var AuthClient = class {
2875
2886
  method: "POST",
2876
2887
  headers: _headers,
2877
2888
  contentType: "application/json",
2878
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
2889
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
2879
2890
  requestType: "json",
2880
2891
  body: _body,
2881
2892
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -2970,9 +2981,9 @@ var AuthClient = class {
2970
2981
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
2971
2982
  }
2972
2983
  /**
2973
- * 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.
2984
+ * 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.
2974
2985
  *
2975
- * @param {Vectros.AppContextRequest} request
2986
+ * @param {Vectros.CreateAppContextRequest} request
2976
2987
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
2977
2988
  *
2978
2989
  * @throws {@link Vectros.BadRequestError}
@@ -2981,14 +2992,20 @@ var AuthClient = class {
2981
2992
  *
2982
2993
  * @example
2983
2994
  * await client.auth.createAppContext({
2984
- * contextId: "myapp",
2985
- * name: "My Internal App"
2995
+ * body: {
2996
+ * contextId: "myapp",
2997
+ * name: "My Internal App"
2998
+ * }
2986
2999
  * })
2987
3000
  */
2988
3001
  createAppContext(request, requestOptions) {
2989
3002
  return HttpResponsePromise.fromPromise(this.__createAppContext(request, requestOptions));
2990
3003
  }
2991
3004
  async __createAppContext(request, requestOptions) {
3005
+ const { upsert, body: _body } = request;
3006
+ const _queryParams = {
3007
+ upsert
3008
+ };
2992
3009
  const _authRequest = await this._options.authProvider.getAuthRequest();
2993
3010
  const _headers = mergeHeaders(
2994
3011
  _authRequest.headers,
@@ -3003,9 +3020,9 @@ var AuthClient = class {
3003
3020
  method: "POST",
3004
3021
  headers: _headers,
3005
3022
  contentType: "application/json",
3006
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3023
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3007
3024
  requestType: "json",
3008
- body: request,
3025
+ body: _body,
3009
3026
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
3010
3027
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
3011
3028
  abortSignal: requestOptions?.abortSignal,
@@ -3101,7 +3118,7 @@ var AuthClient = class {
3101
3118
  );
3102
3119
  }
3103
3120
  /**
3104
- * 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.
3121
+ * 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.
3105
3122
  *
3106
3123
  * @param {Vectros.CreateRoleRequest} request
3107
3124
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3127,7 +3144,10 @@ var AuthClient = class {
3127
3144
  return HttpResponsePromise.fromPromise(this.__createRole(request, requestOptions));
3128
3145
  }
3129
3146
  async __createRole(request, requestOptions) {
3130
- const { contextId, body: _body } = request;
3147
+ const { contextId, upsert, body: _body } = request;
3148
+ const _queryParams = {
3149
+ upsert
3150
+ };
3131
3151
  const _authRequest = await this._options.authProvider.getAuthRequest();
3132
3152
  const _headers = mergeHeaders(
3133
3153
  _authRequest.headers,
@@ -3142,7 +3162,7 @@ var AuthClient = class {
3142
3162
  method: "POST",
3143
3163
  headers: _headers,
3144
3164
  contentType: "application/json",
3145
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3165
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3146
3166
  requestType: "json",
3147
3167
  body: _body,
3148
3168
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4626,23 +4646,30 @@ var DocumentsClient = class {
4626
4646
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
4627
4647
  }
4628
4648
  /**
4629
- * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Requires the `documents:c` scope.
4649
+ * 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.
4630
4650
  *
4631
- * @param {Vectros.DocumentRequest} request
4651
+ * @param {Vectros.IngestDocumentRequest} request
4632
4652
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4633
4653
  *
4634
4654
  * @throws {@link Vectros.BadRequestError}
4655
+ * @throws {@link Vectros.ForbiddenError}
4635
4656
  * @throws {@link Vectros.TooManyRequestsError}
4636
4657
  *
4637
4658
  * @example
4638
4659
  * await client.documents.ingestDocument({
4639
- * title: "Patient Intake Form \u2014 Jane Doe"
4660
+ * body: {
4661
+ * title: "Patient Intake Form \u2014 Jane Doe"
4662
+ * }
4640
4663
  * })
4641
4664
  */
4642
4665
  ingestDocument(request, requestOptions) {
4643
4666
  return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
4644
4667
  }
4645
4668
  async __ingestDocument(request, requestOptions) {
4669
+ const { upsert, body: _body } = request;
4670
+ const _queryParams = {
4671
+ upsert
4672
+ };
4646
4673
  const _authRequest = await this._options.authProvider.getAuthRequest();
4647
4674
  const _headers = mergeHeaders(
4648
4675
  _authRequest.headers,
@@ -4657,9 +4684,9 @@ var DocumentsClient = class {
4657
4684
  method: "POST",
4658
4685
  headers: _headers,
4659
4686
  contentType: "application/json",
4660
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4687
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4661
4688
  requestType: "json",
4662
- body: request,
4689
+ body: _body,
4663
4690
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4664
4691
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4665
4692
  abortSignal: requestOptions?.abortSignal,
@@ -4673,6 +4700,8 @@ var DocumentsClient = class {
4673
4700
  switch (_response.error.statusCode) {
4674
4701
  case 400:
4675
4702
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4703
+ case 403:
4704
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4676
4705
  case 429:
4677
4706
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4678
4707
  default:
@@ -4939,7 +4968,7 @@ var DocumentsClient = class {
4939
4968
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
4940
4969
  }
4941
4970
  /**
4942
- * 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.
4971
+ * Finds documents of a given type by field value. Supported fields: `externalId` (the document's first-class external identifier — no schema declaration required) and any field declared as a lookup field on the bound schema. A lookup on a sensitive field is rejected here because the value would appear in the URL query string; use POST /v1/documents/lookup (the request-body variant) for a sensitive field instead. Results are paginated: set `limit` for the page size and feed the returned `nextCursor` back as `startFrom` to fetch the next page. The response is a `{data, nextCursor}` envelope. Requires the `documents:r` scope.
4943
4972
  *
4944
4973
  * @param {Vectros.LookupDocumentsRequest} request
4945
4974
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5234,12 +5263,13 @@ var DocumentsClient = class {
5234
5263
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
5235
5264
  }
5236
5265
  /**
5237
- * 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.
5266
+ * 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).
5238
5267
  *
5239
5268
  * @param {Vectros.FileUploadRequest} request
5240
5269
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
5241
5270
  *
5242
5271
  * @throws {@link Vectros.BadRequestError}
5272
+ * @throws {@link Vectros.ForbiddenError}
5243
5273
  * @throws {@link Vectros.TooManyRequestsError}
5244
5274
  *
5245
5275
  * @example
@@ -5252,6 +5282,10 @@ var DocumentsClient = class {
5252
5282
  return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
5253
5283
  }
5254
5284
  async __uploadDocument(request, requestOptions) {
5285
+ const { upsert, ..._body } = request;
5286
+ const _queryParams = {
5287
+ upsert
5288
+ };
5255
5289
  const _authRequest = await this._options.authProvider.getAuthRequest();
5256
5290
  const _headers = mergeHeaders(
5257
5291
  _authRequest.headers,
@@ -5266,9 +5300,9 @@ var DocumentsClient = class {
5266
5300
  method: "POST",
5267
5301
  headers: _headers,
5268
5302
  contentType: "application/json",
5269
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5303
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5270
5304
  requestType: "json",
5271
- body: request,
5305
+ body: _body,
5272
5306
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5273
5307
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5274
5308
  abortSignal: requestOptions?.abortSignal,
@@ -5282,6 +5316,8 @@ var DocumentsClient = class {
5282
5316
  switch (_response.error.statusCode) {
5283
5317
  case 400:
5284
5318
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5319
+ case 403:
5320
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5285
5321
  case 429:
5286
5322
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5287
5323
  default:
@@ -5360,23 +5396,30 @@ var FoldersClient = class {
5360
5396
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
5361
5397
  }
5362
5398
  /**
5363
- * 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.
5399
+ * 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.
5364
5400
  *
5365
- * @param {Vectros.FolderRequest} request
5401
+ * @param {Vectros.CreateFolderRequest} request
5366
5402
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5367
5403
  *
5368
5404
  * @throws {@link Vectros.BadRequestError}
5405
+ * @throws {@link Vectros.ForbiddenError}
5369
5406
  * @throws {@link Vectros.TooManyRequestsError}
5370
5407
  *
5371
5408
  * @example
5372
5409
  * await client.folders.createFolder({
5373
- * name: "Patient Records 2024"
5410
+ * body: {
5411
+ * name: "Patient Records 2024"
5412
+ * }
5374
5413
  * })
5375
5414
  */
5376
5415
  createFolder(request, requestOptions) {
5377
5416
  return HttpResponsePromise.fromPromise(this.__createFolder(request, requestOptions));
5378
5417
  }
5379
5418
  async __createFolder(request, requestOptions) {
5419
+ const { upsert, body: _body } = request;
5420
+ const _queryParams = {
5421
+ upsert
5422
+ };
5380
5423
  const _authRequest = await this._options.authProvider.getAuthRequest();
5381
5424
  const _headers = mergeHeaders(
5382
5425
  _authRequest.headers,
@@ -5391,9 +5434,9 @@ var FoldersClient = class {
5391
5434
  method: "POST",
5392
5435
  headers: _headers,
5393
5436
  contentType: "application/json",
5394
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5437
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5395
5438
  requestType: "json",
5396
- body: request,
5439
+ body: _body,
5397
5440
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5398
5441
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5399
5442
  abortSignal: requestOptions?.abortSignal,
@@ -5407,6 +5450,8 @@ var FoldersClient = class {
5407
5450
  switch (_response.error.statusCode) {
5408
5451
  case 400:
5409
5452
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5453
+ case 403:
5454
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5410
5455
  case 429:
5411
5456
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5412
5457
  default:
@@ -5815,23 +5860,30 @@ var IdentityClient = class {
5815
5860
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/clients");
5816
5861
  }
5817
5862
  /**
5818
- * 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.
5863
+ * 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.
5819
5864
  *
5820
- * @param {Vectros.ClientRequest} request
5865
+ * @param {Vectros.CreateClientRequest} request
5821
5866
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
5822
5867
  *
5823
5868
  * @throws {@link Vectros.BadRequestError}
5869
+ * @throws {@link Vectros.ForbiddenError}
5824
5870
  * @throws {@link Vectros.TooManyRequestsError}
5825
5871
  *
5826
5872
  * @example
5827
5873
  * await client.identity.createClient({
5828
- * externalId: "patient_789"
5874
+ * body: {
5875
+ * externalId: "patient_789"
5876
+ * }
5829
5877
  * })
5830
5878
  */
5831
5879
  createClient(request, requestOptions) {
5832
5880
  return HttpResponsePromise.fromPromise(this.__createClient(request, requestOptions));
5833
5881
  }
5834
5882
  async __createClient(request, requestOptions) {
5883
+ const { upsert, body: _body } = request;
5884
+ const _queryParams = {
5885
+ upsert
5886
+ };
5835
5887
  const _authRequest = await this._options.authProvider.getAuthRequest();
5836
5888
  const _headers = mergeHeaders(
5837
5889
  _authRequest.headers,
@@ -5846,9 +5898,9 @@ var IdentityClient = class {
5846
5898
  method: "POST",
5847
5899
  headers: _headers,
5848
5900
  contentType: "application/json",
5849
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5901
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5850
5902
  requestType: "json",
5851
- body: request,
5903
+ body: _body,
5852
5904
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5853
5905
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5854
5906
  abortSignal: requestOptions?.abortSignal,
@@ -5862,6 +5914,8 @@ var IdentityClient = class {
5862
5914
  switch (_response.error.statusCode) {
5863
5915
  case 400:
5864
5916
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5917
+ case 403:
5918
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5865
5919
  case 429:
5866
5920
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5867
5921
  default:
@@ -6250,23 +6304,30 @@ var IdentityClient = class {
6250
6304
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/orgs");
6251
6305
  }
6252
6306
  /**
6253
- * 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.
6307
+ * 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.
6254
6308
  *
6255
- * @param {Vectros.OrgRequest} request
6309
+ * @param {Vectros.CreateOrgRequest} request
6256
6310
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6257
6311
  *
6258
6312
  * @throws {@link Vectros.BadRequestError}
6313
+ * @throws {@link Vectros.ForbiddenError}
6259
6314
  * @throws {@link Vectros.TooManyRequestsError}
6260
6315
  *
6261
6316
  * @example
6262
6317
  * await client.identity.createOrg({
6263
- * externalId: "clinic_001"
6318
+ * body: {
6319
+ * externalId: "clinic_001"
6320
+ * }
6264
6321
  * })
6265
6322
  */
6266
6323
  createOrg(request, requestOptions) {
6267
6324
  return HttpResponsePromise.fromPromise(this.__createOrg(request, requestOptions));
6268
6325
  }
6269
6326
  async __createOrg(request, requestOptions) {
6327
+ const { upsert, body: _body } = request;
6328
+ const _queryParams = {
6329
+ upsert
6330
+ };
6270
6331
  const _authRequest = await this._options.authProvider.getAuthRequest();
6271
6332
  const _headers = mergeHeaders(
6272
6333
  _authRequest.headers,
@@ -6281,9 +6342,9 @@ var IdentityClient = class {
6281
6342
  method: "POST",
6282
6343
  headers: _headers,
6283
6344
  contentType: "application/json",
6284
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6345
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6285
6346
  requestType: "json",
6286
- body: request,
6347
+ body: _body,
6287
6348
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6288
6349
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6289
6350
  abortSignal: requestOptions?.abortSignal,
@@ -6297,6 +6358,8 @@ var IdentityClient = class {
6297
6358
  switch (_response.error.statusCode) {
6298
6359
  case 400:
6299
6360
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6361
+ case 403:
6362
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6300
6363
  case 429:
6301
6364
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6302
6365
  default:
@@ -6671,23 +6734,30 @@ var IdentityClient = class {
6671
6734
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
6672
6735
  }
6673
6736
  /**
6674
- * 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.
6737
+ * 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.
6675
6738
  *
6676
- * @param {Vectros.UserRequest} request
6739
+ * @param {Vectros.CreateUserRequest} request
6677
6740
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6678
6741
  *
6679
6742
  * @throws {@link Vectros.BadRequestError}
6743
+ * @throws {@link Vectros.ForbiddenError}
6680
6744
  * @throws {@link Vectros.TooManyRequestsError}
6681
6745
  *
6682
6746
  * @example
6683
6747
  * await client.identity.createUser({
6684
- * externalId: "usr_12345"
6748
+ * body: {
6749
+ * externalId: "usr_12345"
6750
+ * }
6685
6751
  * })
6686
6752
  */
6687
6753
  createUser(request, requestOptions) {
6688
6754
  return HttpResponsePromise.fromPromise(this.__createUser(request, requestOptions));
6689
6755
  }
6690
6756
  async __createUser(request, requestOptions) {
6757
+ const { upsert, body: _body } = request;
6758
+ const _queryParams = {
6759
+ upsert
6760
+ };
6691
6761
  const _authRequest = await this._options.authProvider.getAuthRequest();
6692
6762
  const _headers = mergeHeaders(
6693
6763
  _authRequest.headers,
@@ -6702,9 +6772,9 @@ var IdentityClient = class {
6702
6772
  method: "POST",
6703
6773
  headers: _headers,
6704
6774
  contentType: "application/json",
6705
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6775
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6706
6776
  requestType: "json",
6707
- body: request,
6777
+ body: _body,
6708
6778
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6709
6779
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6710
6780
  abortSignal: requestOptions?.abortSignal,
@@ -6718,6 +6788,8 @@ var IdentityClient = class {
6718
6788
  switch (_response.error.statusCode) {
6719
6789
  case 400:
6720
6790
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6791
+ case 403:
6792
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6721
6793
  case 429:
6722
6794
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6723
6795
  default:
@@ -7535,9 +7607,9 @@ var RecordsClient = class {
7535
7607
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
7536
7608
  }
7537
7609
  /**
7538
- * 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.
7610
+ * 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.
7539
7611
  *
7540
- * @param {Vectros.RecordRequest} request
7612
+ * @param {Vectros.CreateRecordRequest} request
7541
7613
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7542
7614
  *
7543
7615
  * @throws {@link Vectros.BadRequestError}
@@ -7546,19 +7618,25 @@ var RecordsClient = class {
7546
7618
  *
7547
7619
  * @example
7548
7620
  * await client.records.createRecord({
7549
- * typeName: "intake_form",
7550
- * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7551
- * payload: {
7552
- * "first_name": "Jane",
7553
- * "email": "jane@example.com"
7554
- * },
7555
- * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7621
+ * body: {
7622
+ * typeName: "intake_form",
7623
+ * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7624
+ * payload: {
7625
+ * "first_name": "Jane",
7626
+ * "email": "jane@example.com"
7627
+ * },
7628
+ * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7629
+ * }
7556
7630
  * })
7557
7631
  */
7558
7632
  createRecord(request, requestOptions) {
7559
7633
  return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
7560
7634
  }
7561
7635
  async __createRecord(request, requestOptions) {
7636
+ const { upsert, body: _body } = request;
7637
+ const _queryParams = {
7638
+ upsert
7639
+ };
7562
7640
  const _authRequest = await this._options.authProvider.getAuthRequest();
7563
7641
  const _headers = mergeHeaders(
7564
7642
  _authRequest.headers,
@@ -7573,9 +7651,9 @@ var RecordsClient = class {
7573
7651
  method: "POST",
7574
7652
  headers: _headers,
7575
7653
  contentType: "application/json",
7576
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
7654
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7577
7655
  requestType: "json",
7578
- body: request,
7656
+ body: _body,
7579
7657
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7580
7658
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7581
7659
  abortSignal: requestOptions?.abortSignal,
@@ -8171,43 +8249,50 @@ var SchemasClient = class {
8171
8249
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
8172
8250
  }
8173
8251
  /**
8174
- * 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.
8252
+ * 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.
8175
8253
  *
8176
- * @param {Vectros.SchemaRequest} request
8254
+ * @param {Vectros.CreateSchemaRequest} request
8177
8255
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8178
8256
  *
8179
8257
  * @throws {@link Vectros.BadRequestError}
8258
+ * @throws {@link Vectros.ForbiddenError}
8180
8259
  * @throws {@link Vectros.TooManyRequestsError}
8181
8260
  *
8182
8261
  * @example
8183
8262
  * await client.schemas.createSchema({
8184
- * typeName: "intake_form",
8185
- * displayName: "Client Intake Form",
8186
- * description: "Captures initial client information",
8187
- * fields: [{
8188
- * fieldId: "first_name",
8189
- * fieldType: "string",
8190
- * required: true,
8191
- * searchable: true
8192
- * }, {
8193
- * fieldId: "email",
8194
- * fieldType: "string",
8195
- * required: true
8196
- * }],
8197
- * lookupFields: [{
8198
- * fieldName: "email",
8199
- * unique: true
8200
- * }],
8201
- * capabilities: {
8202
- * "auditHistory": true
8203
- * },
8204
- * allowedSurfaces: ["record"]
8263
+ * body: {
8264
+ * typeName: "intake_form",
8265
+ * displayName: "Client Intake Form",
8266
+ * description: "Captures initial client information",
8267
+ * fields: [{
8268
+ * fieldId: "first_name",
8269
+ * fieldType: "string",
8270
+ * required: true,
8271
+ * searchable: true
8272
+ * }, {
8273
+ * fieldId: "email",
8274
+ * fieldType: "string",
8275
+ * required: true
8276
+ * }],
8277
+ * lookupFields: [{
8278
+ * fieldName: "email",
8279
+ * unique: true
8280
+ * }],
8281
+ * capabilities: {
8282
+ * "auditHistory": true
8283
+ * },
8284
+ * allowedSurfaces: ["record"]
8285
+ * }
8205
8286
  * })
8206
8287
  */
8207
8288
  createSchema(request, requestOptions) {
8208
8289
  return HttpResponsePromise.fromPromise(this.__createSchema(request, requestOptions));
8209
8290
  }
8210
8291
  async __createSchema(request, requestOptions) {
8292
+ const { upsert, body: _body } = request;
8293
+ const _queryParams = {
8294
+ upsert
8295
+ };
8211
8296
  const _authRequest = await this._options.authProvider.getAuthRequest();
8212
8297
  const _headers = mergeHeaders(
8213
8298
  _authRequest.headers,
@@ -8222,9 +8307,9 @@ var SchemasClient = class {
8222
8307
  method: "POST",
8223
8308
  headers: _headers,
8224
8309
  contentType: "application/json",
8225
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
8310
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
8226
8311
  requestType: "json",
8227
- body: request,
8312
+ body: _body,
8228
8313
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
8229
8314
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
8230
8315
  abortSignal: requestOptions?.abortSignal,
@@ -8238,6 +8323,8 @@ var SchemasClient = class {
8238
8323
  switch (_response.error.statusCode) {
8239
8324
  case 400:
8240
8325
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8326
+ case 403:
8327
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
8241
8328
  case 429:
8242
8329
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
8243
8330
  default:
@@ -8677,8 +8764,8 @@ function warnOnSuboptimalKey(key, log) {
8677
8764
 
8678
8765
  // src/build-info.ts
8679
8766
  var BUILD_INFO = {
8680
- mcpServer: true ? "0.5.2" : "dev",
8681
- sdk: true ? "0.30.0" : "dev"
8767
+ mcpServer: true ? "0.6.0" : "dev",
8768
+ sdk: true ? "0.32.0" : "dev"
8682
8769
  };
8683
8770
  function formatBuildInfo() {
8684
8771
  return `vectros-mcp-server ${BUILD_INFO.mcpServer} (sdk ${BUILD_INFO.sdk})`;
@@ -8918,6 +9005,32 @@ ${detail}${pointer}`
8918
9005
  // src/tools/hybrid_search.ts
8919
9006
  var MCP_DEFAULT_LIMIT = 3;
8920
9007
  var MCP_MAX_LIMIT = 10;
9008
+ var MCP_RESPONSE_CHAR_BUDGET = 24e3;
9009
+ var SNIPPET_MAX_CHARS = 160;
9010
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["tenantId", "owner_id", "model_type", "rootFolderId", "folderId"]);
9011
+ function trimMetadata(md) {
9012
+ if (!md || typeof md !== "object" || Array.isArray(md)) return md;
9013
+ const out = {};
9014
+ for (const [k, v] of Object.entries(md)) {
9015
+ if (!INTERNAL_METADATA_KEYS.has(k)) out[k] = v;
9016
+ }
9017
+ return out;
9018
+ }
9019
+ function reshapeHit(hit, includeContext) {
9020
+ const out = { ...hit };
9021
+ if ("metadata" in out) out.metadata = trimMetadata(out.metadata);
9022
+ const chunk = typeof hit.chunkText === "string" ? hit.chunkText : void 0;
9023
+ const ctx = typeof hit.contextText === "string" ? hit.contextText : void 0;
9024
+ if (includeContext) {
9025
+ if (chunk && ctx && ctx.includes(chunk)) delete out.chunkText;
9026
+ } else {
9027
+ delete out.contextText;
9028
+ }
9029
+ if ((out.snippet == null || out.snippet === "") && chunk) {
9030
+ out.snippet = chunk.length > SNIPPET_MAX_CHARS ? chunk.slice(0, SNIPPET_MAX_CHARS) + "\u2026" : chunk;
9031
+ }
9032
+ return out;
9033
+ }
8921
9034
  var inputSchema = {
8922
9035
  query: z.string().min(1, "query must be non-empty").describe("Natural-language or keyword query."),
8923
9036
  mode: z.enum(["HYBRID", "TEXT", "SEMANTIC"]).optional().describe("HYBRID (BM25 + dense, default), TEXT (BM25 only), or SEMANTIC (dense only)."),
@@ -8932,7 +9045,9 @@ var inputSchema = {
8932
9045
  rootFolderId: z.string().optional().describe("Scope to content under this folder AND all its descendants (subtree-root folder id)."),
8933
9046
  // Content narrowing.
8934
9047
  contentTypes: z.array(z.enum(["documents", "records"])).optional().describe('Narrow to content types. ["documents"] or ["records"]; omit for unified (both).'),
8935
- typeName: z.string().optional().describe('Restrict record hits to this schema type (e.g. "patient"). No-op for documents.'),
9048
+ typeName: z.string().optional().describe(
9049
+ '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.'
9050
+ ),
8936
9051
  filters: z.record(z.unknown()).optional().describe(
8937
9052
  '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}}.'
8938
9053
  ),
@@ -8947,6 +9062,9 @@ var inputSchema = {
8947
9062
  minSimilarity: z.number().min(0).max(1).optional().describe("Minimum semantic similarity (0.0\u20131.0); results below are excluded."),
8948
9063
  minTextRelevance: z.number().min(0).max(1).optional().describe("Minimum relative keyword (BM25) relevance (0.0\u20131.0); the keyword-leg analogue of minSimilarity."),
8949
9064
  uniqueDocuments: z.boolean().optional().describe("When true, return at most one chunk per source document."),
9065
+ includeContext: z.boolean().optional().describe(
9066
+ "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."
9067
+ ),
8950
9068
  requireComplete: z.boolean().optional().describe(
8951
9069
  "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)."
8952
9070
  )
@@ -8954,7 +9072,7 @@ var inputSchema = {
8954
9072
  var hybridSearch = ({ client, log }) => ({
8955
9073
  name: "hybrid_search",
8956
9074
  title: "Hybrid search",
8957
- 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.",
9075
+ 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".`,
8958
9076
  inputSchema,
8959
9077
  handler: async (args) => {
8960
9078
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -8981,13 +9099,30 @@ var hybridSearch = ({ client, log }) => ({
8981
9099
  uniqueDocuments: args.uniqueDocuments,
8982
9100
  requireComplete: args.requireComplete
8983
9101
  });
9102
+ const hits = Array.isArray(
9103
+ result.results
9104
+ ) ? result.results : [];
9105
+ const includeContext = args.includeContext ?? false;
9106
+ let shaped = hits.map((h) => reshapeHit(h, includeContext));
9107
+ const textLegEmpty = mode !== "SEMANTIC" && hits.length > 0 && hits.every((h) => !(Number(h.textScore) > 0));
9108
+ const envelope = {
9109
+ ...result,
9110
+ results: shaped
9111
+ };
9112
+ if (textLegEmpty) envelope.textLegEmpty = true;
9113
+ let text = JSON.stringify(envelope, null, 2);
9114
+ while (shaped.length > 1 && text.length > MCP_RESPONSE_CHAR_BUDGET) {
9115
+ shaped = shaped.slice(0, -1);
9116
+ envelope.results = shaped;
9117
+ envelope.truncated = true;
9118
+ envelope.returnedResults = shaped.length;
9119
+ text = JSON.stringify(envelope, null, 2);
9120
+ }
8984
9121
  log.debug(
8985
- { tool: "hybrid_search", mode, limit, returned: result.results?.length },
9122
+ { tool: "hybrid_search", mode, limit, returned: shaped.length, textLegEmpty },
8986
9123
  "hybrid_search ok"
8987
9124
  );
8988
- return {
8989
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
8990
- };
9125
+ return { content: [{ type: "text", text }] };
8991
9126
  } catch (err) {
8992
9127
  log.warn({ tool: "hybrid_search", err: String(err) }, "hybrid_search failed");
8993
9128
  return toolError("hybrid_search", err);
@@ -9022,7 +9157,9 @@ var MCP_MAX_LIMIT2 = 10;
9022
9157
  var inputSchema2 = {
9023
9158
  type: z2.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9024
9159
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9025
- field: z2.string().optional().describe("Lookup mode: name of the lookup-indexed field to query by."),
9160
+ field: z2.string().optional().describe(
9161
+ '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.)'
9162
+ ),
9026
9163
  value: z2.string().optional().describe("Lookup mode (equality): exact-match value for `field`."),
9027
9164
  from: z2.string().optional().describe("Lookup mode (range): inclusive lower bound; requires `to`. Non-sensitive fields only."),
9028
9165
  to: z2.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
@@ -9117,19 +9254,77 @@ var record_query_default = recordQuery;
9117
9254
 
9118
9255
  // src/tools/record_get.ts
9119
9256
  import { z as z3 } from "zod";
9257
+
9258
+ // src/tools/resolve-external-id.ts
9259
+ function validateSelector(sel, entity) {
9260
+ const hasId = sel.id !== void 0 && sel.id !== "";
9261
+ const hasExt = sel.externalId !== void 0 && sel.externalId !== "";
9262
+ if (hasId && hasExt) {
9263
+ return `Provide either id or externalId (not both) to identify the ${entity}.`;
9264
+ }
9265
+ if (!hasId && !hasExt) {
9266
+ return `Provide id, or externalId + type, to identify the ${entity}.`;
9267
+ }
9268
+ if (hasExt && (sel.type === void 0 || sel.type === "")) {
9269
+ return "type is required when selecting by externalId (externalId is unique within a type).";
9270
+ }
9271
+ return void 0;
9272
+ }
9273
+ async function resolveRecordIdByExternalId(client, sel) {
9274
+ const err = validateSelector(sel, "record");
9275
+ if (err) return { error: err };
9276
+ if (sel.id) return { id: sel.id };
9277
+ const page = await client.records.lookupRecordsByBody({
9278
+ type: sel.type,
9279
+ field: "externalId",
9280
+ value: sel.externalId,
9281
+ limit: 1
9282
+ });
9283
+ const items = pageItems(page);
9284
+ if (items.length === 0) {
9285
+ return { error: `No record of type '${sel.type}' with externalId '${sel.externalId}'.` };
9286
+ }
9287
+ return { id: items[0].id };
9288
+ }
9289
+ async function resolveDocumentIdByExternalId(client, sel) {
9290
+ const err = validateSelector(sel, "document");
9291
+ if (err) return { error: err };
9292
+ if (sel.id) return { id: sel.id };
9293
+ const page = await client.documents.lookupDocumentsByBody({
9294
+ type: sel.type,
9295
+ field: "externalId",
9296
+ value: sel.externalId,
9297
+ limit: 1
9298
+ });
9299
+ const items = pageItems(page);
9300
+ if (items.length === 0) {
9301
+ return { error: `No document of type '${sel.type}' with externalId '${sel.externalId}'.` };
9302
+ }
9303
+ return { id: items[0].id };
9304
+ }
9305
+
9306
+ // src/tools/record_get.ts
9120
9307
  var MAX_PAYLOAD_CHARS = 32e3;
9121
9308
  var inputSchema3 = {
9122
- id: z3.string().min(1, "id is required").describe("The Vectros record id (e.g. from record_query or hybrid_search).")
9309
+ id: z3.string().optional().describe("The Vectros record id (e.g. from record_query or hybrid_search). Provide this OR externalId+type."),
9310
+ externalId: z3.string().optional().describe(
9311
+ "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."
9312
+ ),
9313
+ type: z3.string().optional().describe("Record type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type).")
9123
9314
  };
9124
9315
  var recordGet = ({ client, log }) => ({
9125
9316
  name: "record_get",
9126
- title: "Get a record by id",
9127
- 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.",
9317
+ title: "Get a record by id or externalId",
9318
+ 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.",
9128
9319
  inputSchema: inputSchema3,
9129
9320
  handler: async (args) => {
9130
9321
  const id = args.id;
9322
+ const externalId = args.externalId;
9323
+ const type = args.type;
9131
9324
  try {
9132
- const record = await client.records.getRecord({ id });
9325
+ const resolved = await resolveRecordIdByExternalId(client, { id, externalId, type });
9326
+ if ("error" in resolved) return toolError("record_get", new Error(resolved.error));
9327
+ const record = await client.records.getRecord({ id: resolved.id });
9133
9328
  const out = { ...record };
9134
9329
  if (record.payload !== void 0) {
9135
9330
  const json = JSON.stringify(record.payload);
@@ -9141,12 +9336,12 @@ var recordGet = ({ client, log }) => ({
9141
9336
  }
9142
9337
  }
9143
9338
  log.debug(
9144
- { tool: "record_get", id, truncated: out.payloadTruncated === true },
9339
+ { tool: "record_get", id: resolved.id, externalId, truncated: out.payloadTruncated === true },
9145
9340
  "record_get ok"
9146
9341
  );
9147
9342
  return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
9148
9343
  } catch (err) {
9149
- log.warn({ tool: "record_get", id, err: String(err) }, "record_get failed");
9344
+ log.warn({ tool: "record_get", id, externalId, type, err: String(err) }, "record_get failed");
9150
9345
  return toolError("record_get", err);
9151
9346
  }
9152
9347
  }
@@ -9181,15 +9376,17 @@ var recordCreate = ({ client, log }) => ({
9181
9376
  const type = args.type;
9182
9377
  try {
9183
9378
  const created = await client.records.createRecord({
9184
- typeName: type,
9185
- payload: args.fields,
9186
- externalId: args.externalId,
9187
- indexMode: args.indexMode,
9188
- status: args.status,
9189
- folderId: args.folderId,
9190
- userId: args.userId,
9191
- orgId: args.orgId,
9192
- clientId: args.clientId
9379
+ body: {
9380
+ typeName: type,
9381
+ payload: args.fields,
9382
+ externalId: args.externalId,
9383
+ indexMode: args.indexMode,
9384
+ status: args.status,
9385
+ folderId: args.folderId,
9386
+ userId: args.userId,
9387
+ orgId: args.orgId,
9388
+ clientId: args.clientId
9389
+ }
9193
9390
  });
9194
9391
  log.debug({ tool: "record_create", type, id: created.id }, "record_create ok");
9195
9392
  return { content: [{ type: "text", text: JSON.stringify(created, null, 2) }] };
@@ -9204,7 +9401,11 @@ var record_create_default = recordCreate;
9204
9401
  // src/tools/record_update.ts
9205
9402
  import { z as z5 } from "zod";
9206
9403
  var inputSchema5 = {
9207
- id: z5.string().min(1, "id is required").describe("The Vectros record id to update."),
9404
+ id: z5.string().optional().describe("The Vectros record id to update. Provide this OR externalId+type."),
9405
+ externalId: z5.string().optional().describe(
9406
+ "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."
9407
+ ),
9408
+ type: z5.string().optional().describe("Record type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9208
9409
  fields: z5.record(z5.string(), z5.unknown()).describe(
9209
9410
  "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."
9210
9411
  ),
@@ -9215,23 +9416,27 @@ var inputSchema5 = {
9215
9416
  };
9216
9417
  var recordUpdate = ({ client, log }) => ({
9217
9418
  name: "record_update",
9218
- title: "Update a record",
9219
- 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.",
9419
+ title: "Update a record by id or externalId",
9420
+ 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.",
9220
9421
  inputSchema: inputSchema5,
9221
9422
  handler: async (args) => {
9222
9423
  const id = args.id;
9424
+ const externalId = args.externalId;
9425
+ const type = args.type;
9223
9426
  const fields = args.fields;
9224
9427
  const status = args.status;
9225
9428
  const expectedVersion = args.expectedVersion;
9226
9429
  try {
9430
+ const resolved = await resolveRecordIdByExternalId(client, { id, externalId, type });
9431
+ if ("error" in resolved) return toolError("record_update", new Error(resolved.error));
9227
9432
  const body = { payload: fields };
9228
9433
  if (status !== void 0) body.status = status;
9229
9434
  if (expectedVersion !== void 0) body.expectedVersion = expectedVersion;
9230
- const updated = await client.records.patchRecord({ id, body });
9231
- log.debug({ tool: "record_update", id }, "record_update ok");
9435
+ const updated = await client.records.patchRecord({ id: resolved.id, body });
9436
+ log.debug({ tool: "record_update", id: resolved.id, externalId }, "record_update ok");
9232
9437
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
9233
9438
  } catch (err) {
9234
- log.warn({ tool: "record_update", id, err: String(err) }, "record_update failed");
9439
+ log.warn({ tool: "record_update", id, externalId, type, err: String(err) }, "record_update failed");
9235
9440
  return toolError("record_update", err);
9236
9441
  }
9237
9442
  }
@@ -9498,7 +9703,11 @@ var list_schemas_default = listSchemas;
9498
9703
  import { z as z10 } from "zod";
9499
9704
  var MAX_TEXT_CHARS = 32e3;
9500
9705
  var inputSchema10 = {
9501
- documentId: z10.string().min(1, "documentId is required").describe("The document ID returned by document_ingest or hybrid_search."),
9706
+ documentId: z10.string().optional().describe("The document ID returned by document_ingest or hybrid_search. Provide this OR externalId+type."),
9707
+ externalId: z10.string().optional().describe(
9708
+ "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."
9709
+ ),
9710
+ type: z10.string().optional().describe("Document type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9502
9711
  includeText: z10.boolean().optional().describe(
9503
9712
  "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."
9504
9713
  ),
@@ -9518,13 +9727,18 @@ function isNotAvailable(err) {
9518
9727
  var documentGet = ({ client, log }) => ({
9519
9728
  name: "document_get",
9520
9729
  title: "Get document metadata (and optionally text)",
9521
- 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.",
9730
+ 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`.",
9522
9731
  inputSchema: inputSchema10,
9523
9732
  handler: async (args) => {
9524
- const id = args.documentId;
9733
+ const documentId = args.documentId;
9734
+ const externalId = args.externalId;
9735
+ const type = args.type;
9525
9736
  const includeText = args.includeText ?? false;
9526
9737
  const includeDownloadUrl = args.includeDownloadUrl ?? false;
9527
9738
  try {
9739
+ const resolved = await resolveDocumentIdByExternalId(client, { id: documentId, externalId, type });
9740
+ if ("error" in resolved) return toolError("document_get", new Error(resolved.error));
9741
+ const id = resolved.id;
9528
9742
  const doc = await client.documents.getDocument({ id });
9529
9743
  let extra = {};
9530
9744
  if (includeText) {
@@ -9571,7 +9785,7 @@ var documentGet = ({ client, log }) => ({
9571
9785
  ]
9572
9786
  };
9573
9787
  } catch (err) {
9574
- log.warn({ tool: "document_get", id, err: String(err) }, "document_get failed");
9788
+ log.warn({ tool: "document_get", documentId, externalId, type, err: String(err) }, "document_get failed");
9575
9789
  return toolError("document_get", err);
9576
9790
  }
9577
9791
  }
@@ -9686,13 +9900,23 @@ var document_query_default = documentQuery;
9686
9900
  // src/tools/document_update.ts
9687
9901
  import { z as z12 } from "zod";
9688
9902
  var inputSchema12 = {
9689
- documentId: z12.string().min(1, "documentId is required").describe("The Vectros document id to update."),
9903
+ documentId: z12.string().optional().describe("The Vectros document id to update. Provide this OR externalId+type."),
9904
+ externalId: z12.string().optional().describe(
9905
+ "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."
9906
+ ),
9907
+ type: z12.string().optional().describe("Document type \u2014 REQUIRED when selecting by `externalId` (externalId is unique within a type)."),
9690
9908
  title: z12.string().optional().describe("New title. Omit to keep the current title."),
9691
9909
  fields: z12.record(z12.string(), z12.unknown()).optional().describe(
9692
9910
  "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."
9693
9911
  ),
9912
+ text: z12.string().optional().describe(
9913
+ "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."
9914
+ ),
9694
9915
  folderId: z12.string().optional().describe("Move the document into this folder (cannot be cleared once set)."),
9695
9916
  storeText: z12.boolean().optional().describe("Whether the raw text is stored + retrievable."),
9917
+ status: z12.enum(["ACTIVE", "ARCHIVED"]).optional().describe(
9918
+ "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."
9919
+ ),
9696
9920
  userId: z12.string().optional().describe("Reassign the owning user (Vectros UUID)."),
9697
9921
  orgId: z12.string().optional().describe("Reassign the owning org (Vectros UUID)."),
9698
9922
  clientId: z12.string().optional().describe("Reassign the associated client (Vectros UUID)."),
@@ -9703,33 +9927,41 @@ var inputSchema12 = {
9703
9927
  var documentUpdate = ({ client, log }) => ({
9704
9928
  name: "document_update",
9705
9929
  title: "Update a document",
9706
- 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.",
9930
+ 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.',
9707
9931
  inputSchema: inputSchema12,
9708
9932
  handler: async (args) => {
9709
9933
  const documentId = args.documentId;
9934
+ const externalId = args.externalId;
9935
+ const type = args.type;
9710
9936
  const title = args.title;
9711
9937
  const fields = args.fields;
9938
+ const text = args.text;
9712
9939
  const folderId = args.folderId;
9713
9940
  const storeText = args.storeText;
9941
+ const status = args.status;
9714
9942
  const userId = args.userId;
9715
9943
  const orgId = args.orgId;
9716
9944
  const clientId = args.clientId;
9717
9945
  const expectedVersion = args.expectedVersion;
9718
9946
  try {
9947
+ const resolved = await resolveDocumentIdByExternalId(client, { id: documentId, externalId, type });
9948
+ if ("error" in resolved) return toolError("document_update", new Error(resolved.error));
9719
9949
  const body = {};
9720
9950
  if (title !== void 0) body.title = title;
9721
9951
  if (fields !== void 0) body.payload = fields;
9952
+ if (text !== void 0) body.text = text;
9722
9953
  if (folderId !== void 0) body.folderId = folderId;
9723
9954
  if (storeText !== void 0) body.storeText = storeText;
9955
+ if (status !== void 0) body.status = status;
9724
9956
  if (userId !== void 0) body.userId = userId;
9725
9957
  if (orgId !== void 0) body.orgId = orgId;
9726
9958
  if (clientId !== void 0) body.clientId = clientId;
9727
9959
  if (expectedVersion !== void 0) body.expectedVersion = expectedVersion;
9728
9960
  const updated = await client.documents.patchDocument({
9729
- id: documentId,
9961
+ id: resolved.id,
9730
9962
  body
9731
9963
  });
9732
- log.debug({ tool: "document_update", documentId }, "document_update ok");
9964
+ log.debug({ tool: "document_update", documentId: resolved.id, externalId }, "document_update ok");
9733
9965
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
9734
9966
  } catch (err) {
9735
9967
  log.warn({ tool: "document_update", documentId, err: String(err) }, "document_update failed");
@@ -9747,7 +9979,7 @@ var inputSchema13 = {
9747
9979
  var documentDelete = ({ client, log }) => ({
9748
9980
  name: "document_delete",
9749
9981
  title: "Delete a document",
9750
- 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.",
9982
+ 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).',
9751
9983
  inputSchema: inputSchema13,
9752
9984
  handler: async (args) => {
9753
9985
  const documentId = args.documentId;
@@ -9831,13 +10063,15 @@ var folderCreate = ({ client, log }) => ({
9831
10063
  handler: async (args) => {
9832
10064
  try {
9833
10065
  const folder = await client.folders.createFolder({
9834
- name: args.name,
9835
- description: args.description,
9836
- parentFolderId: args.parentId,
9837
- slug: args.slug,
9838
- userId: args.userId,
9839
- orgId: args.orgId,
9840
- clientId: args.clientId
10066
+ body: {
10067
+ name: args.name,
10068
+ description: args.description,
10069
+ parentFolderId: args.parentId,
10070
+ slug: args.slug,
10071
+ userId: args.userId,
10072
+ orgId: args.orgId,
10073
+ clientId: args.clientId
10074
+ }
9841
10075
  });
9842
10076
  log.debug({ tool: "folder_create", id: folder.id, name: folder.name }, "folder_create ok");
9843
10077
  return { content: [{ type: "text", text: JSON.stringify(folder, null, 2) }] };
@@ -10025,6 +10259,9 @@ var inputSchema19 = {
10025
10259
  externalId: z18.string().optional().describe(
10026
10260
  "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."
10027
10261
  ),
10262
+ upsert: z18.boolean().optional().describe(
10263
+ "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)."
10264
+ ),
10028
10265
  schemaId: z18.string().optional().describe(
10029
10266
  "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."
10030
10267
  ),
@@ -10050,13 +10287,14 @@ var inputSchema19 = {
10050
10287
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10051
10288
  name: "document_ingest",
10052
10289
  title: "Ingest a new document (text body or file upload)",
10053
- 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).",
10290
+ 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).",
10054
10291
  inputSchema: inputSchema19,
10055
10292
  handler: async (args) => {
10056
10293
  const title = args.title;
10057
10294
  const text = args.text;
10058
10295
  const filePath = args.filePath;
10059
10296
  const schemaId = args.schemaId;
10297
+ const upsert = args.upsert;
10060
10298
  const indexMode = args.indexMode ?? (schemaId ? void 0 : "HYBRID");
10061
10299
  if (!text && !filePath) {
10062
10300
  return toolError(
@@ -10091,11 +10329,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10091
10329
  if (text) {
10092
10330
  const storeText = args.storeText ?? true;
10093
10331
  const result = await client.documents.ingestDocument({
10094
- title,
10095
- text,
10096
- indexMode,
10097
- storeText,
10098
- ...common
10332
+ upsert,
10333
+ body: {
10334
+ title,
10335
+ text,
10336
+ indexMode,
10337
+ storeText,
10338
+ ...common
10339
+ }
10099
10340
  });
10100
10341
  log.debug(
10101
10342
  { tool: "document_ingest", mode: "text", id: result?.id, indexMode },
@@ -10125,6 +10366,7 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10125
10366
  const fileName = basename(safePath);
10126
10367
  const fileType = args.fileType ?? inferMimeType(safePath);
10127
10368
  const upload = await client.documents.uploadDocument({
10369
+ upsert,
10128
10370
  fileName,
10129
10371
  fileType,
10130
10372
  indexMode,
@@ -10163,8 +10405,8 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10163
10405
  text: JSON.stringify(
10164
10406
  {
10165
10407
  ...upload,
10166
- status: "PENDING_INDEX",
10167
- _note: "File uploaded; indexing is asynchronous. Poll document_get(id) until status is INDEXED."
10408
+ indexStatus: "PENDING_INDEX",
10409
+ _note: "File uploaded; indexing is asynchronous. Poll document_get(id) until indexStatus is INDEXED."
10168
10410
  },
10169
10411
  null,
10170
10412
  2
@@ -10461,6 +10703,14 @@ function zodTypeToJsonSchema(schema) {
10461
10703
 
10462
10704
  // src/server.ts
10463
10705
  var DEFAULT_ENVIRONMENT = "https://api.vectros.ai";
10706
+ function formatArgError(tool, error) {
10707
+ const unknownKeys = error.issues.filter((i) => i.code === "unrecognized_keys").flatMap((i) => i.keys);
10708
+ if (unknownKeys.length > 0) {
10709
+ const valid = Object.keys(tool.inputSchema).join(", ");
10710
+ return `Unknown argument(s): ${unknownKeys.join(", ")}. Valid arguments for ${tool.name}: ${valid}. Pass only these \u2014 an unrecognized argument is rejected, not ignored.`;
10711
+ }
10712
+ return `Invalid arguments: ${error.message}`;
10713
+ }
10464
10714
  var VectrosMCPServer = class {
10465
10715
  server;
10466
10716
  log;
@@ -10550,7 +10800,11 @@ var VectrosMCPServer = class {
10550
10800
  name: t.name,
10551
10801
  title: t.title,
10552
10802
  description: t.description,
10553
- inputSchema: zodShapeToJsonSchema(t.inputSchema)
10803
+ // additionalProperties:false advertises strict argument validation to the
10804
+ // MCP client (a well-behaved client rejects an unknown key before the call).
10805
+ // The server enforces it too (z.object(...).strict() below) so a raw JSON-RPC
10806
+ // call that skips client validation is still rejected — belt and suspenders.
10807
+ inputSchema: { ...zodShapeToJsonSchema(t.inputSchema), additionalProperties: false }
10554
10808
  }))
10555
10809
  })
10556
10810
  );
@@ -10560,9 +10814,9 @@ var VectrosMCPServer = class {
10560
10814
  if (!tool) {
10561
10815
  result = toolError(req.params.name ?? "unknown", new Error("No such tool."));
10562
10816
  } else {
10563
- const parsed = z22.object(tool.inputSchema).safeParse(req.params.arguments ?? {});
10817
+ const parsed = z22.object(tool.inputSchema).strict().safeParse(req.params.arguments ?? {});
10564
10818
  if (!parsed.success) {
10565
- result = toolError(tool.name, new Error(`Invalid arguments: ${parsed.error.message}`));
10819
+ result = toolError(tool.name, new Error(formatArgError(tool, parsed.error)));
10566
10820
  } else {
10567
10821
  try {
10568
10822
  result = await tool.handler(