@vectros-ai/mcp-server 0.16.1 → 0.17.1

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,246 @@
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.17.1 — 2026-09-17
7
+
8
+ ### Security
9
+
10
+ - **README no longer instructs committing a live API key.** Both "Configure manually" examples
11
+ (Claude Desktop and Claude Code) led with a JSON snippet embedding a real `ssk_live_...` value,
12
+ and the Claude Code section explicitly said to commit the resulting `.mcp.json` "to share the
13
+ server with the repo" — on the page that renders as this package's npm landing page. Both
14
+ sections now lead with the keyring-based config (no `env` block, no secret in the file — the
15
+ server resolves the key from the local `vectros` CLI keyring, same as before) and explicitly warn
16
+ against committing a config that does carry a real key.
17
+
18
+ ### Added
19
+
20
+ - **All 23 tools now declare MCP annotations** (`readOnlyHint`/`destructiveHint`/`idempotentHint`)
21
+ in the actual `tools/list` response, not just their own source — a host previously had nothing
22
+ to gate a confirmation prompt on, so every tool looked alike including the deletes and the
23
+ batch-write. Annotated per tool against what its handler actually calls, not its name:
24
+ - Reads with no real-money leg (`hybrid_search`, `record_get`/`_query`/`_batch_get`,
25
+ `document_get`/`_query`, `folder_query`, `list_schemas`, `current_identity`,
26
+ `lookup_principal`, `version_history`) are `readOnlyHint: true`.
27
+ - `rag_ask` and `document_ask` are **not** read-only: both reserve an atomic hold against the
28
+ caller's prepaid inference balance before generation and debit it on completion, which can
29
+ trigger a configured auto-recharge against a payment method on file. Also not idempotent —
30
+ every repeat charges again, and generation is non-deterministic.
31
+ - `record_create`, `document_ingest`, and `record_batch_write` each dedupe by an OPTIONAL
32
+ `externalId` — supplying it makes a retry converge, omitting it does not — so all three are
33
+ annotated for that less-safe default (`idempotentHint: false`), not the safer opt-in;
34
+ `document_ingest` and `record_batch_write` are additionally flagged destructive because their
35
+ `upsert:true` paths overwrite existing content.
36
+ - `folder_create` is idempotent **only when the caller supplies `slug` explicitly** — an omitted
37
+ slug is derived through a collision-probing suffix loop that returns only a value already
38
+ proved free, so a retry with no `slug` creates a new sibling folder rather than converging.
39
+ Annotated `idempotentHint: false` for that default, the same standard as the optional-key
40
+ create tools above.
41
+ - The three `*_update` tools (JSON Merge Patch) and the three `*_delete` tools are all flagged
42
+ destructive.
43
+
44
+ Verified against the real, over-the-wire `tools/list` response from the compiled binary, not the
45
+ source that builds it.
46
+
47
+ ### Fixed
48
+
49
+ - **`document_ingest` in file mode now works against an API that makes presigned upload URLs
50
+ single-use.** Such an API bakes a conditional-write header into the upload URL's signature and
51
+ names it in the upload response (`requiredHeaderName` / `requiredHeaderValue`); the tool's PUT
52
+ omitted it, so storage rejected the upload with a 403 and the tool reported the document as
53
+ created but incomplete. The tool now sends whatever header the response names, and no extra
54
+ header when the response names none, so it works against API versions on either side of the
55
+ change.
56
+
57
+ - **A failed startup could abort the whole process with a native Windows crash instead of the
58
+ server's own exit code.** Both entry points (`vectros-mcp-server`, `vectros-mcp-server-http`)
59
+ end EVERY startup failure path — a missing/invalid credential, a bad `--` env config, a rejected
60
+ `GET /v1/ping` check (enabled by default; `VECTROS_MCP_SKIP_PING_VALIDATION` to opt out) — with a
61
+ raw `process.exit()`. On Windows, an async operation still settling (a `fetch` for the ping check;
62
+ an `execFile` spawn out to the local CLI keyring helper for credential resolution) when
63
+ `process.exit()` runs can race libuv and abort the process, printing an `Assertion failed` line
64
+ and replacing the real exit code with `127` — the same class of defect fixed in `@vectros-ai/cli`
65
+ around the same time. Every startup-failure exit in both files now sets `process.exitCode` and
66
+ lets the event loop drain naturally, instead of hard-exiting.
67
+
68
+ This needed one adjustment `@vectros-ai/cli`'s equivalent fix didn't: both entry points register
69
+ `SIGINT`/`SIGTERM` handlers for graceful shutdown, and Node keeps a process alive for as long as
70
+ either signal has a registered listener. Registering them before the connect attempt would mean a
71
+ failed connect never actually exits under the new idiom — it hangs forever instead, waiting for a
72
+ signal that will never come. Both files register those handlers only after a successful connect,
73
+ so a failed startup still exits promptly; the alternative (register early, make the shutdown
74
+ handler itself exit-code-safe too) doesn't work here, because the ping check's underlying fetch
75
+ has no timeout and no cancellation — that path trades the crash for a possible silent hang on a
76
+ stuck connection instead of fixing it.
77
+
78
+ - **Two integration tests carried an internal issue number and internal review-process wording**
79
+ (a bare tracker reference in two test names/comments describing the exit-code fix above, and one
80
+ comment describing a defect an earlier internal review pass had caught). This package's GitHub
81
+ source mirror ships `tests/` verbatim, so both leaked into the public repo. Reworded to
82
+ self-contained descriptive text; no behavioral change.
83
+
84
+ - **Four more source comments carried an internal workstream/finding code**, on the base-URL
85
+ SSRF/credential-exfil guard in `cli.ts`, `cli-http.ts`, `server.ts`, and its test — found on a
86
+ later sweep with a wider scan pattern. The substantive rationale was already self-contained in
87
+ each comment; only the bare internal code was dropped. No behavioral change.
88
+
89
+ - **One more source comment on that same base-URL guard named a sibling package's file by
90
+ monorepo-relative path** (`base-url.ts`'s own doc comment, describing that the same guard is
91
+ ported into another package's CLI) — found by a post-merge audit. Reworded to describe the
92
+ sibling generically. No behavioral change.
93
+
94
+ - **One source comment named an internal backend class by name** (`record_query.ts`, describing
95
+ how a composite field's identity is joined on the backend). Reworded to describe the mechanism
96
+ without the class name. No behavioral change.
97
+
98
+ ### Docs
99
+
100
+ - **`current_identity`'s capability-gap list now includes `trigger-control-plane-grant`**, the
101
+ sixth named `granted_capabilities` value, alongside the five it already tracked. **`document_get`'s
102
+ `downloadUrl` description now notes the platform always forces `attachment` disposition** on
103
+ download, so a caller expecting inline rendering of a safe type knows to expect a save prompt
104
+ instead. No behavior changed — both were description-only gaps.
105
+
106
+ ### Changed
107
+
108
+ - **Dependency maintenance** — repinned the bundled `@vectros-ai/sdk` to `0.44.0`.
109
+
110
+ ## 0.17.0 — 2026-09-07
111
+
112
+ ### Added
113
+
114
+ - **`record_batch_write`** — create or upsert up to 50 structured records in one call,
115
+ wrapping `POST /v1/records/batch` (API 0.43.0: previously a `501` stub, now live).
116
+ Use it instead of N `record_create` round-trips when you hold several records.
117
+ Items may mix types, and each one goes through the same schema validation,
118
+ `externalId` idempotency, unique-field enforcement and `records:c:<type>` scope
119
+ check as a single create, applied per item against that item's own type — a batch
120
+ is not a way to write a type your credential could not write one at a time.
121
+ `atomicity: "all_or_nothing"` commits every item in one transaction and writes
122
+ nothing at all if any item fails, which is a better failure shape for an agent than
123
+ a loop of creates that half-succeeds; the default `best_effort` writes each item
124
+ independently. The item vocabulary deliberately mirrors `record_create` (`type` +
125
+ `fields`), so nothing new has to be learned to use it.
126
+
127
+ **The call reports success whenever the batch was *processed* — including when
128
+ every item failed.** That is the endpoint's own contract, so the tool does not
129
+ fake an error result; read the per-item `results` (matched to what you sent by
130
+ `index`) and the `succeeded`/`failed` counts. Per-item `status` carries two values
131
+ beyond the ordinary outcomes, and they call for different remedies:
132
+ `forbidden` (your credential lacked the scope or ownership that item needed — fix
133
+ the credential, not the item) and `not_committed` (this item was fine, but an
134
+ `all_or_nothing` batch was aborted by a different one).
135
+
136
+ - **`record_query` can list by `folderId`, and serve the account-wide `recent` feed.**
137
+ Two list-mode selectors the API has carried on `GET /v1/records` for many releases
138
+ but this tool never exposed: `folderId` lists every record filed in a folder
139
+ regardless of type (combine with `type` for one type within one folder), and
140
+ `recent: true` returns the recently-updated feed across all types — the only way to
141
+ read records without naming a type. `record_create` has always accepted `folderId`,
142
+ so until now a record could be filed into a folder and never listed back out of it.
143
+
144
+ This matters more from 0.43.0 on, because `folder_delete` now refuses a folder that
145
+ still holds records and the supported way to find them is exactly this call.
146
+ `type` is consequently optional now: a lookup (`field`) still requires it, a list
147
+ with no selector at all is refused, and `recent` is refused alongside
148
+ `type`/`folderId`/`userId`/`scope` rather than silently ignoring them the way the
149
+ API does.
150
+
151
+ - **`scopeFilters` on `hybrid_search` and `rag_ask`** — narrow a search or a RAG
152
+ retrieval by more than one ownership dimension at once, e.g.
153
+ `["org:<id>", "client:<id>"]` for one specific client within one specific org.
154
+ Something `scope` alone could not express, for a credential whose access spans
155
+ several dimensions. Mutually exclusive with `scope`; passing both is refused
156
+ locally with a message naming both fields, rather than spending a round trip on
157
+ the API's own rejection.
158
+
159
+ ### Changed
160
+
161
+ - **`folder_delete` now states its emptiness precondition** — the folder must contain
162
+ no documents, **no records**, and no sub-folders, and the description says how to clear
163
+ each: a document can be moved out or deleted, a record can only be deleted (nothing on
164
+ this surface moves one), and a sub-folder cannot be re-parented, so nesting must be
165
+ cleared innermost-first. The description previously named only protected folders, so
166
+ the most common way this call fails read as impossible.
167
+
168
+ **API 0.43.0 widened the refusal, and it lands harder on this server than on most
169
+ callers.** The old guard saw sub-folders and *file-backed* documents only — it was blind
170
+ to *text-ingested* documents and to records, and a delete that hit one of those returned
171
+ success while orphaning the contents behind a folder id that no longer resolved.
172
+ Text-inline ingest is `document_ingest`'s default path, so folders this server filled are
173
+ precisely the ones whose deletes used to succeed and now do not.
174
+
175
+ One consequence worth planning for: the emptiness check counts **everything** in the
176
+ folder, including items the calling credential cannot read. A scoped credential can be
177
+ refused by content it has no way to enumerate, so a refusal that contradicts your own
178
+ listing is expected rather than a bug.
179
+ - **`rag_ask`'s `truncationWarning` now carries `truncatedCount` and `noContentCount`**,
180
+ and its `reason` can be `no_groundable_content` or `context_window_budget_and_no_content`
181
+ as well as `context_window_budget`. Results can be dropped before the prompt is built
182
+ for two independent reasons — not fitting the context window, or having no groundable
183
+ text at all — and these separate them. Inherited from the API with no code change here;
184
+ noted because it is new data an agent sees.
185
+ - **`hybrid_search`'s ARCHIVED note now covers records, and stops claiming an absolute.**
186
+ It said ARCHIVED *documents* never appear in results, which read as an asymmetry implying
187
+ archived records still match — they do not, and have refused to index since API 0.35.
188
+ API 0.43.0 additionally fixed two document write paths that could silently re-index an
189
+ already-archived document. But it deliberately does **not** sweep items already stranded
190
+ in that state: they stay searchable until written to again. So "never appear" was a false
191
+ absolute, and the description now names the repair — re-send `status: "ARCHIVED"` on the
192
+ item — instead of implying the case cannot arise.
193
+ - **A search hit's returned `createdAt` is documented as the INDEX timestamp.** It is the moment
194
+ the item entered the search index — the same moment it was created in the common case, but later
195
+ for anything re-indexed since — so it should not be reported to a user as the creation time; fetch
196
+ the item with `document_get`/`record_get` for that. The `createdAfter`/`createdBefore` FILTERS are a
197
+ separate thing and are unchanged: they filter on the item's true creation time, which does not move
198
+ when it is re-indexed, with a bounded allowance that lets an item matched on its FIRST index be
199
+ checked against its index time instead (clock skew between creating and indexing).
200
+ - **`folder_delete` no longer implies a record cannot be moved.** It said a record must be deleted
201
+ because it cannot be re-filed. The API supports re-filing one (`folderId` is patchable); it is
202
+ *this server* that does not expose it — `record_update` has no `folderId` argument. As written, an
203
+ agent clearing a folder could have deleted partner data where a move was available, then reported
204
+ that a move was impossible. It now names the gap as this server's and says to move the record
205
+ through the API/SDK rather than deleting it here.
206
+ - **`record_batch_write` no longer claims duplicate `externalId`s within one batch are refused.**
207
+ That holds under `atomicity: "all_or_nothing"` only. Under the default `best_effort` each item is
208
+ written independently, so a second item carrying the same `externalId` matches what the first just
209
+ wrote and reports `updated` — a duplicated key silently collapses two rows into one, counted as a
210
+ success. Both modes are now described.
211
+ - **`record_create` / `record_batch_write`: `scopes` is bounded by `data_scope`, not by identity.**
212
+ Both said the values "must come from the credential's own identity". The platform states the
213
+ opposite: identity supplies the DEFAULT when you state none and does not limit which value you may
214
+ state; each entry must fall inside the `data_scope` of a granting clause. The old wording would
215
+ stop an agent setting a scope it was entitled to set.
216
+ - **`record_query` rejects `folderId`/`recent` on a lookup** instead of ignoring them, matching the
217
+ existing guard for lookup arguments passed without `field`.
218
+ - **Bundled `@vectros-ai/sdk` repinned to the 0.43.0 line.**
219
+
220
+ ### Notes
221
+
222
+ - **A root `sk_*` key can no longer file into a non-`default` app context.** As of API
223
+ 0.43.0, a `folderId` / `parentFolderId` — or a document's `schemaId` — belonging to
224
+ another context is refused with a uniform `400`, on `document_ingest`,
225
+ `document_update`, `record_create`, `record_update` and `folder_create`. Those calls
226
+ previously succeeded, but never coherently: a root key's writes are stamped
227
+ `default`, so the row landed in `default` while its folder or schema lived elsewhere.
228
+ Existing rows are unaffected. Use a scoped key bound to the target context — the
229
+ credential this server recommends anyway. See README § Recommended credential.
230
+ - **A `filters` clause on `status` means something different for records now.** Records
231
+ no longer publish their lifecycle `status` into the search index (API 0.43.0), so on
232
+ `hybrid_search` / `rag_ask` a `status` filter matches a schema's own `status` field
233
+ where one is declared, and nothing where none is — it no longer collides with the
234
+ platform's lifecycle value. Record schemas can now use `status` as a filterable field
235
+ of their own.
236
+ - **0.43.0 capabilities this server deliberately does not expose:** stored scripts and
237
+ synchronous script execution, trigger rules, trigger-failure history, and usage
238
+ reporting. The reasoning is in README § What this server deliberately doesn't expose
239
+ — briefly: authoring stored code is a design-time act rather than a data-plane one;
240
+ declaring a trigger rule grants live authority; trigger-failure and usage reporting
241
+ are operational questions for a person, not mid-task tool calls; and synchronous
242
+ script execution has a failure surface (an undetermined-outcome run, and an
243
+ idempotency key a tool call has no natural identity for) that needs a deliberate
244
+ design rather than a thin wrapper.
245
+
6
246
  ## 0.16.1 — 2026-08-27
7
247
 
8
248
  ### Fixed
package/README.md CHANGED
@@ -123,6 +123,29 @@ Precision is the pitch — what this server deliberately does *not* do:
123
123
 
124
124
  ## Configure manually (Claude Desktop or any MCP client)
125
125
 
126
+ If `@vectros-ai/cli` is installed **globally** (`npm install -g @vectros-ai/cli`,
127
+ not a one-off `npx` run — the server needs `vectros` to still be on `PATH`
128
+ later, when IT runs, not just while bootstrap ran) and you've run
129
+ `vectros bootstrap` with it, omit the `env` block entirely — the server
130
+ resolves your key from the local `vectros` CLI keyring automatically (see
131
+ [Credential resolution](#credential-resolution) below). `vectros login` alone
132
+ does **not** do this — it stores a separate sign-in session, not this key —
133
+ so `bootstrap` is the step that actually has to have run:
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "vectros": {
139
+ "command": "npx",
140
+ "args": ["-y", "@vectros-ai/mcp-server"]
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ No CLI installed, or a machine you can't log in on? Set the key directly
147
+ instead:
148
+
126
149
  ```json
127
150
  {
128
151
  "mcpServers": {
@@ -137,34 +160,53 @@ Precision is the pitch — what this server deliberately does *not* do:
137
160
  }
138
161
  ```
139
162
 
163
+ This file lives under Claude Desktop's own per-user app-support directory,
164
+ not a shared repo — but a real key pasted into it is still a live secret at
165
+ rest: don't paste it into a chat, ticket, or screenshot with the value
166
+ intact, and prefer the keyring form above whenever you can.
167
+
140
168
  Restart Claude Desktop. The agent now sees the Vectros tools and
141
169
  two resources as callable surfaces.
142
170
 
143
171
  ## Configure manually (Claude Code)
144
172
 
145
- Claude Code reads a project-scoped `.mcp.json` with the same shape — drop this
146
- at your project root (commit it to share the server with the repo):
173
+ Claude Code reads a project-scoped `.mcp.json` with the same shape.
174
+ **Never commit a real key into it** — `.mcp.json` is exactly the kind of file
175
+ teams check in to share a project's tooling, and a live `ssk_*`/`sk_*` value
176
+ inside it is a credential leak the moment it lands in git history, not just
177
+ if the repo is public.
178
+
179
+ The way to share this config safely: commit the entry with **no `env` block
180
+ at all**, and let each developer's own `vectros` CLI keyring supply the key
181
+ (see [Credential resolution](#credential-resolution) below — this is the
182
+ same delegation `git credential` / `docker-credential-*` use). This needs
183
+ each teammate to have `@vectros-ai/cli` installed **globally** and to have
184
+ run `vectros bootstrap` with it — `npx`-ing the CLI once, or running
185
+ `vectros login` alone (a separate sign-in session, not this key), leaves
186
+ nothing on `PATH` or in the keyring for the server to find later:
147
187
 
148
188
  ```json
149
189
  {
150
190
  "mcpServers": {
151
191
  "vectros": {
152
192
  "command": "npx",
153
- "args": ["-y", "@vectros-ai/mcp-server"],
154
- "env": {
155
- "VECTROS_API_KEY": "ssk_live_..."
156
- }
193
+ "args": ["-y", "@vectros-ai/mcp-server"]
157
194
  }
158
195
  }
159
196
  }
160
197
  ```
161
198
 
162
- Or let Claude Code's CLI write it for you:
199
+ Or let Claude Code's CLI write that keyring-based entry for you:
163
200
 
164
201
  ```bash
165
- claude mcp add vectros -e VECTROS_API_KEY=ssk_live_... -- npx -y @vectros-ai/mcp-server
202
+ claude mcp add vectros -- npx -y @vectros-ai/mcp-server
166
203
  ```
167
204
 
205
+ If a machine genuinely has no `vectros` CLI installed, you can pass the key
206
+ directly with `-e VECTROS_API_KEY=ssk_live_...` — but then keep that config
207
+ **local**: add `.mcp.json` to `.gitignore` first, rather than committing it
208
+ with the key inside.
209
+
168
210
  Add `-e VECTROS_API_BASE_URL=https://api.staging.vectros.ai` for a non-production
169
211
  environment.
170
212
 
@@ -184,14 +226,14 @@ resolves to its main repo's `.mcp.json` — add and open from the same project.
184
226
  > for a plain `npx` run, or pin an explicit version (`npx -y
185
227
  > @vectros-ai/mcp-server@<version>`) known to work.
186
228
 
187
- ## Tools (22 tools)
229
+ ## Tools (23 tools)
188
230
 
189
231
  **Search & RAG**
190
232
 
191
233
  | Tool | What it does |
192
234
  |---|---|
193
- | `hybrid_search` | Hybrid BM25 + dense search across the tenant's indexed content (records + documents). Narrow by ownership, folder, type, metadata filters, a created date window, and keyword-precision (`textMode`) / relevance floors. Returns the indexed projection of each hit. |
194
- | `rag_ask` | Ask a question grounded against the indexed corpus. Scope retrieval (ownership / folder / type / metadata filters / date window) and steer generation (`instructions` / `temperature`). Streaming generation aggregated; progress notifications keep the call alive for the generation window. |
235
+ | `hybrid_search` | Hybrid BM25 + dense search across the tenant's indexed content (records + documents). Narrow by ownership (`scope` for one dimension, `scopeFilters` for several at once — e.g. one client within one org), folder, type, metadata filters, a created date window, and keyword-precision (`textMode`) / relevance floors. Returns the indexed projection of each hit. |
236
+ | `rag_ask` | Ask a question grounded against the indexed corpus. Scope retrieval (ownership — `scope` or multi-dimension `scopeFilters` — / folder / type / metadata filters / date window) and steer generation (`instructions` / `temperature`). Streaming generation aggregated; progress notifications keep the call alive for the generation window. |
195
237
  | `document_ask` | Ask a question grounded against a single document. Same aggregation + progress-notification shape as `rag_ask`. |
196
238
 
197
239
  **Records** (structured, schema-validated data)
@@ -199,10 +241,11 @@ resolves to its main repo's `.mcp.json` — add and open from the same project.
199
241
  | Tool | What it does |
200
242
  |---|---|
201
243
  | `list_schemas` | List the record-schema catalog the credential can see (filter by `surface` or resolve one by `recordType`). Makes `record_query` / `record_create` discoverable. |
202
- | `record_query` | Query records by lookup field — equality (`value`), range, or prefix, with `asc`/`desc` ordering and an optional `sortFrom`/`sortTo` window — or list mode (filter by ownership + type). Also supports composite equality across 2-3 fields (`values`), but only against a lookup the *schema* declares over those fields together (see `list_schemas`) — not any two fields you pick. |
244
+ | `record_query` | Query records by lookup field — equality (`value`), range, or prefix, with `asc`/`desc` ordering and an optional `sortFrom`/`sortTo` window — or list mode, choosing one of `type`, `folderId` (every record in a folder, any type — combine with `type` to narrow), or `recent` (the account-wide recently-updated feed across all types), then optionally filtering the first two by ownership. Also supports composite equality across 2-3 fields (`values`), but only against a lookup the *schema* declares over those fields together (see `list_schemas`) — not any two fields you pick. |
203
245
  | `record_get` | Fetch one record by id, including its full payload (large payloads truncated to protect the agent context window). |
204
246
  | `record_batch_get` | Fetch several records by id (1-100) in one call, each with its full payload. Returns `missingIds` for any requested id you can't access, since the API silently omits them. |
205
247
  | `record_create` | Create a record of a given type; idempotent by `externalId`; optional per-record `indexMode`. |
248
+ | `record_batch_write` | Create or upsert up to 50 records in one call, instead of N `record_create` round-trips. Items may mix types and are validated and scope-checked individually. `atomicity: all_or_nothing` commits them as one transaction (nothing is written if any item fails); the default `best_effort` writes each independently. Reports success whenever the batch was *processed* — read the per-item `results`, not just the absence of an error. |
206
249
  | `record_update` | Patch a record's payload (deep-merged; `null` deletes a key); optimistic concurrency via `expectedVersion`. |
207
250
  | `record_delete` | Permanently delete a record by id (leaves a tombstone). |
208
251
 
@@ -212,7 +255,7 @@ resolves to its main repo's `.mcp.json` — add and open from the same project.
212
255
  |---|---|
213
256
  | `document_ingest` | Create a document — inline text body OR local file upload (file mode is stdio-transport only). Idempotent by `externalId`; optional `schemaId` + `payload` for a typed, lookup-queryable document. |
214
257
  | `document_query` | Query documents by lookup field (equality / range / prefix, with `asc`/`desc` ordering) or list mode (filter by ownership + type). |
215
- | `document_get` | Fetch a document by id (metadata incl. lifecycle `status` + processing `indexStatus`; optional text truncated at ~8K tokens; optional presigned `downloadUrl` for file-backed documents). |
258
+ | `document_get` | Fetch a document by id (metadata incl. lifecycle `status` + processing `indexStatus`; optional text truncated at ~8K tokens; optional presigned `downloadUrl` for file-backed documents — always forces a download, never inline rendering, regardless of file type). |
216
259
  | `document_update` | Patch a document's metadata / typed payload (deep-merged); archive/restore via `status` (`ARCHIVED` soft-retracts from search, `ACTIVE` restores); optimistic concurrency via `expectedVersion`. |
217
260
  | `document_delete` | Permanently delete a document by id (removes it and its indexed content). |
218
261
 
@@ -223,17 +266,17 @@ resolves to its main repo's `.mcp.json` — add and open from the same project.
223
266
  | `folder_query` | Get a folder by id, or list folders (a parent's children for tree navigation, or a flat tenant list; paginated via `nextCursor`). |
224
267
  | `folder_create` | Create a folder. |
225
268
  | `folder_update` | Update a folder's name / description / ownership (merge-patch; optimistic concurrency via `expectedVersion`; folders cannot be re-parented). |
226
- | `folder_delete` | Delete a folder. |
269
+ | `folder_delete` | Delete a folder. It must be empty first — no documents, no records, and no sub-folders — and a context root is protected outright. |
227
270
 
228
271
  **Identity & history**
229
272
 
230
273
  | Tool | What it does |
231
274
  |---|---|
232
- | `current_identity` | Describe the credential: tenantId, environment, principalType, principalKeyId, principalLabel, and (for scoped credentials) allowedActions + dataScope. Does **not** yet include `granted_capabilities` (`member-lifecycle` / `forensic-read` / `context-directory-read` / `delegate-mint`, as of API 0.40.0, joined by `delegate-principal-stamp` in 0.42.0) — a separate reach dimension a scope clause can carry that `/v1/ping` doesn't report yet, so allowedActions + dataScope may understate a credential's true reach. Also reports this server's own version and the bundled SDK version (`mcpServerVersion`, `sdkVersion`). |
275
+ | `current_identity` | Describe the credential: tenantId, environment, principalType, principalKeyId, principalLabel, and (for scoped credentials) allowedActions + dataScope. Does **not** yet include `granted_capabilities` (`member-lifecycle` / `forensic-read` / `context-directory-read` / `delegate-mint`, as of API 0.40.0, joined by `delegate-principal-stamp` in 0.42.0 and `trigger-control-plane-grant` in 0.44.0) — a separate reach dimension a scope clause can carry that `/v1/ping` doesn't report yet, so allowedActions + dataScope may understate a credential's true reach. Also reports this server's own version and the bundled SDK version (`mcpServerVersion`, `sdkVersion`). |
233
276
  | `lookup_principal` | Resolve a user, or an identity entity in a namespace (`org`/`client`/any namespace you registered), by your own `externalId` (→ its Vectros UUID, for the ownership filters) or by a schema lookup field. Pass `contextId` to target a specific app context for a context-owned namespace. Read-only. |
234
277
  | `version_history` | Read the audit/version trail (CREATE/UPDATE/DELETE, with actor + diff) for one record or document. Read-only. |
235
278
 
236
- All 22 tools wrap published Vectros HTTP API endpoints. JSON
279
+ All 23 tools wrap published Vectros HTTP API endpoints. JSON
237
280
  responses are what the agent sees as tool output. Per-call cost
238
281
  surfaces via the `usage` field on inference responses.
239
282
 
@@ -275,6 +318,20 @@ AccessProfile for MCP") for least-privilege credential setup — the
275
318
  `vectros bootstrap` flow provisions a scoped `ssk_*` key and its AccessProfile
276
319
  in one command.
277
320
 
321
+ > **A root `sk_*` can no longer file into another app context, as of API 0.43.0.**
322
+ > If you run this server on a root key and a tool call names a `folderId` /
323
+ > `parentFolderId` — or a `schemaId` on a document — belonging to a context other
324
+ > than `default`, it is now refused with a uniform `400 "Folder not found"` /
325
+ > `"Schema not found"`. This affects `document_ingest`, `document_update`,
326
+ > `record_create`, `record_update` and `folder_create`, and it is a change in
327
+ > outcome: those calls used to succeed. They never did what they appeared to,
328
+ > though — a root key's writes are always stamped `default`, so the row landed in
329
+ > `default` while its folder or schema lived elsewhere, permanently invisible to
330
+ > the context that owned them. Existing rows written the old way are untouched and
331
+ > still readable, updatable and deletable. The fix is the same scoped key
332
+ > recommended above: one bound to the target context can file into it and always
333
+ > could.
334
+
278
335
  ## Credential resolution
279
336
 
280
337
  The server resolves its API key from the first source that yields one:
@@ -378,6 +435,49 @@ const server = new VectrosMCPServer({
378
435
  await server.connect(createStdioTransport());
379
436
  ```
380
437
 
438
+ ## What this server deliberately doesn't expose
439
+
440
+ This is a decision, not a backlog. An MCP server is a tool surface handed to an
441
+ autonomous caller, so a capability the API offers is not automatically a tool —
442
+ each one has to earn its place on an agent's surface.
443
+
444
+ The line is the **data plane**: records, documents, folders and search are here in
445
+ full. Anything that grants or administers authority is not, and neither is the
446
+ design-time layer that defines the data model.
447
+
448
+ - **Stored scripts** (`/v1/scripts` — push, list, fetch, delete). A stored script is
449
+ code, not data. Authoring it is a design-time act, the same reason schema mutation
450
+ lives in the CLI rather than here: an agent that can write the code that later runs
451
+ under your credential is a different proposition from one that can write records.
452
+ - **Synchronous script execution** (`POST /v1/scripts/execute`, the `scripts:x` scope).
453
+ The closer call of the two, and excluded on operational grounds rather than reach —
454
+ a `scripts:x:<name>` grant is a deliberately narrow, per-script permission, and an
455
+ ordinary scoped key can hold it. What an agent handles badly is the failure surface:
456
+ a run whose outcome cannot be determined returns `500 EXECUTION_OUTCOME_UNKNOWN` with
457
+ writes that may or may not have committed, which is not something a caller resolves
458
+ by retrying — it has to go and look. Getting a retry right also means reusing an
459
+ `Idempotency-Key` across attempts, and a tool call has no natural retry identity, so
460
+ an agent re-invoking after a timeout would silently run the script a second time.
461
+ Exposing this well needs a deliberate design for those two things, not a thin wrapper.
462
+ Until then, run scripts from a caller that can handle them — the API, the SDK, or the
463
+ CLI.
464
+ - **Trigger rules** (`/v1/triggers`). Declaring a rule grants authority — a rule's
465
+ `scopes`/`roleIds` are live, and declaring one is scope-monotonicity-checked like
466
+ any other authority-granting surface. Minting authority is not an agent action.
467
+ - **Trigger failure history** (`GET /v1/trigger-failures`) and **usage/billing**
468
+ (`GET /v1/usage`). Both are read-only, and both are operational rather than
469
+ data-plane — they describe how your automation and your account are behaving, not
470
+ what your content says. That is a question for the developer portal or the CLI,
471
+ where a person is looking at it, rather than a tool an agent reaches for mid-task.
472
+ - **Identity, access and credential administration** — users, access profiles,
473
+ roles, app contexts, issuer registrations, and scoped-key minting. Unchanged
474
+ since launch: identity CRUD stays off the agent tool surface by design.
475
+ `lookup_principal` resolves an identifier to an id and does nothing else.
476
+ - **Compliance operations** (erasure, export). Same reason.
477
+
478
+ If one of these belongs on your agent's surface, that's worth telling us — the
479
+ line is drawn on purpose and can be redrawn with a reason.
480
+
381
481
  ## What this server doesn't do (yet)
382
482
 
383
483
  - **No prompts capability** — `/rag` and `/ingest_pdf` slash-command