MCP server
Explanation — what & why
Vectros ships an official Model Context Protocol (MCP) server
(@vectros-ai/mcp-server) that connects MCP-aware agents — Claude Desktop,
Cursor, Cline, Continue, VS Code, and hosted agent platforms — directly to a
tenant's hybrid search, structured records, documents, folders, and in-perimeter
inference. Drop one line into the agent's config and the model can search the
indexed corpus, query and write records, ingest documents, and ask questions
grounded against documents — with no custom integration code.
The MCP server is a thin shim over the Vectros SDK: what your app observes matches the SDK and the underlying HTTP API one-to-one. Tenant isolation and scope enforcement all hold — running an MCP server does not expand a credential's reach. What it does is make that reach available to the agent as callable tools.
Two properties are worth understanding up front:
- Data-plane tools only. The server exposes exactly 23 tools, and every one of them wraps a Vectros data-plane operation. There are no web-search or third-party agent tools — none are present, by design. An agent driven through this server reaches your tenant's data and nothing else.
- Built for the context window. A tool result is injected straight into the model's context, a fundamentally different consumption shape than a UI response. The server applies deliberately smaller default limits than the HTTP API to protect the window and the cost.
How-to
Install and configure (stdio — the desktop case)
If you have @vectros-ai/cli installed globally (npm install -g @vectros-ai/cli — not a one-off npx run; the server needs vectros still
on PATH when IT runs, not just while bootstrap ran) and have already run
vectros bootstrap with it, drop this into your MCP client config with no
key in the file at all (Claude Desktop: claude_desktop_config.json;
Cursor / Cline / Continue have equivalents) — the server resolves the key
from the local vectros CLI keyring automatically (see Credential
resolution). vectros login alone does not
satisfy this — it stores a separate sign-in session, not this key:
{
"mcpServers": {
"vectros": {
"command": "npx",
"args": ["-y", "@vectros-ai/mcp-server"]
}
}
}
This is also the config shape that's safe to commit and share — a project
.mcp.json with no env block leaks nothing, and each teammate's own
keyring supplies their own key.
No CLI installed, or a non-interactive box you can't log in on? Set the key directly instead:
{
"mcpServers": {
"vectros": {
"command": "npx",
"args": ["-y", "@vectros-ai/mcp-server"],
"env": {
"VECTROS_API_KEY": "ssk_live_..."
}
}
}
}
Never commit a config file with a real key inside it — that's a live secret at rest in git history the moment it lands, whether the repo is public or not.
Restart the client. That is the whole install. The server runs as a stdio subprocess of the client; JSON-RPC over stdin/stdout is the wire protocol. No ports, no URLs, no auth ceremony.
VECTROS_API_KEY accepts any of the three credential types, but a scoped
permanent key (ssk_*) is the right shape for a desktop install — the blast
radius of a root key on a desktop is too broad.
Provision the credential in one command
You do not have to hand-build that ssk_*. The companion CLI mints a
least-privilege scoped key and its access profile and merges the config entry
above for you — no root key, no manual portal steps:
npx -y @vectros-ai/cli bootstrap
See cli.md for the full bootstrap flow. The minted key is data-plane only and per-machine.
Install and configure (HTTP — the hosted case)
For self-hosted scenarios — running the server behind a network boundary, sharing it across agent instances, deploying as a sidecar — the same package exposes a second binary that speaks Streamable HTTP instead of stdio:
VECTROS_API_KEY=ssk_live_... \
VECTROS_MCP_HTTP_PORT=8765 \
VECTROS_MCP_HTTP_BEARER_TOKEN=$(openssl rand -hex 32) \
npx -y -p @vectros-ai/mcp-server vectros-mcp-server-http
The server listens on 127.0.0.1:8765 by default. A bearer token is optional on
localhost but required to bind a non-loopback host — without one, anyone who
can reach the port could call Vectros with your credentials, so the server
refuses to start (override with VECTROS_MCP_HTTP_ALLOW_INSECURE=1, which is not
recommended). DNS-rebinding protection is built in; set
VECTROS_MCP_HTTP_ALLOWED_HOSTS to your public hostname(s) when running behind a
reverse proxy.
An inline KEY=value prefix like the one above lands in shell history and
process argv (visible to anything on the box that can list processes) —
fine for a quick local run, but for anything longer-lived export the
variable from your secret manager/CI secret store instead of typing it on
the command line, and never put it in a checked-in script.
Constrain which tools an agent gets
Narrow the catalog at install time with VECTROS_MCP_TOOLS — a comma-separated
list of tool names (e.g. "hybrid_search,rag_ask"). This gives an agent
read-only search without exposing writes or inference cost. Unknown tool names
fail fast at startup with a clear error.
Reference
The 23 tools
Every tool wraps a Vectros data-plane operation. Records, documents, and folders
each have a full create / read / update / delete / query set; search and
inference round it out; list_schemas, current_identity, lookup_principal,
and version_history are discovery and history.
| Tool | Purpose |
|---|---|
hybrid_search | Keyword + dense ranking across indexed documents and records. Narrow by ownership (scope for one dimension, scopeFilters for several at once — e.g. one client within one org), folder, type, metadata filters, date window, keyword precision, and relevance floors. |
list_schemas | Discover the available record / document / identity types (filter by surface, or resolve one by type name). |
current_identity | Describe the credential's tenant and principal scope. |
lookup_principal | Resolve a user / org / client by your own externalId (to its Vectros id, for the ownership filters) or by a schema lookup field. Read-only. |
record_create | Create a structured record. Idempotent by externalId; optional per-record indexMode. |
record_get | Fetch a record by id. |
record_batch_get | Fetch several records by id (1-100) in one call, each with its full payload. |
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; the default best_effort writes each independently. Reports success whenever the batch was processed, so read the per-item results. |
record_update | Update a record. |
record_delete | Delete a record. |
record_query | Look up (exact / composite / range / prefix, ascending or descending), or list records by type, by folderId (any type in one folder), or recent (the account-wide recently-updated feed) — see record_query supports composite (multi-field) lookups below. |
document_ingest | Create a document — inline text, or a local file upload. Idempotent by externalId; optional schemaId + payload for a typed, lookup-queryable document. |
document_get | Fetch a document by id (metadata; optional text; optional presigned file download URL). |
document_update | Update a document. |
document_delete | Delete a document. |
document_query | Look up (exact / range / prefix, ascending or descending) or list documents. |
document_ask | Ask a question scoped to a single document. |
folder_create | Create a folder. |
folder_update | Update a folder. |
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. |
folder_query | Look up or list folders (paginated). |
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). |
version_history | Read the change history (create / update / delete, with actor and diff) of a record or document. Read-only. |
There are deliberately no web, search-the-internet, or third-party agent tools — the server's reach is your tenant's data plane only.
Read-only resources
Alongside the tools, the server exposes two read-only MCP resources for ambient context an agent can pick up without spending a tool call:
- a schema catalog resource (the same payload as
list_schemas), and - an identity resource (the same payload as
current_identity).
Transports
| Transport | Binary | Default bind | Use for |
|---|---|---|---|
| stdio | vectros-mcp-server | n/a (subprocess) | Desktop agents. |
| Streamable HTTP | vectros-mcp-server-http | 127.0.0.1:8765 | Hosted / shared / sidecar deployments. |
document_ingest is dual-mode
Pass text for an inline-body ingest, or filePath for a local-file upload —
exactly one; both is an error.
textmode works on both transports.filePathmode is stdio-only. The HTTP transport rejectsfilePathat validation time, because a remote server cannot read the caller's local filesystem. On HTTP, ingest text inline or call the SDK's upload method from your own code.- On stdio, an uploaded path is jailed to a configured ingest root
(
VECTROS_MCP_INGEST_ROOT, else the server's working directory). Paths that escape the root (traversal, absolute, or symlink) or match a sensitive-file pattern (SSH/AWS/credential files,.env, and similar) are refused before any bytes are read. - File uploads are asynchronous: the tool returns with status
PENDING_INDEX; polldocument_getuntil it reportsINDEXED. - Idempotent by
externalId. Pass a stableexternalIdand re-ingesting the same one returns the existing document instead of creating a duplicate — the same semanticsrecord_createhas, so a retried ingest is safe. - Typed documents. Pass
schemaId+payloadto bind the document to a schema; its declared lookup fields then become directly queryable viadocument_query, exactly like records.
record_query supports composite (multi-field) lookups
A schema can declare a lookup over 2–3 fields together (see
blueprints.md); record_query queries one by comma-joining
the field names in field (e.g. "status,area") and passing values — one
value per field, in the schema's declared order — or a single value to match
just the first named field, which groups the results by the rest. Supplying
fewer values than the lookup declares is allowed only as a leading run of
the declared fields (the first field alone, the first two, and so on — never a
later field alone). A composite lookup is exact-match only: from/to/prefix
don't apply. sortFrom/sortTo narrow an exact match to a window of the sort
key, but on a composite lookup need a value for every declared field — a
partial (grouped) match has no single ordering to window. Document, user, and
entity lookups remain single-field only; composite lookups exist for records
only.
Context-window protection
Because tool results land directly in the model's context, the server caps results more tightly than the HTTP API:
| Tool | API default | API max | MCP default | MCP max |
|---|---|---|---|---|
hybrid_search | 10 | 50 | 3 | 50 |
record_query | 100 | 100 | 3 | 100 |
rag_ask retrieval | — | — | 5 | 50 |
hybrid_search pages with offset (skip the first N hits). record_query
pages with the cursor envelope instead — pass the nextCursor a page returns
back as startFrom to fetch the next one, and stop only when nextCursor
comes back null; a full page is not itself a signal to keep going, and a
hand-built startFrom is rejected. rag_ask retrieval does not page at all —
limit is a one-shot cap on how many passages ground a single answer, not an
offset you advance; ask again with different scoping filters to change what's
retrieved. Agents that want a full document fall back to document_ask for
question-driven extraction rather than dumping text into context.
Long-running calls stay alive
rag_ask and document_ask can run tens of seconds. MCP clients have
tool-execution timeouts that would otherwise fire mid-generation. The server
emits an MCP progress notification for each streamed chunk, keeping the
JSON-RPC connection warm; the final aggregated answer arrives as the tool result.
Clients that surface progress show the answer building; clients that ignore it
still receive the complete answer.
Startup credential validation
On connect, the server performs a credential check so a bad key fails fast as a
startup error rather than opaquely failing on the first tool call. Disable it
with VECTROS_MCP_SKIP_PING_VALIDATION=1 (useful in CI where the API is not
reachable).
Credential resolution
The server takes its key from the first source that supplies one:
VECTROS_API_KEY— always wins when set.- The
vectrosCLI keyring — if the key is unset and the CLI is onPATH, the server runsvectros keyring show --format rawas a subprocess and uses the key it prints (the active entry, orVECTROS_KEYRING_ALIASif set). One credential store, shared by the server, scripts, and hooks — no plaintext copy per tool. Same pattern asgit credential/aws credential_process. - Neither — startup fails with guidance naming both options.
The key is held in memory only and never logged; startup logs the resolved alias
so you can see which identity the server is running as. vectros keyring doctor
shows that view, including which entries hold live keys (the ones that act on
real data).
Startup warns when it picks an identity you didn't name — that is, when
VECTROS_API_KEY is unset and no VECTROS_KEYRING_ALIAS is given, so the key comes
from whichever entry vectros switch last made active. That is easy to arrive at by
accident: a blank placeholder ("VECTROS_API_KEY": "" in a client config, or
-e VECTROS_API_KEY passing an unset var through Docker) looks unconfigured but
resolves like an unset key. The warning does not depend on which key turns up —
silently getting a ssk_live_* key (acting on real data) and silently getting a
ssk_test_* one (not acting on it, when you thought you were) are both surprises.
Nothing is blocked: name an entry with VECTROS_KEYRING_ALIAS, or set
VECTROS_API_KEY, and the warning stops. vectros keyring doctor shows which entry
is active and which keys are live.
After rotating the key this server runs on: an explicit VECTROS_API_KEY in this
config always wins over the keyring (rule 1 above), so a stale copy left here
keeps authenticating with the OLD key until you edit it by hand — pointing this
config at VECTROS_KEYRING_ALIAS instead means a future rotation needs no config
edit at all. See cli.md for how key rotate picks which key to replace.
Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
VECTROS_API_KEY | no* | — | Vectros credential. Recommended: ssk_*. *Required unless the vectros CLI is installed with a usable keyring entry: when this is unset, the server resolves the key by running vectros keyring show --format raw (see Credential resolution). Takes precedence when set. |
VECTROS_KEYRING_ALIAS | no | (the active entry) | Resolve this vectros keyring entry instead of the active one. Ignored when VECTROS_API_KEY is set. |
VECTROS_API_BASE_URL | no | https://api.vectros.ai | API base URL. Use the staging URL for the staging tenant. Validated before the key is attached. |
VECTROS_MCP_TOOLS | no | (all) | Comma-separated tool filter. |
VECTROS_MCP_INGEST_ROOT | no | server cwd | Root directory document_ingest file uploads are jailed to (stdio). |
VECTROS_MCP_DEBUG | no | — | Set 1 for debug logging on stderr. |
VECTROS_MCP_SKIP_PING_VALIDATION | no | — | Set 1 to skip the startup credential check. |
VECTROS_MCP_HTTP_PORT | HTTP only | 8765 | Listen port. |
VECTROS_MCP_HTTP_HOST | HTTP only | 127.0.0.1 | Bind address. |
VECTROS_MCP_HTTP_BEARER_TOKEN | HTTP only | — | Client→server bearer token. Required to bind a non-loopback host. |
VECTROS_MCP_HTTP_ALLOWED_HOSTS | HTTP only | — | Extra allowed Host header values (DNS-rebinding protection). |
VECTROS_MCP_HTTP_ALLOWED_ORIGINS | HTTP only | — | Extra allowed Origin header values. |
VECTROS_MCP_HTTP_ALLOW_INSECURE | HTTP only | — | Set 1 to bind a non-loopback host without a bearer token (not recommended). |
Notes & limits
- Exactly 23 data-plane tools; no web/agent tools. The server cannot reach the public internet or any non-Vectros service.
document_ingestfile upload is stdio-only. Use text mode (or the SDK) on HTTP.- One upstream credential per process. The HTTP transport uses its
VECTROS_API_KEYfor all upstream calls; the incoming HTTP bearer token is for client→server auth only, not mapped to a per-request Vectros credential. Deploy one server per credential boundary you want to enforce. current_identityreturns the full identity shape — tenant, environment, principal type, principal key id and label, and (for scoped credentials) the allowed actions and data scope ({ userId, scopes[] }). It degrades to a derived-only shape only when no upstream call can be made (e.g. a test mock).rag_ask/document_askaggregate before returning. The full answer is assembled server-side (progress notifications cover the latency); native tool-level streaming is not yet exposed.
Where to go next
- cli.md —
bootstrapprovisions thessk_*this server runs on. - sdk.md — the SDK the tools wrap, for when you want code instead of an agent.
- blueprints.md — declare the schemas the agent will read and write.
- The blueprint walkthroughs — narrated builds where an agent drives a provisioned blueprint over MCP.