@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 +240 -0
- package/README.md +116 -16
- package/dist/cli-http.js +1526 -317
- package/dist/cli-http.js.map +1 -1
- package/dist/cli-http.mjs +1526 -317
- package/dist/cli-http.mjs.map +1 -1
- package/dist/cli.js +1533 -325
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1533 -325
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1512 -310
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1512 -310
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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
|
|
146
|
-
|
|
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
|
|
199
|
+
Or let Claude Code's CLI write that keyring-based entry for you:
|
|
163
200
|
|
|
164
201
|
```bash
|
|
165
|
-
claude mcp add vectros
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|