@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-http.mjs CHANGED
@@ -652,6 +652,10 @@ var ReadAccessLogResponse;
652
652
  })(ReadAccessLogResponse || (ReadAccessLogResponse = {}));
653
653
  var RecordRequest;
654
654
  ((RecordRequest2) => {
655
+ RecordRequest2.Status = {
656
+ Active: "ACTIVE",
657
+ Archived: "ARCHIVED"
658
+ };
655
659
  RecordRequest2.IndexMode = {
656
660
  Hybrid: "HYBRID",
657
661
  Semantic: "SEMANTIC",
@@ -661,6 +665,10 @@ var RecordRequest;
661
665
  })(RecordRequest || (RecordRequest = {}));
662
666
  var RecordResponse;
663
667
  ((RecordResponse2) => {
668
+ RecordResponse2.Status = {
669
+ Active: "ACTIVE",
670
+ Archived: "ARCHIVED"
671
+ };
664
672
  RecordResponse2.IndexStatus = {
665
673
  PendingIndex: "PENDING_INDEX",
666
674
  Indexed: "INDEXED",
@@ -3135,7 +3143,7 @@ var AuthClient = class {
3135
3143
  * roleId: "engineering-member",
3136
3144
  * name: "Engineering Team Member",
3137
3145
  * scopes: [{
3138
- * allowed_actions: ["read", "write"]
3146
+ * allowed_actions: ["records:cru", "search:r"]
3139
3147
  * }]
3140
3148
  * }
3141
3149
  * })
@@ -3683,7 +3691,7 @@ var AuthClient = class {
3683
3691
  * roleId: "engineering-member",
3684
3692
  * name: "Engineering Team Member",
3685
3693
  * scopes: [{
3686
- * allowed_actions: ["read", "write"]
3694
+ * allowed_actions: ["records:cru", "search:r"]
3687
3695
  * }]
3688
3696
  * }
3689
3697
  * })
@@ -4586,7 +4594,7 @@ var DocumentsClient = class {
4586
4594
  this._options = normalizeClientOptionsWithAuth(options);
4587
4595
  }
4588
4596
  /**
4589
- * 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.
4597
+ * 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.
4590
4598
  *
4591
4599
  * @param {Vectros.ListDocumentsRequest} request
4592
4600
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4596,6 +4604,7 @@ var DocumentsClient = class {
4596
4604
  * userId: "550e8400-e29b-41d4-a716-446655440000",
4597
4605
  * orgId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
4598
4606
  * clientId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4607
+ * scope: "group:eng-team",
4599
4608
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4600
4609
  * startFrom: "doc_prev123"
4601
4610
  * })
@@ -4604,11 +4613,12 @@ var DocumentsClient = class {
4604
4613
  return HttpResponsePromise.fromPromise(this.__listDocuments(request, requestOptions));
4605
4614
  }
4606
4615
  async __listDocuments(request = {}, requestOptions) {
4607
- const { userId, orgId, clientId, folderId, startFrom, limit } = request;
4616
+ const { userId, orgId, clientId, scope, folderId, startFrom, limit } = request;
4608
4617
  const _queryParams = {
4609
4618
  userId,
4610
4619
  orgId,
4611
4620
  clientId,
4621
+ scope,
4612
4622
  folderId,
4613
4623
  startFrom,
4614
4624
  limit
@@ -4666,9 +4676,10 @@ var DocumentsClient = class {
4666
4676
  return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
4667
4677
  }
4668
4678
  async __ingestDocument(request, requestOptions) {
4669
- const { upsert, body: _body } = request;
4679
+ const { upsert, allowClear, body: _body } = request;
4670
4680
  const _queryParams = {
4671
- upsert
4681
+ upsert,
4682
+ allowClear
4672
4683
  };
4673
4684
  const _authRequest = await this._options.authProvider.getAuthRequest();
4674
4685
  const _headers = mergeHeaders(
@@ -4792,7 +4803,10 @@ var DocumentsClient = class {
4792
4803
  return HttpResponsePromise.fromPromise(this.__updateDocument(request, requestOptions));
4793
4804
  }
4794
4805
  async __updateDocument(request, requestOptions) {
4795
- const { id, body: _body } = request;
4806
+ const { id, allowClear, body: _body } = request;
4807
+ const _queryParams = {
4808
+ allowClear
4809
+ };
4796
4810
  const _authRequest = await this._options.authProvider.getAuthRequest();
4797
4811
  const _headers = mergeHeaders(
4798
4812
  _authRequest.headers,
@@ -4807,7 +4821,7 @@ var DocumentsClient = class {
4807
4821
  method: "PUT",
4808
4822
  headers: _headers,
4809
4823
  contentType: "application/json",
4810
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4824
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4811
4825
  requestType: "json",
4812
4826
  body: _body,
4813
4827
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4898,7 +4912,7 @@ var DocumentsClient = class {
4898
4912
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/documents/{id}");
4899
4913
  }
4900
4914
  /**
4901
- * 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.
4915
+ * 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.
4902
4916
  *
4903
4917
  * @param {Vectros.PatchDocumentRequest} request
4904
4918
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5150,7 +5164,7 @@ var DocumentsClient = class {
5150
5164
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/download");
5151
5165
  }
5152
5166
  /**
5153
- * 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.
5167
+ * 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.
5154
5168
  *
5155
5169
  * @param {Vectros.GetDocumentTextRequest} request
5156
5170
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7543,7 +7557,7 @@ var RecordsClient = class {
7543
7557
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/records/batch");
7544
7558
  }
7545
7559
  /**
7546
- * 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.
7560
+ * 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.
7547
7561
  *
7548
7562
  * @param {Vectros.ListRecordsRequest} request
7549
7563
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7555,6 +7569,7 @@ var RecordsClient = class {
7555
7569
  * userId: "550e8400-e29b-41d4-a716-446655440000",
7556
7570
  * orgId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7557
7571
  * clientId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7572
+ * scope: "group:eng-team",
7558
7573
  * startFrom: "550e8400-e29b-41d4-a716-446655440000"
7559
7574
  * })
7560
7575
  */
@@ -7562,13 +7577,25 @@ var RecordsClient = class {
7562
7577
  return HttpResponsePromise.fromPromise(this.__listRecords(request, requestOptions));
7563
7578
  }
7564
7579
  async __listRecords(request = {}, requestOptions) {
7565
- const { type: type_, folderId, userId, orgId, clientId, startFrom, limit, includePayload, recent } = request;
7580
+ const {
7581
+ type: type_,
7582
+ folderId,
7583
+ userId,
7584
+ orgId,
7585
+ clientId,
7586
+ scope,
7587
+ startFrom,
7588
+ limit,
7589
+ includePayload,
7590
+ recent
7591
+ } = request;
7566
7592
  const _queryParams = {
7567
7593
  type: type_,
7568
7594
  folderId,
7569
7595
  userId,
7570
7596
  orgId,
7571
7597
  clientId,
7598
+ scope,
7572
7599
  startFrom,
7573
7600
  limit,
7574
7601
  includePayload,
@@ -7633,9 +7660,10 @@ var RecordsClient = class {
7633
7660
  return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
7634
7661
  }
7635
7662
  async __createRecord(request, requestOptions) {
7636
- const { upsert, body: _body } = request;
7663
+ const { upsert, allowClear, body: _body } = request;
7637
7664
  const _queryParams = {
7638
- upsert
7665
+ upsert,
7666
+ allowClear
7639
7667
  };
7640
7668
  const _authRequest = await this._options.authProvider.getAuthRequest();
7641
7669
  const _headers = mergeHeaders(
@@ -7757,7 +7785,10 @@ var RecordsClient = class {
7757
7785
  return HttpResponsePromise.fromPromise(this.__updateRecord(request, requestOptions));
7758
7786
  }
7759
7787
  async __updateRecord(request, requestOptions) {
7760
- const { id, body: _body } = request;
7788
+ const { id, allowClear, body: _body } = request;
7789
+ const _queryParams = {
7790
+ allowClear
7791
+ };
7761
7792
  const _authRequest = await this._options.authProvider.getAuthRequest();
7762
7793
  const _headers = mergeHeaders(
7763
7794
  _authRequest.headers,
@@ -7772,7 +7803,7 @@ var RecordsClient = class {
7772
7803
  method: "PUT",
7773
7804
  headers: _headers,
7774
7805
  contentType: "application/json",
7775
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
7806
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7776
7807
  requestType: "json",
7777
7808
  body: _body,
7778
7809
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -8764,8 +8795,8 @@ function warnOnSuboptimalKey(key, log) {
8764
8795
 
8765
8796
  // src/build-info.ts
8766
8797
  var BUILD_INFO = {
8767
- mcpServer: true ? "0.6.0" : "dev",
8768
- sdk: true ? "0.32.0" : "dev"
8798
+ mcpServer: true ? "0.9.0" : "dev",
8799
+ sdk: true ? "0.34.0" : "dev"
8769
8800
  };
8770
8801
 
8771
8802
  // src/log.ts
@@ -9001,7 +9032,7 @@ ${detail}${pointer}`
9001
9032
 
9002
9033
  // src/tools/hybrid_search.ts
9003
9034
  var MCP_DEFAULT_LIMIT = 3;
9004
- var MCP_MAX_LIMIT = 10;
9035
+ var MCP_MAX_LIMIT = 50;
9005
9036
  var MCP_RESPONSE_CHAR_BUDGET = 24e3;
9006
9037
  var SNIPPET_MAX_CHARS = 160;
9007
9038
  var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["tenantId", "owner_id", "model_type", "rootFolderId", "folderId"]);
@@ -9035,9 +9066,9 @@ var inputSchema = {
9035
9066
  "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.)"
9036
9067
  ),
9037
9068
  limit: z.number().int().min(1).max(MCP_MAX_LIMIT).optional().describe(
9038
- `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.`
9069
+ `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.`
9039
9070
  ),
9040
- offset: z.number().int().min(0).optional().describe("Skip the first N hits \u2014 for pagination."),
9071
+ offset: z.number().int().min(0).max(200).optional().describe("Skip the first N hits \u2014 for pagination (0\u2013200)."),
9041
9072
  folderId: z.string().optional().describe("Scope to content in this EXACT folder (Vectros folder id)."),
9042
9073
  rootFolderId: z.string().optional().describe("Scope to content under this folder AND all its descendants (subtree-root folder id)."),
9043
9074
  // Content narrowing.
@@ -9052,6 +9083,9 @@ var inputSchema = {
9052
9083
  userId: z.string().optional().describe("Restrict to content owned by this user (Vectros UUID)."),
9053
9084
  orgId: z.string().optional().describe("Restrict to content belonging to this org (Vectros UUID)."),
9054
9085
  clientId: z.string().optional().describe("Restrict to content associated with this client (Vectros UUID)."),
9086
+ scope: z.string().optional().describe(
9087
+ '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.'
9088
+ ),
9055
9089
  // Date window.
9056
9090
  createdAfter: z.string().optional().describe("Restrict to content created at/after this ISO 8601 UTC timestamp."),
9057
9091
  createdBefore: z.string().optional().describe("Restrict to content created at/before this ISO 8601 UTC timestamp."),
@@ -9069,7 +9103,7 @@ var inputSchema = {
9069
9103
  var hybridSearch = ({ client, log }) => ({
9070
9104
  name: "hybrid_search",
9071
9105
  title: "Hybrid search",
9072
- 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".`,
9106
+ 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\".",
9073
9107
  inputSchema,
9074
9108
  handler: async (args) => {
9075
9109
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9089,6 +9123,7 @@ var hybridSearch = ({ client, log }) => ({
9089
9123
  userId: args.userId,
9090
9124
  orgId: args.orgId,
9091
9125
  clientId: args.clientId,
9126
+ scope: args.scope,
9092
9127
  createdAfter: args.createdAfter,
9093
9128
  createdBefore: args.createdBefore,
9094
9129
  minSimilarity: args.minSimilarity,
@@ -9115,6 +9150,12 @@ var hybridSearch = ({ client, log }) => ({
9115
9150
  envelope.returnedResults = shaped.length;
9116
9151
  text = JSON.stringify(envelope, null, 2);
9117
9152
  }
9153
+ const offset = args.offset ?? 0;
9154
+ const totalResults = result.totalResults;
9155
+ if (typeof totalResults === "number" && offset + shaped.length < totalResults) {
9156
+ envelope.hasMore = true;
9157
+ text = JSON.stringify(envelope, null, 2);
9158
+ }
9118
9159
  log.debug(
9119
9160
  { tool: "hybrid_search", mode, limit, returned: shaped.length, textLegEmpty },
9120
9161
  "hybrid_search ok"
@@ -9150,7 +9191,7 @@ async function drainPages(fetchPage) {
9150
9191
 
9151
9192
  // src/tools/record_query.ts
9152
9193
  var MCP_DEFAULT_LIMIT2 = 3;
9153
- var MCP_MAX_LIMIT2 = 10;
9194
+ var MCP_MAX_LIMIT2 = 100;
9154
9195
  var inputSchema2 = {
9155
9196
  type: z2.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9156
9197
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
@@ -9168,14 +9209,18 @@ var inputSchema2 = {
9168
9209
  userId: z2.string().optional().describe("List mode: scope to records owned by this user."),
9169
9210
  orgId: z2.string().optional().describe("List mode: scope to records owned by this org."),
9170
9211
  clientId: z2.string().optional().describe("List mode: scope to records associated with this client."),
9212
+ scope: z2.string().optional().describe(
9213
+ '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.'
9214
+ ),
9215
+ startFrom: z2.string().optional().describe("Pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next. Works in both list and lookup mode."),
9171
9216
  limit: z2.number().int().min(1).max(MCP_MAX_LIMIT2).optional().describe(
9172
- `Max records to return. MCP-specific cap of ${MCP_MAX_LIMIT2} (vs API max of 100) to protect the agent context window. Default 3.`
9217
+ `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.`
9173
9218
  )
9174
9219
  };
9175
9220
  var recordQuery = ({ client, log }) => ({
9176
9221
  name: "record_query",
9177
9222
  title: "Record query (list or lookup)",
9178
- 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.",
9223
+ 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.",
9179
9224
  inputSchema: inputSchema2,
9180
9225
  handler: async (args) => {
9181
9226
  const limit = args.limit ?? MCP_DEFAULT_LIMIT2;
@@ -9185,8 +9230,9 @@ var recordQuery = ({ client, log }) => ({
9185
9230
  const from = args.from;
9186
9231
  const to = args.to;
9187
9232
  const prefix = args.prefix;
9233
+ const startFrom = args.startFrom;
9188
9234
  try {
9189
- let records;
9235
+ let page;
9190
9236
  if (field) {
9191
9237
  const hasEquality = value !== void 0;
9192
9238
  const hasRange = from !== void 0 || to !== void 0;
@@ -9209,7 +9255,7 @@ var recordQuery = ({ client, log }) => ({
9209
9255
  if (hasRange && !(from !== void 0 && to !== void 0)) {
9210
9256
  return toolError("record_query", new Error("range lookup requires both 'from' and 'to'."));
9211
9257
  }
9212
- const page = await client.records.lookupRecordsByBody({
9258
+ page = await client.records.lookupRecordsByBody({
9213
9259
  type,
9214
9260
  field,
9215
9261
  value,
@@ -9217,29 +9263,32 @@ var recordQuery = ({ client, log }) => ({
9217
9263
  to,
9218
9264
  prefix,
9219
9265
  order: args.order,
9266
+ startFrom,
9220
9267
  limit
9221
9268
  });
9222
- records = pageItems(page);
9223
9269
  log.debug(
9224
- { tool: "record_query", mode: "lookup", type, field, returned: records.length },
9270
+ { tool: "record_query", mode: "lookup", type, field, returned: pageItems(page).length },
9225
9271
  "record_query lookup ok"
9226
9272
  );
9227
9273
  } else {
9228
- const page = await client.records.listRecords({
9274
+ page = await client.records.listRecords({
9229
9275
  type,
9230
9276
  userId: args.userId,
9231
9277
  orgId: args.orgId,
9232
9278
  clientId: args.clientId,
9279
+ scope: args.scope,
9280
+ startFrom,
9233
9281
  limit
9234
9282
  });
9235
- records = pageItems(page);
9236
9283
  log.debug(
9237
- { tool: "record_query", mode: "list", type, limit, returned: records.length },
9284
+ { tool: "record_query", mode: "list", type, limit, returned: pageItems(page).length },
9238
9285
  "record_query list ok"
9239
9286
  );
9240
9287
  }
9288
+ const records = pageItems(page);
9289
+ const nextCursor = page.nextCursor ?? null;
9241
9290
  return {
9242
- content: [{ type: "text", text: JSON.stringify(records, null, 2) }]
9291
+ content: [{ type: "text", text: JSON.stringify({ data: records, nextCursor }, null, 2) }]
9243
9292
  };
9244
9293
  } catch (err) {
9245
9294
  log.warn({ tool: "record_query", err: String(err) }, "record_query failed");
@@ -9362,7 +9411,10 @@ var inputSchema4 = {
9362
9411
  folderId: z4.string().optional().describe("Group this record into a folder."),
9363
9412
  userId: z4.string().optional().describe("Owning user id."),
9364
9413
  orgId: z4.string().optional().describe("Owning organization id."),
9365
- clientId: z4.string().optional().describe("Associated client id.")
9414
+ clientId: z4.string().optional().describe("Associated client id."),
9415
+ scopes: z4.array(z4.string()).optional().describe(
9416
+ '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.'
9417
+ )
9366
9418
  };
9367
9419
  var recordCreate = ({ client, log }) => ({
9368
9420
  name: "record_create",
@@ -9382,7 +9434,8 @@ var recordCreate = ({ client, log }) => ({
9382
9434
  folderId: args.folderId,
9383
9435
  userId: args.userId,
9384
9436
  orgId: args.orgId,
9385
- clientId: args.clientId
9437
+ clientId: args.clientId,
9438
+ scopes: args.scopes
9386
9439
  }
9387
9440
  });
9388
9441
  log.debug({ tool: "record_create", type, id: created.id }, "record_create ok");
@@ -9467,24 +9520,29 @@ var record_delete_default = recordDelete;
9467
9520
  // src/tools/rag_ask.ts
9468
9521
  import { z as z7 } from "zod";
9469
9522
  var SEARCH_MCP_DEFAULT_LIMIT = 5;
9470
- var SEARCH_MCP_MAX_LIMIT = 10;
9523
+ var SEARCH_MCP_MAX_LIMIT = 50;
9471
9524
  var inputSchema7 = {
9472
9525
  query: z7.string().min(1, "query must be non-empty").describe("Question to ground against retrieved content."),
9473
9526
  instructions: z7.string().optional().describe(
9474
9527
  '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.'
9475
9528
  ),
9476
9529
  model: z7.string().optional().describe(
9477
- "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."
9530
+ "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."
9478
9531
  ),
9479
9532
  search: z7.object({
9480
9533
  mode: z7.enum(["HYBRID", "TEXT", "SEMANTIC"]).optional().describe("Retrieval mode. Default HYBRID."),
9481
- limit: z7.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}.`),
9534
+ limit: z7.number().int().min(1).max(SEARCH_MCP_MAX_LIMIT).optional().describe(
9535
+ `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.`
9536
+ ),
9482
9537
  // Retrieval scoping — parity with hybrid_search so an agent can ground the
9483
9538
  // answer on a specific owner / folder / type / metadata subset, not just the
9484
9539
  // whole-tenant corpus.
9485
9540
  userId: z7.string().optional().describe("Restrict retrieval to content owned by this user (Vectros UUID)."),
9486
9541
  orgId: z7.string().optional().describe("Restrict retrieval to content owned by this org (Vectros UUID)."),
9487
9542
  clientId: z7.string().optional().describe("Restrict retrieval to content tagged with this client (Vectros UUID)."),
9543
+ scope: z7.string().optional().describe(
9544
+ '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.'
9545
+ ),
9488
9546
  folderId: z7.string().optional().describe("Restrict retrieval to this exact folder."),
9489
9547
  rootFolderId: z7.string().optional().describe("Restrict retrieval to this folder and all its descendants."),
9490
9548
  typeName: z7.string().optional().describe("Restrict record retrieval to this schema type."),
@@ -9502,7 +9560,7 @@ var inputSchema7 = {
9502
9560
  var ragAsk = ({ client, log }) => ({
9503
9561
  name: "rag_ask",
9504
9562
  title: "RAG ask (corpus-wide)",
9505
- 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.",
9563
+ 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.",
9506
9564
  inputSchema: inputSchema7,
9507
9565
  handler: async (args, extra) => {
9508
9566
  try {
@@ -9513,6 +9571,7 @@ var ragAsk = ({ client, log }) => ({
9513
9571
  userId: searchArg.userId,
9514
9572
  orgId: searchArg.orgId,
9515
9573
  clientId: searchArg.clientId,
9574
+ scope: searchArg.scope,
9516
9575
  folderId: searchArg.folderId,
9517
9576
  rootFolderId: searchArg.rootFolderId,
9518
9577
  typeName: searchArg.typeName,
@@ -9670,7 +9729,7 @@ var inputSchema9 = {
9670
9729
  var listSchemas = ({ client, log }) => ({
9671
9730
  name: "list_schemas",
9672
9731
  title: "List record schemas",
9673
- 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.',
9732
+ 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.',
9674
9733
  inputSchema: inputSchema9,
9675
9734
  handler: async (args) => {
9676
9735
  const userId = args.userId;
@@ -9724,7 +9783,7 @@ function isNotAvailable(err) {
9724
9783
  var documentGet = ({ client, log }) => ({
9725
9784
  name: "document_get",
9726
9785
  title: "Get document metadata (and optionally text)",
9727
- 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`.",
9786
+ 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`.",
9728
9787
  inputSchema: inputSchema10,
9729
9788
  handler: async (args) => {
9730
9789
  const documentId = args.documentId;
@@ -9748,7 +9807,7 @@ var documentGet = ({ client, log }) => ({
9748
9807
  if (isNotAvailable(textErr)) {
9749
9808
  log.debug(
9750
9809
  { tool: "document_get", id },
9751
- "document_get text unavailable (ingested without storeText)"
9810
+ "document_get text unavailable (not retained \u2014 storeText:false upload)"
9752
9811
  );
9753
9812
  extra = { ...extra, textAvailable: false };
9754
9813
  } else {
@@ -9792,7 +9851,7 @@ var document_get_default = documentGet;
9792
9851
  // src/tools/document_query.ts
9793
9852
  import { z as z11 } from "zod";
9794
9853
  var MCP_DEFAULT_LIMIT3 = 3;
9795
- var MCP_MAX_LIMIT3 = 10;
9854
+ var MCP_MAX_LIMIT3 = 100;
9796
9855
  var inputSchema11 = {
9797
9856
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9798
9857
  field: z11.string().optional().describe("Lookup mode: name of the lookup-indexed field to query by."),
@@ -9809,14 +9868,18 @@ var inputSchema11 = {
9809
9868
  userId: z11.string().optional().describe("List mode: scope to documents owned by this user."),
9810
9869
  orgId: z11.string().optional().describe("List mode: scope to documents belonging to this org."),
9811
9870
  clientId: z11.string().optional().describe("List mode: scope to documents associated with this client."),
9871
+ scope: z11.string().optional().describe(
9872
+ '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.'
9873
+ ),
9874
+ startFrom: z11.string().optional().describe("Pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next. Works in both list and lookup mode."),
9812
9875
  limit: z11.number().int().min(1).max(MCP_MAX_LIMIT3).optional().describe(
9813
- `Max documents to return. MCP-specific cap of ${MCP_MAX_LIMIT3} (vs API max of 100) to protect the agent context window. Default 3.`
9876
+ `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.`
9814
9877
  )
9815
9878
  };
9816
9879
  var documentQuery = ({ client, log }) => ({
9817
9880
  name: "document_query",
9818
9881
  title: "Document query (list or lookup)",
9819
- 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.",
9882
+ 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.",
9820
9883
  inputSchema: inputSchema11,
9821
9884
  handler: async (args) => {
9822
9885
  const limit = args.limit ?? MCP_DEFAULT_LIMIT3;
@@ -9826,8 +9889,9 @@ var documentQuery = ({ client, log }) => ({
9826
9889
  const to = args.to;
9827
9890
  const prefix = args.prefix;
9828
9891
  const type = args.type;
9892
+ const startFrom = args.startFrom;
9829
9893
  try {
9830
- let documents;
9894
+ let page;
9831
9895
  if (field) {
9832
9896
  const hasEquality = value !== void 0;
9833
9897
  const hasRange = from !== void 0 || to !== void 0;
@@ -9856,7 +9920,7 @@ var documentQuery = ({ client, log }) => ({
9856
9920
  new Error("lookup requires 'type' (the document's bound schema type) alongside 'field'.")
9857
9921
  );
9858
9922
  }
9859
- const page = await client.documents.lookupDocumentsByBody({
9923
+ page = await client.documents.lookupDocumentsByBody({
9860
9924
  type,
9861
9925
  field,
9862
9926
  value,
@@ -9864,28 +9928,31 @@ var documentQuery = ({ client, log }) => ({
9864
9928
  to,
9865
9929
  prefix,
9866
9930
  order: args.order,
9931
+ startFrom,
9867
9932
  limit
9868
9933
  });
9869
- documents = pageItems(page);
9870
9934
  log.debug(
9871
- { tool: "document_query", mode: "lookup", type, field, returned: documents.length },
9935
+ { tool: "document_query", mode: "lookup", type, field, returned: pageItems(page).length },
9872
9936
  "document_query lookup ok"
9873
9937
  );
9874
9938
  } else {
9875
- const page = await client.documents.listDocuments({
9939
+ page = await client.documents.listDocuments({
9876
9940
  folderId: args.folderId,
9877
9941
  userId: args.userId,
9878
9942
  orgId: args.orgId,
9879
9943
  clientId: args.clientId,
9944
+ scope: args.scope,
9945
+ startFrom,
9880
9946
  limit
9881
9947
  });
9882
- documents = pageItems(page);
9883
9948
  log.debug(
9884
- { tool: "document_query", mode: "list", limit, returned: documents.length },
9949
+ { tool: "document_query", mode: "list", limit, returned: pageItems(page).length },
9885
9950
  "document_query list ok"
9886
9951
  );
9887
9952
  }
9888
- return { content: [{ type: "text", text: JSON.stringify(documents, null, 2) }] };
9953
+ const documents = pageItems(page);
9954
+ const nextCursor = page.nextCursor ?? null;
9955
+ return { content: [{ type: "text", text: JSON.stringify({ data: documents, nextCursor }, null, 2) }] };
9889
9956
  } catch (err) {
9890
9957
  log.warn({ tool: "document_query", err: String(err) }, "document_query failed");
9891
9958
  return toolError("document_query", err);
@@ -9910,7 +9977,6 @@ var inputSchema12 = {
9910
9977
  "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."
9911
9978
  ),
9912
9979
  folderId: z12.string().optional().describe("Move the document into this folder (cannot be cleared once set)."),
9913
- storeText: z12.boolean().optional().describe("Whether the raw text is stored + retrievable."),
9914
9980
  status: z12.enum(["ACTIVE", "ARCHIVED"]).optional().describe(
9915
9981
  "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."
9916
9982
  ),
@@ -9934,7 +10000,6 @@ var documentUpdate = ({ client, log }) => ({
9934
10000
  const fields = args.fields;
9935
10001
  const text = args.text;
9936
10002
  const folderId = args.folderId;
9937
- const storeText = args.storeText;
9938
10003
  const status = args.status;
9939
10004
  const userId = args.userId;
9940
10005
  const orgId = args.orgId;
@@ -9948,7 +10013,6 @@ var documentUpdate = ({ client, log }) => ({
9948
10013
  if (fields !== void 0) body.payload = fields;
9949
10014
  if (text !== void 0) body.text = text;
9950
10015
  if (folderId !== void 0) body.folderId = folderId;
9951
- if (storeText !== void 0) body.storeText = storeText;
9952
10016
  if (status !== void 0) body.status = status;
9953
10017
  if (userId !== void 0) body.userId = userId;
9954
10018
  if (orgId !== void 0) body.orgId = orgId;
@@ -9997,7 +10061,7 @@ var document_delete_default = documentDelete;
9997
10061
  // src/tools/folder_query.ts
9998
10062
  import { z as z14 } from "zod";
9999
10063
  var MCP_DEFAULT_LIMIT4 = 10;
10000
- var MCP_MAX_LIMIT4 = 50;
10064
+ var MCP_MAX_LIMIT4 = 100;
10001
10065
  var inputSchema14 = {
10002
10066
  id: z14.string().optional().describe("Get mode: fetch this single folder by id."),
10003
10067
  parentId: z14.string().optional().describe("List mode: direct children of this folder (tree navigation). Omit for a flat tenant list."),
@@ -10005,12 +10069,14 @@ var inputSchema14 = {
10005
10069
  orgId: z14.string().optional().describe("List mode: scope to folders belonging to this org."),
10006
10070
  clientId: z14.string().optional().describe("List mode: scope to folders associated with this client."),
10007
10071
  startFrom: z14.string().optional().describe("List mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10008
- limit: z14.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.`)
10072
+ limit: z14.number().int().min(1).max(MCP_MAX_LIMIT4).optional().describe(
10073
+ `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\`.`
10074
+ )
10009
10075
  };
10010
10076
  var folderQuery = ({ client, log }) => ({
10011
10077
  name: "folder_query",
10012
10078
  title: "Folder query (list or get)",
10013
- 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.",
10079
+ 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.",
10014
10080
  inputSchema: inputSchema14,
10015
10081
  handler: async (args) => {
10016
10082
  const id = args.id;
@@ -10050,7 +10116,10 @@ var inputSchema15 = {
10050
10116
  slug: z15.string().optional().describe("Optional stable slug (sibling-unique; idempotency key). Lowercase letters/digits/hyphens. Derived from name when omitted."),
10051
10117
  userId: z15.string().optional().describe("Owning user (Vectros UUID)."),
10052
10118
  orgId: z15.string().optional().describe("Owning org (Vectros UUID)."),
10053
- clientId: z15.string().optional().describe("Associated client (Vectros UUID).")
10119
+ clientId: z15.string().optional().describe("Associated client (Vectros UUID)."),
10120
+ scopes: z15.array(z15.string()).optional().describe(
10121
+ '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.'
10122
+ )
10054
10123
  };
10055
10124
  var folderCreate = ({ client, log }) => ({
10056
10125
  name: "folder_create",
@@ -10067,7 +10136,8 @@ var folderCreate = ({ client, log }) => ({
10067
10136
  slug: args.slug,
10068
10137
  userId: args.userId,
10069
10138
  orgId: args.orgId,
10070
- clientId: args.clientId
10139
+ clientId: args.clientId,
10140
+ scopes: args.scopes
10071
10141
  }
10072
10142
  });
10073
10143
  log.debug({ tool: "folder_create", id: folder.id, name: folder.name }, "folder_create ok");
@@ -10268,7 +10338,7 @@ var inputSchema19 = {
10268
10338
  // Inline text mode (mutually exclusive with filePath):
10269
10339
  text: z18.string().min(1).optional().describe("Text body to ingest inline. Mutually exclusive with filePath. Either text or filePath is required."),
10270
10340
  storeText: z18.boolean().optional().describe(
10271
- "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."
10341
+ "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."
10272
10342
  ),
10273
10343
  // File upload mode (mutually exclusive with text; stdio-transport-only):
10274
10344
  filePath: z18.string().min(1).optional().describe(
@@ -10279,12 +10349,15 @@ var inputSchema19 = {
10279
10349
  folderId: z18.string().optional().describe("Optional folder to ingest into. Default: tenant root."),
10280
10350
  userId: z18.string().optional().describe("Owning user ID. Optional."),
10281
10351
  orgId: z18.string().optional().describe("Owning org ID. Optional."),
10282
- clientId: z18.string().optional().describe("Owning client ID. Optional.")
10352
+ clientId: z18.string().optional().describe("Owning client ID. Optional."),
10353
+ scopes: z18.array(z18.string()).optional().describe(
10354
+ "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."
10355
+ )
10283
10356
  };
10284
10357
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10285
10358
  name: "document_ingest",
10286
10359
  title: "Ingest a new document (text body or file upload)",
10287
- 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).",
10360
+ 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.",
10288
10361
  inputSchema: inputSchema19,
10289
10362
  handler: async (args) => {
10290
10363
  const title = args.title;
@@ -10313,6 +10386,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10313
10386
  )
10314
10387
  );
10315
10388
  }
10389
+ if (filePath && args.scopes !== void 0) {
10390
+ return toolError(
10391
+ "document_ingest",
10392
+ new Error(
10393
+ "`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)."
10394
+ )
10395
+ );
10396
+ }
10316
10397
  const common = {
10317
10398
  folderId: args.folderId,
10318
10399
  payload: args.payload,
@@ -10324,14 +10405,15 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10324
10405
  };
10325
10406
  try {
10326
10407
  if (text) {
10327
- const storeText = args.storeText ?? true;
10328
10408
  const result = await client.documents.ingestDocument({
10329
10409
  upsert,
10330
10410
  body: {
10331
10411
  title,
10332
10412
  text,
10333
10413
  indexMode,
10334
- storeText,
10414
+ // Scope ownership is a TEXT-mode contract only (the upload-init
10415
+ // endpoint has no scopes field — see the gate above).
10416
+ scopes: args.scopes,
10335
10417
  ...common
10336
10418
  }
10337
10419
  });
@@ -10362,11 +10444,13 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10362
10444
  }
10363
10445
  const fileName = basename(safePath);
10364
10446
  const fileType = args.fileType ?? inferMimeType(safePath);
10447
+ const storeTextFile = args.storeText ?? true;
10365
10448
  const upload = await client.documents.uploadDocument({
10366
10449
  upsert,
10367
10450
  fileName,
10368
10451
  fileType,
10369
10452
  indexMode,
10453
+ storeText: storeTextFile,
10370
10454
  ...common
10371
10455
  });
10372
10456
  const uploadUrl = upload?.uploadUrl;
@@ -10422,7 +10506,7 @@ var document_ingest_default = documentIngest;
10422
10506
  // src/tools/lookup_principal.ts
10423
10507
  import { z as z19 } from "zod";
10424
10508
  var MCP_DEFAULT_LIMIT5 = 10;
10425
- var MCP_MAX_LIMIT5 = 50;
10509
+ var MCP_MAX_LIMIT5 = 100;
10426
10510
  var inputSchema20 = {
10427
10511
  kind: z19.enum(["user", "org", "client"]).describe("Which principal to look up: a user, an organization, or a client."),
10428
10512
  externalId: z19.string().optional().describe(
@@ -10436,12 +10520,15 @@ var inputSchema20 = {
10436
10520
  to: z19.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
10437
10521
  prefix: z19.string().optional().describe("Lookup mode (prefix): match values starting with this. Range-enabled string fields only."),
10438
10522
  order: z19.enum(["asc", "desc"]).optional().describe("Lookup mode: sort direction by the looked-up field. `asc` (default) or `desc`."),
10439
- limit: z19.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}.`)
10523
+ startFrom: z19.string().optional().describe("Lookup mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10524
+ limit: z19.number().int().min(1).max(MCP_MAX_LIMIT5).optional().describe(
10525
+ `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\`.`
10526
+ )
10440
10527
  };
10441
10528
  var lookupPrincipal = ({ client, log }) => ({
10442
10529
  name: "lookup_principal",
10443
10530
  title: "Look up a user / org / client identity",
10444
- 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.",
10531
+ 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.",
10445
10532
  inputSchema: inputSchema20,
10446
10533
  handler: async (args) => {
10447
10534
  const kind = args.kind;
@@ -10454,6 +10541,7 @@ var lookupPrincipal = ({ client, log }) => ({
10454
10541
  const to = args.to;
10455
10542
  const prefix = args.prefix;
10456
10543
  const order = args.order;
10544
+ const startFrom = args.startFrom;
10457
10545
  try {
10458
10546
  let page;
10459
10547
  if (externalId !== void 0) {
@@ -10491,7 +10579,7 @@ var lookupPrincipal = ({ client, log }) => ({
10491
10579
  new Error("lookup requires 'type' (the identity schema's record type) alongside 'field'.")
10492
10580
  );
10493
10581
  }
10494
- const req = { type, field, value, from, to, prefix, order, limit };
10582
+ const req = { type, field, value, from, to, prefix, order, startFrom, limit };
10495
10583
  if (kind === "user") {
10496
10584
  page = await client.identity.lookupUsers(req);
10497
10585
  } else if (kind === "org") {
@@ -10507,7 +10595,8 @@ var lookupPrincipal = ({ client, log }) => ({
10507
10595
  );
10508
10596
  }
10509
10597
  const principals = pageItems(page);
10510
- return { content: [{ type: "text", text: JSON.stringify(principals, null, 2) }] };
10598
+ const nextCursor = page.nextCursor ?? null;
10599
+ return { content: [{ type: "text", text: JSON.stringify({ data: principals, nextCursor }, null, 2) }] };
10511
10600
  } catch (err) {
10512
10601
  log.warn({ tool: "lookup_principal", kind, err: String(err) }, "lookup_principal failed");
10513
10602
  return toolError("lookup_principal", err);