Skip to content
 
 

Repository files navigation

VectorSmith

VectorSmith

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.

License Python 3.11+ TDS MCP Version Docs

What it is · How it works · Write YAML · Python · Claude / Codex / Cursor · Production HTTP · Try it · Docs


Why this exists

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


How it works

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
Loading

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.


Write a tool, not a prompt

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: 50

vectorsmith init ./demo writes a starter file. The full field list — kinds, operators, pipelines, built-ins, every backend — is in docs/tools-yaml-reference.md.

What the model sees

{
  "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.

Kinds you can declare

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.


In your agent (Python)

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.


In Claude, Codex, Cursor

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.


Production HTTP server

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-embed

Localhost 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.


Stores

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.


Try it

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.example

Tickets are a second file / second MCP name: tools.tickets.yaml--name tickets.

Example walkthrough


CLI

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.


Documentation

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/

Develop

uv sync
uv run ruff check .
uv run pytest -m "not conformance"
uv run lint-imports

Workspace: packages/core (vectorsmith_core, unpublished) · packages/cli (published vectorsmith). Core must not import the CLI.

Contributing · Support · Security · Changelog · Code of conduct


Apache-2.0 · LICENSE · NOTICE

Forge the tools. Keep the store.

About

Your vector database, as typed tools. Write a tools.yaml, then load_tools in Python or serve over MCP.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages