|
| 1 | +# Virtual MCP Server |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +Organizations need to consolidate multiple MCP servers into a unified interface for complex workflows spanning multiple tools and services. Currently, clients must manage connections to individual MCP servers separately, leading to complexity in orchestration and authentication management. |
| 6 | + |
| 7 | +## Goals |
| 8 | + |
| 9 | +- Aggregate multiple MCP servers from a ToolHive group into a single virtual MCP server |
| 10 | +- Enable tool namespace management and conflict resolution |
| 11 | +- Support composite tools for multi-step workflows across backends |
| 12 | +- Handle per-backend authentication/authorization requirements |
| 13 | +- Maintain full MCP protocol compatibility |
| 14 | + |
| 15 | +## Proposed Solution |
| 16 | + |
| 17 | +### High-Level Design |
| 18 | + |
| 19 | +The Virtual MCP Server (`thv virtual`) acts as an aggregation proxy that: |
| 20 | +1. References an existing ToolHive group containing multiple MCP server workloads |
| 21 | +2. Discovers and merges capabilities (tools, resources, prompts) from all workloads |
| 22 | +3. Routes incoming MCP requests to appropriate backend workloads |
| 23 | +4. Manages per-backend authentication requirements |
| 24 | + |
| 25 | +``` |
| 26 | +MCP Client → Virtual MCP → [Workload A, Workload B, ... from Group] |
| 27 | +``` |
| 28 | + |
| 29 | +### Configuration Schema |
| 30 | + |
| 31 | +```yaml |
| 32 | +# virtual-mcp-config.yaml |
| 33 | +version: v1 |
| 34 | +name: "engineering-tools" |
| 35 | +description: "Virtual MCP for engineering team" |
| 36 | + |
| 37 | +# Reference existing ToolHive group (required) |
| 38 | +group: "engineering-team" |
| 39 | + |
| 40 | +# Tool aggregation using existing ToolHive constructs |
| 41 | +aggregation: |
| 42 | + conflict_resolution: "prefix" # prefix | priority | manual |
| 43 | + |
| 44 | + tools: |
| 45 | + - workload: "github" |
| 46 | + # Filter to only include specific tools (uses existing ToolsFilter) |
| 47 | + filter: ["create_pr", "merge_pr", "list_issues"] |
| 48 | + # Override tool names/descriptions (uses existing ToolOverride) |
| 49 | + overrides: |
| 50 | + create_pr: |
| 51 | + name: "gh_create_pr" |
| 52 | + description: "Create a GitHub pull request" |
| 53 | + |
| 54 | + - workload: "jira" |
| 55 | + # If no filter specified, all tools are included |
| 56 | + overrides: |
| 57 | + create_issue: |
| 58 | + name: "jira_create_issue" |
| 59 | + |
| 60 | + - workload: "slack" |
| 61 | + # No filter or overrides - include all tools as-is |
| 62 | + |
| 63 | +# Per-backend authentication |
| 64 | +backend_auth: |
| 65 | + github: |
| 66 | + type: "token_exchange" |
| 67 | + token_url: "https://github.com/oauth/token" |
| 68 | + audience: "github-api" |
| 69 | + client_id_ref: |
| 70 | + name: "github-oauth" |
| 71 | + key: "client_id" |
| 72 | + |
| 73 | + jira: |
| 74 | + type: "pass_through" |
| 75 | + |
| 76 | + internal_db: |
| 77 | + type: "service_account" |
| 78 | + credentials_ref: |
| 79 | + name: "db-creds" |
| 80 | + key: "token" |
| 81 | + |
| 82 | + slack: |
| 83 | + type: "header_injection" |
| 84 | + headers: |
| 85 | + - name: "Authorization" |
| 86 | + value_ref: |
| 87 | + name: "slack-creds" |
| 88 | + key: "bot_token" |
| 89 | + |
| 90 | +# Composite tools (Phase 2) |
| 91 | +composite_tools: |
| 92 | + - name: "deploy_and_notify" |
| 93 | + description: "Deploy PR and notify team" |
| 94 | + parameters: |
| 95 | + pr_number: {type: "integer"} |
| 96 | + steps: |
| 97 | + - id: "merge" |
| 98 | + tool: "github.merge_pr" |
| 99 | + arguments: {pr: "{{.params.pr_number}}"} |
| 100 | + |
| 101 | + - id: "notify" |
| 102 | + tool: "slack.send" |
| 103 | + arguments: {text: "Deployed PR {{.params.pr_number}}"} |
| 104 | + depends_on: ["merge"] |
| 105 | + |
| 106 | +# Operational settings |
| 107 | +operational: |
| 108 | + timeouts: |
| 109 | + default: 30s |
| 110 | + per_workload: |
| 111 | + github: 45s |
| 112 | + |
| 113 | + failover: |
| 114 | + strategy: "automatic" |
| 115 | +``` |
| 116 | +
|
| 117 | +### Key Features |
| 118 | +
|
| 119 | +#### 1. Group-Based Backend Management |
| 120 | +
|
| 121 | +Virtual MCP references a ToolHive group and automatically discovers all workloads within it: |
| 122 | +- Leverages existing `groups.Manager` and `workloads.Manager` |
| 123 | +- Dynamic workload discovery via `ListWorkloadsInGroup()` |
| 124 | +- Inherits workload configurations from group |
| 125 | + |
| 126 | +#### 2. Capability Aggregation |
| 127 | + |
| 128 | +Merges MCP capabilities from all backends: |
| 129 | +- **Tools**: Aggregated with namespace conflict resolution |
| 130 | +- **Resources**: Combined from all backends |
| 131 | +- **Prompts**: Merged with prefixing |
| 132 | +- **Logging/Sampling**: Enabled if any backend supports it |
| 133 | + |
| 134 | +#### 3. Tool Filtering and Overrides |
| 135 | + |
| 136 | +Uses existing ToolHive constructs: |
| 137 | +- **ToolsFilter**: Include only specific tools from a workload |
| 138 | +- **ToolOverride**: Rename tools and update descriptions to avoid conflicts |
| 139 | + |
| 140 | +#### 4. Per-Backend Authentication |
| 141 | + |
| 142 | +Different backends may require different authentication strategies: |
| 143 | +- **pass_through**: Forward client credentials unchanged |
| 144 | +- **token_exchange**: Exchange incoming token for backend-specific token (RFC 8693) |
| 145 | +- **service_account**: Use stored credentials for backend |
| 146 | +- **header_injection**: Add authentication headers from secrets |
| 147 | +- **mapped_claims**: Transform JWT claims for backend requirements |
| 148 | + |
| 149 | +Example authentication flow: |
| 150 | +``` |
| 151 | +Client → (Bearer token for Virtual MCP) → Virtual MCP |
| 152 | + ├→ GitHub backend (token exchanged for GitHub PAT) |
| 153 | + ├→ Jira backend (original token passed through) |
| 154 | + └→ Internal DB (service account token injected) |
| 155 | +``` |
| 156 | + |
| 157 | +#### 5. Request Routing |
| 158 | + |
| 159 | +Routes MCP protocol requests to appropriate backends: |
| 160 | +- Tool calls routed based on tool-to-workload mapping |
| 161 | +- Resource requests routed by resource namespace |
| 162 | +- Prompt requests handled similarly |
| 163 | +- Load balancing for duplicate capabilities |
| 164 | + |
| 165 | +### CLI Usage |
| 166 | + |
| 167 | +```bash |
| 168 | +# Start virtual MCP for a group |
| 169 | +thv virtual --group engineering-team --config virtual-config.yaml |
| 170 | +
|
| 171 | +# Quick start with defaults |
| 172 | +thv virtual --group engineering-team |
| 173 | +
|
| 174 | +# With inline tool filtering |
| 175 | +thv virtual --group engineering-team \ |
| 176 | + --tools github:create_pr,merge_pr \ |
| 177 | + --tools jira:create_issue |
| 178 | +
|
| 179 | +# With tool overrides file |
| 180 | +thv virtual --group engineering-team \ |
| 181 | + --tools-override overrides.yaml |
| 182 | +``` |
| 183 | + |
| 184 | +### Implementation Phases |
| 185 | + |
| 186 | +**Phase 1 (MVP)**: Basic aggregation |
| 187 | +- Group-based workload discovery |
| 188 | +- Tool aggregation with filter and override support |
| 189 | +- Simple request routing |
| 190 | +- Pass-through authentication |
| 191 | + |
| 192 | +**Phase 2**: Advanced features |
| 193 | +- Composite tool execution |
| 194 | +- Per-backend authentication strategies |
| 195 | +- Token exchange support |
| 196 | +- Failover and load balancing |
| 197 | + |
| 198 | +**Phase 3**: Enterprise features |
| 199 | +- Dynamic configuration updates |
| 200 | +- Multi-group support |
| 201 | +- Advanced routing strategies |
| 202 | +- Comprehensive observability |
| 203 | + |
| 204 | +## Benefits |
| 205 | + |
| 206 | +- **Simplified Client Integration**: Single MCP connection instead of multiple |
| 207 | +- **Unified Authentication**: Handle diverse backend auth requirements centrally |
| 208 | +- **Workflow Orchestration**: Composite tools enable cross-service workflows |
| 209 | +- **Operational Efficiency**: Centralized monitoring and management |
| 210 | +- **Backward Compatible**: Works with existing MCP clients and servers |
| 211 | + |
| 212 | +## Implementation Notes |
| 213 | + |
| 214 | +### Reusing Existing Components |
| 215 | + |
| 216 | +The implementation will maximize reuse of existing ToolHive components: |
| 217 | + |
| 218 | +1. **Tool Filtering**: Use existing `mcp.WithToolsFilter()` middleware |
| 219 | +2. **Tool Overrides**: Use existing `mcp.WithToolsOverride()` middleware |
| 220 | +3. **Groups**: Use `groups.Manager` for group operations |
| 221 | +4. **Workloads**: Use `workloads.Manager` for workload discovery |
| 222 | +5. **Authentication**: Extend existing auth middleware patterns |
| 223 | + |
| 224 | +### Authentication Complexity |
| 225 | + |
| 226 | +Different MCP servers may have vastly different authentication requirements: |
| 227 | +- Public servers may need no auth |
| 228 | +- Enterprise servers may require OAuth/OIDC |
| 229 | +- Internal services may use service accounts |
| 230 | +- Third-party APIs may need API keys |
| 231 | + |
| 232 | +The Virtual MCP must handle this complexity transparently, maintaining separate authentication contexts per backend while presenting a unified interface to clients. |
| 233 | + |
| 234 | +## Alternative Approaches Considered |
| 235 | + |
| 236 | +1. **Kubernetes CRD**: More complex, requires operator changes |
| 237 | +2. **Standalone Service**: Loses integration with ToolHive infrastructure |
| 238 | +3. **LLM-generated backend**: Adds more randomness to the equation which is undesirable for agents. |
| 239 | + |
| 240 | +## Open Questions |
| 241 | + |
| 242 | +1. How to handle streaming responses across multiple backends? |
| 243 | +2. Should we cache backend capabilities or query dynamically? |
| 244 | +3. How to handle backend-specific rate limits? |
| 245 | + |
| 246 | +## Success Criteria |
| 247 | + |
| 248 | +- Aggregate 5+ MCP servers from a group with < 10ms routing overhead |
| 249 | +- Support 100+ concurrent client connections |
| 250 | +- Zero changes required to existing MCP servers or clients |
| 251 | +- Successfully handle different authentication methods per backend |
| 252 | +- Maintain existing tool filter and override semantics |
0 commit comments