diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 886b91b5..79b3788c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -107,6 +107,7 @@ Common terms used in this project: - open source (not "open-source") - large language model (LLM) - Visual Studio Code ("VS Code" after first use) +- Virtual MCP Server (vMCP) - a feature of ToolHive that aggregates multiple MCP servers into a single endpoint; use "Virtual MCP Server (vMCP)" on first use, "vMCP" thereafter Check this list for consistent use within the documentation. If you find inconsistencies, update the text to match the preferred term. If you find a term that is not listed here, consider adding it to the list for future reference. diff --git a/AGENTS.md b/AGENTS.md index c424e0a3..0efaf163 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,6 +107,7 @@ Common terms used in this project: - open source (not "open-source") - large language model (LLM) - Visual Studio Code ("VS Code" after first use) +- Virtual MCP Server (vMCP) - a feature of ToolHive that aggregates multiple MCP servers into a single endpoint; use "Virtual MCP Server (vMCP)" on first use, "vMCP" thereafter Check this list for consistent use within the documentation. If you find inconsistencies, update the text to match the preferred term. If you find a term that is not listed here, consider adding it to the list for future reference. diff --git a/STYLE-GUIDE.md b/STYLE-GUIDE.md index 6c6c051f..8e8a08d3 100644 --- a/STYLE-GUIDE.md +++ b/STYLE-GUIDE.md @@ -263,6 +263,11 @@ all caps. Written out, it is lower-cased. applications provide context to LLMs. MCP is an abbreviation, so it's written in all caps. Written out, it is proper-cased. +**vMCP**: Virtual MCP Server, a feature of ToolHive that aggregates multiple MCP +servers into a single endpoint. It's written with a lowercase "v" followed by +"MC" in all caps and a capital "P" (not "VMCP" or "Vmcp"). Use "Virtual MCP +Server (vMCP)" on first use, "vMCP" thereafter. + **npm**: the registry for JavaScript packages (the "npm registry"), and the default package manager for JavaScript. Since it's both the registry _and_ the package manager, it may be useful to disambiguate "the npm registry". It's not diff --git a/docs/toolhive/concepts/vmcp.mdx b/docs/toolhive/concepts/vmcp.mdx new file mode 100644 index 00000000..b3287f55 --- /dev/null +++ b/docs/toolhive/concepts/vmcp.mdx @@ -0,0 +1,145 @@ +--- +title: Understanding Virtual MCP Server +sidebar_label: Virtual MCP Server (vMCP) +description: + Learn what Virtual MCP Server does, why it exists, and when to use it. +--- + +This document explains Virtual MCP Server (vMCP), a feature of the ToolHive +Kubernetes Operator. You'll learn why it exists, when to use it, and how it +simplifies managing multiple MCP servers while enabling powerful multi-system +workflows. + +## The problem vMCP solves + +**Before vMCP**: Engineers manage 10+ separate MCP server connections, each with +its own authentication, manually coordinate multi-step workflows across systems, +and repeatedly configure the same parameters. + +**With vMCP**: Connect once to a unified endpoint that aggregates all backend +MCP servers, execute complex multi-system workflows declaratively, and use +pre-configured tools with sensible defaults. + +## Core value propositions + +vMCP delivers four key benefits: + +1. **Reduce complexity**: Many connections become one, dramatically simplifying + configuration +2. **Speed up workflows**: Parallel execution across systems instead of + sequential calls +3. **Improve security**: Centralized authentication and authorization with a + two-boundary model +4. **Enable reusability**: Define workflows once, use them everywhere + +## Key capabilities + +### Multi-server aggregation + +Managing 10-20+ MCP server connections is overwhelming. Each server needs its +own configuration, authentication, and maintenance. vMCP aggregates all backend +MCP servers into one endpoint with automatic conflict resolution. + +**Example scenario**: An engineering team needs access to 8 backend servers +(GitHub, Jira, Slack, Confluence, PagerDuty, Datadog, AWS, and internal company +docs). Instead of configuring 8 separate connections, they configure one vMCP +connection with SSO. This significantly reduces configuration complexity and +makes onboarding new team members much easier. + +When multiple backend MCP servers have tools with the same name (for example, +both GitHub and Jira have `create_issue`), vMCP automatically prefixes them: +`github_create_issue`, `jira_create_issue`. You can also define custom names for +clarity. + +### Multi-step workflows (composition) + +Real-world tasks span multiple systems and require manual orchestration. vMCP +lets you define declarative workflows with parallel execution, conditionals, +error handling, and human-in-the-loop approval gates. + +**Example scenario**: During an incident investigation, you need logs from your +logging system, metrics from your monitoring platform, traces from your tracing +service, and infrastructure status from your cloud provider. Without vMCP, an +engineer manually runs 4 commands sequentially and aggregates results. With +vMCP, you fetch all of this in parallel, automatically aggregate it into a +formatted report, and create a Jira ticket with all the data. This workflow is +reusable for every incident. + +**Example scenario**: For an app deployment, merge the pull request, wait for +tests, ask a human for approval, deploy only if approved, and notify the team in +Slack. vMCP handles this entire flow declaratively, with automatic rollback on +deployment failure. + +### Tool customization and overrides + +Third-party MCP servers often have generic names, descriptions, and unrestricted +parameters. vMCP lets you filter, rename, and wrap tools without modifying the +upstream servers. + +**Security policy enforcement**: You can restrict a fetch tool to internal +domains only (`*.company.com`), validate URLs before calling the backend, and +provide clear error messages for policy violations. + +**Simplified interfaces**: A complex tool like AWS EC2 might have 20+ +parameters, but your frontend team only needs 3. You can create a simplified +wrapper that uses instance names instead of IDs and pre-fills safe defaults for +all other parameters. + +### Parameter defaults and pre-configuration + +Generic servers require repetitive parameter specification. You always use the +same repo, channel, or database. vMCP lets you create specialized instances with +pre-configured defaults. + +**Example scenario**: Without vMCP, every GitHub query requires specifying +`repo: stacklok/toolhive`. With vMCP, you pre-configure this default - engineers +never specify the repo parameter, and they can't accidentally query the wrong +repository. This eliminates hundreds of repetitive parameter entries per week. + +**Example scenario**: Configure staging database as the default (safe for +development), while production queries require an explicit approval gate. +Connection details are centralized in vMCP configuration instead of scattered +across individual tool configurations. + +### Authentication boundary separation + +Different authentication is needed for clients versus backend MCP servers, and +managing credentials across multiple systems is complex. vMCP implements a +two-boundary auth model that separates these concerns. + +**How it works**: Clients authenticate to vMCP using OAuth 2.1 authorization as +defined in the MCP specification. vMCP then handles authentication to each +backend MCP server independently. Revoking access is simple: disable the user in +your identity provider and all backend access is revoked instantly. + +This approach provides single sign-on for users, centralized access control, and +a complete audit trail. + +## When to use vMCP + +### Good fit + +- Teams managing 5+ MCP servers +- Tasks requiring coordination across multiple systems +- Centralized authentication and authorization requirements +- Workflows that should be reusable across the team +- Security policies that need centralized enforcement + +### Not needed + +- Single MCP server usage +- Simple, one-step operations +- No orchestration requirements + +## Summary + +vMCP transforms MCP from individual servers into a unified orchestration +platform. The use cases range from simple aggregation to complex workflows with +approval gates, making it valuable for teams managing multiple MCP servers. + +## Related information + +- [Deploy vMCP](../guides-vmcp/intro.mdx) +- [Configure authentication](../guides-vmcp/authentication.mdx) +- [Tool aggregation and conflict resolution](../guides-vmcp/tool-aggregation.mdx) +- [Composite tools and workflows](../guides-vmcp/composite-tools.mdx) diff --git a/docs/toolhive/guides-k8s/deploy-operator-helm.mdx b/docs/toolhive/guides-k8s/deploy-operator-helm.mdx index 53b7154e..768e8b28 100644 --- a/docs/toolhive/guides-k8s/deploy-operator-helm.mdx +++ b/docs/toolhive/guides-k8s/deploy-operator-helm.mdx @@ -33,7 +33,7 @@ chart. To install a specific version, append `--version ` to the command, for example: ```bash -helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --version 0.0.52 +helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --version 0.0.73 ``` ## Install the operator @@ -52,7 +52,7 @@ chart. To install a specific version, append `--version ` to the command, for example: ```bash -helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace --version 0.3.7 +helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace --version 0.5.6 ``` Verify the installation: @@ -237,21 +237,23 @@ and then apply the CRDs using `kubectl`. First, upgrade the CRD Helm chart to match your target operator version: ```bash -helm upgrade -i toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --version 0.0.52 +helm upgrade -i toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --version 0.0.73 ``` Then apply the CRDs from the same version tag: ```bash -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml -kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.52/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml +kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/toolhive-operator-crds-0.0.73/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml ``` -Replace `0.0.52` in both commands with your target CRD version. +Replace `0.0.73` in both commands with your target CRD version. ### Upgrade the operator Helm release @@ -267,7 +269,7 @@ This upgrades the operator to the latest version available in the OCI registry. To upgrade to a specific version, add the `--version` flag: ```bash -helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --reuse-values --version 0.3.7 +helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --reuse-values --version 0.5.6 ``` If you have a custom `values.yaml` file, include it with the `-f` flag: @@ -302,6 +304,8 @@ kubectl delete crd mcpremoteproxies.toolhive.stacklok.dev kubectl delete crd mcpservers.toolhive.stacklok.dev kubectl delete crd mcpgroups.toolhive.stacklok.dev kubectl delete crd mcpregistries.toolhive.stacklok.dev +kubectl delete crd virtualmcpcompositetooldefinitions.toolhive.stacklok.dev +kubectl delete crd virtualmcpservers.toolhive.stacklok.dev ``` Finally, uninstall the CRD Helm chart metadata: diff --git a/docs/toolhive/guides-vmcp/authentication.mdx b/docs/toolhive/guides-vmcp/authentication.mdx new file mode 100644 index 00000000..4a2c9036 --- /dev/null +++ b/docs/toolhive/guides-vmcp/authentication.mdx @@ -0,0 +1,164 @@ +--- +title: Authentication +description: Configure client and backend authentication for vMCP. +--- + +Virtual MCP Server (vMCP) implements a two-boundary authentication model that +separates client and backend authentication, giving you centralized control over +access while supporting diverse backend requirements. + +## Two-boundary authentication model + +```mermaid +flowchart LR + subgraph Boundary1[" "] + direction TB + Client[MCP Client] + B1Label["**Boundary 1**
Client → vMCP"] + end + + subgraph vMCP["Virtual MCP Server (vMCP)"] + Auth[Token validation] + Backend[Backend auth] + end + + subgraph Boundary2[" "] + direction TB + B2Label["**Boundary 2**
vMCP → Backend APIs"] + GitHub[GitHub API] + Jira[Jira API] + end + + Client -->|"vMCP-scoped
token"| Auth + Auth --> Backend + Backend -->|"Backend-scoped
token"| GitHub + Backend -->|"Backend-scoped
token"| Jira +``` + +**Boundary 1 (Incoming):** Clients authenticate to vMCP using OAuth 2.1 +authorization as defined in the +[MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization). +This is your organization's identity layer. + +**Boundary 2 (Outgoing):** vMCP obtains appropriate credentials for each +backend. Each backend API receives a token or credential scoped to its +requirements. + +## Incoming authentication + +Configure how clients authenticate to vMCP. + +### Anonymous (development only) + +No authentication required: + +```yaml title="VirtualMCPServer resource" +spec: + incomingAuth: + type: anonymous +``` + +:::warning + +Do not use `anonymous` authentication in production environments. This setting +disables all access control, allowing anyone to use the vMCP without +credentials. + +::: + +### OIDC authentication + +Validate tokens from an external identity provider: + +```yaml title="VirtualMCPServer resource" +spec: + incomingAuth: + type: oidc + oidcConfig: + type: inline + inline: + issuer: https://auth.example.com + clientId: + audience: vmcp +``` + +When using an identity provider that issues opaque OAuth tokens, add a +`clientSecretRef` referencing a Kubernetes Secret to enable token introspection: + +```yaml title="VirtualMCPServer resource" +spec: + incomingAuth: + type: oidc + oidcConfig: + type: inline + inline: + issuer: https://auth.example.com + clientId: + audience: vmcp + clientSecretRef: + name: oidc-client-secret + key: clientSecret +``` + +Create the Secret: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: oidc-client-secret + namespace: toolhive-system +type: Opaque +stringData: + clientSecret: +``` + +### Kubernetes service account tokens + +Authenticate using Kubernetes service account tokens for in-cluster clients: + +```yaml title="VirtualMCPServer resource" +spec: + incomingAuth: + type: oidc + oidcConfig: + type: kubernetes + kubernetes: + audience: toolhive +``` + +This configuration uses the Kubernetes API server as the OIDC issuer and +validates service account tokens. The defaults work for most clusters: + +- **issuer**: `https://kubernetes.default.svc` (auto-detected) +- **audience**: `toolhive` (configurable) + +## Outgoing authentication + +Configure how vMCP authenticates to backend MCP servers. + +### Discovery mode + +When using discovery mode, vMCP checks each backend MCPServer's +`externalAuthConfigRef` to determine how to authenticate. If a backend has no +auth config, vMCP connects without authentication. + +```yaml title="VirtualMCPServer resource" +spec: + outgoingAuth: + source: discovered +``` + +This is the recommended approach for most deployments. Backends that don't +require authentication work automatically, while backends with +`externalAuthConfigRef` configured use their specified authentication method. + +See +[Configure token exchange for backend authentication](../guides-k8s/token-exchange-k8s.mdx) +for details on using service account token exchange for backend authentication. + +## Related information + +- [Authentication framework concepts](../concepts/auth-framework.mdx) +- [VirtualMCPServer configuration](./configuration.mdx) +- [Token exchange in Kubernetes](../guides-k8s/token-exchange-k8s.mdx) diff --git a/docs/toolhive/guides-vmcp/composite-tools.mdx b/docs/toolhive/guides-vmcp/composite-tools.mdx new file mode 100644 index 00000000..c1281184 --- /dev/null +++ b/docs/toolhive/guides-vmcp/composite-tools.mdx @@ -0,0 +1,365 @@ +--- +title: Composite tools and workflows +description: Create multi-step workflows that span multiple backend MCP servers. +--- + +Composite tools let you define multi-step workflows that execute across multiple +backend MCP servers with parallel execution, conditional logic, approval gates, +and error handling. + +## Overview + +A composite tool combines multiple backend tool calls into a single workflow. +When a client calls a composite tool, vMCP orchestrates the execution across +backend MCP servers, handling dependencies and collecting results. + +## Key capabilities + +- **Parallel execution**: Independent steps run concurrently; dependent steps + wait for their prerequisites +- **Template expansion**: Dynamic arguments using step outputs +- **Elicitation**: Request user input mid-workflow (approval gates, choices) +- **Error handling**: Configurable abort, continue, or retry behavior +- **Timeouts**: Workflow and per-step timeout configuration + +:::info + +Elicitation (user prompts during workflow execution) is defined in the CRD but +has not been extensively tested. Test thoroughly in non-production environments +first. + +::: + +## Configuration location + +Composite tools are defined in the VirtualMCPServer resource under the +`spec.compositeTools` array: + +```yaml +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: VirtualMCPServer +metadata: + name: my-vmcp +spec: + groupRef: + name: my-tools + # ... other configuration ... + compositeTools: + - name: my_workflow + description: A multi-step workflow + parameters: + # Input parameters (JSON Schema) + steps: + # Workflow steps +``` + +See the [CompositeToolSpec](../reference/crd-spec.mdx#compositetoolspec) +definition in the CRD for all available fields. + +## Simple example + +Here's a basic composite tool that fetches a URL and then summarizes it: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: fetch_and_summarize + description: Fetch a URL and create a summary + parameters: + type: object + properties: + url: + type: string + required: + - url + steps: + - id: fetch + tool: fetch.fetch + arguments: + url: '{{.params.url}}' + - id: summarize + tool: llm.summarize + arguments: + text: '{{.steps.fetch.output.content}}' + dependsOn: [fetch] +``` + +**What's happening:** + +1. **Parameters**: Define the workflow inputs (just `url` in this case) +2. **Step 1 (fetch)**: Calls the `fetch.fetch` tool with the URL from parameters + using template syntax `{{.params.url}}` +3. **Step 2 (summarize)**: Waits for the fetch step (`dependsOn: [fetch]`), then + calls `llm.summarize` with the fetched content using + `{{.steps.fetch.output.content}}` + +When a client calls this composite tool, vMCP executes both steps in sequence +and returns the final summary. + +## Use cases + +### Incident investigation + +Gather data from multiple monitoring systems in parallel: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: investigate_incident + description: Gather incident data from multiple sources in parallel + parameters: + type: object + properties: + incident_id: + type: string + required: + - incident_id + steps: + # These steps run in parallel (no dependencies) + - id: get_logs + tool: logging.search_logs + arguments: + query: 'incident_id={{.params.incident_id}}' + timerange: '1h' + - id: get_metrics + tool: monitoring.get_metrics + arguments: + filter: 'error_rate' + timerange: '1h' + - id: get_alerts + tool: pagerduty.list_alerts + arguments: + incident: '{{.params.incident_id}}' + # This step waits for all parallel steps to complete + - id: create_summary + tool: docs.create_document + arguments: + title: 'Incident {{.params.incident_id}} Summary' + content: 'Logs: {{.steps.get_logs.output.results}}' + dependsOn: [get_logs, get_metrics, get_alerts] +``` + +### Deployment with approval + +Human-in-the-loop workflow for production deployments: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: deploy_with_approval + description: Deploy to production with human approval gate + parameters: + type: object + properties: + pr_number: + type: string + environment: + type: string + default: production + required: + - pr_number + steps: + - id: get_pr_details + tool: github.get_pull_request + arguments: + pr: '{{.params.pr_number}}' + - id: approval + type: elicitation + message: + 'Deploy PR #{{.params.pr_number}} to {{.params.environment}}?' + schema: + type: object + properties: + approved: + type: boolean + timeout: '10m' + dependsOn: [get_pr_details] + - id: deploy + tool: deploy.trigger_deployment + arguments: + ref: '{{.steps.get_pr_details.output.head_sha}}' + environment: '{{.params.environment}}' + condition: '{{.steps.approval.content.approved}}' + dependsOn: [approval] +``` + +### Cross-system data aggregation + +Collect and correlate data from multiple backend MCP servers: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: security_scan_report + description: Run security scans and create consolidated report + parameters: + type: object + properties: + repo: + type: string + required: + - repo + steps: + - id: vulnerability_scan + tool: osv.scan_dependencies + arguments: + repository: '{{.params.repo}}' + - id: secret_scan + tool: gitleaks.scan_repo + arguments: + repository: '{{.params.repo}}' + - id: create_issue + tool: github.create_issue + arguments: + repo: '{{.params.repo}}' + title: 'Security Scan Results' + body: + 'Found {{.steps.vulnerability_scan.output.count}} vulnerabilities' + dependsOn: [vulnerability_scan, secret_scan] + onError: + action: continue +``` + +## Workflow definition + +### Parameters + +Define input parameters using JSON Schema format: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: + parameters: + type: object + properties: + required_param: + type: string + optional_param: + type: integer + default: 10 + required: + - required_param +``` + +### Steps + +Each step can be a tool call or an elicitation: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: + steps: + - id: step_name # Unique identifier + tool: backend.tool # Tool to call + arguments: # Arguments with template expansion + arg1: '{{.params.input}}' + dependsOn: [other_step] # Dependencies (this step waits for other_step) + condition: '{{.steps.check.output.approved}}' # Optional condition + timeout: '30s' # Step timeout + onError: + action: abort # abort | continue | retry +``` + +### Elicitation (user prompts) + +Request input from users during workflow execution: + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: + steps: + - id: approval + type: elicitation + message: 'Proceed with deployment?' + schema: + type: object + properties: + confirm: { type: boolean } + timeout: '5m' +``` + +### Error handling + +Configure behavior when steps fail: + +| Action | Description | +| ---------- | ------------------------------- | +| `abort` | Stop workflow immediately | +| `continue` | Log error, proceed to next step | +| `retry` | Retry with exponential backoff | + +```yaml title="VirtualMCPServer resource" +spec: + compositeTools: + - name: + steps: + - id: + # ... other step config (tool, arguments, etc.) + onError: + action: retry + maxRetries: 3 +``` + +## Template syntax + +Access workflow context in arguments: + +| Template | Description | +| ----------------------- | ------------------------------------------ | +| `{{.params.name}}` | Input parameter | +| `{{.steps.id.output}}` | Step output | +| `{{.steps.id.content}}` | Elicitation response content | +| `{{.steps.id.action}}` | Elicitation action (accept/decline/cancel) | + +## Complete example + +A VirtualMCPServer with an inline composite tool: + +```yaml +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: VirtualMCPServer +metadata: + name: workflow-vmcp + namespace: toolhive-system +spec: + groupRef: + name: my-tools + incomingAuth: + type: anonymous + aggregation: + conflictResolution: prefix + conflictResolutionConfig: + prefixFormat: '{workload}_' + compositeTools: + - name: fetch_and_summarize + description: Fetch a URL and create a summary + parameters: + type: object + properties: + url: + type: string + description: URL to fetch + required: + - url + steps: + - id: fetch_content + tool: fetch.fetch + arguments: + url: '{{.params.url}}' + - id: summarize + tool: llm.summarize # Hypothetical backend - replace with your actual LLM server + arguments: + text: '{{.steps.fetch_content.output.content}}' + dependsOn: [fetch_content] + timeout: '5m' +``` + +For complex, reusable workflows, use `VirtualMCPCompositeToolDefinition` +resources and reference them with `compositeToolRefs`. + +## Related information + +- [Configure vMCP servers](./configuration.mdx) diff --git a/docs/toolhive/guides-vmcp/configuration.mdx b/docs/toolhive/guides-vmcp/configuration.mdx new file mode 100644 index 00000000..0e84e8ed --- /dev/null +++ b/docs/toolhive/guides-vmcp/configuration.mdx @@ -0,0 +1,106 @@ +--- +title: Configure vMCP servers +description: How to configure a Virtual MCP Server for common scenarios. +--- + +This guide covers common configuration patterns for vMCP using the +VirtualMCPServer resource. For a complete field reference, see the +[VirtualMCPServer CRD specification](../reference/crd-spec.mdx#virtualmcpserver). + +## Create an MCPGroup + +Before creating a VirtualMCPServer, you need an +[MCPGroup](../reference/crd-spec.mdx#mcpgroup) to organize the backend MCP +servers. An MCPGroup is a logical container that groups related MCPServer +resources together. + +Create a basic MCPGroup: + +```yaml +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPGroup +metadata: + name: my-group + namespace: toolhive-system +spec: + description: Group of backend MCP servers for vMCP aggregation +``` + +The MCPGroup must exist in the same namespace as your VirtualMCPServer and be in +a Ready state before the VirtualMCPServer can start. Backend MCPServers +reference this group using the `groupRef` field in their spec. + +## Create a VirtualMCPServer + +At minimum, a VirtualMCPServer requires a reference to an MCPGroup and an +authentication type: + +```yaml +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: VirtualMCPServer +metadata: + name: my-vmcp + namespace: toolhive-system +spec: + groupRef: + name: my-group + incomingAuth: + type: anonymous # Disables authentication; do not use in production +``` + +The MCPGroup must exist in the same namespace and be in a Ready state before the +VirtualMCPServer can start. + +## Configure authentication + +vMCP uses a two-boundary authentication model: client-to-vMCP (incoming) and +vMCP-to-backends (outgoing). See the +[Authentication guide](./authentication.mdx) for complete configuration options +including anonymous, OIDC, and Kubernetes service account authentication. + +## Expose the service + +Choose how to expose the vMCP endpoint. The Service resource is created +automatically on port 4483. + +```yaml title="VirtualMCPServer resource" +spec: + serviceType: ClusterIP # Default: cluster-internal (can be exposed via Ingress/Gateway) + # serviceType: LoadBalancer # Direct external access via cloud load balancer + # serviceType: NodePort # Direct external access via node ports +``` + +**Service types:** + +- **ClusterIP** (default): For production, use with Ingress or Gateway API for + controlled external access with TLS termination +- **LoadBalancer**: Direct external access via cloud provider's load balancer + (simpler but less control) +- **NodePort**: Direct access via node ports (typically for development/testing) + +The Service is named `vmcp-`, where `` is from `metadata.name` in +the VirtualMCPServer resource. + +## Monitor status + +Check the VirtualMCPServer status to verify it's ready: + +```bash +kubectl get virtualmcpserver my-vmcp +``` + +Key status fields: + +| Field | Description | +| -------------------- | ------------------------------------------------ | +| `phase` | Current state (Pending, Ready, Degraded, Failed) | +| `url` | Service URL for client connections | +| `backendCount` | Number of discovered backend MCP servers | +| `discoveredBackends` | Details about each backend and its auth type | + +## Related information + +- [VirtualMCPServer CRD specification](../reference/crd-spec.mdx#virtualmcpserver) +- [Introduction to vMCP](./intro.mdx) +- [Tool aggregation](./tool-aggregation.mdx) +- [Authentication](./authentication.mdx) diff --git a/docs/toolhive/guides-vmcp/index.mdx b/docs/toolhive/guides-vmcp/index.mdx new file mode 100644 index 00000000..b37f4255 --- /dev/null +++ b/docs/toolhive/guides-vmcp/index.mdx @@ -0,0 +1,27 @@ +--- +title: Virtual MCP Server +description: Aggregate multiple MCP servers into a single unified endpoint. +--- + +Virtual MCP Server (vMCP) is a feature of the ToolHive Kubernetes Operator that +aggregates multiple backend MCP servers into a single endpoint, enabling unified +tool access, centralized authentication, and multi-step workflows. + +## When to use vMCP + +- You manage multiple MCP servers that should appear as one +- You need to centralize authentication across backends +- You want to create reusable workflows spanning multiple systems + +## Get started + +- [Understanding Virtual MCP Server](../concepts/vmcp.mdx) - Learn what vMCP + does and when to use it +- [Quickstart: Virtual MCP Server](../tutorials/quickstart-vmcp.mdx) - Deploy + your first vMCP + +## Contents + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/toolhive/guides-vmcp/intro.mdx b/docs/toolhive/guides-vmcp/intro.mdx new file mode 100644 index 00000000..d36eb8b5 --- /dev/null +++ b/docs/toolhive/guides-vmcp/intro.mdx @@ -0,0 +1,68 @@ +--- +title: Introduction to vMCP +description: Understand what Virtual MCP Server (vMCP) does and when to use it. +--- + +## Overview + +Virtual MCP Server (vMCP) is a feature of the ToolHive Kubernetes Operator that +acts as an aggregation proxy, consolidating multiple backend MCP servers into a +single unified interface. Instead of configuring clients to connect to each MCP +server individually, you connect once to vMCP and access all backend tools +through a single endpoint. + +## Core capabilities + +- **Multi-server aggregation**: Connect to one endpoint instead of many +- **Tool conflict resolution**: Automatic namespacing when backend MCP servers + have overlapping tool names +- **Centralized authentication**: Single sign-on with per-backend token exchange +- **Composite workflows**: Multi-step operations across backend MCP servers with + parallel execution, approval gates, and error handling + +## When to use vMCP + +### Good fit + +- You manage 5+ MCP servers +- You need cross-system workflows requiring coordination +- You have centralized authentication and authorization requirements +- You need reusable workflow definitions + +### Not needed + +- You use a single MCP server +- You have simple, one-step operations +- You have no orchestration requirements + +## Architecture overview + +```mermaid +flowchart TB + Client[MCP Client] --> vMCP[Virtual MCP Server] + + subgraph Backends[Backend MCP Servers] + GitHub[GitHub MCP] + Jira[Jira MCP] + Fetch[Fetch MCP] + end + + vMCP --> GitHub + vMCP --> Jira + vMCP --> Fetch +``` + +## How it works + +1. You define an MCPGroup (a resource that organizes related MCP servers) + containing your backend MCPServer resources +2. You create a VirtualMCPServer that references the group +3. The operator discovers all backend MCP servers in the group and aggregates + their capabilities +4. Clients connect to the VirtualMCPServer endpoint and see a unified view of + all tools + +## Related information + +- [Understanding Virtual MCP Server](../concepts/vmcp.mdx) +- [Quickstart: Virtual MCP Server](../tutorials/quickstart-vmcp.mdx) diff --git a/docs/toolhive/guides-vmcp/tool-aggregation.mdx b/docs/toolhive/guides-vmcp/tool-aggregation.mdx new file mode 100644 index 00000000..db6f2177 --- /dev/null +++ b/docs/toolhive/guides-vmcp/tool-aggregation.mdx @@ -0,0 +1,229 @@ +--- +title: Tool aggregation and conflict resolution +description: + How vMCP aggregates tools from multiple backend MCP servers and resolves + naming conflicts. +--- + +When aggregating multiple MCP servers, tool name conflicts can occur when +different backend servers expose tools with the same name. Virtual MCP Server +(vMCP) provides strategies to resolve these conflicts automatically. + +## Overview + +vMCP discovers tools from all backend MCPServers in the referenced group and +presents them as a unified set to clients. When two backend MCP servers have +tools with the same name (for example, both GitHub and Jira have a +`create_issue` tool), a conflict resolution strategy determines how to handle +the collision. + +## Conflict resolution strategies + +### Prefix strategy (default) + +By default, vMCP prefixes all tool names with the workload identifier (the +`metadata.name` of each MCPServer resource). This guarantees unique names and is +the safest option for most deployments. + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + conflictResolution: prefix + conflictResolutionConfig: + prefixFormat: '{workload}_' +``` + +**Prefix format options:** + +| Format | Example result | +| ------------- | --------------------- | +| `{workload}` | `githubcreate_issue` | +| `{workload}_` | `github_create_issue` | +| `{workload}.` | `github.create_issue` | + +**Example:** + +With backend servers `github` and `jira`, both exposing `create_issue`: + +- GitHub's tool becomes `github_create_issue` +- Jira's tool becomes `jira_create_issue` + +### Priority strategy + +When multiple backend MCP servers offer tools with the same name, the `priority` +strategy keeps the tool from the first backend in the priority order and drops +the duplicate tools from lower-priority backend servers. + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + conflictResolution: priority + conflictResolutionConfig: + priorityOrder: ['github', 'jira', 'slack'] +``` + +In this example, if both GitHub and Jira provide a `create_issue` tool, only +GitHub's version is exposed. Jira's duplicate is dropped. + +**When to use:** When you have a preferred backend MCP server for specific tools +and want to hide duplicates. + +:::warning + +The priority strategy drops tools from lower-priority backend servers. Ensure +this is the intended behavior before using in production. + +::: + +### Manual strategy + +The `manual` strategy gives you explicit control over tool naming when conflicts +occur. You must provide overrides for all conflicting tools, or the vMCP will +fail to start. + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + conflictResolution: manual + tools: + - workload: github + overrides: + create_issue: + name: gh_create_issue + - workload: jira + overrides: + create_issue: + name: jira_ticket +``` + +**When to use:** Production deployments where you want explicit control over +tool names. + +## Tool filtering + +Use filters to expose only specific tools from a backend MCP server, excluding +all others. This is useful for reducing the tool surface area presented to LLM +clients or removing unnecessary tools: + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + tools: + - workload: github + filter: ['create_issue', 'list_issues', 'get_issue'] +``` + +Only the listed tools are included; all others from that backend MCP server are +excluded. + +## Tool overrides + +Use overrides to customize tool names and descriptions without modifying backend +MCP server configurations. This is useful for disambiguating similarly-named +tools or providing more context to LLM clients: + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + tools: + - workload: github + overrides: + create_issue: + name: gh_new_issue + description: 'Create a new GitHub issue in the repository' +``` + +:::info + +You can also reference an `MCPToolConfig` resource using `toolConfigRef` instead +of inline filter and overrides. This feature is currently in development. + +::: + +## Combine filters and overrides + +You can combine filtering and overrides for fine-grained control: + +```yaml title="VirtualMCPServer resource" +spec: + aggregation: + conflictResolution: prefix + conflictResolutionConfig: + prefixFormat: '{workload}_' + tools: + - workload: github + filter: ['create_issue', 'list_issues'] + overrides: + create_issue: + description: 'Create a GitHub issue (engineering team)' + - workload: jira + filter: ['create_issue', 'search_issues'] +``` + +## Example: Aggregating multiple MCP servers + +This example shows two MCP servers (fetch and osv) aggregated with prefix-based +conflict resolution: + +```yaml +# MCPGroup to organize backend servers +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPGroup +metadata: + name: demo-tools + namespace: toolhive-system +spec: + description: Demo group for tool aggregation +--- +# First backend: fetch server +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPServer +metadata: + name: fetch + namespace: toolhive-system +spec: + image: ghcr.io/stackloklabs/gofetch/server + transport: streamable-http + proxyPort: 8080 + mcpPort: 8080 + groupRef: demo-tools +--- +# Second backend: osv server +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPServer +metadata: + name: osv + namespace: toolhive-system +spec: + image: ghcr.io/stackloklabs/osv-mcp/server + transport: streamable-http + proxyPort: 8080 + mcpPort: 8080 + groupRef: demo-tools +--- +# VirtualMCPServer aggregating both backends +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: VirtualMCPServer +metadata: + name: demo-vmcp + namespace: toolhive-system +spec: + groupRef: + name: demo-tools + incomingAuth: + type: anonymous + aggregation: + conflictResolution: prefix + conflictResolutionConfig: + prefixFormat: '{workload}_' +``` + +With this configuration, tools from each backend are prefixed: + +- `fetch_*` tools from the fetch server +- `osv_*` tools from the osv server + +## Related information + +- [VirtualMCPServer configuration reference](./configuration.mdx) +- [Customize MCP server tools](../guides-k8s/customize-tools.mdx) diff --git a/docs/toolhive/tutorials/quickstart-vmcp.mdx b/docs/toolhive/tutorials/quickstart-vmcp.mdx new file mode 100644 index 00000000..944eae33 --- /dev/null +++ b/docs/toolhive/tutorials/quickstart-vmcp.mdx @@ -0,0 +1,292 @@ +--- +title: 'Quickstart: Virtual MCP Server' +description: + Learn how to aggregate multiple MCP servers into a single endpoint using + Virtual MCP Server. +--- + +In this tutorial, you'll learn how to deploy Virtual MCP Server (vMCP) to +aggregate multiple MCP servers into a single endpoint. By the end, you'll have a +working deployment that combines tools from multiple backends. + +## What you'll learn + +- How to create an MCPGroup to organize backend servers +- How to deploy multiple MCPServers in a group +- How to create a VirtualMCPServer that aggregates them +- How tool conflict resolution works +- How to connect your AI client to the aggregated endpoint + +## Prerequisites + +Before starting this tutorial, make sure you have: + +- A Kubernetes cluster with the ToolHive operator installed (see + [Quickstart: Kubernetes Operator](./quickstart-k8s.mdx)) +- `kubectl` configured to communicate with your cluster +- An MCP client (Visual Studio Code with Copilot is used in this tutorial) + +## Step 1: Create an MCPGroup + +First, create an MCPGroup to organize your backend MCP servers: + +```yaml title="mcpgroup.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPGroup +metadata: + name: demo-tools + namespace: toolhive-system +spec: + description: Demo group for vMCP aggregation +``` + +Apply the resource: + +```bash +kubectl apply -f mcpgroup.yaml +``` + +Verify the group was created: + +```bash +kubectl get mcpgroups -n toolhive-system +``` + +## Step 2: Deploy backend MCPServers + +Deploy two MCP servers that will be aggregated. Both reference the `demo-tools` +group in the `groupRef` field: + +```yaml {11,30} title="mcpservers.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPServer +metadata: + name: fetch + namespace: toolhive-system +spec: + image: ghcr.io/stackloklabs/gofetch/server + transport: streamable-http + proxyPort: 8080 + mcpPort: 8080 + groupRef: demo-tools + resources: + limits: + cpu: '100m' + memory: '128Mi' + requests: + cpu: '50m' + memory: '64Mi' +--- +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPServer +metadata: + name: osv + namespace: toolhive-system +spec: + image: ghcr.io/stackloklabs/osv-mcp/server + transport: streamable-http + proxyPort: 8080 + mcpPort: 8080 + groupRef: demo-tools + resources: + limits: + cpu: '100m' + memory: '128Mi' + requests: + cpu: '50m' + memory: '64Mi' +``` + +Apply the resources: + +```bash +kubectl apply -f mcpservers.yaml +``` + +Wait for both servers to be running: + +```bash +kubectl get mcpservers -n toolhive-system -w +``` + +You should see both servers with `Running` status before continuing. + +## Step 3: Create a VirtualMCPServer + +Create a VirtualMCPServer that aggregates both backends: + +```yaml title="virtualmcpserver.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: VirtualMCPServer +metadata: + name: demo-vmcp + namespace: toolhive-system +spec: + # Reference the MCPGroup containing fetch and osv servers + groupRef: + name: demo-tools + + # No incoming auth for development (anonymous access) + incomingAuth: + type: anonymous + + # Auto-discover auth config from backend MCPServers + outgoingAuth: + source: inline + # No default specified will use anonymous + + # Tool aggregation with prefix strategy to avoid naming conflicts + aggregation: + conflictResolution: prefix + conflictResolutionConfig: + prefixFormat: '{workload}_' + + # Expose as ClusterIP (cluster-internal or exposed via Ingress/Gateway) + serviceType: ClusterIP +``` + +Apply the resource: + +```bash +kubectl apply -f virtualmcpserver.yaml +``` + +Check the status: + +```bash +kubectl get virtualmcpservers -n toolhive-system +``` + +After about 30 seconds, you should see output similar to: + +```text +NAME PHASE URL BACKENDS AGE READY +demo-vmcp Ready http://vmcp-demo-vmcp.toolhive-system.svc.cluster.local:4483 2 30s True +``` + +Note the port number for step 5. + +:::info[What's happening?] + +The operator discovered both MCPServers in the group and configured vMCP to +aggregate their tools. With the `prefix` conflict resolution strategy, all tools +are prefixed with the backend name. + +::: + +## Step 4: Verify the aggregation + +Check the discovered backends: + +```bash +kubectl describe virtualmcpserver demo-vmcp -n toolhive-system +``` + +Look for the `Discovered Backends` section in the status, which should show both +backends. + +## Step 5: Connect your client + +In a separate terminal, port-forward the vMCP service to your local machine: + +```bash +kubectl port-forward service/vmcp-demo-vmcp -n toolhive-system 4483:4483 +``` + +Test the health endpoint: + +```bash +curl http://localhost:4483/health +``` + +You should see `{"status":"ok"}`. + +Configure VS Code to connect to vMCP. Add the following to your `mcp.json` file: + +- **macOS**: `~/Library/Application Support/Code/User/mcp.json` +- **Windows**: `%APPDATA%\Code\User\mcp.json` +- **Linux**: `~/.config/Code/User/mcp.json` + +```json +{ + "servers": { + "demo-vmcp": { + "url": "http://localhost:4483/mcp", + "type": "http" + } + } +} +``` + +Reload VS Code to apply the configuration. + +:::tip + +For other MCP clients, see the +[Client compatibility reference](../reference/client-compatibility.mdx) for +configuration file locations and formats. + +::: + +## Step 6: Test the aggregated tools + +Try asking your AI assistant questions that use the aggregated tools. Both tools +work through the same vMCP endpoint! + +## Step 7: Clean up + +Delete the resources when you're done: + +```bash +kubectl delete virtualmcpserver demo-vmcp -n toolhive-system +kubectl delete mcpserver fetch osv -n toolhive-system +kubectl delete mcpgroup demo-tools -n toolhive-system +``` + +## What's next? + +Congratulations! You've successfully deployed vMCP and aggregated multiple +backends into a single endpoint. + +Next steps: + +- [Configure authentication](../guides-vmcp/authentication.mdx) for production +- [Customize tool aggregation](../guides-vmcp/tool-aggregation.mdx) with + filtering and overrides +- [Understanding Virtual MCP Server](../concepts/vmcp.mdx) + +## Troubleshooting + +
+VirtualMCPServer stuck in Pending + +Check that the MCPGroup exists and backend MCPServers are running: + +```bash +kubectl get mcpgroups,mcpservers -n toolhive-system +``` + +Check the operator logs: + +```bash +kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator +``` + +
+ +
+Only some tools appearing + +Verify both backends are discovered: + +```bash +kubectl get virtualmcpserver demo-vmcp -n toolhive-system -o jsonpath='{.status.discoveredBackends[*].name}' +``` + +Check backend health in the status: + +```bash +kubectl describe virtualmcpserver demo-vmcp -n toolhive-system +``` + +
diff --git a/docusaurus.config.ts b/docusaurus.config.ts index f2c85703..607581db 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -171,6 +171,10 @@ const config: Config = { label: 'Kubernetes Operator', to: 'toolhive/guides-k8s', }, + { + label: 'Virtual MCP Server', + to: 'toolhive/guides-vmcp', + }, ], }, { diff --git a/sidebars.ts b/sidebars.ts index 588d3946..f743b242 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -165,6 +165,24 @@ const sidebars: SidebarsConfig = { ], }, + { + type: 'category', + label: 'Guides: Virtual MCP Server', + description: + 'How to aggregate multiple MCP servers into a unified endpoint', + link: { + type: 'doc', + id: 'toolhive/guides-vmcp/index', + }, + items: [ + 'toolhive/guides-vmcp/intro', + 'toolhive/guides-vmcp/configuration', + 'toolhive/guides-vmcp/authentication', + 'toolhive/guides-vmcp/tool-aggregation', + 'toolhive/guides-vmcp/composite-tools', + ], + }, + { type: 'category', label: 'Concepts', @@ -182,6 +200,7 @@ const sidebars: SidebarsConfig = { 'toolhive/concepts/auth-framework', 'toolhive/concepts/cedar-policies', 'toolhive/concepts/backend-auth', + 'toolhive/concepts/vmcp', ], }, @@ -201,6 +220,7 @@ const sidebars: SidebarsConfig = { href: '/toolhive/quickstart', label: 'Quickstart guides', }, + 'toolhive/tutorials/quickstart-vmcp', 'toolhive/tutorials/mcp-optimizer', 'toolhive/tutorials/custom-registry', 'toolhive/tutorials/vault-integration',