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

- Category: [API layer & connectors](https://llms-technical-reviews.com/connectors/)
- Repository: https://github.com/Klavis-AI/klavis (reviewed at commit `45c9f7da83d1cf43f7429b96f9c8e8153542ea1e`, 2026-06-01)
- Stars: 5807 · Language: Python · License: Apache-2.0
- Canonical page: https://llms-technical-reviews.com/p/klavis/

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

```mermaid
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

1. `strata run` (or `strata run --port N`) calls `run_stdio_server` or `run_server` ([cli.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/cli.py#L179-L197)). In HTTP mode, Starlette mounts `/sse`, `/messages/` and a stateless `/mcp` ([server.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/server.py#L129-L205)).
2. `config_watching_context` calls `initialize_from_config`. This connects every enabled server from `servers.json`. Then it starts a `watchgod` watcher, which calls `sync_with_config` when the file changes ([server.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/server.py#L31-L75), [mcp_client_manager.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_client_manager.py#L139-L188)).
3. `_connect_server` builds an `HTTPTransport` (for `sse`/`http`) or a `StdioTransport` (for a command) and opens an MCP `ClientSession` ([mcp_client_manager.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_client_manager.py#L82-L118)).
4. The agent calls `list_tools`. It receives the five meta-tools, and their `server_names` enum lists the connected servers ([server.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/server.py#L78-L95), [tools.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L22-L145)).
5. `discover_server_actions` gets each server's tool list (cached in `MCPClient._tools_cache`). Then it ranks the tools with `UniversalToolSearcher` and returns only the action names ([tools.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L155-L203)). `get_action_details` returns one tool's `inputSchema`.
6. `execute_action` parses `path_params`, `query_params` and `body_schema` from JSON strings, merges them into one flat argument dict and calls `client.call_tool` ([tools.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L246-L292)).
7. `MCPClient.call_tool` sends the call to the downstream session. If the result has `isError`, it raises `RuntimeError`. The dispatcher catches the error and returns it as JSON text ([client.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/client.py#L97-L122)).
8. The downstream server (for example Airtable) reads its token from `AUTH_DATA` or from a base64 `x-auth-data` header, puts it in a `ContextVar` and calls the vendor API ([airtable/server.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#L43-L70)).

## 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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/utils/shared_search.py#L47-L120)). `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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#L450-L480)), and serves SSE and streamable HTTP from the same app ([L711-L752](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/_oauth_support/docker/Dockerfile.template#L1-L30). Its entrypoint sources [oauth_acquire.sh](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/_oauth_support/oauth_acquire.sh#L23-L87). 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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L20-L54), [L160-L199](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/mcp_client.py#L271-L304)). 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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/mcp_client.py#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](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/base_bot.py#L149-L183)).

## Extending it

- **New integration:** add `mcp_servers/<name>/` with a server and a Dockerfile (see `MCP_SERVER_GUIDE.md`). To build an OAuth image, add the name to `_oauth_support/server_name.json`. The `mcp-servers-build.yml` workflow builds only the changed servers.
- **New server in Strata:** run `strata add` or edit `servers.json`. The fields are `type`, `url`/`command`, `headers`, `env`, `auth` and `enabled` ([config.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/config.py#L12-L38)). 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`, then `strata add ...`, then `strata run` (stdio) or `strata run --port 8080`. Config lives at `~/.config/strata/servers.json` (from `platformdirs`).
- A single server: build its Dockerfile from the repo root, or run the published GHCR image. Pass the token with `AUTH_DATA` or the `x-auth-data` header. You can also use the `-oauth` image with `KLAVIS_API_KEY`.
- Bots: use the `Dockerfile.slack`, `.discord`, `.web` or `.whatsapp` file in `mcp-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_failure` is a stub. `save_auth_data` returns "success" but stores nothing ([tools.py](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L322-L360)).
- **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_action` merges path, query and body params into one dict, so a key that appears in two of them is overwritten.
- **Caveat:** `strata run --config-path` only sets `MCP_CONFIG_PATH`. The router's global `MCPClientManager` is 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 `$?` after `rm`, 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 Klavis-AI/klavis answers the API layer & connectors questions

### How is third-party authentication implemented? (answered)

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


Citations: [open-strata/src/strata/mcp_proxy/auth_provider.py:160-200](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L160-L200) · [open-strata/src/strata/mcp_proxy/auth_provider.py:27-54](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L27-L54) · [open-strata/src/strata/mcp_proxy/transport/http.py:46-80](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/transport/http.py#L46-L80) · [mcp_servers/airtable/server.py:43-70](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#L43-L70)

### How is an integration / connector defined? (answered)

Each 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`).

> **Editor's note.** Correction: `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`).

Citations: [mcp_servers/airtable/server.py:1-80](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/airtable/server.py#L1-L80) · [mcp_servers/github/server.go:1-80](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp_servers/github/server.go#L1-L80) · [open-strata/src/strata/config.py:13-87](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/config.py#L13-L87) · [fern/generators.yml:1-69](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/fern/generators.yml#L1-L69) · [open-strata/pyproject.toml:1-5](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/pyproject.toml#L1-L5)

### How are integrations exposed to LLM agents? (answered)

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


Citations: [open-strata/src/strata/server.py:81-90](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/server.py#L81-L90) · [open-strata/src/strata/tools.py:22-145](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L22-L145) · [open-strata/src/strata/utils/shared_search.py:47-151](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/utils/shared_search.py#L47-L151) · [mcp-clients/src/mcp_clients/mcp_client.py:271-304](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/mcp_client.py#L271-L304) · [LLM.md:242-250](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/LLM.md#L242-L250)

### How is a tool call executed? (answered)

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


Citations: [open-strata/src/strata/tools.py:246-286](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/tools.py#L246-L286) · [open-strata/src/strata/mcp_proxy/client.py:97-122](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/client.py#L97-L122) · [open-strata/src/strata/mcp_client_manager.py:82-118](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_client_manager.py#L82-L118) · [open-strata/src/strata/mcp_proxy/transport/base.py:32-56](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/transport/base.py#L32-L56) · [mcp-clients/src/mcp_clients/mcp_client.py:331-386](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/mcp_client.py#L331-L386)

### How are data sync, webhooks and triggers implemented? (insufficient evidence)

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


Citations: [mcp-clients/src/mcp_clients/slack/event_routes.py:1-50](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/mcp-clients/src/mcp_clients/slack/event_routes.py#L1-L50)

### How is it self-hosted and what is open vs proprietary? (answered)

**License**: 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.


Citations: [LICENSE:1-3](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/LICENSE#L1-L3) · [_oauth_support/oauth_acquire.sh:25-30](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/_oauth_support/oauth_acquire.sh#L25-L30) · [open-strata/src/strata/mcp_proxy/auth_provider.py:160-200](https://github.com/Klavis-AI/klavis/blob/45c9f7da83d1cf43f7429b96f9c8e8153542ea1e/open-strata/src/strata/mcp_proxy/auth_provider.py#L160-L200)
