Skip to content

MCP Servers in Keen

Keen supports MCP (Model Context Protocol) servers as external tool providers. MCP servers are loaded from a user-level JSON config, connected at startup, and exposed to the LLM through Keen's call_mcp_tool tool.

What is supported

Area Support
MCP capability exposed to the LLM Tools: Keen lists MCP tools and calls MCP tools.
Transports Streamable HTTP and stdio.
HTTP auth none, api_key, and oauth.
Stdio auth No HTTP auth. Use process environment variables for stdio server credentials.
Tool discovery Keen lists tools during startup and on manual refresh/connect. Paginated tool lists are supported.
Tool calls Keen calls a named tool on a connected configured server with JSON object arguments.
OAuth Browser-based OAuth during /mcp connect <server> and persisted OAuth tokens under Keen's auth store.
Permissions Every call_mcp_tool invocation asks for user approval before calling the remote tool.
Status UI /mcp status shows configured servers, connection state, auth type, and errors.

What is not supported

Keen's current MCP integration is intentionally tool-focused:

  • MCP resources and prompts are not exposed to the LLM.
  • Server-initiated tool-list changes are logged, but they do not automatically regenerate skills until refresh/connect or restart.
  • There is no project-local MCP config file; MCP config is read from the user home directory.
  • There is no live reload of ~/.keen/mcp/configs.json; restart Keen after adding or removing servers.
  • There is no explicit transport field in config. Keen infers transport from the presence of command.
  • Legacy SSE is not a first-class config transport. Keen uses the SDK's streamable HTTP client for HTTP MCP servers.
  • WebSocket, raw TCP, and Unix socket transports are not configured by Keen.
  • Per-server timeouts and retry settings are not configurable in the JSON file.
  • call_mcp_tool does not locally validate arguments against the schema; the LLM is instructed to read the generated schema and the MCP server remains the final authority.
  • Stdio servers cannot use Keen's HTTP auth config. Put credentials in env for the subprocess instead.

Configuration location

Put MCP server configuration in:

~/.keen/mcp/configs.json

If the file does not exist, Keen starts with no configured MCP servers.

The top-level format is:

{
  "servers": {
    "server-name": {
      "url": "https://example.com/mcp",
      "auth": { "type": "none" }
    }
  }
}

Server names must be 1-128 characters and may contain letters, numbers, underscores, dashes, and dots. They must start with a letter or number.

Transport selection

Keen infers the transport from each server entry:

Config field Transport used
command is present and non-empty stdio
command is absent or empty streamable HTTP

If both command and url are present, command wins and Keen treats the server as stdio.

Streamable HTTP examples

No auth

{
  "servers": {
    "deepwiki": {
      "url": "https://mcp.example.com/mcp",
      "auth": { "type": "none" }
    }
  }
}

auth may also be omitted for no-auth HTTP servers.

API key auth with default bearer header

When type is api_key and no header is provided, Keen sends:

Authorization: Bearer <key>
{
  "servers": {
    "docs": {
      "url": "https://docs.example.com/mcp",
      "auth": {
        "type": "api_key",
        "key": "YOUR_API_KEY"
      }
    }
  }
}

API key auth with a custom header

When header is provided and scheme is empty, Keen sends the raw key value in that header.

{
  "servers": {
    "context7": {
      "url": "https://mcp.context7.com/mcp",
      "auth": {
        "type": "api_key",
        "header": "CONTEXT7_API_KEY",
        "scheme": "",
        "key": "YOUR_CONTEXT7_API_KEY"
      }
    }
  }
}

To use a custom scheme with a custom header, set scheme explicitly:

{
  "servers": {
    "example": {
      "url": "https://mcp.example.com/mcp",
      "auth": {
        "type": "api_key",
        "header": "Authorization",
        "scheme": "Token",
        "key": "YOUR_TOKEN"
      }
    }
  }
}

This sends:

Authorization: Token <key>

OAuth auth

{
  "servers": {
    "posthog": {
      "url": "https://mcp.posthog.com/mcp",
      "auth": {
        "type": "oauth",
        "scopes": ["read", "write"]
      }
    }
  }
}

OAuth behavior:

  1. At startup, Keen can reuse a stored OAuth token for the server.
  2. If no valid token is available, the server enters auth_required or auth_failed.
  3. Run /mcp connect <server> to start browser-based OAuth.
  4. Keen listens for the callback at:
http://localhost:1456/auth/mcp/callback

OAuth tokens are stored in Keen's auth store under provider names like mcp:<server>.

Stdio example

Use command, optional args, and optional env for stdio MCP servers.

{
  "servers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_TOKEN"
      }
    }
  }
}

Stdio behavior:

  • Keen starts the command as a subprocess.
  • args are passed as command arguments.
  • env is merged into Keen's process environment and overrides matching variables.
  • Stdio server stderr is logged at debug level and truncated.
  • auth must be omitted or set to { "type": "none" }.

Runtime behavior

When Keen starts:

  1. It reads ~/.keen/mcp/configs.json.
  2. It creates one runtime entry per configured server.
  3. It connects to all configured servers concurrently.
  4. It lists each connected server's tools.
  5. It stores each server state for /mcp status and call_mcp_tool.

Connection states include:

State Meaning
configured Server exists in config but has not connected yet.
connecting Keen is connecting or refreshing the server.
connected Server is connected and tools were discovered.
disconnected Server failed to connect or a session closed.
auth_required OAuth authentication is needed.
auth_failed Authentication failed, such as an invalid token or API key.

Commands

Command Purpose
/mcp status Show all configured MCP servers and their state.
/mcp connect <server> Refresh/connect a server. For OAuth servers, starts browser auth and forces re-authentication.

/mcp connect accepts either a server name or a unique tool name. If multiple servers expose the same tool name, Keen asks for the server name.

Calling MCP tools

Keen registers one built-in LLM tool when MCP runtime starts successfully:

call_mcp_tool

The LLM must provide:

{
  "server": "github",
  "tool": "create_issue",
  "arguments": {
    "title": "Bug report",
    "body": "Details..."
  }
}

Before execution, Keen asks the user to approve the remote tool call.

Failure behavior

Scenario Behavior
Missing configs.json Keen starts with no configured MCP servers.
Invalid JSON or invalid server config MCP startup is skipped; call_mcp_tool is not registered.
One invalid server entry Config validation fails, so MCP startup is skipped for the whole config.
HTTP connection fails Server state becomes disconnected; /mcp status shows the error.
API key is missing in config Config validation fails.
API key is rejected by server Server usually becomes auth_failed. API key values are redacted from errors/logs.
OAuth token is missing Server becomes auth_required; run /mcp connect <server>.
OAuth token is rejected Server becomes auth_failed; run /mcp connect <server> to re-authenticate.
Stdio command exits or cannot start Server becomes disconnected.
Server removed from config After restart, it is no longer configured; generated MCP skills may remain on disk but are disabled by sync when previously enabled.