MCP (Model Context Protocol)
MCP lets KOPI O Agent connect to external tool servers so the agent can use tools that live outside Kopi itself — GitHub, databases, file systems, browser stacks, internal APIs, and more.
If you have ever wanted Kopi to use a tool that already exists somewhere else, MCP is usually the cleanest way to do it.
What MCP gives you
- Access to external tool ecosystems without writing a native Kopi tool first
- Local stdio servers and remote HTTP MCP servers in the same config
- Automatic tool discovery and registration at startup
- Utility wrappers for MCP resources and prompts when supported by the server
- Per-server filtering so you can expose only the MCP tools you actually want Kopi to see
Quick start
- Install MCP support (already included if you used the standard install script):
cd ~/.kopi/kopi-agent
uv pip install -e ".[mcp]"
- Add an MCP server to
~/.kopi/config.yaml:
mcp_servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
- Start Kopi:
kopi chat
- Ask Kopi to use the MCP-backed capability.
For example:
List the files in /home/user/projects and summarize the repo structure.
Kopi will discover the MCP server's tools and use them like any other tool.
Two kinds of MCP servers
Stdio servers
Stdio servers run as local subprocesses and talk over stdin/stdout.
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
Use stdio servers when:
- the server is installed locally
- you want low-latency access to local resources
- you are following MCP server docs that show
command,args, andenv
HTTP servers
HTTP MCP servers are remote endpoints Kopi connects to directly.
mcp_servers:
remote_api:
url: "https://mcp.example.com/mcp"
headers:
Authorization: "Bearer ***"
Use HTTP servers when:
- the MCP server is hosted elsewhere
- your organization exposes internal MCP endpoints
- you do not want Kopi spawning a local subprocess for that integration
Basic configuration reference
Kopi reads MCP config from ~/.kopi/config.yaml under mcp_servers.
Common keys
| Key | Type | Meaning |
|---|---|---|
command | string | Executable for a stdio MCP server |
args | list | Arguments for the stdio server |
env | mapping | Environment variables passed to the stdio server |
url | string | HTTP MCP endpoint |
headers | mapping | HTTP headers for remote servers |
timeout | number | Tool call timeout |
connect_timeout | number | Initial connection timeout |
enabled | bool | If false, Kopi skips the server entirely |
tools | mapping | Per-server tool filtering and utility policy |
Minimal stdio example
mcp_servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Minimal HTTP example
mcp_servers:
company_api:
url: "https://mcp.internal.example.com"
headers:
Authorization: "Bearer ***"
How Kopi registers MCP tools
Kopi prefixes MCP tools so they do not collide with built-in names:
mcp_<server_name>_<tool_name>
Examples:
| Server | MCP tool | Registered name |
|---|---|---|
filesystem | read_file | mcp_filesystem_read_file |
github | create-issue | mcp_github_create_issue |
my-api | query.data | mcp_my_api_query_data |
In practice, you usually do not need to call the prefixed name manually — Kopi sees the tool and chooses it during normal reasoning.
MCP utility tools
When supported, Kopi also registers utility tools around MCP resources and prompts:
list_resourcesread_resourcelist_promptsget_prompt
These are registered per server with the same prefix pattern, for example:
mcp_github_list_resourcesmcp_github_get_prompt
Important
These utility tools are now capability-aware:
- Kopi only registers resource utilities if the MCP session actually supports resource operations
- Kopi only registers prompt utilities if the MCP session actually supports prompt operations
So a server that exposes callable tools but no resources/prompts will not get those extra wrappers.
Per-server filtering
You can control which tools each MCP server contributes to Kopi, allowing fine-grained management of your tool namespace.
Disable a server entirely
mcp_servers:
legacy:
url: "https://mcp.legacy.internal"
enabled: false
If enabled: false, Kopi skips the server completely and does not even attempt a connection.
Whitelist server tools
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
tools:
include: [create_issue, list_issues]
Only those MCP server tools are registered.
Blacklist server tools
mcp_servers:
stripe:
url: "https://mcp.stripe.com"
tools:
exclude: [delete_customer]
All server tools are registered except the excluded ones.
Precedence rule
If both are present:
tools:
include: [create_issue]
exclude: [create_issue, delete_issue]
include wins.
Filter utility tools too
You can also separately disable Kopi-added utility wrappers:
mcp_servers:
docs:
url: "https://mcp.docs.example.com"
tools:
prompts: false
resources: false
That means:
tools.resources: falsedisableslist_resourcesandread_resourcetools.prompts: falsedisableslist_promptsandget_prompt
Full example
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
tools:
include: [create_issue, list_issues, search_code]
prompts: false
stripe:
url: "https://mcp.stripe.com"
headers:
Authorization: "Bearer ***"
tools:
exclude: [delete_customer]
resources: false
legacy:
url: "https://mcp.legacy.internal"
enabled: false
What happens if everything is filtered out?
If your config filters out all callable tools and disables or omits all supported utilities, Kopi does not create an empty runtime MCP toolset for that server.
That keeps the tool list clean.
Runtime behavior
Discovery time
Kopi discovers MCP servers at startup and registers their tools into the normal tool registry.
Dynamic Tool Discovery
MCP servers can notify Kopi when their available tools change at runtime by sending a notifications/tools/list_changed notification. When Kopi receives this notification, it automatically re-fetches the server's tool list and updates the registry — no manual /reload-mcp required.
This is useful for MCP servers whose capabilities change dynamically (e.g. a server that adds tools when a new database schema is loaded, or removes tools when a service goes offline).
The refresh is lock-protected so rapid-fire notifications from the same server don't cause overlapping refreshes. Prompt and resource change notifications (prompts/list_changed, resources/list_changed) are received but not yet acted on.
Reloading
If you change MCP config, use:
/reload-mcp
This reloads MCP servers from config and refreshes the available tool list. For runtime tool changes pushed by the server itself, see Dynamic Tool Discovery above.
Toolsets
Each configured MCP server also creates a runtime toolset when it contributes at least one registered tool:
mcp-<server>
That makes MCP servers easier to reason about at the toolset level.
Security model
Stdio env filtering
For stdio servers, Kopi does not blindly pass your full shell environment.
Only explicitly configured env plus a safe baseline are passed through. This reduces accidental secret leakage.
Config-level exposure control
The new filtering support is also a security control:
- disable dangerous tools you do not want the model to see
- expose only a minimal whitelist for a sensitive server
- disable resource/prompt wrappers when you do not want that surface exposed
Example use cases
GitHub server with a minimal issue-management surface
mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "***"
tools:
include: [list_issues, create_issue, update_issue]
prompts: false
resources: false
Use it like:
Show me open issues labeled bug, then draft a new issue for the flaky MCP reconnection behavior.
Stripe server with dangerous actions removed
mcp_servers:
stripe:
url: "https://mcp.stripe.com"
headers:
Authorization: "Bearer ***"
tools:
exclude: [delete_customer, refund_payment]
Use it like:
Look up the last 10 failed payments and summarize common failure reasons.
Filesystem server for a single project root
mcp_servers:
project_fs:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/my-project"]
Use it like:
Inspect the project root and explain the directory layout.
Troubleshooting
MCP server not connecting
Check:
# Verify MCP deps are installed (already included in standard install)
cd ~/.kopi/kopi-agent && uv pip install -e ".[mcp]"
node --version
npx --version
Then verify your config and restart Kopi.
Tools not appearing
Possible causes:
- the server failed to connect
- discovery failed
- your filter config excluded the tools
- the utility capability does not exist on that server
- the server is disabled with
enabled: false
If you are intentionally filtering, this is expected.
Why didn't resource or prompt utilities appear?
Because Kopi now only registers those wrappers when both are true:
- your config allows them
- the server session actually supports the capability
This is intentional and keeps the tool list honest.
MCP Sampling Support
MCP servers can request LLM inference from Kopi via the sampling/createMessage protocol. This allows an MCP server to ask Kopi to generate text on its behalf — useful for servers that need LLM capabilities but don't have their own model access.
Sampling is enabled by default for all MCP servers (when the MCP SDK supports it). Configure it per-server under the sampling key:
mcp_servers:
my_server:
command: "my-mcp-server"
sampling:
enabled: true # Enable sampling (default: true)
model: "openai/gpt-4o" # Override model for sampling requests (optional)
max_tokens_cap: 4096 # Max tokens per sampling response (default: 4096)
timeout: 30 # Timeout in seconds per request (default: 30)
max_rpm: 10 # Rate limit: max requests per minute (default: 10)
max_tool_rounds: 5 # Max tool-use rounds in sampling loops (default: 5)
allowed_models: [] # Allowlist of model names the server may request (empty = any)
log_level: "info" # Audit log level: debug, info, or warning (default: info)
The sampling handler includes a sliding-window rate limiter, per-request timeouts, and tool-loop depth limits to prevent runaway usage. Metrics (request count, errors, tokens used) are tracked per server instance.
To disable sampling for a specific server:
mcp_servers:
untrusted_server:
url: "https://mcp.example.com"
sampling:
enabled: false
Running Kopi as an MCP server
In addition to connecting to MCP servers, Kopi can also be an MCP server. This lets other MCP-capable agents (Claude Code, Cursor, Codex, or any MCP client) use Kopi's messaging capabilities — list conversations, read message history, and send messages across all your connected platforms.
When to use this
- You want Claude Code, Cursor, or another coding agent to send and read Telegram/Discord/Slack messages through Kopi
- You want a single MCP server that bridges to all of Kopi's connected messaging platforms at once
- You already have a running Kopi gateway with connected platforms
Quick start
kopi mcp serve
This starts a stdio MCP server. The MCP client (not you) manages the process lifecycle.
MCP client configuration
Add Kopi to your MCP client config. For example, in Claude Code's ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"kopi": {
"command": "kopi",
"args": ["mcp", "serve"]
}
}
}
Or if you installed Kopi in a specific location:
{
"mcpServers": {
"kopi": {
"command": "/home/user/.kopi/kopi-agent/venv/bin/kopi",
"args": ["mcp", "serve"]
}
}
}
Available tools
The MCP server exposes 10 tools, matching OpenClaw's channel bridge surface plus a Kopi-specific channel browser:
| Tool | Description |
|---|---|
conversations_list | List active messaging conversations. Filter by platform or search by name. |
conversation_get | Get detailed info about one conversation by session key. |
messages_read | Read recent message history for a conversation. |
attachments_fetch | Extract non-text attachments (images, media) from a specific message. |
events_poll | Poll for new conversation events since a cursor position. |
events_wait | Long-poll / block until the next event arrives (near-real-time). |
messages_send | Send a message through a platform (e.g. telegram:123456, discord:#general). |
channels_list | List available messaging targets across all platforms. |
permissions_list_open | List pending approval requests observed during this bridge session. |
permissions_respond | Allow or deny a pending approval request. |
Event system
The MCP server includes a live event bridge that polls Kopi's session database for new messages. This gives MCP clients near-real-time awareness of incoming conversations:
# Poll for new events (non-blocking)
events_poll(after_cursor=0)
# Wait for next event (blocks up to timeout)
events_wait(after_cursor=42, timeout_ms=30000)
Event types: message, approval_requested, approval_resolved
The event queue is in-memory and starts when the bridge connects. Older messages are available through messages_read.
Options
kopi mcp serve # Normal mode
kopi mcp serve --verbose # Debug logging on stderr
How it works
The MCP server reads conversation data directly from Kopi's session store (~/.kopi/sessions/sessions.json and the SQLite database). A background thread polls the database for new messages and maintains an in-memory event queue. For sending messages, it uses the same send_message infrastructure as the Kopi agent itself.
The gateway does NOT need to be running for read operations (listing conversations, reading history, polling events). It DOES need to be running for send operations, since the platform adapters need active connections.
Current limits
- Stdio transport only (no HTTP MCP transport yet)
- Event polling at ~200ms intervals via mtime-optimized DB polling (skips work when files are unchanged)
- No
claude/channelpush notification protocol yet - Text-only sends (no media/attachment sending through
messages_send)