Klavis-AI/klavis
Apache-2.0 monorepo of ~100 self-hostable MCP servers plus Strata, an MCP router that hides them behind five meta-tools.
Overview
Klavis is a monorepo of Model Context Protocol (MCP) servers and the tooling around them. It is not one service with a database. It is a set of independent parts that share a repository: about 100 MCP server directories under mcp_servers/ (Gmail, GitHub, Slack, Notion, Salesforce, Jira and others), a router called Strata (open-strata/, published to PyPI as strata-mcp), a Docker layer that gets OAuth tokens for those servers, and a set of chat bots (mcp-clients/) that connect Slack, Discord, WhatsApp and a web UI to MCP servers.
The main idea is in Strata. When an agent connects to many MCP servers, the full tool list can grow to hundreds of schemas. Strata does not show them. It shows five fixed meta-tools: discover actions, get action details, execute action, search documentation, and handle auth failure. The agent finds a tool with BM25 search and then calls it through the router. Use Klavis if you want MCP servers that you can run yourself, or one MCP endpoint in front of many servers. Do not use it if you need a multi-tenant credential store. The open code does not have one: the managed OAuth flow and the hosted SDK backend are at api.klavis.ai.
Architecture
flowchart LR
A["Agent / IDE"] -->|"MCP stdio or HTTP"| S["Strata router"]
S --> T["5 meta-tools (tools.py)"]
T --> B["BM25 search"]
T --> M["MCPClientManager"]
M -->|"stdio"| L["Local MCP server process"]
M -->|"HTTP / SSE"| R["Remote MCP server"]
R --> X["Third-party API"]
L --> X
C["servers.json"] -.->|"watched"| M
W["OAuth wrapper image"] -->|"AUTH_DATA"| R
W -->|"create instance, poll"| K["api.klavis.ai"]
| Component | Path | Role |
|---|---|---|
| Strata server | open-strata/src/strata/server.py |
MCP server over stdio, SSE or streamable HTTP. It exposes only the meta-tools. |
| Meta-tools | open-strata/src/strata/tools.py |
Tool schemas and the execute_tool dispatcher |
| Client manager | open-strata/src/strata/mcp_client_manager.py |
Connects to each configured server and re-syncs when the config changes |
| Config | open-strata/src/strata/config.py |
MCPServerConfig dataclass, servers.json loader, file watcher |
| Search | open-strata/src/strata/utils/shared_search.py, bm25_search.py |
Field-weighted BM25 over tool names and descriptions |
| OAuth client | open-strata/src/strata/mcp_proxy/auth_provider.py |
MCP-SDK OAuth provider with a localhost callback and token files |
| MCP servers | mcp_servers/<name>/ |
One server per integration, each with its own Dockerfile |
| OAuth layer | _oauth_support/ |
Wraps a server image. It fetches AUTH_DATA from the Klavis API before start-up. |
| Chat clients | mcp-clients/src/mcp_clients/ |
Slack, Discord, WhatsApp and web bots that use MCP servers through an LLM |
| SDK codegen | fern/ |
Fern config that builds Python and TS SDKs from the hosted API’s OpenAPI spec |
How a request flows
strata run(orstrata run --port N) callsrun_stdio_serverorrun_server(cli.py). In HTTP mode, Starlette mounts/sse,/messages/and a stateless/mcp(server.py).config_watching_contextcallsinitialize_from_config. This connects every enabled server fromservers.json. Then it starts awatchgodwatcher, which callssync_with_configwhen the file changes (server.py, mcp_client_manager.py)._connect_serverbuilds anHTTPTransport(forsse/http) or aStdioTransport(for a command) and opens an MCPClientSession(mcp_client_manager.py).- The agent calls
list_tools. It receives the five meta-tools, and theirserver_namesenum lists the connected servers (server.py, tools.py). discover_server_actionsgets each server’s tool list (cached inMCPClient._tools_cache). Then it ranks the tools withUniversalToolSearcherand returns only the action names (tools.py).get_action_detailsreturns one tool’sinputSchema.execute_actionparsespath_params,query_paramsandbody_schemafrom JSON strings, merges them into one flat argument dict and callsclient.call_tool(tools.py).MCPClient.call_toolsends the call to the downstream session. If the result hasisError, it raisesRuntimeError. The dispatcher catches the error and returns it as JSON text (client.py).- The downstream server (for example Airtable) reads its token from
AUTH_DATAor from a base64x-auth-dataheader, puts it in aContextVarand calls the vendor API (airtable/server.py).
Key components
Strata router
Strata is about 3,000 lines of Python. It has no database. Its only state is the in-memory client map and the config file. Search uses bm25s with a stemmer. Each field (server name, tool name, description, parameters) is indexed as a separate document and gets a weight (shared_search.py). strata tool add writes the router into the MCP config of Cursor, VS Code, Claude Code or Gemini CLI (utils/tool_integration.py).
MCP servers
Each directory in mcp_servers/ is its own project. Most are Python or TypeScript, and a few are Go (github, and forks such as github_official and slack_atlas). There is no shared manifest or base class. A Python server registers @app.list_tools() and @app.call_tool() on a low-level MCP Server. Then it dispatches on the tool name in a long if/elif chain (airtable/server.py), and serves SSE and streamable HTTP from the same app (L711-L752). The repo also has several benchmark variants (*_toolathlon, *_mcpmark, *_atlas), so the number of separate SaaS products is smaller than the number of directories.
Auth
There are two separate paths. Server images: CI builds an -oauth variant from Dockerfile.template. Its entrypoint sources oauth_acquire.sh. This script creates an instance at api.klavis.ai, prints the OAuth URL and polls until authData arrives. Then the wrapper writes the result to .env. This flow needs a KLAVIS_API_KEY. Strata: a server with auth: "oauth" gets an MCP-SDK OAuthClientProvider with dynamic client registration, a callback on localhost:3030, and tokens saved as plaintext JSON under .tokens/<server>/tokens.json relative to the working directory (auth_provider.py, L160-L199).
Chat clients
mcp-clients gives one MCPClient to all the bots. It converts MCP tools to Anthropic or OpenAI tool formats (mcp_client.py). For each call, it finds the server that owns the tool and sends a progress message every 30 seconds while the call runs (L331-L386). The production path (USE_PRODUCTION_DB=true) imports mcp_clients.database, but that package is an empty directory at this SHA. Self-hosters get the local path, which reads local_mcp_servers.json (base_bot.py).
Extending it
- New integration: add
mcp_servers/<name>/with a server and a Dockerfile (seeMCP_SERVER_GUIDE.md). To build an OAuth image, add the name to_oauth_support/server_name.json. Themcp-servers-build.ymlworkflow builds only the changed servers. - New server in Strata: run
strata addor editservers.json. The fields aretype,url/command,headers,env,authandenabled(config.py). A running router picks up the change by itself. - Framework examples:
examples/has small integrations for LangChain, LlamaIndex, CrewAI, Mastra, Google ADK, OpenAI and others. These call the hosted Klavis SDK.
Running it
- Router:
pip install strata-mcp, thenstrata add ..., thenstrata run(stdio) orstrata run --port 8080. Config lives at~/.config/strata/servers.json(fromplatformdirs). - A single server: build its Dockerfile from the repo root, or run the published GHCR image. Pass the token with
AUTH_DATAor thex-auth-dataheader. You can also use the-oauthimage withKLAVIS_API_KEY. - Bots: use the
Dockerfile.slack,.discord,.webor.whatsappfile inmcp-clients/. Each needs LLM API keys and server URLs.
Strengths and caveats
- Strength: Strata keeps the agent’s tool list at five, however many servers sit behind it, and it works with any MCP server, not only Klavis servers.
- Strength: each integration is a normal, separate MCP server that runs without the router.
- Caveat:
handle_auth_failureis a stub.save_auth_datareturns “success” but stores nothing (tools.py). - Caveat: the router has no retries or rate limits, and it sets no timeout for a tool call. Tool lists stay cached until a reconnect. Each server handles pagination and errors in its own way.
- Caveat:
execute_actionmerges path, query and body params into one dict, so a key that appears in two of them is overwritten. - Caveat:
strata run --config-pathonly setsMCP_CONFIG_PATH. The router’s globalMCPClientManageris created at import time with the default path, and no code reads that variable. - Caveat: the OAuth image depends on the hosted Klavis API. In
oauth_acquire.sh, the timeout check reads$?afterrm, so it never sees the timeout’s exit code, and the message says 5 minutes while the limit is 600 s. - Caveat: the code has no multi-tenant credential store, encryption at rest, scheduled sync or webhook triggers. Those features are in the hosted product, if anywhere.
Sources: code at 45c9f7d, deepwiki-open wiki (12 pages), verified Q&A.
How it answers the API layer & connectors questions
Each answer was drafted by a code-reading agent at commit 45c9f7d. Its citations were checked mechanically. Compare with the other api layer & connectors →
How is third-party authentication implemented?
answeredThird-party auth uses two approaches. OAuth2 via Klavis cloud (_oauth_support/oauth_acquire.sh:1-93): Docker wrapper calls api.klavis.ai to create OAuth instance, shows URL, polls until authorized. Strata OAuth provider (open-strata/src/strata/mcp_proxy/auth_provider.py:160-200): OAuthClientProvider with authorization_code/refresh_token grants. LocalTokenStorage (auth_provider.py:27-54) persists tokens to .tokens/{name}/tokens.json. CallbackServer on port 3030 (auth_provider.py:110-154). HTTPTransport (transport/http.py:46-80) passes provider when auth=oauth. Multi-tenant: per-server OAuth instances. Servers like Airtable (mcp_servers/airtable/server.py:43-70) extract tokens from AUTH_DATA or x-auth-data header.
How is an integration / connector defined?
answeredEach integration is an MCP server in mcp_servers/. 105 servers ship with the repo. No single manifest format; servers in Python (Airtable: mcp_servers/airtable/server.py:1-80), Go (GitHub: mcp_servers/github/server.go:1-80), or TypeScript, each with Dockerfile. Code-defined via app.list_tools() and app.call_tool(). Strata config (open-strata/src/strata/config.py:13-87): MCPServerConfig dataclass with name, type, url/command, headers, env, auth, enabled. Stored in ~/.config/strata/servers.json. OpenAPI codegen (fern/generators.yml:1-69): Fern generates Python and TypeScript SDKs from openapi.json. Versions: Fern v3.5.0 (fern/fern.config.json:1-4); strata-mcp 1.0.2 (open-strata/pyproject.toml:3).
mcp_servers/ holds 101 server directories, not 105. The other entries are README and package files, and several directories are benchmark variants (*_toolathlon, *_mcpmark, *_atlas).How are integrations exposed to LLM agents?
answeredTwo layers. Strata (open-strata/src/strata/server.py:1-206): single MCP server exposing 5 meta-tools (tools.py:22-145): discover_server_actions, get_action_details, execute_action, search_documentation, handle_auth_failure. list_tools() (server.py:81-90) returns only these 5. BM25 search (shared_search.py:47-151): UniversalToolSearcher with weighted indexes. MCP Clients (mcp_client.py:271-304): list_all_tools() converts to Anthropic/OpenAI formats. Per-LLM (llms/openai.py, llms/anthropic.py): streaming with tool calls, unified ChatMessage model. Platform bots: Slack, Discord, Web, WhatsApp. Formats (LLM.md:242-250): OpenAI, Anthropic, Gemini, MCP native.
How is a tool call executed?
answeredLayered proxy. Strata (tools.py:246-286): execute_action looks up MCPClient, merges params, calls client.call_tool(). MCPClient (mcp_proxy/client.py:97-122): session.call_tool() on MCP ClientSession; result.isError raises RuntimeError. Transport (mcp_client_manager.py:82-118): HTTPTransport or StdioTransport. Base (transport/base.py:32-56) uses AsyncExitStack. Direct client (mcp_client.py:331-386): find_server_for_tool(), 30s progress updates. Error mapping: exceptions caught/logged, returned as TextContent (tools.py:288-292). No sandboxing (process/container isolation). No rate limiting/retries. Sensitive params redacted (mcp_client.py:109-139). Pagination is server-specific.
How are data sync, webhooks and triggers implemented?
insufficient evidenceNo centralized webhook or sync framework found. MCP servers are stateless request-response. Slack event routes (mcp-clients/src/mcp_clients/slack/event_routes.py:1-50) handle incoming Slack events (POST /slack/events) and interactive components (POST /slack/interactive) -- platform-specific message routing, not a generic webhook system. No scheduled syncs, incremental cursors, or event triggers for agents. Pagination handled internally by servers. These features may exist in the hosted Klavis cloud.
How is it self-hosted and what is open vs proprietary?
answeredLicense: Apache 2.0 (LICENSE, open-strata/LICENSE). Open in repo: (1) 105 MCP server integrations; (2) Strata (open-strata/) with CLI, config, OAuth provider, BM25 search; (3) Docker OAuth wrapper (_oauth_support/); (4) MCP clients for Slack/Discord/Web/WhatsApp (mcp-clients/); (5) Fern codegen config. Self-host: pip install strata-mcp, configure ~/.config/strata/servers.json. No external database. Cloud-dependent: Klavis API for OAuth (oauth_acquire.sh:25-30, line 49). Managed Strata and SDKs are hosted. But Strata's own OAuth provider (auth_provider.py:160-200) works with standard OAuth independently.