@vectros-ai/mcp-server 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -3,6 +3,62 @@
3
3
  All notable changes to `@vectros-ai/mcp-server` are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.11.0
7
+
8
+ Refreshed to the 0.36.0 Vectros API, with clearer, more actionable errors and a
9
+ few tool-surface corrections.
10
+
11
+ ### Changed
12
+
13
+ - **Tool errors now carry the server's real reason, a typed code, and a request
14
+ id.** When a tool call fails, the result surfaces the API's own message, the
15
+ machine-readable `errorCode` (so an agent can branch on the cause — e.g.
16
+ `INSUFFICIENT_BALANCE` "top up your balance" versus `SUBSCRIPTION_LIMIT_EXCEEDED`
17
+ "upgrade your plan" on a payment-required failure, or a placement/authorization
18
+ denial), and the `requestId` to quote to support — instead of a bare HTTP status
19
+ and a generic class name.
20
+ - **`document_ingest` rejects `storeText` in text mode instead of ignoring it.**
21
+ `storeText` only applies to file uploads (it controls whether a file's extracted
22
+ text is retained). A text-ingested body is the document itself and is always
23
+ retained, so passing `storeText` alongside `text` is now refused with a clear
24
+ message rather than silently having no effect.
25
+
26
+ ### Added
27
+
28
+ - **Errors surface the 0.36.0 typed error codes.** Payment-required failures on
29
+ `rag_ask` / `document_ask` now distinguish an exhausted pre-paid balance from a
30
+ reached plan limit; a number outside the signed 64-bit range is reported as a
31
+ `400` naming the field.
32
+ - **Guidance for search-indexing failures.** `document_get` and `record_get`
33
+ descriptions explain the `indexFailure.code` that accompanies a `FAILED`
34
+ `indexStatus`, so an agent can tell content that is still partly findable
35
+ (e.g. `VECTOR_LIMIT_EXCEEDED`, keyword search still serves it) from content that
36
+ is not findable at all (`INDEXING_FAILED`).
37
+ - **Large-number guidance.** `record_create` and `document_ingest` note that every
38
+ number must fall within the signed 64-bit range and that larger whole numbers
39
+ (e.g. big external ids) should be sent as strings, which store exactly and support
40
+ exact-match lookup.
41
+
42
+ ### Fixed
43
+
44
+ - **`current_identity` / the `identity` resource describe the identity shape they
45
+ actually return.** The obsolete "the backend is still rolling this out" caveat is
46
+ gone; the extended shape (`principalType`, `principalKeyId`, `allowedActions`, and
47
+ a `dataScope` of `{ userId, scopes[] }`) is returned today.
48
+ - **`hybrid_search` gives accurate keyword-leg guidance.** When the keyword leg
49
+ matches nothing (`textLegEmpty`), the description now explains that the default
50
+ phrase match found no contiguous hit for a long query and points to shortening the
51
+ query or setting `textMode` to `AND` (precision) or `OR` (recall), rather than
52
+ prescribing a single fix.
53
+ - **`record_create` and `folder_create` describe the scope needed to receive an
54
+ existing item on a collision, not just to create one.** Both tools are
55
+ idempotent by key (`externalId` for records, `slug` for folders); the
56
+ description previously said only `records:c`/`folders:c` was required, but
57
+ being handed back the existing item on a collision is a read of that item
58
+ and separately requires `records:r`/`folders:r`. A key holding only the
59
+ create scope now gets an accurate description of the "already exists"
60
+ error it will see instead of the record.
61
+
6
62
  ## 0.10.0
7
63
 
8
64
  ### Changed (breaking)
package/README.md CHANGED
@@ -213,7 +213,7 @@ available.
213
213
 
214
214
  | Tool | What it does |
215
215
  |---|---|
216
- | `current_identity` | Describe the credential: tenantId, environment, principalType, and (where surfaced) allowedActions + dataScope. |
216
+ | `current_identity` | Describe the credential: tenantId, environment, principalType, principalKeyId, and (for scoped credentials) allowedActions + dataScope. |
217
217
  | `lookup_principal` | Resolve a user / org / client by your own `externalId` (→ its Vectros UUID, for the ownership filters) or by a schema lookup field. Read-only. |
218
218
  | `version_history` | Read the audit/version trail (CREATE/UPDATE/DELETE, with actor + diff) for one record or document. Read-only. |
219
219
 
package/dist/cli-http.js CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/server.ts
27
27
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
28
28
 
29
- // node_modules/@vectros-ai/sdk/dist/index.mjs
29
+ // ../../node_modules/@vectros-ai/sdk/dist/index.mjs
30
30
  var __defProp2 = Object.defineProperty;
31
31
  var __export = (target, all) => {
32
32
  for (var name in all)
@@ -61,6 +61,7 @@ __export(api_exports, {
61
61
  FileUploadRequest: () => FileUploadRequest,
62
62
  ForbiddenError: () => ForbiddenError,
63
63
  IdentityLookupRequest: () => IdentityLookupRequest,
64
+ IndexFailure: () => IndexFailure,
64
65
  InternalServerError: () => InternalServerError,
65
66
  ListEntitiesRequestOrder: () => ListEntitiesRequestOrder,
66
67
  ListUsersRequestOrder: () => ListUsersRequestOrder,
@@ -582,6 +583,17 @@ var IdentityLookupRequest;
582
583
  Desc: "desc"
583
584
  };
584
585
  })(IdentityLookupRequest || (IdentityLookupRequest = {}));
586
+ var IndexFailure;
587
+ ((IndexFailure2) => {
588
+ IndexFailure2.Code = {
589
+ SourceUnavailable: "SOURCE_UNAVAILABLE",
590
+ TextIndexFailed: "TEXT_INDEX_FAILED",
591
+ EmbeddingFailed: "EMBEDDING_FAILED",
592
+ IndexingFailed: "INDEXING_FAILED",
593
+ VectorLimitExceeded: "VECTOR_LIMIT_EXCEEDED",
594
+ Internal: "INTERNAL"
595
+ };
596
+ })(IndexFailure || (IndexFailure = {}));
585
597
  var ModelDataVersionResponse;
586
598
  ((ModelDataVersionResponse2) => {
587
599
  ModelDataVersionResponse2.ChangeType = {
@@ -4098,7 +4110,7 @@ var AuthClient = class {
4098
4110
  );
4099
4111
  }
4100
4112
  /**
4101
- * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user, organization, or client. Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
4113
+ * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user or identity entity (in any namespace). Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
4102
4114
  *
4103
4115
  * @param {Vectros.TokenRequest} request
4104
4116
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4315,7 +4327,7 @@ var ComplianceClient = class {
4315
4327
  this._options = normalizeClientOptionsWithAuth(options);
4316
4328
  }
4317
4329
  /**
4318
- * Submits a right-to-erasure request for a single end-subject (a user, client, or organization). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
4330
+ * Submits a right-to-erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
4319
4331
  *
4320
4332
  * @param {Vectros.ErasureRequest} request
4321
4333
  * @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8611,8 +8623,8 @@ function warnOnSuboptimalKey(key, log) {
8611
8623
 
8612
8624
  // src/build-info.ts
8613
8625
  var BUILD_INFO = {
8614
- mcpServer: true ? "0.10.0" : "dev",
8615
- sdk: true ? "0.35.0" : "dev"
8626
+ mcpServer: true ? "0.11.0" : "dev",
8627
+ sdk: true ? "0.36.0" : "dev"
8616
8628
  };
8617
8629
 
8618
8630
  // src/log.ts
@@ -8816,31 +8828,45 @@ async function consumeStream(stream, onProgress) {
8816
8828
  }
8817
8829
 
8818
8830
  // src/tools/errors.ts
8831
+ function asErrorBody(body) {
8832
+ if (body && typeof body === "object") return body;
8833
+ return void 0;
8834
+ }
8819
8835
  function toolError(toolName, err, docPointer) {
8820
8836
  let summary = `${toolName} failed`;
8821
8837
  let detail = String(err);
8838
+ let errorCode;
8839
+ let requestId;
8822
8840
  if (err instanceof StreamError) {
8823
8841
  summary = `${toolName} stream error`;
8824
8842
  detail = err.message;
8825
8843
  } else if (err instanceof Error) {
8826
8844
  const e = err;
8827
8845
  detail = e.message;
8846
+ const body = asErrorBody(e.body);
8847
+ if (body) {
8848
+ if (typeof body.message === "string" && body.message.trim().length > 0) detail = body.message;
8849
+ if (typeof body.errorCode === "string" && body.errorCode.length > 0) errorCode = body.errorCode;
8850
+ if (typeof body.requestId === "string" && body.requestId.length > 0) requestId = body.requestId;
8851
+ }
8828
8852
  if (e.statusCode) {
8829
- summary = `${toolName} failed: HTTP ${e.statusCode}`;
8853
+ summary = `${toolName} failed: HTTP ${e.statusCode}${errorCode ? ` (${errorCode})` : ""}`;
8830
8854
  } else if (e.name && e.name !== "Error") {
8831
8855
  summary = `${toolName} failed: ${e.name}`;
8832
8856
  }
8833
8857
  }
8834
- const pointer = docPointer ? `
8858
+ const trailer = (errorCode ? `
8859
+ errorCode: ${errorCode}` : "") + (requestId ? `
8860
+ requestId: ${requestId}` : "") + (docPointer ? `
8835
8861
 
8836
- See: ${docPointer}` : "";
8862
+ See: ${docPointer}` : "");
8837
8863
  return {
8838
8864
  isError: true,
8839
8865
  content: [
8840
8866
  {
8841
8867
  type: "text",
8842
8868
  text: `${summary}
8843
- ${detail}${pointer}`
8869
+ ${detail}${trailer}`
8844
8870
  }
8845
8871
  ]
8846
8872
  };
@@ -8917,7 +8943,7 @@ var inputSchema = {
8917
8943
  var hybridSearch = ({ client, log }) => ({
8918
8944
  name: "hybrid_search",
8919
8945
  title: "Hybrid search",
8920
- 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/scope), 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\".",
8946
+ 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/scope), 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 (BM25) leg matched nothing (all textScores 0) \u2014 typically the default PHRASE mode finding no contiguous match for a long natural-language query. Loosen the keyword leg: shorten the query to its key terms, or set textMode "AND" (all terms, higher precision) or "OR" (any term, broad recall).',
8921
8947
  inputSchema,
8922
8948
  handler: async (args) => {
8923
8949
  const limit = args.limit ?? MCP_DEFAULT_LIMIT;
@@ -9169,7 +9195,7 @@ var inputSchema3 = {
9169
9195
  var recordGet = ({ client, log }) => ({
9170
9196
  name: "record_get",
9171
9197
  title: "Get a record by id or externalId",
9172
- description: "Fetch a single structured record, including the full payload. Select it EITHER by its Vectros `id` (e.g. from record_query / hybrid_search) OR by your own `externalId` + `type` \u2014 the same key you passed to record_create, resolved for you (no manual record_query lookup needed). Very large payloads are truncated to protect the agent context window.",
9198
+ description: "Fetch a single structured record, including the full payload. Select it EITHER by its Vectros `id` (e.g. from record_query / hybrid_search) OR by your own `externalId` + `type` \u2014 the same key you passed to record_create, resolved for you (no manual record_query lookup needed). Very large payloads are truncated to protect the agent context window. The response carries the record's processing `indexStatus`; when it is FAILED an `indexFailure.code` explains whether the content is still searchable (e.g. VECTOR_LIMIT_EXCEEDED keeps keyword search vs INDEXING_FAILED = not findable at all) \u2014 branch on the `code`, not the message.",
9173
9199
  inputSchema: inputSchema3,
9174
9200
  handler: async (args) => {
9175
9201
  const id = args.id;
@@ -9207,7 +9233,7 @@ var import_zod4 = require("zod");
9207
9233
  var inputSchema4 = {
9208
9234
  type: import_zod4.z.string().min(1, "type is required").describe('Record type / schema name (e.g. "task"). Must match an existing schema \u2014 see list_schemas.'),
9209
9235
  fields: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).describe(
9210
- "The record payload \u2014 a JSON object of field\u2192value, validated against the type schema. Call list_schemas to learn required fields, types, and enum values."
9236
+ "The record payload \u2014 a JSON object of field\u2192value, validated against the type schema. Call list_schemas to learn required fields, types, and enum values. Every number must fall within the signed 64-bit range (a value outside it, or with >38 significant digits, is refused with a 400 naming the field) \u2014 send a larger whole number (e.g. a big external id) as a STRING, which stores it exactly and supports exact-match lookup."
9211
9237
  ),
9212
9238
  externalId: import_zod4.z.string().optional().describe(
9213
9239
  "Stable caller-supplied id. Immutable; unique per type. Re-creating with the same externalId returns the existing record (idempotent) \u2014 use it to make creates safely retryable."
@@ -9225,7 +9251,7 @@ var inputSchema4 = {
9225
9251
  var recordCreate = ({ client, log }) => ({
9226
9252
  name: "record_create",
9227
9253
  title: "Create a record",
9228
- description: "Create a structured record of a given type. Provide `type` and `fields` (the payload, validated against the type schema \u2014 call list_schemas first to learn required fields and enums). Idempotent by `externalId`. Requires the key to allow records:c.",
9254
+ description: "Create a structured record of a given type. Provide `type` and `fields` (the payload, validated against the type schema \u2014 call list_schemas first to learn required fields and enums). Idempotent by `externalId`. Requires the key to allow records:c (and records:r to receive the existing record on a collision).",
9229
9255
  inputSchema: inputSchema4,
9230
9256
  handler: async (args) => {
9231
9257
  const type = args.type;
@@ -9583,7 +9609,7 @@ function isNotAvailable(err) {
9583
9609
  var documentGet = ({ client, log }) => ({
9584
9610
  name: "document_get",
9585
9611
  title: "Get document metadata (and optionally text)",
9586
- 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`.",
9612
+ 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`. When `indexStatus` is FAILED the response also carries `indexFailure` with a machine-readable `code` telling you whether the content is still findable \u2014 e.g. TEXT_INDEX_FAILED / VECTOR_LIMIT_EXCEEDED (one leg still serves it) vs INDEXING_FAILED (not findable by search at all). Branch on `code`, not on the message.",
9587
9613
  inputSchema: inputSchema10,
9588
9614
  handler: async (args) => {
9589
9615
  const documentId = args.documentId;
@@ -9917,7 +9943,7 @@ var inputSchema15 = {
9917
9943
  var folderCreate = ({ client, log }) => ({
9918
9944
  name: "folder_create",
9919
9945
  title: "Create a folder",
9920
- description: "Create a folder to organize documents and records. Pass `parentId` to nest it (create-only \u2014 folders cannot be moved later). Idempotent by `slug` within the parent. Requires the key to allow folders:c.",
9946
+ description: "Create a folder to organize documents and records. Pass `parentId` to nest it (create-only \u2014 folders cannot be moved later). Idempotent by `slug` within the parent. Requires the key to allow folders:c (and folders:r to receive the existing folder on a collision).",
9921
9947
  inputSchema: inputSchema15,
9922
9948
  handler: async (args) => {
9923
9949
  try {
@@ -10016,7 +10042,7 @@ var inputSchema18 = {
10016
10042
  var currentIdentity = ({ log, apiKey, environment }) => ({
10017
10043
  name: "current_identity",
10018
10044
  title: "Current identity (tenant + principal scope)",
10019
- description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, and (for scoped keys) allowedActions + dataScope. Use this when the user asks 'what can you do here?' or 'what tenant am I in?'. Calls GET /v1/ping under the hood. Backend may still be rolling out the extended /v1/ping response shape \u2014 until then this tool returns a minimal shape; richer fields appear automatically as backend ships.",
10045
+ description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, principalLabel, and (for scoped credentials) allowedActions plus dataScope \u2014 the credential's reach as { userId, scopes[] }, where each scope is a `namespace:value` string (e.g. \"org:<uuid>\"). Use this when the user asks 'what can you do here?' or 'what tenant am I in?'. Calls GET /v1/ping under the hood.",
10020
10046
  inputSchema: inputSchema18,
10021
10047
  handler: async () => {
10022
10048
  try {
@@ -10114,7 +10140,7 @@ var inputSchema19 = {
10114
10140
  "Index strategy. HYBRID (BM25 + dense, default; best for general retrieval), SEMANTIC (dense only; concept-driven queries), TEXT (BM25 only; exact-match / keyword), NONE (store-only / archival \u2014 retrievable by id and structured-field lookup but never in search results). Omit to inherit the bound schema's default."
10115
10141
  ),
10116
10142
  externalId: import_zod18.z.string().optional().describe(
10117
- "Stable caller-supplied id. Immutable; unique per type within your context. Re-ingesting with the same externalId returns the existing document (idempotent) instead of creating a duplicate \u2014 use it to make ingests safely retryable and as the key other records reference. Mirrors record_create."
10143
+ 'Stable caller-supplied id. Immutable; unique per type within your context. Re-ingesting with the same externalId returns the existing document (idempotent) instead of creating a duplicate \u2014 use it to make ingests safely retryable and as the key other records reference. Mirrors record_create. Receiving the existing document back is a read of it and needs documents:r in addition to documents:c; a create-only key gets an "already exists" error instead of the document.'
10118
10144
  ),
10119
10145
  upsert: import_zod18.z.boolean().optional().describe(
10120
10146
  "When true, re-ingesting an existing externalId OVERWRITES that document and re-indexes the new body (text mode) / re-uploads and re-indexes the new file (file mode), instead of returning the existing one unchanged. This is the curation/re-sync primitive: edit a source, re-ingest with upsert:true, and the search index reflects the new content. Requires the documents:u scope. IMPORTANT: pass the SAME schemaId (and type) you used originally \u2014 externalId is unique WITHIN a type, so omitting schemaId resolves in the untyped namespace and mints a duplicate instead of updating. The response `created` flag confirms which happened (created:true = a new document was minted)."
@@ -10123,12 +10149,12 @@ var inputSchema19 = {
10123
10149
  "Bind this document to a record schema. When set, `payload` is validated against the schema and its lookup fields become directly queryable via document_query (records parity). Resolve a schema id from its type via list_schemas. Omit for an untyped document."
10124
10150
  ),
10125
10151
  payload: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.unknown()).optional().describe(
10126
- "The document's structured data \u2014 a flat key/value object. With `schemaId`, declared fields are validated and lookup fields indexed; undeclared keys pass through as free-form and are searchable via the hybrid_search `filters` param. Without a schema it is stored as free-form metadata."
10152
+ "The document's structured data \u2014 a flat key/value object. With `schemaId`, declared fields are validated and lookup fields indexed; undeclared keys pass through as free-form and are searchable via the hybrid_search `filters` param. Without a schema it is stored as free-form metadata. Every number must fall within the signed 64-bit range \u2014 send a larger whole number as a STRING (stored exactly, exact-match lookup), else the ingest is refused with a 400 naming the field."
10127
10153
  ),
10128
10154
  // Inline text mode (mutually exclusive with filePath):
10129
10155
  text: import_zod18.z.string().min(1).optional().describe("Text body to ingest inline. Mutually exclusive with filePath. Either text or filePath is required."),
10130
10156
  storeText: import_zod18.z.boolean().optional().describe(
10131
- "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."
10157
+ "FILE MODE ONLY. Whether the uploaded file's EXTRACTED text is retained after indexing (default true), fixed at ingest. true keeps the extracted text retrievable via document_get(includeText:true) and usable by document_ask; false discards it once indexing completes \u2014 search still works and the original file remains downloadable, but includeText returns textAvailable:false and document_ask is unavailable. NOT accepted with `text`: a text-ingested body is the document itself and is ALWAYS retained, so the backend has no storeText knob on the text path \u2014 passing it with `text` is rejected rather than silently ignored."
10132
10158
  ),
10133
10159
  // File upload mode (mutually exclusive with text; stdio-transport-only):
10134
10160
  filePath: import_zod18.z.string().min(1).optional().describe(
@@ -10145,7 +10171,7 @@ var inputSchema19 = {
10145
10171
  var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10146
10172
  name: "document_ingest",
10147
10173
  title: "Ingest a new document (text body or file upload)",
10148
- 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.",
10174
+ 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 \u2014 requires documents:r in addition to documents:c, since being handed the existing document is a read of it). 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 is a FILE-MODE knob (default true): false discards the uploaded file's extracted text after indexing (search + file download keep working; text retrieval and document_ask do not). Text-mode bodies are always retained, so storeText is not accepted with `text`.",
10149
10175
  inputSchema: inputSchema19,
10150
10176
  handler: async (args) => {
10151
10177
  const title = args.title;
@@ -10182,6 +10208,14 @@ var documentIngest = ({ client, log, transport, ingestRoot }) => ({
10182
10208
  )
10183
10209
  );
10184
10210
  }
10211
+ if (text && args.storeText !== void 0) {
10212
+ return toolError(
10213
+ "document_ingest",
10214
+ new Error(
10215
+ "`storeText` is not supported with `text` \u2014 a text-ingested body is the document itself and is always retained; the text path has no storeText control. Omit it (text mode), or use `filePath` mode where storeText:false discards the uploaded file's extracted text after indexing."
10216
+ )
10217
+ );
10218
+ }
10185
10219
  const common = {
10186
10220
  folderId: args.folderId,
10187
10221
  payload: args.payload,
@@ -10486,7 +10520,7 @@ var identityResource = ({ log, apiKey, environment }) => ({
10486
10520
  name: "identity",
10487
10521
  uri: "vectros://identity",
10488
10522
  title: "Current identity (tenant + principal scope)",
10489
- description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, environment, principalType, principalKeyId, and (for scoped keys) allowedActions + dataScope. Until backend ships the extended /v1/ping response, returns a minimal derived shape; richer fields appear automatically as backend rolls out.",
10523
+ description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, tenantId, environment, principalType, principalKeyId, principalLabel, and (for scoped credentials) allowedActions + dataScope ({ userId, scopes[] }). The /v1/ping backing call ships this extended shape today.",
10490
10524
  mimeType: "application/json",
10491
10525
  read: async () => {
10492
10526
  const identity = await resolveIdentity({ log, apiKey, environment });