Your vector database, forged into tools an agent can actually use.
Write a tools.yaml. VectorSmith compiles it into typed, tenant-guarded tools — then you either import them in Python or serve them over MCP.
What it is · How it works · Write YAML · Python · Claude / Codex / Cursor · Production HTTP · Try it · Docs
Agents that talk to your invoices, tickets, or catalog usually get one of two bad options:
| Typical approach | What goes wrong |
|---|---|
| Vendor MCP (Qdrant / Pinecone / …) | Cluster admin tools. Upsert, delete, create-collection. The model can wander. |
| Hand-bind JSON schemas to LangChain / the OpenAI SDK | You re-implement filters, limits, and tenant isolation in Python. Every agent copies it. |
“Just embed and search() in the system prompt” |
No typed args. No enums. No hidden tenant = acme. |
VectorSmith is the third option: the data store stays yours. The tools are a YAML contract. The compiler turns that contract into MCP schemas or in-process tools. The agent never sees the URL, the API key, or the tenant filter.
you write VectorSmith the agent sees
───────────── ───────────────── ────────────────
tools.yaml ──▶ interpolate → validate → compile ──▶ search_invoices
tenant: acme Engine stays internal query, client, status
${QDRANT_URL} (no tenant, no URL)
Watch the 50s demo · YAML → Claude / Cursor / Python
flowchart LR
subgraph author["You"]
Y["tools.yaml"]
E[".env / ${VAR}"]
end
subgraph vs["VectorSmith"]
L["load + secret lint"]
V["validate VBxxxx"]
C["compile schemas + plan"]
end
subgraph out["Consume once"]
P["load_tools() / connect()"]
M["vectorsmith serve"]
end
subgraph hosts["Hosts"]
A["LangChain · LangGraph · Agents SDK · Anthropic"]
H["Claude · Codex · Cursor · claude.ai"]
end
Y --> L
E --> L
L --> V --> C
C --> P --> A
C --> M --> H
One file, two doors. Same compiled tools.
| Python app | Chat / IDE host | |
|---|---|---|
| Install | pip install "vectorsmith[qdrant,langchain]" |
pip install "vectorsmith[qdrant]" so vectorsmith is on PATH |
| Call | from vectorsmith import load_tools |
vectorsmith serve tools.yaml --name invoices |
| Process | In-process. No subprocess. | The host spawns the CLI (MCP stdio or HTTP) |
| Mix-in | Your @tools + Slack/GitHub via an MCP client |
Other mcpServers keys sit next to it |
You do not import an executor. You do not copy inputSchema into the LLM SDK.
A tool is a name, a description (so the model picks it), a collection, optional text search, parameters the model may pass, and filters it must never see:
tds_version: "1"
connections:
invoices:
backend: qdrant
url: ${QDRANT_URL} # secrets only here, only as ${VAR}
api_key: ${QDRANT_API_KEY:-}
tools:
- name: search_invoices
kind: search
description: >
Search invoices by free text and filter by client, status, or amount.
Use when the user asks about invoices, billing, or payments.
target: { connection: invoices, collection: invoices }
query: { param: query, required: false }
static_filters:
- { path: tenant, op: eq, value: acme } # hidden from the model
parameters:
- { name: client, path: client_name, dtype: keyword, op: eq }
- { name: status, path: status, dtype: keyword, op: in,
enum: [draft, sent, paid, overdue] }
- { name: min_amount, path: amount, dtype: float, op: gte }
output:
fields: [invoice_id, client_name, status, amount]
limit_default: 10
limit_max: 50vectorsmith init ./demo writes a starter file. The full field list — kinds, operators, pipelines, built-ins, every backend — is in docs/tools-yaml-reference.md.
{
"name": "search_invoices",
"description": "Search invoices by free text and filter by client, status, or amount. …",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"client": { "type": "string" },
"status": {
"type": "array",
"items": { "type": "string", "enum": ["draft", "sent", "paid", "overdue"] }
},
"min_amount": { "type": "number" },
"limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
}
}
}tenant: acme is not in that schema. The engine ANDs it on every call. Credentials never leave connections.
kind |
For | Typical tool |
|---|---|---|
search |
Semantic retrieve + filters | search_invoices |
lookup |
Exact id, limit 1 | get_invoice |
count |
“How many overdue?” | count_invoices |
scroll |
Filter / page, no ANN | list-style tools |
pipeline |
Retrieve → post_filter / group_by / sort / project |
top-N per client |
Built-ins (search_<connection>, get_<connection>_by_id, …) are opt-in on the connection. Turn them off if you already named a user tool the same way.
pip install "vectorsmith[qdrant,langchain]"from vectorsmith import load_tools
from langchain.agents import create_agent
tools = load_tools("tools.invoices.yaml", "tools.tickets.yaml")
agent = create_agent("openai:gpt-4.1", tools)
# … await tools.aclose()Same YAML, other stacks:
from vectorsmith.langgraph import load_tools # create_react_agent / ToolNode
from vectorsmith.openai_agents import load_tools # Agent + Runner
from vectorsmith.anthropic import load_tools # messages.create(tools=vs.tools)
from vectorsmith import connect # await vs.call("search_invoices", {…})| Extra | Import |
|---|---|
vectorsmith[langchain] |
from vectorsmith import load_tools |
vectorsmith[langgraph] |
same tools; LangGraph graph |
vectorsmith[openai-agents] |
from vectorsmith.openai_agents import load_tools |
vectorsmith[anthropic] |
from vectorsmith.anthropic import load_tools |
Worked apps: examples/langchain_agent · langgraph_agent · openai_agents · anthropic_agent.
Those products cannot import vectorsmith. They spawn a process. Point them at serve with the same YAML.
{
"mcpServers": {
"invoices": {
"command": "vectorsmith",
"args": ["serve", "tools.invoices.yaml", "--name", "invoices"]
}
}
}Codex is TOML (~/.codex/config.toml), not JSON. Claude Code uses .mcp.json — it does not read the Desktop file.
| Host | Config | Guide |
|---|---|---|
| Claude Desktop | claude_desktop_config.json |
docs/integrations/claude-desktop.md |
| Claude Code | .mcp.json / claude mcp add |
docs/integrations/claude-code.md |
| OpenAI Codex | ~/.codex/config.toml |
docs/integrations/openai-codex.md |
| Cursor | .cursor/mcp.json |
docs/integrations/cursor.md |
| claude.ai | serve --http --auth builtin |
docs/quickstart-selfhost.md |
Copy-paste snippets: examples/mcp_hosts/. Slack, GitHub, filesystem stay separate servers — coexistence.
0.2.0 is the production HTTP cut. vectorsmith serve --http is Streamable HTTP MCP (POST /mcp) for claude.ai, gateways, and Kubernetes. Every security.* / observability.* / credential / profiles.enterprise knob in YAML is applied at process start — the same contract connect / load_tools use in-process.
pip install "vectorsmith[qdrant,auth-jwt,otel]"
vectorsmith serve tools.yaml --http 0.0.0.0:8080 --auth jwt \
--jwks-url https://auth.example.com/.well-known/jwks.json \
--jwt-issuer https://auth.example.com --jwt-audience vectorsmith \
--log-format json --live-embedLocalhost demo: --http 127.0.0.1:8080 --auth none. --auth none off loopback exits 3. Builtin OAuth (--auth builtin, the HTTP default) needs https:// --public-url.
| Concern | How |
|---|---|
| Who is calling | --auth jwt (JWKS / RS256) · api_key · builtin OAuth. Extra vectorsmith[auth-jwt] |
| Tenant isolation | Hidden static_filters and/or security.tenancy (claim / header). Pick one layer unless you intend both |
| Which tools | security.rbac (roles, deny_tools). Applied on the inner name of run_tool |
| Secrets | connections.*.credentials.provider: env · vault · aws_sm · k8s. Extra vectorsmith[creds-aws] |
| Quotas | security.rate_limit (off by default). In-memory or Redis (vectorsmith[auth-redis]). HTTP 429 |
| Health | GET /healthz (liveness). GET /readyz 503 if a connection, required embedder, or JWT JWKS is down |
| Observability | --log-format json (request_id, trace_id, span_id). observability.tracing → OTLP (vectorsmith[otel]). GET /metrics. Audit file / HTTP / OTLP |
| Hardening | profiles.enterprise + validate --enterprise --strict. Refuses serve / connect if tenancy / limits / backends fail |
| Search quality | Pluggable embedders, query.expand, tool.rerank (http / cohere / local cross_encoder) |
| Many catalogs | Extra YAML args, --route-by-claim, --default-project. Duplicate tool names fail at start |
| Drain | --shutdown-grace-s (default 30). New POST /mcp is 503 while draining |
Reference catalog: examples/enterprise/. Chart and probes: Kubernetes. Full model: enterprise · hardening · observability.
backend on a connection is one of six shipped adapters. Full matrix (extras, hybrid, nested paths): vector stores.
qdrant · pgvector · chroma · pinecone · weaviate · milvus
pgvector can run in table mode (no vector column) for lookup / count / scroll. Hybrid search is capability-gated (Qdrant / Weaviate / Milvus / Pinecone) and checked with validate --live.
Production extras (install what you turn on in YAML):
| Extra | For |
|---|---|
auth-jwt |
serve --auth jwt |
auth-redis |
Shared OAuth tokens and Redis rate limits |
otel |
OTLP traces from observability.tracing |
creds-aws |
credentials.provider: aws_sm |
embed-openai / embed-cohere |
Hosted embedders (and Cohere rerank) |
rerank-local |
rerank.provider: cross_encoder |
Full inventory: library surface.
The invoice example is a tools.yaml plus an env file. Copy .env.example and set QDRANT_URL to your cluster before validate / test / serve.
# clone, then:
uv sync
uv run vectorsmith validate examples/qdrant_invoices/tools.invoices.yaml \
--env-file examples/qdrant_invoices/.env.example
uv run vectorsmith test examples/qdrant_invoices/tools.invoices.yaml search_invoices \
--args '{"query":"Globex invoice","limit":3}' \
--env-file examples/qdrant_invoices/.env.example
uv run vectorsmith serve examples/qdrant_invoices/tools.invoices.yaml --name invoices \
--env-file examples/qdrant_invoices/.env.exampleTickets are a second file / second MCP name: tools.tickets.yaml → --name tickets.
| Command | Does |
|---|---|
init |
Write a starter tools.yaml + .env.example |
validate |
Compile + lint. --live pings the store. --live-embed smoke-tests the embedder. --enterprise / --policy / --policy-builtin for production gates. --strict fails on warnings |
test |
Call one compiled tool without serving |
serve |
MCP stdio (Desktop / Codex / Cursor; --watch on by default) or --http HOST:PORT (no watch). HTTP --auth: builtin (needs https --public-url) · jwt · api_key · none (loopback only). --live-embed includes the embedder on /readyz. |
introspect |
Collection / field metadata to --out (default schema.json). Requires --connection. |
drafts / approve |
drafts list|reject NAME. approve NAME [--file tools.yaml] promotes into that file. Drafts live in ./tools.drafts.yaml (process cwd). |
auth |
rotate-secret | revoke for builtin HTTP OAuth |
migrate |
tds_version 1 → 2 (--dry-run / --write) |
validate exits 0 / 1 (--strict warnings) / 2 (errors). test and introspect use 3 on a live failure. serve --http --auth none off localhost exits 3.
kjgpta.github.io/vectorsmith is the rendered manual (Material for MkDocs). Source is docs/.
| I want to… | Go here |
|---|---|
| Get a tool working in five minutes | Getting started |
| See which vector stores ship | Vector stores |
Understand every tools.yaml field |
YAML reference |
| Plug into Claude, Codex, Cursor, LangChain, … | Integrations |
| Look up a CLI flag | CLI |
| See every extra, route, and exception | Library surface |
| Call tools from Python | Python API |
| JWT / tenancy / RBAC / credentials / audit | Enterprise |
validate --enterprise at serve time |
Security hardening |
| Traces, metrics, JSON logs, audit sinks | Observability |
| Embedders and rerank | Embedding providers |
| Helm / probes / Redis auth store | Kubernetes |
| Fix Desktop disconnect / env / HTTP auth | FAQ |
| Copy a host config | examples/mcp_hosts |
| See agent apps | examples/ |
uv sync
uv run ruff check .
uv run pytest -m "not conformance"
uv run lint-importsWorkspace: packages/core (vectorsmith_core, unpublished) · packages/cli (published vectorsmith). Core must not import the CLI.
Contributing · Support · Security · Changelog · Code of conduct