@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.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
  function formatBuildInfo() {
8771
8802
  return `vectros-mcp-server ${BUILD_INFO.mcpServer} (sdk ${BUILD_INFO.sdk})`;
@@ -9004,7 +9035,7 @@ ${detail}${pointer}`
9004
9035
 
9005
9036
  // src/tools/hybrid_search.ts
9006
9037
  var MCP_DEFAULT_LIMIT = 3;
9007
- var MCP_MAX_LIMIT = 10;
9038
+ var MCP_MAX_LIMIT = 50;
9008
9039
  var MCP_RESPONSE_CHAR_BUDGET = 24e3;
9009
9040
  var SNIPPET_MAX_CHARS = 160;
9010
9041
  var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["tenantId", "owner_id", "model_type", "rootFolderId", "folderId"]);
@@ -9038,9 +9069,9 @@ var inputSchema = {
9038
9069
  "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.)"
9039
9070
  ),
9040
9071
  limit: z.number().int().min(1).max(MCP_MAX_LIMIT).optional().describe(
9041
- `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.`
9072
+ `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.`
9042
9073
  ),
9043
- offset: z.number().int().min(0).optional().describe("Skip the first N hits \u2014 for pagination."),
9074
+ offset: z.number().int().min(0).max(200).optional().describe("Skip the first N hits \u2014 for pagination (0\u2013200)."),
9044
9075
  folderId: z.string().optional().describe("Scope to content in this EXACT folder (Vectros folder id)."),
9045
9076
  rootFolderId: z.string().optional().describe("Scope to content under this folder AND all its descendants (subtree-root folder id)."),
9046
9077
  // Content narrowing.
@@ -9055,6 +9086,9 @@ var inputSchema = {
9055
9086
  userId: z.string().optional().describe("Restrict to content owned by this user (Vectros UUID)."),
9056
9087
  orgId: z.string().optional().describe("Restrict to content belonging to this org (Vectros UUID)."),
9057
9088
  clientId: z.string().optional().describe("Restrict to content associated with this client (Vectros UUID)."),
9089
+ scope: z.string().optional().describe(
9090
+ '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.'
9091
+ ),
9058
9092
  // Date window.
9059
9093
  createdAfter: z.string().optional().describe("Restrict to content created at/after this ISO 8601 UTC timestamp."),
9060
9094
  createdBefore: z.string().optional().describe("Restrict to content created at/before this ISO 8601 UTC timestamp."),
@@ -9072,7 +9106,7 @@ var inputSchema = {
9072
9106
  var hybridSearch = ({ client, log }) => ({
9073
9107
  name: "hybrid_search",
9074
9108
  title: "Hybrid search",
9075
- description: `Search the partner tenant's indexed content (documents + structured records) using hybrid BM25 + dense ranking. Returns up to 10 results (default 3) with citations and surrounding context for grounding follow-up reasoning. Narrow with contentTypes/typeName/filters, ownership (userId/orgId/clientId), folder scope, a created date window, keyword precision (textMode OR/AND/PHRASE), relevance floors (minSimilarity/minTextRelevance), uniqueDocuments, and requireComplete (fail closed on a degraded backend). Tenant-isolated; the caller's scoped key fully constrains which content is visible. ARCHIVED (soft-retracted) documents never appear in results \u2014 fetch them by id via document_get, or restore them via document_update. To stay within the agent context window each hit returns its matched chunkText + a short snippet by default; set includeContext:true for the broader surrounding passage. If the whole result would be too large it is truncated to the top hits (truncated:true). textLegEmpty:true flags that the keyword leg matched nothing (all textScores 0) \u2014 usually a too-long PHRASE query; retry with textMode:"OR".`,
9109
+ 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\".",
9076
9110
  inputSchema,
9077
9111
  handler: async (args) => {
9078
9112
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9092,6 +9126,7 @@ var hybridSearch = ({ client, log }) => ({
9092
9126
  userId: args.userId,
9093
9127
  orgId: args.orgId,
9094
9128
  clientId: args.clientId,
9129
+ scope: args.scope,
9095
9130
  createdAfter: args.createdAfter,
9096
9131
  createdBefore: args.createdBefore,
9097
9132
  minSimilarity: args.minSimilarity,
@@ -9118,6 +9153,12 @@ var hybridSearch = ({ client, log }) => ({
9118
9153
  envelope.returnedResults = shaped.length;
9119
9154
  text = JSON.stringify(envelope, null, 2);
9120
9155
  }
9156
+ const offset = args.offset ?? 0;
9157
+ const totalResults = result.totalResults;
9158
+ if (typeof totalResults === "number" && offset + shaped.length < totalResults) {
9159
+ envelope.hasMore = true;
9160
+ text = JSON.stringify(envelope, null, 2);
9161
+ }
9121
9162
  log.debug(
9122
9163
  { tool: "hybrid_search", mode, limit, returned: shaped.length, textLegEmpty },
9123
9164
  "hybrid_search ok"
@@ -9153,7 +9194,7 @@ async function drainPages(fetchPage) {
9153
9194
 
9154
9195
  // src/tools/record_query.ts
9155
9196
  var MCP_DEFAULT_LIMIT2 = 3;
9156
- var MCP_MAX_LIMIT2 = 10;
9197
+ var MCP_MAX_LIMIT2 = 100;
9157
9198
  var inputSchema2 = {
9158
9199
  type: z2.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9159
9200
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
@@ -9171,14 +9212,18 @@ var inputSchema2 = {
9171
9212
  userId: z2.string().optional().describe("List mode: scope to records owned by this user."),
9172
9213
  orgId: z2.string().optional().describe("List mode: scope to records owned by this org."),
9173
9214
  clientId: z2.string().optional().describe("List mode: scope to records associated with this client."),
9215
+ scope: z2.string().optional().describe(
9216
+ '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.'
9217
+ ),
9218
+ 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."),
9174
9219
  limit: z2.number().int().min(1).max(MCP_MAX_LIMIT2).optional().describe(
9175
- `Max records to return. MCP-specific cap of ${MCP_MAX_LIMIT2} (vs API max of 100) to protect the agent context window. Default 3.`
9220
+ `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.`
9176
9221
  )
9177
9222
  };
9178
9223
  var recordQuery = ({ client, log }) => ({
9179
9224
  name: "record_query",
9180
9225
  title: "Record query (list or lookup)",
9181
- 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.",
9226
+ 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.",
9182
9227
  inputSchema: inputSchema2,
9183
9228
  handler: async (args) => {
9184
9229
  const limit = args.limit ?? MCP_DEFAULT_LIMIT2;
@@ -9188,8 +9233,9 @@ var recordQuery = ({ client, log }) => ({
9188
9233
  const from = args.from;
9189
9234
  const to = args.to;
9190
9235
  const prefix = args.prefix;
9236
+ const startFrom = args.startFrom;
9191
9237
  try {
9192
- let records;
9238
+ let page;
9193
9239
  if (field) {
9194
9240
  const hasEquality = value !== void 0;
9195
9241
  const hasRange = from !== void 0 || to !== void 0;
@@ -9212,7 +9258,7 @@ var recordQuery = ({ client, log }) => ({
9212
9258
  if (hasRange && !(from !== void 0 && to !== void 0)) {
9213
9259
  return toolError("record_query", new Error("range lookup requires both 'from' and 'to'."));
9214
9260
  }
9215
- const page = await client.records.lookupRecordsByBody({
9261
+ page = await client.records.lookupRecordsByBody({
9216
9262
  type,
9217
9263
  field,
9218
9264
  value,
@@ -9220,29 +9266,32 @@ var recordQuery = ({ client, log }) => ({
9220
9266
  to,
9221
9267
  prefix,
9222
9268
  order: args.order,
9269
+ startFrom,
9223
9270
  limit
9224
9271
  });
9225
- records = pageItems(page);
9226
9272
  log.debug(
9227
- { tool: "record_query", mode: "lookup", type, field, returned: records.length },
9273
+ { tool: "record_query", mode: "lookup", type, field, returned: pageItems(page).length },
9228
9274
  "record_query lookup ok"
9229
9275
  );
9230
9276
  } else {
9231
- const page = await client.records.listRecords({
9277
+ page = await client.records.listRecords({
9232
9278
  type,
9233
9279
  userId: args.userId,
9234
9280
  orgId: args.orgId,
9235
9281
  clientId: args.clientId,
9282
+ scope: args.scope,
9283
+ startFrom,
9236
9284
  limit
9237
9285
  });
9238
- records = pageItems(page);
9239
9286
  log.debug(
9240
- { tool: "record_query", mode: "list", type, limit, returned: records.length },
9287
+ { tool: "record_query", mode: "list", type, limit, returned: pageItems(page).length },
9241
9288
  "record_query list ok"
9242
9289
  );
9243
9290
  }
9291
+ const records = pageItems(page);
9292
+ const nextCursor = page.nextCursor ?? null;
9244
9293
  return {
9245
- content: [{ type: "text", text: JSON.stringify(records, null, 2) }]
9294
+ content: [{ type: "text", text: JSON.stringify({ data: records, nextCursor }, null, 2) }]
9246
9295
  };
9247
9296
  } catch (err) {
9248
9297
  log.warn({ tool: "record_query", err: String(err) }, "record_query failed");
@@ -9365,7 +9414,10 @@ var inputSchema4 = {
9365
9414
  folderId: z4.string().optional().describe("Group this record into a folder."),
9366
9415
  userId: z4.string().optional().describe("Owning user id."),
9367
9416
  orgId: z4.string().optional().describe("Owning organization id."),
9368
- clientId: z4.string().optional().describe("Associated client id.")
9417
+ clientId: z4.string().optional().describe("Associated client id."),
9418
+ scopes: z4.array(z4.string()).optional().describe(
9419
+ '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.'
9420
+ )
9369
9421
  };
9370
9422
  var recordCreate = ({ client, log }) => ({
9371
9423
  name: "record_create",
@@ -9385,7 +9437,8 @@ var recordCreate = ({ client, log }) => ({
9385
9437
  folderId: args.folderId,
9386
9438
  userId: args.userId,
9387
9439
  orgId: args.orgId,
9388
- clientId: args.clientId
9440
+ clientId: args.clientId,
9441
+ scopes: args.scopes
9389
9442
  }
9390
9443
  });
9391
9444
  log.debug({ tool: "record_create", type, id: created.id }, "record_create ok");
@@ -9470,24 +9523,29 @@ var record_delete_default = recordDelete;
9470
9523
  // src/tools/rag_ask.ts
9471
9524
  import { z as z7 } from "zod";
9472
9525
  var SEARCH_MCP_DEFAULT_LIMIT = 5;
9473
- var SEARCH_MCP_MAX_LIMIT = 10;
9526
+ var SEARCH_MCP_MAX_LIMIT = 50;
9474
9527
  var inputSchema7 = {
9475
9528
  query: z7.string().min(1, "query must be non-empty").describe("Question to ground against retrieved content."),
9476
9529
  instructions: z7.string().optional().describe(
9477
9530
  '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.'
9478
9531
  ),
9479
9532
  model: z7.string().optional().describe(
9480
- "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."
9533
+ "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."
9481
9534
  ),
9482
9535
  search: z7.object({
9483
9536
  mode: z7.enum(["HYBRID", "TEXT", "SEMANTIC"]).optional().describe("Retrieval mode. Default HYBRID."),
9484
- 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}.`),
9537
+ limit: z7.number().int().min(1).max(SEARCH_MCP_MAX_LIMIT).optional().describe(
9538
+ `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.`
9539
+ ),
9485
9540
  // Retrieval scoping — parity with hybrid_search so an agent can ground the
9486
9541
  // answer on a specific owner / folder / type / metadata subset, not just the
9487
9542
  // whole-tenant corpus.
9488
9543
  userId: z7.string().optional().describe("Restrict retrieval to content owned by this user (Vectros UUID)."),
9489
9544
  orgId: z7.string().optional().describe("Restrict retrieval to content owned by this org (Vectros UUID)."),
9490
9545
  clientId: z7.string().optional().describe("Restrict retrieval to content tagged with this client (Vectros UUID)."),
9546
+ scope: z7.string().optional().describe(
9547
+ '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.'
9548
+ ),
9491
9549
  folderId: z7.string().optional().describe("Restrict retrieval to this exact folder."),
9492
9550
  rootFolderId: z7.string().optional().describe("Restrict retrieval to this folder and all its descendants."),
9493
9551
  typeName: z7.string().optional().describe("Restrict record retrieval to this schema type."),
@@ -9505,7 +9563,7 @@ var inputSchema7 = {
9505
9563
  var ragAsk = ({ client, log }) => ({
9506
9564
  name: "rag_ask",
9507
9565
  title: "RAG ask (corpus-wide)",
9508
- 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.",
9566
+ 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.",
9509
9567
  inputSchema: inputSchema7,
9510
9568
  handler: async (args, extra) => {
9511
9569
  try {
@@ -9516,6 +9574,7 @@ var ragAsk = ({ client, log }) => ({
9516
9574
  userId: searchArg.userId,
9517
9575
  orgId: searchArg.orgId,
9518
9576
  clientId: searchArg.clientId,
9577
+ scope: searchArg.scope,
9519
9578
  folderId: searchArg.folderId,
9520
9579
  rootFolderId: searchArg.rootFolderId,
9521
9580
  typeName: searchArg.typeName,
@@ -9673,7 +9732,7 @@ var inputSchema9 = {
9673
9732
  var listSchemas = ({ client, log }) => ({
9674
9733
  name: "list_schemas",
9675
9734
  title: "List record schemas",
9676
- 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.',
9735
+ 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.',
9677
9736
  inputSchema: inputSchema9,
9678
9737
  handler: async (args) => {
9679
9738
  const userId = args.userId;
@@ -9727,7 +9786,7 @@ function isNotAvailable(err) {
9727
9786
  var documentGet = ({ client, log }) => ({
9728
9787
  name: "document_get",
9729
9788
  title: "Get document metadata (and optionally text)",
9730
- description: "Fetch metadata for a single document \u2014 select it by its Vectros `documentId` OR by your own `externalId` + `type` (resolved for you). Pass `includeText: true` to also fetch the text body (capped at ~8K tokens; `truncated: true` flag if cut). If the document was ingested without `storeText: true`, text is unavailable \u2014 `textAvailable: false` flag is set and the agent should use `document_ask` for question-driven extraction instead. Pass `includeDownloadUrl: true` to also get a short-lived presigned `downloadUrl` for the original file (file-backed documents only). Returns the full document metadata, including its lifecycle `status` (ACTIVE, or ARCHIVED when soft-retracted from search \u2014 restorable via document_update) and its processing `indexStatus`.",
9789
+ 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`.",
9731
9790
  inputSchema: inputSchema10,
9732
9791
  handler: async (args) => {
9733
9792
  const documentId = args.documentId;
@@ -9751,7 +9810,7 @@ var documentGet = ({ client, log }) => ({
9751
9810
  if (isNotAvailable(textErr)) {
9752
9811
  log.debug(
9753
9812
  { tool: "document_get", id },
9754
- "document_get text unavailable (ingested without storeText)"
9813
+ "document_get text unavailable (not retained \u2014 storeText:false upload)"
9755
9814
  );
9756
9815
  extra = { ...extra, textAvailable: false };
9757
9816
  } else {
@@ -9795,7 +9854,7 @@ var document_get_default = documentGet;
9795
9854
  // src/tools/document_query.ts
9796
9855
  import { z as z11 } from "zod";
9797
9856
  var MCP_DEFAULT_LIMIT3 = 3;
9798
- var MCP_MAX_LIMIT3 = 10;
9857
+ var MCP_MAX_LIMIT3 = 100;
9799
9858
  var inputSchema11 = {
9800
9859
  // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9801
9860
  field: z11.string().optional().describe("Lookup mode: name of the lookup-indexed field to query by."),
@@ -9812,14 +9871,18 @@ var inputSchema11 = {
9812
9871
  userId: z11.string().optional().describe("List mode: scope to documents owned by this user."),
9813
9872
  orgId: z11.string().optional().describe("List mode: scope to documents belonging to this org."),
9814
9873
  clientId: z11.string().optional().describe("List mode: scope to documents associated with this client."),
9874
+ scope: z11.string().optional().describe(
9875
+ '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.'
9876
+ ),
9877
+ 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."),
9815
9878
  limit: z11.number().int().min(1).max(MCP_MAX_LIMIT3).optional().describe(
9816
- `Max documents to return. MCP-specific cap of ${MCP_MAX_LIMIT3} (vs API max of 100) to protect the agent context window. Default 3.`
9879
+ `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.`
9817
9880
  )
9818
9881
  };
9819
9882
  var documentQuery = ({ client, log }) => ({
9820
9883
  name: "document_query",
9821
9884
  title: "Document query (list or lookup)",
9822
- 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.",
9885
+ 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.",
9823
9886
  inputSchema: inputSchema11,
9824
9887
  handler: async (args) => {
9825
9888
  const limit = args.limit ?? MCP_DEFAULT_LIMIT3;
@@ -9829,8 +9892,9 @@ var documentQuery = ({ client, log }) => ({
9829
9892
  const to = args.to;
9830
9893
  const prefix = args.prefix;
9831
9894
  const type = args.type;
9895
+ const startFrom = args.startFrom;
9832
9896
  try {
9833
- let documents;
9897
+ let page;
9834
9898
  if (field) {
9835
9899
  const hasEquality = value !== void 0;
9836
9900
  const hasRange = from !== void 0 || to !== void 0;
@@ -9859,7 +9923,7 @@ var documentQuery = ({ client, log }) => ({
9859
9923
  new Error("lookup requires 'type' (the document's bound schema type) alongside 'field'.")
9860
9924
  );
9861
9925
  }
9862
- const page = await client.documents.lookupDocumentsByBody({
9926
+ page = await client.documents.lookupDocumentsByBody({
9863
9927
  type,
9864
9928
  field,
9865
9929
  value,
@@ -9867,28 +9931,31 @@ var documentQuery = ({ client, log }) => ({
9867
9931
  to,
9868
9932
  prefix,
9869
9933
  order: args.order,
9934
+ startFrom,
9870
9935
  limit
9871
9936
  });
9872
- documents = pageItems(page);
9873
9937
  log.debug(
9874
- { tool: "document_query", mode: "lookup", type, field, returned: documents.length },
9938
+ { tool: "document_query", mode: "lookup", type, field, returned: pageItems(page).length },
9875
9939
  "document_query lookup ok"
9876
9940
  );
9877
9941
  } else {
9878
- const page = await client.documents.listDocuments({
9942
+ page = await client.documents.listDocuments({
9879
9943
  folderId: args.folderId,
9880
9944
  userId: args.userId,
9881
9945
  orgId: args.orgId,
9882
9946
  clientId: args.clientId,
9947
+ scope: args.scope,
9948
+ startFrom,
9883
9949
  limit
9884
9950
  });
9885
- documents = pageItems(page);
9886
9951
  log.debug(
9887
- { tool: "document_query", mode: "list", limit, returned: documents.length },
9952
+ { tool: "document_query", mode: "list", limit, returned: pageItems(page).length },
9888
9953
  "document_query list ok"
9889
9954
  );
9890
9955
  }
9891
- return { content: [{ type: "text", text: JSON.stringify(documents, null, 2) }] };
9956
+ const documents = pageItems(page);
9957
+ const nextCursor = page.nextCursor ?? null;
9958
+ return { content: [{ type: "text", text: JSON.stringify({ data: documents, nextCursor }, null, 2) }] };
9892
9959
  } catch (err) {
9893
9960
  log.warn({ tool: "document_query", err: String(err) }, "document_query failed");
9894
9961
  return toolError("document_query", err);
@@ -9913,7 +9980,6 @@ var inputSchema12 = {
9913
9980
  "New raw text body. Supplying it re-ingests the document body and re-queues it for indexing, so the search index reflects the edit (text-ingested documents only \u2014 not file-backed ones). Omit to leave the stored text unchanged. This is the in-place alternative to re-ingesting with upsert:true."
9914
9981
  ),
9915
9982
  folderId: z12.string().optional().describe("Move the document into this folder (cannot be cleared once set)."),
9916
- storeText: z12.boolean().optional().describe("Whether the raw text is stored + retrievable."),
9917
9983
  status: z12.enum(["ACTIVE", "ARCHIVED"]).optional().describe(
9918
9984
  "Lifecycle status. `ARCHIVED` soft-retracts the document \u2014 it is pulled from search and recall but kept and recoverable; set it back to `ACTIVE` to re-index and restore it. The way to retire superseded content when the credential has no delete authority. Omit to leave unchanged."
9919
9985
  ),
@@ -9937,7 +10003,6 @@ var documentUpdate = ({ client, log }) => ({
9937
10003
  const fields = args.fields;
9938
10004
  const text = args.text;
9939
10005
  const folderId = args.folderId;
9940
- const storeText = args.storeText;
9941
10006
  const status = args.status;
9942
10007
  const userId = args.userId;
9943
10008
  const orgId = args.orgId;
@@ -9951,7 +10016,6 @@ var documentUpdate = ({ client, log }) => ({
9951
10016
  if (fields !== void 0) body.payload = fields;
9952
10017
  if (text !== void 0) body.text = text;
9953
10018
  if (folderId !== void 0) body.folderId = folderId;
9954
- if (storeText !== void 0) body.storeText = storeText;
9955
10019
  if (status !== void 0) body.status = status;
9956
10020
  if (userId !== void 0) body.userId = userId;
9957
10021
  if (orgId !== void 0) body.orgId = orgId;
@@ -10000,7 +10064,7 @@ var document_delete_default = documentDelete;
10000
10064
  // src/tools/folder_query.ts
10001
10065
  import { z as z14 } from "zod";
10002
10066
  var MCP_DEFAULT_LIMIT4 = 10;
10003
- var MCP_MAX_LIMIT4 = 50;
10067
+ var MCP_MAX_LIMIT4 = 100;
10004
10068
  var inputSchema14 = {
10005
10069
  id: z14.string().optional().describe("Get mode: fetch this single folder by id."),
10006
10070
  parentId: z14.string().optional().describe("List mode: direct children of this folder (tree navigation). Omit for a flat tenant list."),
@@ -10008,12 +10072,14 @@ var inputSchema14 = {
10008
10072
  orgId: z14.string().optional().describe("List mode: scope to folders belonging to this org."),
10009
10073
  clientId: z14.string().optional().describe("List mode: scope to folders associated with this client."),
10010
10074
  startFrom: z14.string().optional().describe("List mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10011
- 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.`)
10075
+ limit: z14.number().int().min(1).max(MCP_MAX_LIMIT4).optional().describe(
10076
+ `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\`.`
10077
+ )
10012
10078
  };
10013
10079
  var folderQuery = ({ client, log }) => ({
10014
10080
  name: "folder_query",
10015
10081
  title: "Folder query (list or get)",
10016
- 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.",
10082
+ 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.",
10017
10083
  inputSchema: inputSchema14,
10018
10084
  handler: async (args) => {
10019
10085
  const id = args.id;
@@ -10053,7 +10119,10 @@ var inputSchema15 = {
10053
10119
  slug: z15.string().optional().describe("Optional stable slug (sibling-unique; idempotency key). Lowercase letters/digits/hyphens. Derived from name when omitted."),
10054
10120
  userId: z15.string().optional().describe("Owning user (Vectros UUID)."),
10055
10121
  orgId: z15.string().optional().describe("Owning org (Vectros UUID)."),
10056
- clientId: z15.string().optional().describe("Associated client (Vectros UUID).")
10122
+ clientId: z15.string().optional().describe("Associated client (Vectros UUID)."),
10123
+ scopes: z15.array(z15.string()).optional().describe(
10124
+ '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.'
10125
+ )
10057
10126
  };
10058
10127
  var folderCreate = ({ client, log }) => ({
10059
10128
  name: "folder_create",
@@ -10070,7 +10139,8 @@ var folderCreate = ({ client, log }) => ({
10070
10139
  slug: args.slug,
10071
10140
  userId: args.userId,
10072
10141
  orgId: args.orgId,
10073
- clientId: args.clientId
10142
+ clientId: args.clientId,
10143
+ scopes: args.scopes
10074
10144
  }
10075
10145
  });
10076
10146
  log.debug({ tool: "folder_create", id: folder.id, name: folder.name }, "folder_create ok");
@@ -10271,7 +10341,7 @@ var inputSchema19 = {
10271
10341
  // Inline text mode (mutually exclusive with filePath):
10272
10342
  text: z18.string().min(1).optional().describe("Text body to ingest inline. Mutually exclusive with filePath. Either text or filePath is required."),
10273
10343
  storeText: z18.boolean().optional().describe(
10274
- "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."
10344
+ "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."
10275
10345
  ),
10276
10346
  // File upload mode (mutually exclusive with text; stdio-transport-only):
10277
10347
  filePath: z18.string().min(1).optional().describe(
@@ -10282,12 +10352,15 @@ var inputSchema19 = {
10282
10352
  folderId: z18.string().optional().describe("Optional folder to ingest into. Default: tenant root."),
10283
10353
  userId: z18.string().optional().describe("Owning user ID. Optional."),
10284
10354
  orgId: z18.string().optional().describe("Owning org ID. Optional."),
10285
- clientId: z18.string().optional().describe("Owning client ID. Optional.")
10355
+ clientId: z18.string().optional().describe("Owning client ID. Optional."),
10356
+ scopes: z18.array(z18.string()).optional().describe(
10357
+ "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."
10358
+ )
10286
10359
  };
10287
10360
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10288
10361
  name: "document_ingest",
10289
10362
  title: "Ingest a new document (text body or file upload)",
10290
- description: "Create a new document in your Vectros tenant. Two modes:\n \u2022 Text mode: pass `text` (string body). Single-call. Use for crawled content, notes, generated text.\n \u2022 File mode: pass `filePath` (local path on the MCP server's host machine). STDIO-TRANSPORT ONLY \u2014 rejected on HTTP transport. MCP server reads the bytes, requests a presigned upload URL, and PUTs them. Returns when the upload is accepted (indexStatus: PENDING_INDEX); poll with `document_get` until indexStatus is INDEXED.\nEither `text` or `filePath` must be present; both is an error. Idempotent by `externalId` (re-ingest returns the existing document, not a duplicate). To UPDATE an existing document instead \u2014 re-index an edited body \u2014 pass `upsert:true` with the same `externalId` (and the same `schemaId`); this is the re-sync primitive for keeping a knowledge base current. Pass `schemaId` + `payload` for a typed, lookup-queryable document (records parity). indexMode defaults to HYBRID for untyped documents (omit to inherit a bound schema's default). storeText defaults to true (set false to skip storing the text body).",
10363
+ 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.",
10291
10364
  inputSchema: inputSchema19,
10292
10365
  handler: async (args) => {
10293
10366
  const title = args.title;
@@ -10316,6 +10389,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10316
10389
  )
10317
10390
  );
10318
10391
  }
10392
+ if (filePath && args.scopes !== void 0) {
10393
+ return toolError(
10394
+ "document_ingest",
10395
+ new Error(
10396
+ "`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)."
10397
+ )
10398
+ );
10399
+ }
10319
10400
  const common = {
10320
10401
  folderId: args.folderId,
10321
10402
  payload: args.payload,
@@ -10327,14 +10408,15 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10327
10408
  };
10328
10409
  try {
10329
10410
  if (text) {
10330
- const storeText = args.storeText ?? true;
10331
10411
  const result = await client.documents.ingestDocument({
10332
10412
  upsert,
10333
10413
  body: {
10334
10414
  title,
10335
10415
  text,
10336
10416
  indexMode,
10337
- storeText,
10417
+ // Scope ownership is a TEXT-mode contract only (the upload-init
10418
+ // endpoint has no scopes field — see the gate above).
10419
+ scopes: args.scopes,
10338
10420
  ...common
10339
10421
  }
10340
10422
  });
@@ -10365,11 +10447,13 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10365
10447
  }
10366
10448
  const fileName = basename(safePath);
10367
10449
  const fileType = args.fileType ?? inferMimeType(safePath);
10450
+ const storeTextFile = args.storeText ?? true;
10368
10451
  const upload = await client.documents.uploadDocument({
10369
10452
  upsert,
10370
10453
  fileName,
10371
10454
  fileType,
10372
10455
  indexMode,
10456
+ storeText: storeTextFile,
10373
10457
  ...common
10374
10458
  });
10375
10459
  const uploadUrl = upload?.uploadUrl;
@@ -10425,7 +10509,7 @@ var document_ingest_default = documentIngest;
10425
10509
  // src/tools/lookup_principal.ts
10426
10510
  import { z as z19 } from "zod";
10427
10511
  var MCP_DEFAULT_LIMIT5 = 10;
10428
- var MCP_MAX_LIMIT5 = 50;
10512
+ var MCP_MAX_LIMIT5 = 100;
10429
10513
  var inputSchema20 = {
10430
10514
  kind: z19.enum(["user", "org", "client"]).describe("Which principal to look up: a user, an organization, or a client."),
10431
10515
  externalId: z19.string().optional().describe(
@@ -10439,12 +10523,15 @@ var inputSchema20 = {
10439
10523
  to: z19.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
10440
10524
  prefix: z19.string().optional().describe("Lookup mode (prefix): match values starting with this. Range-enabled string fields only."),
10441
10525
  order: z19.enum(["asc", "desc"]).optional().describe("Lookup mode: sort direction by the looked-up field. `asc` (default) or `desc`."),
10442
- 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}.`)
10526
+ startFrom: z19.string().optional().describe("Lookup mode: pagination cursor \u2014 pass the `nextCursor` from the previous page to fetch the next."),
10527
+ limit: z19.number().int().min(1).max(MCP_MAX_LIMIT5).optional().describe(
10528
+ `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\`.`
10529
+ )
10443
10530
  };
10444
10531
  var lookupPrincipal = ({ client, log }) => ({
10445
10532
  name: "lookup_principal",
10446
10533
  title: "Look up a user / org / client identity",
10447
- 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.",
10534
+ 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.",
10448
10535
  inputSchema: inputSchema20,
10449
10536
  handler: async (args) => {
10450
10537
  const kind = args.kind;
@@ -10457,6 +10544,7 @@ var lookupPrincipal = ({ client, log }) => ({
10457
10544
  const to = args.to;
10458
10545
  const prefix = args.prefix;
10459
10546
  const order = args.order;
10547
+ const startFrom = args.startFrom;
10460
10548
  try {
10461
10549
  let page;
10462
10550
  if (externalId !== void 0) {
@@ -10494,7 +10582,7 @@ var lookupPrincipal = ({ client, log }) => ({
10494
10582
  new Error("lookup requires 'type' (the identity schema's record type) alongside 'field'.")
10495
10583
  );
10496
10584
  }
10497
- const req = { type, field, value, from, to, prefix, order, limit };
10585
+ const req = { type, field, value, from, to, prefix, order, startFrom, limit };
10498
10586
  if (kind === "user") {
10499
10587
  page = await client.identity.lookupUsers(req);
10500
10588
  } else if (kind === "org") {
@@ -10510,7 +10598,8 @@ var lookupPrincipal = ({ client, log }) => ({
10510
10598
  );
10511
10599
  }
10512
10600
  const principals = pageItems(page);
10513
- return { content: [{ type: "text", text: JSON.stringify(principals, null, 2) }] };
10601
+ const nextCursor = page.nextCursor ?? null;
10602
+ return { content: [{ type: "text", text: JSON.stringify({ data: principals, nextCursor }, null, 2) }] };
10514
10603
  } catch (err) {
10515
10604
  log.warn({ tool: "lookup_principal", kind, err: String(err) }, "lookup_principal failed");
10516
10605
  return toolError("lookup_principal", err);