@vectros-ai/mcp-server 0.6.0 → 0.9.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.js CHANGED
@@ -675,6 +675,10 @@ var ReadAccessLogResponse;
675
675
  })(ReadAccessLogResponse || (ReadAccessLogResponse = {}));
676
676
  var RecordRequest;
677
677
  ((RecordRequest2) => {
678
+ RecordRequest2.Status = {
679
+ Active: "ACTIVE",
680
+ Archived: "ARCHIVED"
681
+ };
678
682
  RecordRequest2.IndexMode = {
679
683
  Hybrid: "HYBRID",
680
684
  Semantic: "SEMANTIC",
@@ -684,6 +688,10 @@ var RecordRequest;
684
688
  })(RecordRequest || (RecordRequest = {}));
685
689
  var RecordResponse;
686
690
  ((RecordResponse2) => {
691
+ RecordResponse2.Status = {
692
+ Active: "ACTIVE",
693
+ Archived: "ARCHIVED"
694
+ };
687
695
  RecordResponse2.IndexStatus = {
688
696
  PendingIndex: "PENDING_INDEX",
689
697
  Indexed: "INDEXED",
@@ -3158,7 +3166,7 @@ var AuthClient = class {
3158
3166
  * roleId: "engineering-member",
3159
3167
  * name: "Engineering Team Member",
3160
3168
  * scopes: [{
3161
- * allowed_actions: ["read", "write"]
3169
+ * allowed_actions: ["records:cru", "search:r"]
3162
3170
  * }]
3163
3171
  * }
3164
3172
  * })
@@ -3706,7 +3714,7 @@ var AuthClient = class {
3706
3714
  * roleId: "engineering-member",
3707
3715
  * name: "Engineering Team Member",
3708
3716
  * scopes: [{
3709
- * allowed_actions: ["read", "write"]
3717
+ * allowed_actions: ["records:cru", "search:r"]
3710
3718
  * }]
3711
3719
  * }
3712
3720
  * })
@@ -4609,7 +4617,7 @@ var DocumentsClient = class {
4609
4617
  this._options = normalizeClientOptionsWithAuth(options);
4610
4618
  }
4611
4619
  /**
4612
- * Returns a paginated list of your documents, optionally filtered by folder (`folderId`) and/or owner (`userId`, `orgId`, or `clientId`). The response is a `{data, nextCursor}` envelope; pass `nextCursor` back as `startFrom` to fetch the next page. Requires the `documents:r` scope.
4620
+ * Returns a paginated list of your documents, optionally filtered by folder (`folderId`) and/or owner (`userId`, `orgId`, `clientId`, or `scope`). The response is a `{data, nextCursor}` envelope; pass `nextCursor` back as `startFrom` to fetch the next page. Requires the `documents:r` scope.
4613
4621
  *
4614
4622
  * @param {Vectros.ListDocumentsRequest} request
4615
4623
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4619,6 +4627,7 @@ var DocumentsClient = class {
4619
4627
  * userId: "550e8400-e29b-41d4-a716-446655440000",
4620
4628
  * orgId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
4621
4629
  * clientId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4630
+ * scope: "group:eng-team",
4622
4631
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4623
4632
  * startFrom: "doc_prev123"
4624
4633
  * })
@@ -4627,11 +4636,12 @@ var DocumentsClient = class {
4627
4636
  return HttpResponsePromise.fromPromise(this.__listDocuments(request, requestOptions));
4628
4637
  }
4629
4638
  async __listDocuments(request = {}, requestOptions) {
4630
- const { userId, orgId, clientId, folderId, startFrom, limit } = request;
4639
+ const { userId, orgId, clientId, scope, folderId, startFrom, limit } = request;
4631
4640
  const _queryParams = {
4632
4641
  userId,
4633
4642
  orgId,
4634
4643
  clientId,
4644
+ scope,
4635
4645
  folderId,
4636
4646
  startFrom,
4637
4647
  limit
@@ -4689,9 +4699,10 @@ var DocumentsClient = class {
4689
4699
  return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
4690
4700
  }
4691
4701
  async __ingestDocument(request, requestOptions) {
4692
- const { upsert, body: _body } = request;
4702
+ const { upsert, allowClear, body: _body } = request;
4693
4703
  const _queryParams = {
4694
- upsert
4704
+ upsert,
4705
+ allowClear
4695
4706
  };
4696
4707
  const _authRequest = await this._options.authProvider.getAuthRequest();
4697
4708
  const _headers = mergeHeaders(
@@ -4815,7 +4826,10 @@ var DocumentsClient = class {
4815
4826
  return HttpResponsePromise.fromPromise(this.__updateDocument(request, requestOptions));
4816
4827
  }
4817
4828
  async __updateDocument(request, requestOptions) {
4818
- const { id, body: _body } = request;
4829
+ const { id, allowClear, body: _body } = request;
4830
+ const _queryParams = {
4831
+ allowClear
4832
+ };
4819
4833
  const _authRequest = await this._options.authProvider.getAuthRequest();
4820
4834
  const _headers = mergeHeaders(
4821
4835
  _authRequest.headers,
@@ -4830,7 +4844,7 @@ var DocumentsClient = class {
4830
4844
  method: "PUT",
4831
4845
  headers: _headers,
4832
4846
  contentType: "application/json",
4833
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4847
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4834
4848
  requestType: "json",
4835
4849
  body: _body,
4836
4850
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4921,7 +4935,7 @@ var DocumentsClient = class {
4921
4935
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/documents/{id}");
4922
4936
  }
4923
4937
  /**
4924
- * Partially updates a document using an RFC 7386 JSON Merge Patch. The `payload` object is deep-merged: keys you send overwrite existing values (recursing into nested objects), a key set to `null` is deleted, and keys you omit are preserved — unlike PUT, which replaces the whole payload. Top-level fields (`title`, `storeText`, `folderId`, `schemaId`, ownership) are set when present and left unchanged when omitted; sending a top-level field as `null` is rejected. Supplying `text` re-ingests the document body (same as PUT). `indexMode` and `externalId` are immutable and rejected if present. The merged result is validated against the bound schema. Pass `expectedVersion` for optimistic concurrency (409 on conflict). Requires the `documents:u` scope.
4938
+ * Partially updates a document using an RFC 7386 JSON Merge Patch. The `payload` object is deep-merged: keys you send overwrite existing values (recursing into nested objects), a key set to `null` is deleted, and keys you omit are preserved — unlike PUT, which replaces the whole payload. Top-level fields (`title`, `folderId`, `schemaId`, ownership) are set when present and left unchanged when omitted; sending a top-level field as `null` is rejected. Supplying `text` re-ingests the document body (same as PUT). `indexMode`, `externalId`, and `storeText` (text retention is fixed at ingest) are immutable and rejected if present. The merged result is validated against the bound schema. Pass `expectedVersion` for optimistic concurrency (409 on conflict). Requires the `documents:u` scope.
4925
4939
  *
4926
4940
  * @param {Vectros.PatchDocumentRequest} request
4927
4941
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5173,7 +5187,7 @@ var DocumentsClient = class {
5173
5187
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/download");
5174
5188
  }
5175
5189
  /**
5176
- * Returns the full extracted or ingested text body for documents that were stored with `storeText=true`. Returns 404 when the document does not exist or when no text is available (because `storeText` was false, or extraction has not yet completed). Requires the `documents:r` scope.
5190
+ * Returns the document's full text body when it is retained: always available for text-ingested documents, and for file-uploaded documents unless they were uploaded with `storeText=false` (which discards the extracted text once indexing completes — the original file remains available via `GET /{id}/download`). Returns 404 when the document does not exist, its text was not retained, or extraction has not yet completed. Requires the `documents:r` scope.
5177
5191
  *
5178
5192
  * @param {Vectros.GetDocumentTextRequest} request
5179
5193
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7566,7 +7580,7 @@ var RecordsClient = class {
7566
7580
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/records/batch");
7567
7581
  }
7568
7582
  /**
7569
- * Returns a paginated list of records in your account as a `{data, nextCursor}` page. Supply exactly one of `type`, `folderId`, or `recent=true` to choose the mode: `type` lists all records of a single type; `folderId` lists all records in a folder (any type); and `recent=true` returns the account-wide recently-updated feed across all types, newest first. You may combine `type` with `folderId` to list a single type within a folder. The owner filters (`userId`, `orgId`, `clientId`) further narrow the type and folder modes; the `recent` feed is standalone and ignores all filters. Each token only sees the record types it is scoped to read. Requires the `records:r` scope. By default the response returns the indexed projection of each record; set `includePayload=true` to include full payloads.
7583
+ * Returns a paginated list of records in your account as a `{data, nextCursor}` page. Supply exactly one of `type`, `folderId`, or `recent=true` to choose the mode: `type` lists all records of a single type; `folderId` lists all records in a folder (any type); and `recent=true` returns the account-wide recently-updated feed across all types, newest first. You may combine `type` with `folderId` to list a single type within a folder. The owner filters (`userId`, `orgId`, `clientId`, `scope`) further narrow the type and folder modes; the `recent` feed is standalone and ignores all filters. Each token only sees the record types it is scoped to read. Requires the `records:r` scope. By default the response returns the indexed projection of each record; set `includePayload=true` to include full payloads.
7570
7584
  *
7571
7585
  * @param {Vectros.ListRecordsRequest} request
7572
7586
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7578,6 +7592,7 @@ var RecordsClient = class {
7578
7592
  * userId: "550e8400-e29b-41d4-a716-446655440000",
7579
7593
  * orgId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7580
7594
  * clientId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7595
+ * scope: "group:eng-team",
7581
7596
  * startFrom: "550e8400-e29b-41d4-a716-446655440000"
7582
7597
  * })
7583
7598
  */
@@ -7585,13 +7600,25 @@ var RecordsClient = class {
7585
7600
  return HttpResponsePromise.fromPromise(this.__listRecords(request, requestOptions));
7586
7601
  }
7587
7602
  async __listRecords(request = {}, requestOptions) {
7588
- const { type: type_, folderId, userId, orgId, clientId, startFrom, limit, includePayload, recent } = request;
7603
+ const {
7604
+ type: type_,
7605
+ folderId,
7606
+ userId,
7607
+ orgId,
7608
+ clientId,
7609
+ scope,
7610
+ startFrom,
7611
+ limit,
7612
+ includePayload,
7613
+ recent
7614
+ } = request;
7589
7615
  const _queryParams = {
7590
7616
  type: type_,
7591
7617
  folderId,
7592
7618
  userId,
7593
7619
  orgId,
7594
7620
  clientId,
7621
+ scope,
7595
7622
  startFrom,
7596
7623
  limit,
7597
7624
  includePayload,
@@ -7656,9 +7683,10 @@ var RecordsClient = class {
7656
7683
  return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
7657
7684
  }
7658
7685
  async __createRecord(request, requestOptions) {
7659
- const { upsert, body: _body } = request;
7686
+ const { upsert, allowClear, body: _body } = request;
7660
7687
  const _queryParams = {
7661
- upsert
7688
+ upsert,
7689
+ allowClear
7662
7690
  };
7663
7691
  const _authRequest = await this._options.authProvider.getAuthRequest();
7664
7692
  const _headers = mergeHeaders(
@@ -7780,7 +7808,10 @@ var RecordsClient = class {
7780
7808
  return HttpResponsePromise.fromPromise(this.__updateRecord(request, requestOptions));
7781
7809
  }
7782
7810
  async __updateRecord(request, requestOptions) {
7783
- const { id, body: _body } = request;
7811
+ const { id, allowClear, body: _body } = request;
7812
+ const _queryParams = {
7813
+ allowClear
7814
+ };
7784
7815
  const _authRequest = await this._options.authProvider.getAuthRequest();
7785
7816
  const _headers = mergeHeaders(
7786
7817
  _authRequest.headers,
@@ -7795,7 +7826,7 @@ var RecordsClient = class {
7795
7826
  method: "PUT",
7796
7827
  headers: _headers,
7797
7828
  contentType: "application/json",
7798
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
7829
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7799
7830
  requestType: "json",
7800
7831
  body: _body,
7801
7832
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -8782,8 +8813,8 @@ function warnOnSuboptimalKey(key, log) {
8782
8813
 
8783
8814
  // src/build-info.ts
8784
8815
  var BUILD_INFO = {
8785
- mcpServer: true ? "0.6.0" : "dev",
8786
- sdk: true ? "0.32.0" : "dev"
8816
+ mcpServer: true ? "0.9.0" : "dev",
8817
+ sdk: true ? "0.34.0" : "dev"
8787
8818
  };
8788
8819
  function formatBuildInfo() {
8789
8820
  return `vectros-mcp-server ${BUILD_INFO.mcpServer} (sdk ${BUILD_INFO.sdk})`;
@@ -9022,7 +9053,7 @@ ${detail}${pointer}`
9022
9053
 
9023
9054
  // src/tools/hybrid_search.ts
9024
9055
  var MCP_DEFAULT_LIMIT = 3;
9025
- var MCP_MAX_LIMIT = 10;
9056
+ var MCP_MAX_LIMIT = 50;
9026
9057
  var MCP_RESPONSE_CHAR_BUDGET = 24e3;
9027
9058
  var SNIPPET_MAX_CHARS = 160;
9028
9059
  var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["tenantId", "owner_id", "model_type", "rootFolderId", "folderId"]);
@@ -9056,9 +9087,9 @@ var inputSchema = {
9056
9087
  "Keyword-match precision for the BM25 leg (TEXT/HYBRID modes). OR = any term (broad recall), AND = all terms (higher precision), PHRASE = contiguous phrase. Default: PHRASE (slop 3) in HYBRID, OR in TEXT. (The advanced raw-query COMPLEX mode is intentionally not exposed here.)"
9057
9088
  ),
9058
9089
  limit: import_zod.z.number().int().min(1).max(MCP_MAX_LIMIT).optional().describe(
9059
- `Max hits to return. MCP-specific cap of ${MCP_MAX_LIMIT} (vs API max of 50) to protect the agent context window. Default 3. Use offset for pagination.`
9090
+ `Max hits per page. Defaults to a low ${MCP_DEFAULT_LIMIT} to protect the agent context window; raise up to ${MCP_MAX_LIMIT} (the most a single search returns) in one call when you can accept the larger result, or page with \`offset\` instead.`
9060
9091
  ),
9061
- offset: import_zod.z.number().int().min(0).optional().describe("Skip the first N hits \u2014 for pagination."),
9092
+ offset: import_zod.z.number().int().min(0).max(200).optional().describe("Skip the first N hits \u2014 for pagination (0\u2013200)."),
9062
9093
  folderId: import_zod.z.string().optional().describe("Scope to content in this EXACT folder (Vectros folder id)."),
9063
9094
  rootFolderId: import_zod.z.string().optional().describe("Scope to content under this folder AND all its descendants (subtree-root folder id)."),
9064
9095
  // Content narrowing.
@@ -9073,6 +9104,9 @@ var inputSchema = {
9073
9104
  userId: import_zod.z.string().optional().describe("Restrict to content owned by this user (Vectros UUID)."),
9074
9105
  orgId: import_zod.z.string().optional().describe("Restrict to content belonging to this org (Vectros UUID)."),
9075
9106
  clientId: import_zod.z.string().optional().describe("Restrict to content associated with this client (Vectros UUID)."),
9107
+ scope: import_zod.z.string().optional().describe(
9108
+ 'Restrict to content carrying this scope value, in `namespace:value` form (e.g. "org:<uuid>", "group:eng-team"). `scope=org:<id>`/`scope=client:<id>` are equivalent to the orgId/clientId filters.'
9109
+ ),
9076
9110
  // Date window.
9077
9111
  createdAfter: import_zod.z.string().optional().describe("Restrict to content created at/after this ISO 8601 UTC timestamp."),
9078
9112
  createdBefore: import_zod.z.string().optional().describe("Restrict to content created at/before this ISO 8601 UTC timestamp."),
@@ -9090,7 +9124,7 @@ var inputSchema = {
9090
9124
  var hybridSearch = ({ client, log }) => ({
9091
9125
  name: "hybrid_search",
9092
9126
  title: "Hybrid search",
9093
- 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".`,
9127
+ description: "Search your tenant's indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to `limit` results (default 3, max 50 \u2014 raise it in one call when you can accept the larger result) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/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). totalResults is the full matching pool and hasMore:true means results remain past this page \u2014 raise limit or advance offset to fetch them. textLegEmpty:true flags that the keyword leg matched nothing (all textScores 0) \u2014 usually a too-long PHRASE query; retry with textMode:\"OR\".",
9094
9128
  inputSchema,
9095
9129
  handler: async (args) => {
9096
9130
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9110,6 +9144,7 @@ var hybridSearch = ({ client, log }) => ({
9110
9144
  userId: args.userId,
9111
9145
  orgId: args.orgId,
9112
9146
  clientId: args.clientId,
9147
+ scope: args.scope,
9113
9148
  createdAfter: args.createdAfter,
9114
9149
  createdBefore: args.createdBefore,
9115
9150
  minSimilarity: args.minSimilarity,
@@ -9136,6 +9171,12 @@ var hybridSearch = ({ client, log }) => ({
9136
9171
  envelope.returnedResults = shaped.length;
9137
9172
  text = JSON.stringify(envelope, null, 2);
9138
9173
  }
9174
+ const offset = args.offset ?? 0;
9175
+ const totalResults = result.totalResults;
9176
+ if (typeof totalResults === "number" && offset + shaped.length < totalResults) {
9177
+ envelope.hasMore = true;
9178
+ text = JSON.stringify(envelope, null, 2);
9179
+ }
9139
9180
  log.debug(
9140
9181
  { tool: "hybrid_search", mode, limit, returned: shaped.length, textLegEmpty },
9141
9182
  "hybrid_search ok"
@@ -9171,7 +9212,7 @@ async function drainPages(fetchPage) {
9171
9212
 
9172
9213
  // src/tools/record_query.ts
9173
9214
  var MCP_DEFAULT_LIMIT2 = 3;
9174
- var MCP_MAX_LIMIT2 = 10;
9215
+ var MCP_MAX_LIMIT2 = 100;
9175
9216
  var inputSchema2 = {
9176
9217
  type: import_zod2.z.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9177
9218
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
@@ -9189,14 +9230,18 @@ var inputSchema2 = {
9189
9230
  userId: import_zod2.z.string().optional().describe("List mode: scope to records owned by this user."),
9190
9231
  orgId: import_zod2.z.string().optional().describe("List mode: scope to records owned by this org."),
9191
9232
  clientId: import_zod2.z.string().optional().describe("List mode: scope to records associated with this client."),
9233
+ scope: import_zod2.z.string().optional().describe(
9234
+ 'List mode: filter to records carrying this scope value, in `namespace:value` form (e.g. "org:<uuid>", "group:eng-team"). `scope=org:<id>`/`scope=client:<id>` are equivalent to the orgId/clientId filters.'
9235
+ ),
9236
+ startFrom: import_zod2.z.string().optional().describe("Pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next. Works in both list and lookup mode."),
9192
9237
  limit: import_zod2.z.number().int().min(1).max(MCP_MAX_LIMIT2).optional().describe(
9193
- `Max records to return. MCP-specific cap of ${MCP_MAX_LIMIT2} (vs API max of 100) to protect the agent context window. Default 3.`
9238
+ `Max records per page. Defaults to a low ${MCP_DEFAULT_LIMIT2} to protect the agent context window; raise up to ${MCP_MAX_LIMIT2} (the records API max) in one call when you can accept the larger payload, or page with \`startFrom\` instead.`
9194
9239
  )
9195
9240
  };
9196
9241
  var recordQuery = ({ client, log }) => ({
9197
9242
  name: "record_query",
9198
9243
  title: "Record query (list or lookup)",
9199
- description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields too.\n \u2022 List by type: omit `field`; optionally filter by `userId`/`orgId`/`clientId`.\nReturns up to 10 records (default 3). Mode is auto-detected from the arguments present.",
9244
+ description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields too.\n \u2022 List by type: omit `field`; optionally filter by `userId`/`orgId`/`clientId`.\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` records (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. Mode is auto-detected from the arguments present.",
9200
9245
  inputSchema: inputSchema2,
9201
9246
  handler: async (args) => {
9202
9247
  const limit = args.limit ?? MCP_DEFAULT_LIMIT2;
@@ -9206,8 +9251,9 @@ var recordQuery = ({ client, log }) => ({
9206
9251
  const from = args.from;
9207
9252
  const to = args.to;
9208
9253
  const prefix = args.prefix;
9254
+ const startFrom = args.startFrom;
9209
9255
  try {
9210
- let records;
9256
+ let page;
9211
9257
  if (field) {
9212
9258
  const hasEquality = value !== void 0;
9213
9259
  const hasRange = from !== void 0 || to !== void 0;
@@ -9230,7 +9276,7 @@ var recordQuery = ({ client, log }) => ({
9230
9276
  if (hasRange && !(from !== void 0 && to !== void 0)) {
9231
9277
  return toolError("record_query", new Error("range lookup requires both 'from' and 'to'."));
9232
9278
  }
9233
- const page = await client.records.lookupRecordsByBody({
9279
+ page = await client.records.lookupRecordsByBody({
9234
9280
  type,
9235
9281
  field,
9236
9282
  value,
@@ -9238,29 +9284,32 @@ var recordQuery = ({ client, log }) => ({
9238
9284
  to,
9239
9285
  prefix,
9240
9286
  order: args.order,
9287
+ startFrom,
9241
9288
  limit
9242
9289
  });
9243
- records = pageItems(page);
9244
9290
  log.debug(
9245
- { tool: "record_query", mode: "lookup", type, field, returned: records.length },
9291
+ { tool: "record_query", mode: "lookup", type, field, returned: pageItems(page).length },
9246
9292
  "record_query lookup ok"
9247
9293
  );
9248
9294
  } else {
9249
- const page = await client.records.listRecords({
9295
+ page = await client.records.listRecords({
9250
9296
  type,
9251
9297
  userId: args.userId,
9252
9298
  orgId: args.orgId,
9253
9299
  clientId: args.clientId,
9300
+ scope: args.scope,
9301
+ startFrom,
9254
9302
  limit
9255
9303
  });
9256
- records = pageItems(page);
9257
9304
  log.debug(
9258
- { tool: "record_query", mode: "list", type, limit, returned: records.length },
9305
+ { tool: "record_query", mode: "list", type, limit, returned: pageItems(page).length },
9259
9306
  "record_query list ok"
9260
9307
  );
9261
9308
  }
9309
+ const records = pageItems(page);
9310
+ const nextCursor = page.nextCursor ?? null;
9262
9311
  return {
9263
- content: [{ type: "text", text: JSON.stringify(records, null, 2) }]
9312
+ content: [{ type: "text", text: JSON.stringify({ data: records, nextCursor }, null, 2) }]
9264
9313
  };
9265
9314
  } catch (err) {
9266
9315
  log.warn({ tool: "record_query", err: String(err) }, "record_query failed");
@@ -9383,7 +9432,10 @@ var inputSchema4 = {
9383
9432
  folderId: import_zod4.z.string().optional().describe("Group this record into a folder."),
9384
9433
  userId: import_zod4.z.string().optional().describe("Owning user id."),
9385
9434
  orgId: import_zod4.z.string().optional().describe("Owning organization id."),
9386
- clientId: import_zod4.z.string().optional().describe("Associated client id.")
9435
+ clientId: import_zod4.z.string().optional().describe("Associated client id."),
9436
+ scopes: import_zod4.z.array(import_zod4.z.string()).optional().describe(
9437
+ 'Scope ownership as `namespace:value` entries, at most 2 (e.g. ["org:<uuid>", "group:eng-team"]). This is the record\'s COMPLETE scope declaration and values must come from the credential\'s own identity. An empty array `[]` creates a PRIVATE record owned by the calling user alone. Omit to stamp the credential\'s full identity \u2014 the default. `org:`/`client:` entries are equivalent to the orgId/clientId fields.'
9438
+ )
9387
9439
  };
9388
9440
  var recordCreate = ({ client, log }) => ({
9389
9441
  name: "record_create",
@@ -9403,7 +9455,8 @@ var recordCreate = ({ client, log }) => ({
9403
9455
  folderId: args.folderId,
9404
9456
  userId: args.userId,
9405
9457
  orgId: args.orgId,
9406
- clientId: args.clientId
9458
+ clientId: args.clientId,
9459
+ scopes: args.scopes
9407
9460
  }
9408
9461
  });
9409
9462
  log.debug({ tool: "record_create", type, id: created.id }, "record_create ok");
@@ -9488,24 +9541,29 @@ var record_delete_default = recordDelete;
9488
9541
  // src/tools/rag_ask.ts
9489
9542
  var import_zod7 = require("zod");
9490
9543
  var SEARCH_MCP_DEFAULT_LIMIT = 5;
9491
- var SEARCH_MCP_MAX_LIMIT = 10;
9544
+ var SEARCH_MCP_MAX_LIMIT = 50;
9492
9545
  var inputSchema7 = {
9493
9546
  query: import_zod7.z.string().min(1, "query must be non-empty").describe("Question to ground against retrieved content."),
9494
9547
  instructions: import_zod7.z.string().optional().describe(
9495
9548
  'System prompt that overrides the default for the generation step \u2014 e.g. "answer only from the provided context, cite sources, be concise". Default: a generic answer-from-context instruction.'
9496
9549
  ),
9497
9550
  model: import_zod7.z.string().optional().describe(
9498
- "Inference model alias (e.g. claude-haiku-4-5, claude-sonnet-4-6, claude-opus-4-8). Default = tier-appropriate Haiku. See GET /v1/models for the catalog the calling key can reach."
9551
+ "Inference model alias (e.g. claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8). Default = tier-appropriate Haiku. See GET /v1/models for the catalog the calling key can reach."
9499
9552
  ),
9500
9553
  search: import_zod7.z.object({
9501
9554
  mode: import_zod7.z.enum(["HYBRID", "TEXT", "SEMANTIC"]).optional().describe("Retrieval mode. Default HYBRID."),
9502
- limit: import_zod7.z.number().int().min(1).max(SEARCH_MCP_MAX_LIMIT).optional().describe(`Passages to retrieve before generating. Default ${SEARCH_MCP_DEFAULT_LIMIT}, max ${SEARCH_MCP_MAX_LIMIT}.`),
9555
+ limit: import_zod7.z.number().int().min(1).max(SEARCH_MCP_MAX_LIMIT).optional().describe(
9556
+ `Passages to retrieve before generating (the grounding-corpus size). Default ${SEARCH_MCP_DEFAULT_LIMIT}; raise up to ${SEARCH_MCP_MAX_LIMIT} (the RAG search API max) for a broader corpus when the extra grounding is worth the larger prompt.`
9557
+ ),
9503
9558
  // Retrieval scoping — parity with hybrid_search so an agent can ground the
9504
9559
  // answer on a specific owner / folder / type / metadata subset, not just the
9505
9560
  // whole-tenant corpus.
9506
9561
  userId: import_zod7.z.string().optional().describe("Restrict retrieval to content owned by this user (Vectros UUID)."),
9507
9562
  orgId: import_zod7.z.string().optional().describe("Restrict retrieval to content owned by this org (Vectros UUID)."),
9508
9563
  clientId: import_zod7.z.string().optional().describe("Restrict retrieval to content tagged with this client (Vectros UUID)."),
9564
+ scope: import_zod7.z.string().optional().describe(
9565
+ 'Restrict retrieval to content carrying this scope value, in `namespace:value` form (e.g. "org:<uuid>", "group:eng-team"). `scope=org:<id>`/`scope=client:<id>` are equivalent to the orgId/clientId filters.'
9566
+ ),
9509
9567
  folderId: import_zod7.z.string().optional().describe("Restrict retrieval to this exact folder."),
9510
9568
  rootFolderId: import_zod7.z.string().optional().describe("Restrict retrieval to this folder and all its descendants."),
9511
9569
  typeName: import_zod7.z.string().optional().describe("Restrict record retrieval to this schema type."),
@@ -9523,7 +9581,7 @@ var inputSchema7 = {
9523
9581
  var ragAsk = ({ client, log }) => ({
9524
9582
  name: "rag_ask",
9525
9583
  title: "RAG ask (corpus-wide)",
9526
- description: "Ask a question grounded against the partner tenant's indexed content. Vectros performs a hybrid search, injects the top-K passages into the prompt, and streams a model answer back. Scope retrieval with `search` (ownership, folder, type, metadata filters, date window) to ground on a subset \u2014 e.g. one patient or one folder \u2014 and steer generation with `instructions` / `temperature`. The full answer is returned as a single response; progress notifications keep the call alive during the 30-45s generation window. Inference runs in-perimeter against AWS Bedrock \u2014 PHI never leaves the BAA boundary.",
9584
+ description: "Ask a question grounded against your tenant's indexed content. Vectros performs a hybrid search, injects the top-K passages into the prompt, and streams a model answer back. Scope retrieval with `search` (ownership, folder, type, metadata filters, date window) to ground on a subset \u2014 e.g. one patient or one folder \u2014 and steer generation with `instructions` / `temperature`. The full answer is returned as a single response; progress notifications keep the call alive during the 30-45s generation window. Inference runs in-perimeter against AWS Bedrock \u2014 PHI never leaves the BAA boundary.",
9527
9585
  inputSchema: inputSchema7,
9528
9586
  handler: async (args, extra) => {
9529
9587
  try {
@@ -9534,6 +9592,7 @@ var ragAsk = ({ client, log }) => ({
9534
9592
  userId: searchArg.userId,
9535
9593
  orgId: searchArg.orgId,
9536
9594
  clientId: searchArg.clientId,
9595
+ scope: searchArg.scope,
9537
9596
  folderId: searchArg.folderId,
9538
9597
  rootFolderId: searchArg.rootFolderId,
9539
9598
  typeName: searchArg.typeName,
@@ -9691,7 +9750,7 @@ var inputSchema9 = {
9691
9750
  var listSchemas = ({ client, log }) => ({
9692
9751
  name: "list_schemas",
9693
9752
  title: "List record schemas",
9694
- description: 'List the structured-record schema catalog for the partner tenant. Each schema describes a record type (e.g. "patient", "clinical_note") \u2014 its fields, lookup-indexed fields, and capabilities. Use this to discover what record types exist before calling `record_query`. Filter with `surface` (record/document/user/org/client \u2014 e.g. only document types), `recordType` (resolve one schema by its type name), or `userId`/`orgId`; default returns everything the credential can see.',
9753
+ description: 'List the structured-record schema catalog for your tenant. Each schema describes a record type (e.g. "patient", "clinical_note") \u2014 its fields, lookup-indexed fields, and capabilities. Use this to discover what record types exist before calling `record_query`. Filter with `surface` (record/document/user/org/client \u2014 e.g. only document types), `recordType` (resolve one schema by its type name), or `userId`/`orgId`; default returns everything the credential can see.',
9695
9754
  inputSchema: inputSchema9,
9696
9755
  handler: async (args) => {
9697
9756
  const userId = args.userId;
@@ -9745,7 +9804,7 @@ function isNotAvailable(err) {
9745
9804
  var documentGet = ({ client, log }) => ({
9746
9805
  name: "document_get",
9747
9806
  title: "Get document metadata (and optionally text)",
9748
- 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`.",
9807
+ description: "Fetch metadata for a single document \u2014 select it by its Vectros `documentId` OR by your own `externalId` + `type` (resolved for you). Pass `includeText: true` to also fetch the text body (capped at ~8K tokens; `truncated: true` flag if cut). Text is available for every text-ingested document and for file-ingested documents unless they were uploaded with `storeText: false` (extracted text discarded after indexing) \u2014 then `textAvailable: false` is set; the original file is still retrievable via `includeDownloadUrl`. Pass `includeDownloadUrl: true` to also get a short-lived presigned `downloadUrl` for the original file (file-backed documents only). Returns the full document metadata, including its lifecycle `status` (ACTIVE, or ARCHIVED when soft-retracted from search \u2014 restorable via document_update) and its processing `indexStatus`.",
9749
9808
  inputSchema: inputSchema10,
9750
9809
  handler: async (args) => {
9751
9810
  const documentId = args.documentId;
@@ -9769,7 +9828,7 @@ var documentGet = ({ client, log }) => ({
9769
9828
  if (isNotAvailable(textErr)) {
9770
9829
  log.debug(
9771
9830
  { tool: "document_get", id },
9772
- "document_get text unavailable (ingested without storeText)"
9831
+ "document_get text unavailable (not retained \u2014 storeText:false upload)"
9773
9832
  );
9774
9833
  extra = { ...extra, textAvailable: false };
9775
9834
  } else {
@@ -9813,7 +9872,7 @@ var document_get_default = documentGet;
9813
9872
  // src/tools/document_query.ts
9814
9873
  var import_zod11 = require("zod");
9815
9874
  var MCP_DEFAULT_LIMIT3 = 3;
9816
- var MCP_MAX_LIMIT3 = 10;
9875
+ var MCP_MAX_LIMIT3 = 100;
9817
9876
  var inputSchema11 = {
9818
9877
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9819
9878
  field: import_zod11.z.string().optional().describe("Lookup mode: name of the lookup-indexed field to query by."),
@@ -9830,14 +9889,18 @@ var inputSchema11 = {
9830
9889
  userId: import_zod11.z.string().optional().describe("List mode: scope to documents owned by this user."),
9831
9890
  orgId: import_zod11.z.string().optional().describe("List mode: scope to documents belonging to this org."),
9832
9891
  clientId: import_zod11.z.string().optional().describe("List mode: scope to documents associated with this client."),
9892
+ scope: import_zod11.z.string().optional().describe(
9893
+ 'List mode: filter to documents carrying this scope value, in `namespace:value` form (e.g. "org:<uuid>", "group:eng-team"). `scope=org:<id>`/`scope=client:<id>` are equivalent to the orgId/clientId filters.'
9894
+ ),
9895
+ startFrom: import_zod11.z.string().optional().describe("Pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next. Works in both list and lookup mode."),
9833
9896
  limit: import_zod11.z.number().int().min(1).max(MCP_MAX_LIMIT3).optional().describe(
9834
- `Max documents to return. MCP-specific cap of ${MCP_MAX_LIMIT3} (vs API max of 100) to protect the agent context window. Default 3.`
9897
+ `Max documents per page. Defaults to a low ${MCP_DEFAULT_LIMIT3} to protect the agent context window; raise up to ${MCP_MAX_LIMIT3} (the documents API max) in one call when you can accept the larger payload, or page with \`startFrom\` instead.`
9835
9898
  )
9836
9899
  };
9837
9900
  var documentQuery = ({ client, log }) => ({
9838
9901
  name: "document_query",
9839
9902
  title: "Document query (list or lookup)",
9840
- description: "Query document metadata. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `type` and `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields (equality only there); range/prefix need a range-enabled field.\n \u2022 List: omit `field`; optionally filter by `folderId`/`userId`/`orgId`/`clientId`.\nReturns up to 10 documents (default 3) as a bare array. Mode is auto-detected. Use document_get for the full text/download URL of one document.",
9903
+ description: "Query document metadata. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `type` and `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields (equality only there); range/prefix need a range-enabled field.\n \u2022 List: omit `field`; optionally filter by `folderId`/`userId`/`orgId`/`clientId`/`scope`.\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` documents (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. Mode is auto-detected. Use document_get for the full text/download URL of one document.",
9841
9904
  inputSchema: inputSchema11,
9842
9905
  handler: async (args) => {
9843
9906
  const limit = args.limit ?? MCP_DEFAULT_LIMIT3;
@@ -9847,8 +9910,9 @@ var documentQuery = ({ client, log }) => ({
9847
9910
  const to = args.to;
9848
9911
  const prefix = args.prefix;
9849
9912
  const type = args.type;
9913
+ const startFrom = args.startFrom;
9850
9914
  try {
9851
- let documents;
9915
+ let page;
9852
9916
  if (field) {
9853
9917
  const hasEquality = value !== void 0;
9854
9918
  const hasRange = from !== void 0 || to !== void 0;
@@ -9877,7 +9941,7 @@ var documentQuery = ({ client, log }) => ({
9877
9941
  new Error("lookup requires 'type' (the document's bound schema type) alongside 'field'.")
9878
9942
  );
9879
9943
  }
9880
- const page = await client.documents.lookupDocumentsByBody({
9944
+ page = await client.documents.lookupDocumentsByBody({
9881
9945
  type,
9882
9946
  field,
9883
9947
  value,
@@ -9885,28 +9949,31 @@ var documentQuery = ({ client, log }) => ({
9885
9949
  to,
9886
9950
  prefix,
9887
9951
  order: args.order,
9952
+ startFrom,
9888
9953
  limit
9889
9954
  });
9890
- documents = pageItems(page);
9891
9955
  log.debug(
9892
- { tool: "document_query", mode: "lookup", type, field, returned: documents.length },
9956
+ { tool: "document_query", mode: "lookup", type, field, returned: pageItems(page).length },
9893
9957
  "document_query lookup ok"
9894
9958
  );
9895
9959
  } else {
9896
- const page = await client.documents.listDocuments({
9960
+ page = await client.documents.listDocuments({
9897
9961
  folderId: args.folderId,
9898
9962
  userId: args.userId,
9899
9963
  orgId: args.orgId,
9900
9964
  clientId: args.clientId,
9965
+ scope: args.scope,
9966
+ startFrom,
9901
9967
  limit
9902
9968
  });
9903
- documents = pageItems(page);
9904
9969
  log.debug(
9905
- { tool: "document_query", mode: "list", limit, returned: documents.length },
9970
+ { tool: "document_query", mode: "list", limit, returned: pageItems(page).length },
9906
9971
  "document_query list ok"
9907
9972
  );
9908
9973
  }
9909
- return { content: [{ type: "text", text: JSON.stringify(documents, null, 2) }] };
9974
+ const documents = pageItems(page);
9975
+ const nextCursor = page.nextCursor ?? null;
9976
+ return { content: [{ type: "text", text: JSON.stringify({ data: documents, nextCursor }, null, 2) }] };
9910
9977
  } catch (err) {
9911
9978
  log.warn({ tool: "document_query", err: String(err) }, "document_query failed");
9912
9979
  return toolError("document_query", err);
@@ -9931,7 +9998,6 @@ var inputSchema12 = {
9931
9998
  "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."
9932
9999
  ),
9933
10000
  folderId: import_zod12.z.string().optional().describe("Move the document into this folder (cannot be cleared once set)."),
9934
- storeText: import_zod12.z.boolean().optional().describe("Whether the raw text is stored + retrievable."),
9935
10001
  status: import_zod12.z.enum(["ACTIVE", "ARCHIVED"]).optional().describe(
9936
10002
  "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."
9937
10003
  ),
@@ -9955,7 +10021,6 @@ var documentUpdate = ({ client, log }) => ({
9955
10021
  const fields = args.fields;
9956
10022
  const text = args.text;
9957
10023
  const folderId = args.folderId;
9958
- const storeText = args.storeText;
9959
10024
  const status = args.status;
9960
10025
  const userId = args.userId;
9961
10026
  const orgId = args.orgId;
@@ -9969,7 +10034,6 @@ var documentUpdate = ({ client, log }) => ({
9969
10034
  if (fields !== void 0) body.payload = fields;
9970
10035
  if (text !== void 0) body.text = text;
9971
10036
  if (folderId !== void 0) body.folderId = folderId;
9972
- if (storeText !== void 0) body.storeText = storeText;
9973
10037
  if (status !== void 0) body.status = status;
9974
10038
  if (userId !== void 0) body.userId = userId;
9975
10039
  if (orgId !== void 0) body.orgId = orgId;
@@ -10018,7 +10082,7 @@ var document_delete_default = documentDelete;
10018
10082
  // src/tools/folder_query.ts
10019
10083
  var import_zod14 = require("zod");
10020
10084
  var MCP_DEFAULT_LIMIT4 = 10;
10021
- var MCP_MAX_LIMIT4 = 50;
10085
+ var MCP_MAX_LIMIT4 = 100;
10022
10086
  var inputSchema14 = {
10023
10087
  id: import_zod14.z.string().optional().describe("Get mode: fetch this single folder by id."),
10024
10088
  parentId: import_zod14.z.string().optional().describe("List mode: direct children of this folder (tree navigation). Omit for a flat tenant list."),
@@ -10026,12 +10090,14 @@ var inputSchema14 = {
10026
10090
  orgId: import_zod14.z.string().optional().describe("List mode: scope to folders belonging to this org."),
10027
10091
  clientId: import_zod14.z.string().optional().describe("List mode: scope to folders associated with this client."),
10028
10092
  startFrom: import_zod14.z.string().optional().describe("List mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10029
- limit: import_zod14.z.number().int().min(1).max(MCP_MAX_LIMIT4).optional().describe(`List mode: max folders to return. MCP cap of ${MCP_MAX_LIMIT4} (vs API 100). Default 10.`)
10093
+ limit: import_zod14.z.number().int().min(1).max(MCP_MAX_LIMIT4).optional().describe(
10094
+ `List mode: max folders per page. Default 10; raise up to ${MCP_MAX_LIMIT4} (the folders API max) in one call when you can accept the larger page, or page with \`startFrom\`.`
10095
+ )
10030
10096
  };
10031
10097
  var folderQuery = ({ client, log }) => ({
10032
10098
  name: "folder_query",
10033
10099
  title: "Folder query (list or get)",
10034
- description: "Read folders. Two modes:\n \u2022 Get: pass `id` \u2192 returns the single folder.\n \u2022 List: omit `id`; pass `parentId` for a folder's direct children (tree navigation) or omit for a flat tenant list. Optionally filter by `userId`/`orgId`/`clientId`.\nList mode returns `{ data, nextCursor }` (default 10, max 50 per page); pass `nextCursor` back as `startFrom` to page through all folders. Get mode returns the single folder object.",
10100
+ description: "Read folders. Two modes:\n \u2022 Get: pass `id` \u2192 returns the single folder.\n \u2022 List: omit `id`; pass `parentId` for a folder's direct children (tree navigation) or omit for a flat tenant list. Optionally filter by `userId`/`orgId`/`clientId`.\nList mode returns `{ data, nextCursor }` (default 10, max 100 per page \u2014 raise it in one call when you can accept the larger page); a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page through all folders. Get mode returns the single folder object.",
10035
10101
  inputSchema: inputSchema14,
10036
10102
  handler: async (args) => {
10037
10103
  const id = args.id;
@@ -10071,7 +10137,10 @@ var inputSchema15 = {
10071
10137
  slug: import_zod15.z.string().optional().describe("Optional stable slug (sibling-unique; idempotency key). Lowercase letters/digits/hyphens. Derived from name when omitted."),
10072
10138
  userId: import_zod15.z.string().optional().describe("Owning user (Vectros UUID)."),
10073
10139
  orgId: import_zod15.z.string().optional().describe("Owning org (Vectros UUID)."),
10074
- clientId: import_zod15.z.string().optional().describe("Associated client (Vectros UUID).")
10140
+ clientId: import_zod15.z.string().optional().describe("Associated client (Vectros UUID)."),
10141
+ scopes: import_zod15.z.array(import_zod15.z.string()).optional().describe(
10142
+ 'Scope ownership as `namespace:value` entries, at most 2 (e.g. ["org:<uuid>", "group:eng-team"]). This is the folder\'s COMPLETE scope declaration and values must come from the credential\'s own identity. An empty array `[]` creates a PRIVATE folder owned by the calling user alone. Omit to stamp the credential\'s full identity \u2014 the default. `org:`/`client:` entries are equivalent to the orgId/clientId fields.'
10143
+ )
10075
10144
  };
10076
10145
  var folderCreate = ({ client, log }) => ({
10077
10146
  name: "folder_create",
@@ -10088,7 +10157,8 @@ var folderCreate = ({ client, log }) => ({
10088
10157
  slug: args.slug,
10089
10158
  userId: args.userId,
10090
10159
  orgId: args.orgId,
10091
- clientId: args.clientId
10160
+ clientId: args.clientId,
10161
+ scopes: args.scopes
10092
10162
  }
10093
10163
  });
10094
10164
  log.debug({ tool: "folder_create", id: folder.id, name: folder.name }, "folder_create ok");
@@ -10289,7 +10359,7 @@ var inputSchema19 = {
10289
10359
  // Inline text mode (mutually exclusive with filePath):
10290
10360
  text: import_zod18.z.string().min(1).optional().describe("Text body to ingest inline. Mutually exclusive with filePath. Either text or filePath is required."),
10291
10361
  storeText: import_zod18.z.boolean().optional().describe(
10292
- "If true (default), the text body is stored alongside the index so document_get(includeText:true) can retrieve it later. Set false to index only \u2014 saves storage cost on large corpora but document_get(includeText) will return textAvailable:false."
10362
+ "Whether the document's text is retained after indexing (default true). Applies to BOTH modes and is fixed at ingest time. Text mode: the ingested body is always retained (this flag is effectively always true \u2014 the body is the document). File mode: true keeps the extracted text retrievable via document_get(includeText:true) and usable by document_ask; false discards the extracted text once indexing completes \u2014 search still works and the original file remains downloadable, but includeText returns textAvailable:false and document_ask is unavailable."
10293
10363
  ),
10294
10364
  // File upload mode (mutually exclusive with text; stdio-transport-only):
10295
10365
  filePath: import_zod18.z.string().min(1).optional().describe(
@@ -10300,12 +10370,15 @@ var inputSchema19 = {
10300
10370
  folderId: import_zod18.z.string().optional().describe("Optional folder to ingest into. Default: tenant root."),
10301
10371
  userId: import_zod18.z.string().optional().describe("Owning user ID. Optional."),
10302
10372
  orgId: import_zod18.z.string().optional().describe("Owning org ID. Optional."),
10303
- clientId: import_zod18.z.string().optional().describe("Owning client ID. Optional.")
10373
+ clientId: import_zod18.z.string().optional().describe("Owning client ID. Optional."),
10374
+ scopes: import_zod18.z.array(import_zod18.z.string()).optional().describe(
10375
+ "TEXT MODE ONLY. Scope ownership as `namespace:value` entries, at most 2 (e.g. [\"org:<uuid>\", \"group:eng-team\"]). This is the document's COMPLETE scope declaration and values must come from the credential's own identity. An empty array `[]` creates a PRIVATE document owned by the calling user alone. Omit to stamp the credential's full identity \u2014 the default. `org:`/`client:` entries are equivalent to the orgId/clientId fields. Rejected with `filePath` (file uploads do not accept a scope declaration): a file-uploaded document is stamped with the credential's full identity."
10376
+ )
10304
10377
  };
10305
10378
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10306
10379
  name: "document_ingest",
10307
10380
  title: "Ingest a new document (text body or file upload)",
10308
- 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).",
10381
+ description: "Create a new document in your Vectros tenant. Two modes:\n \u2022 Text mode: pass `text` (string body). Single-call. Use for crawled content, notes, generated text.\n \u2022 File mode: pass `filePath` (local path on the MCP server's host machine). STDIO-TRANSPORT ONLY \u2014 rejected on HTTP transport. MCP server reads the bytes, requests a presigned upload URL, and PUTs them. Returns when the upload is accepted (indexStatus: PENDING_INDEX); poll with `document_get` until indexStatus is INDEXED.\nEither `text` or `filePath` must be present; both is an error. Idempotent by `externalId` (re-ingest returns the existing document, not a duplicate). To UPDATE an existing document instead \u2014 re-index an edited body \u2014 pass `upsert:true` with the same `externalId` (and the same `schemaId`); this is the re-sync primitive for keeping a knowledge base current. Pass `schemaId` + `payload` for a typed, lookup-queryable document (records parity). indexMode defaults to HYBRID for untyped documents (omit to inherit a bound schema's default). storeText defaults to true and is fixed at ingest: in file mode, false discards the extracted text after indexing (search + file download keep working; text retrieval and document_ask do not). Text-mode bodies are always retained.",
10309
10382
  inputSchema: inputSchema19,
10310
10383
  handler: async (args) => {
10311
10384
  const title = args.title;
@@ -10334,6 +10407,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10334
10407
  )
10335
10408
  );
10336
10409
  }
10410
+ if (filePath && args.scopes !== void 0) {
10411
+ return toolError(
10412
+ "document_ingest",
10413
+ new Error(
10414
+ "`scopes` is not supported with `filePath` \u2014 file uploads do not accept a scope declaration, and silently ignoring it could make an intended-private document team-visible. Use `text` mode to declare scopes, or omit `scopes` (the upload is stamped with the credential's full identity)."
10415
+ )
10416
+ );
10417
+ }
10337
10418
  const common = {
10338
10419
  folderId: args.folderId,
10339
10420
  payload: args.payload,
@@ -10345,14 +10426,15 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10345
10426
  };
10346
10427
  try {
10347
10428
  if (text) {
10348
- const storeText = args.storeText ?? true;
10349
10429
  const result = await client.documents.ingestDocument({
10350
10430
  upsert,
10351
10431
  body: {
10352
10432
  title,
10353
10433
  text,
10354
10434
  indexMode,
10355
- storeText,
10435
+ // Scope ownership is a TEXT-mode contract only (the upload-init
10436
+ // endpoint has no scopes field — see the gate above).
10437
+ scopes: args.scopes,
10356
10438
  ...common
10357
10439
  }
10358
10440
  });
@@ -10383,11 +10465,13 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10383
10465
  }
10384
10466
  const fileName = (0, import_node_path.basename)(safePath);
10385
10467
  const fileType = args.fileType ?? inferMimeType(safePath);
10468
+ const storeTextFile = args.storeText ?? true;
10386
10469
  const upload = await client.documents.uploadDocument({
10387
10470
  upsert,
10388
10471
  fileName,
10389
10472
  fileType,
10390
10473
  indexMode,
10474
+ storeText: storeTextFile,
10391
10475
  ...common
10392
10476
  });
10393
10477
  const uploadUrl = upload?.uploadUrl;
@@ -10443,7 +10527,7 @@ var document_ingest_default = documentIngest;
10443
10527
  // src/tools/lookup_principal.ts
10444
10528
  var import_zod19 = require("zod");
10445
10529
  var MCP_DEFAULT_LIMIT5 = 10;
10446
- var MCP_MAX_LIMIT5 = 50;
10530
+ var MCP_MAX_LIMIT5 = 100;
10447
10531
  var inputSchema20 = {
10448
10532
  kind: import_zod19.z.enum(["user", "org", "client"]).describe("Which principal to look up: a user, an organization, or a client."),
10449
10533
  externalId: import_zod19.z.string().optional().describe(
@@ -10457,12 +10541,15 @@ var inputSchema20 = {
10457
10541
  to: import_zod19.z.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
10458
10542
  prefix: import_zod19.z.string().optional().describe("Lookup mode (prefix): match values starting with this. Range-enabled string fields only."),
10459
10543
  order: import_zod19.z.enum(["asc", "desc"]).optional().describe("Lookup mode: sort direction by the looked-up field. `asc` (default) or `desc`."),
10460
- limit: import_zod19.z.number().int().min(1).max(MCP_MAX_LIMIT5).optional().describe(`Max principals to return. MCP cap of ${MCP_MAX_LIMIT5} (vs API 100). Default ${MCP_DEFAULT_LIMIT5}.`)
10544
+ startFrom: import_zod19.z.string().optional().describe("Lookup mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10545
+ limit: import_zod19.z.number().int().min(1).max(MCP_MAX_LIMIT5).optional().describe(
10546
+ `Max principals per page. Defaults to ${MCP_DEFAULT_LIMIT5}; raise up to ${MCP_MAX_LIMIT5} (the principals API max) in one call when you can accept the payload, or page with \`startFrom\`.`
10547
+ )
10461
10548
  };
10462
10549
  var lookupPrincipal = ({ client, log }) => ({
10463
10550
  name: "lookup_principal",
10464
10551
  title: "Look up a user / org / client identity",
10465
- description: "Resolve a user, org, or client identity. Two modes:\n \u2022 Resolve by your own id: pass `externalId` \u2192 the matching principal incl. its Vectros UUID (the id the ownership filters on record_query / hybrid_search / rag_ask expect).\n \u2022 Lookup by a schema field: pass `type` and `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Sensitive-field-safe. (If both `externalId` and a `field` lookup are given, `externalId` wins.) Returns up to 50 principals (default 10) as a bare array. Read-only \u2014 does not create or modify identities.",
10552
+ description: "Resolve a user, org, or client identity. Two modes:\n \u2022 Resolve by your own id: pass `externalId` \u2192 the matching principal incl. its Vectros UUID (the id the ownership filters on record_query / hybrid_search / rag_ask expect).\n \u2022 Lookup by a schema field: pass `type` and `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Sensitive-field-safe. (If both `externalId` and a `field` lookup are given, `externalId` wins.) Returns a `{ data, nextCursor }` page (default 10, max 100 \u2014 raise it in one call when you can accept the payload); a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom`. Read-only \u2014 does not create or modify identities.",
10466
10553
  inputSchema: inputSchema20,
10467
10554
  handler: async (args) => {
10468
10555
  const kind = args.kind;
@@ -10475,6 +10562,7 @@ var lookupPrincipal = ({ client, log }) => ({
10475
10562
  const to = args.to;
10476
10563
  const prefix = args.prefix;
10477
10564
  const order = args.order;
10565
+ const startFrom = args.startFrom;
10478
10566
  try {
10479
10567
  let page;
10480
10568
  if (externalId !== void 0) {
@@ -10512,7 +10600,7 @@ var lookupPrincipal = ({ client, log }) => ({
10512
10600
  new Error("lookup requires 'type' (the identity schema's record type) alongside 'field'.")
10513
10601
  );
10514
10602
  }
10515
- const req = { type, field, value, from, to, prefix, order, limit };
10603
+ const req = { type, field, value, from, to, prefix, order, startFrom, limit };
10516
10604
  if (kind === "user") {
10517
10605
  page = await client.identity.lookupUsers(req);
10518
10606
  } else if (kind === "org") {
@@ -10528,7 +10616,8 @@ var lookupPrincipal = ({ client, log }) => ({
10528
10616
  );
10529
10617
  }
10530
10618
  const principals = pageItems(page);
10531
- return { content: [{ type: "text", text: JSON.stringify(principals, null, 2) }] };
10619
+ const nextCursor = page.nextCursor ?? null;
10620
+ return { content: [{ type: "text", text: JSON.stringify({ data: principals, nextCursor }, null, 2) }] };
10532
10621
  } catch (err) {
10533
10622
  log.warn({ tool: "lookup_principal", kind, err: String(err) }, "lookup_principal failed");
10534
10623
  return toolError("lookup_principal", err);