| 1 | # RFC: MCP Modularization |
| 2 | |
| 3 | **Issue:** #2190 |
| 4 | **Status:** Draft |
| 5 | **Date:** 2026-05-26 |
| 6 | |
| 7 | ## 1. Current state |
| 8 | |
| 9 | ### 1.1 `codewhale-mcp` crate (`crates/mcp/`) |
| 10 | |
| 11 | The current MCP implementation lives in a single crate with two responsibilities: |
| 12 | |
| 13 | - **MCP client** — connects to MCP servers over stdio, manages protocol handshake, |
| 14 | tool discovery, and tool invocation. Used by the TUI to surface MCP tools as |
| 15 | `mcp_<server>_<tool>` entries in the tool registry. |
| 16 | - **MCP stdio server** — a minimal MCP server that exposes CodeWhale's own tools |
| 17 | over stdio for external MCP clients. Used by the `codewhale mcp` CLI subcommand. |
| 18 | |
| 19 | Both the client and server share protocol types (JSON-RPC messages, tool schemas) |
| 20 | but have different lifecycle concerns and different callers. |
| 21 | |
| 22 | ### 1.2 Integration points |
| 23 | |
| 24 | - `crates/tui/src/mcp.rs` — MCP client integration: server lifecycle, tool |
| 25 | discovery, tool execution forwarding |
| 26 | - `crates/tui/src/mcp_server.rs` — MCP stdio server: exposes TUI tools via |
| 27 | stdio MCP protocol |
| 28 | - `docs/MCP.md` — user-facing documentation |
| 29 | |
| 30 | ## 2. Motivation |
| 31 | |
| 32 | ### 2.1 Separation of concerns |
| 33 | |
| 34 | The client and server share a crate but have no shared code paths at runtime. |
| 35 | They import the same protocol types but serve different roles: |
| 36 | - The client is **outbound** — it connects to external servers |
| 37 | - The server is **inbound** — it accepts connections from external clients |
| 38 | |
| 39 | Mixing them in one crate creates unnecessary coupling: changes to the server |
| 40 | API recompile the client, and vice versa. |
| 41 | |
| 42 | ### 2.2 OAuth support |
| 43 | |
| 44 | The current MCP client has no OAuth support. MCP servers that require OAuth |
| 45 | (e.g., GitHub, Google) cannot be used. Adding OAuth to the client requires: |
| 46 | - Token storage (keychain, env-based, or config-based) |
| 47 | - OAuth flow (device code, PKCE, or client credentials) |
| 48 | - Token refresh and expiry handling |
| 49 | |
| 50 | These concerns are client-side only and should not affect the server crate. |
| 51 | |
| 52 | ### 2.3 Reuse outside the TUI |
| 53 | |
| 54 | The MCP client is currently embedded in the TUI binary. If we want to use |
| 55 | MCP tools from: |
| 56 | - The `app-server` (HTTP/SSE runtime API) |
| 57 | - The `codewhale` CLI (non-interactive mode) |
| 58 | - External consumers (library use) |
| 59 | |
| 60 | ...the client needs to be a standalone crate with a clean public API. |
| 61 | |
| 62 | ## 3. Proposed crate split |
| 63 | |
| 64 | ``` |
| 65 | crates/mcp/ → crates/mcp-protocol/ (shared types, no I/O) |
| 66 | crates/mcp-client/ (client implementation) |
| 67 | crates/mcp-server/ (server implementation) |
| 68 | ``` |
| 69 | |
| 70 | ### 3.1 `codewhale-mcp-protocol` |
| 71 | |
| 72 | **Contents:** JSON-RPC message types, tool schema types, protocol constants, |
| 73 | handshake types, error types. No I/O, no async runtime dependency. |
| 74 | |
| 75 | **Dependencies:** `serde`, `serde_json`, `codewhale-protocol` (for tool schema) |
| 76 | |
| 77 | **Public API:** |
| 78 | ```rust |
| 79 | pub mod messages; // JSON-RPC request/response/notification types |
| 80 | pub mod tools; // MCP tool schema types |
| 81 | pub mod errors; // MCP error codes |
| 82 | pub mod version; // Protocol version constants |
| 83 | ``` |
| 84 | |
| 85 | ### 3.2 `codewhale-mcp-client` |
| 86 | |
| 87 | **Contents:** MCP client: stdio transport, process management, handshake, |
| 88 | tool discovery, tool invocation, OAuth support. |
| 89 | |
| 90 | **Dependencies:** `codewhale-mcp-protocol`, `tokio`, `serde_json`, `tracing`, |
| 91 | `oauth2` (new, for OAuth), `keyring` (optional, for token storage) |
| 92 | |
| 93 | **Public API:** |
| 94 | ```rust |
| 95 | pub struct McpClient { |
| 96 | // Configuration |
| 97 | } |
| 98 | |
| 99 | impl McpClient { |
| 100 | pub async fn connect(config: McpClientConfig) -> Result<Self>; |
| 101 | pub async fn list_tools(&self) -> Result<Vec<ToolSchema>>; |
| 102 | pub async fn call_tool(&self, name: &str, args: Value) -> Result<Value>; |
| 103 | pub async fn disconnect(self); |
| 104 | } |
| 105 | |
| 106 | pub struct McpClientConfig { |
| 107 | pub command: String, // e.g., "npx", "python" |
| 108 | pub args: Vec<String>, // e.g., ["-y", "@modelcontextprotocol/server-github"] |
| 109 | pub env: HashMap<String, String>, |
| 110 | pub oauth: Option<OAuthConfig>, |
| 111 | pub timeout: Duration, |
| 112 | } |
| 113 | |
| 114 | pub struct OAuthConfig { |
| 115 | pub provider: OAuthProvider, |
| 116 | pub client_id: String, |
| 117 | pub scopes: Vec<String>, |
| 118 | pub token_storage: TokenStorage, |
| 119 | } |
| 120 | |
| 121 | pub enum OAuthProvider { |
| 122 | Github, |
| 123 | Google, |
| 124 | Custom { auth_url: String, token_url: String }, |
| 125 | } |
| 126 | ``` |
| 127 | |
| 128 | ### 3.3 `codewhale-mcp-server` |
| 129 | |
| 130 | **Contents:** MCP stdio server: accepts connections, exposes tool list, |
| 131 | handles tool calls, manages stdio transport. |
| 132 | |
| 133 | **Dependencies:** `codewhale-mcp-protocol`, `codewhale-tools`, `tokio`, |
| 134 | `serde_json`, `tracing` |
| 135 | |
| 136 | **Public API:** |
| 137 | ```rust |
| 138 | pub struct McpServer { |
| 139 | // Tool registry |
| 140 | } |
| 141 | |
| 142 | impl McpServer { |
| 143 | pub fn new(tools: Vec<Arc<dyn ToolSpec>>) -> Self; |
| 144 | pub async fn serve_stdio(self) -> Result<()>; |
| 145 | pub async fn serve_sse(self, addr: SocketAddr) -> Result<()>; |
| 146 | } |
| 147 | ``` |
| 148 | |
| 149 | ## 4. Migration path |
| 150 | |
| 151 | ### Phase 1: Extract protocol crate (non-breaking) |
| 152 | |
| 153 | 1. Move shared types from `crates/mcp/src/` to `crates/mcp-protocol/src/` |
| 154 | 2. Re-export from `codewhale-mcp` for backward compatibility |
| 155 | 3. Update `Cargo.toml` in `codewhale-mcp` to depend on `codewhale-mcp-protocol` |
| 156 | |
| 157 | ### Phase 2: Split client and server (breaking for direct imports) |
| 158 | |
| 159 | 1. Create `crates/mcp-client/` with client code |
| 160 | 2. Create `crates/mcp-server/` with server code |
| 161 | 3. Update `codewhale-tui` to depend on `codewhale-mcp-client` |
| 162 | 4. Update `codewhale-cli` to depend on `codewhale-mcp-server` |
| 163 | 5. Deprecate `codewhale-mcp` crate (re-exports from new crates) |
| 164 | |
| 165 | ### Phase 3: Remove legacy crate |
| 166 | |
| 167 | 1. Remove `crates/mcp/` after a deprecation cycle (one release) |
| 168 | |
| 169 | ## 5. OAuth integration |
| 170 | |
| 171 | ### 5.1 Token storage |
| 172 | |
| 173 | Tokens should be stored securely. Options (in priority order): |
| 174 | 1. OS keychain via `keyring` crate (macOS Keychain, Windows Credential Manager, |
| 175 | Linux Secret Service) |
| 176 | 2. Encrypted file in `~/.codewhale/mcp-credentials/` (fallback) |
| 177 | 3. Environment variable `MCP_OAUTH_TOKEN_<PROVIDER>` |
| 178 | |
| 179 | ### 5.2 OAuth flows |
| 180 | |
| 181 | Initial implementation supports: |
| 182 | - **Device Code Flow** (GitHub) — user opens a URL, enters a code |
| 183 | - **Client Credentials** — for service-to-service MCP servers |
| 184 | |
| 185 | Future (deferred): |
| 186 | - **PKCE** — for user-facing OAuth with redirect |
| 187 | - **Token refresh** — automatic refresh with refresh_token |
| 188 | |
| 189 | ### 5.3 Configuration |
| 190 | |
| 191 | ```toml |
| 192 | # ~/.codewhale/config.toml |
| 193 | [mcp.servers.github] |
| 194 | command = "npx" |
| 195 | args = ["-y", "@modelcontextprotocol/server-github"] |
| 196 | |
| 197 | [mcp.servers.github.oauth] |
| 198 | provider = "github" |
| 199 | client_id = "your-client-id" |
| 200 | scopes = ["repo", "read:org"] |
| 201 | ``` |
| 202 | |
| 203 | ## 6. Risks and unknowns |
| 204 | |
| 205 | | Risk | Mitigation | |
| 206 | |---|---| |
| 207 | | Crate proliferation | 3 small crates vs 1 medium crate; each has a clear purpose | |
| 208 | | Breaking internal imports | Phase 2 carries `codewhale-mcp` deprecation shim for one release | |
| 209 | | OAuth token security | OS keychain preferred; encrypted fallback with file permissions | |
| 210 | | Testing complexity | Each crate has its own test suite; integration tests remain in `crates/tui/tests/` | |
| 211 | | Dependency bloat | `oauth2` and `keyring` are optional features; consumers opt in | |
| 212 | |
| 213 | ## 7. Out of scope (future RFCs) |
| 214 | |
| 215 | - MCP over HTTP/SSE transport (currently stdio only) |
| 216 | - MCP server discovery (currently explicit config) |
| 217 | - MCP tool result streaming (currently request-response) |
| 218 | - MCP server-side tool approval flows |
| 219 | |
| 220 | ## Related |
| 221 | |
| 222 | - `crates/mcp/src/` — current implementation |
| 223 | - `crates/tui/src/mcp.rs` — TUI MCP integration |
| 224 | - `crates/tui/src/mcp_server.rs` — MCP stdio server |
| 225 | - `docs/MCP.md` — user-facing documentation |
| 226 | - Issue #2190 — this RFC |
| 227 |