Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .github/aw/schemas/agentic-workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -2326,20 +2326,6 @@
},
"description": "Volume mounts for the gateway container (format: 'source:dest:mode' where mode is 'ro' or 'rw')",
"examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"]]
},
"network": {
"type": "string",
"description": "Docker network mode for the gateway container (default: 'host')",
"examples": ["host", "bridge", "none"]
},
"ports": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(\\d+:\\d+|\\d+)$"
},
"description": "Port mappings for the gateway container (format: 'host:container' or 'port')",
"examples": [["8080:8080", "9090:9090"]]
}
},
"required": ["container"],
Expand Down
22 changes: 13 additions & 9 deletions docs/src/content/docs/reference/mcp-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ The gateway MUST accept configuration via stdin in JSON format conforming to the
"mcpServers": {
"server-name": {
"container": "string",
"entrypoint": "string",
"entrypointArgs": ["string"],
"mounts": ["source:dest:mode"],
"env": {
"VAR_NAME": "value"
},
Expand All @@ -195,8 +197,7 @@ The gateway MUST accept configuration via stdin in JSON format conforming to the
"apiKey": "string",
"domain": "string",
"startupTimeout": 30,
"toolTimeout": 60,
"mounts": ["source:dest:mode"]
"toolTimeout": 60
}
}
```
Expand All @@ -208,7 +209,9 @@ Each server configuration MUST support:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `container` | string | Conditional* | Container image for the MCP server (required for stdio servers) |
| `entrypoint` | string | No | Optional entrypoint override for container (equivalent to `docker run --entrypoint`) |
| `entrypointArgs` | array[string] | No | Arguments passed to container entrypoint (container only) |
| `mounts` | array[string] | No | Volume mounts for container (format: "source:dest:mode" where mode is "ro" or "rw") |
| `env` | object | No | Environment variables for the server process |
| `type` | string | No | Transport type: "stdio" or "http" (default: "stdio") |
| `url` | string | Conditional** | HTTP endpoint URL for HTTP servers |
Expand All @@ -229,7 +232,6 @@ The optional `gateway` section configures gateway-specific behavior:
| `domain` | string | localhost | Gateway domain (localhost or host.docker.internal) |
| `startupTimeout` | integer | 30 | Server startup timeout in seconds |
| `toolTimeout` | integer | 60 | Tool invocation timeout in seconds |
| `mounts` | array[string] | [] | Volume mounts for gateway container (format: "source:dest:mode") |

### 4.2 Variable Expression Rendering

Expand Down Expand Up @@ -746,23 +748,25 @@ Implementations SHOULD provide:
}
```

#### A.2 Gateway with Volume Mounts
#### A.2 Server with Volume Mounts and Custom Entrypoint

```json
{
"mcpServers": {
"data-server": {
"container": "ghcr.io/example/data-mcp:latest",
"entrypoint": "/custom/entrypoint.sh",
"entrypointArgs": ["--config", "/app/config.json"],
"mounts": [
"/host/data:/container/data:ro",
"/host/config:/container/config:rw"
],
"type": "stdio"
}
},
"gateway": {
"port": 8080,
"apiKey": "gateway-secret-token",
"mounts": [
"/host/data:/container/data:ro",
"/host/config:/container/config:rw"
]
"apiKey": "gateway-secret-token"
}
}
```
Expand Down
25 changes: 14 additions & 11 deletions examples/mcp-gateway-with-volumes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ engine: copilot
features:
mcp-gateway: true

# Example: MCP Gateway with Volume Mounts
# This example demonstrates how to configure volume mounts for the MCP Gateway.
# Example: MCP Server with Volume Mounts
# This example demonstrates how to configure volume mounts for individual MCP servers.

sandbox:
agent: awf
Expand All @@ -14,26 +14,29 @@ sandbox:
container: ghcr.io/example/mcp-gateway
version: latest

# Environment variables for the gateway
env:
LOG_LEVEL: debug
DEBUG: "true"

tools:
bash: ["*"]
custom-mcp-server:
container: "ghcr.io/example/data-server:latest"
# Volume mounts (format: "source:dest:mode")
# - source: host path
# - dest: container path
# - mode: "ro" (read-only) or "rw" (read-write)
mounts:
- "/host/data:/data:ro" # Read-only data mount
- "/host/config:/config:rw" # Read-write config mount

# Environment variables for the gateway
env:
LOG_LEVEL: debug
DEBUG: "true"

tools:
bash: ["*"]
DATA_PATH: "/data"
---

# MCP Gateway with Volume Mounts
# MCP Server with Volume Mounts

This workflow demonstrates how to configure the MCP Gateway with volume mounts.
This workflow demonstrates how to configure volume mounts for individual MCP servers.

## Task

Expand Down
27 changes: 27 additions & 0 deletions pkg/parser/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,33 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
}
}

// Add volume mounts if configured (sorted for deterministic output)
if mounts, hasMounts := mcpConfig["mounts"]; hasMounts {
if mountsSlice, ok := mounts.([]any); ok {
// Collect mounts first
var mountStrings []string
for _, mount := range mountsSlice {
if mountStr, ok := mount.(string); ok {
mountStrings = append(mountStrings, mountStr)
config.Mounts = append(config.Mounts, mountStr)
}
}
// Sort for deterministic output
sort.Strings(mountStrings)
for _, mountStr := range mountStrings {
config.Args = append(config.Args, "-v", mountStr)
}
}
}

// Add entrypoint override if specified
if entrypoint, hasEntrypoint := mcpConfig["entrypoint"]; hasEntrypoint {
if entrypointStr, ok := entrypoint.(string); ok {
config.Entrypoint = entrypointStr
config.Args = append(config.Args, "--entrypoint", entrypointStr)
}
}

config.Args = append(config.Args, containerStr)

// Add entrypoint args after the container image
Expand Down
9 changes: 0 additions & 9 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2317,15 +2317,6 @@
"type": "string",
"enum": ["localhost", "host.docker.internal"],
"description": "Gateway domain for URL generation (default: 'host.docker.internal' when agent is enabled, 'localhost' when disabled)"
},
"mounts": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for the gateway container (format: 'source:dest:mode' where mode is 'ro' or 'rw')",
"examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"]]
}
},
"required": ["container"],
Expand Down
14 changes: 14 additions & 0 deletions pkg/parser/schemas/mcp_config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@
["run", "-i", "my-mcp-server"]
]
},
"entrypoint": {
"type": "string",
"description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)",
"examples": ["/bin/sh", "/custom/entrypoint.sh", "python"]
},
"entrypointArgs": {
"type": "array",
"items": {
Expand All @@ -92,6 +97,15 @@
["--port", "8080", "--debug"]
]
},
"mounts": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for container (format: 'source:dest:mode' where mode is 'ro' or 'rw')",
"examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"], ["/tmp/cache:/app/cache:rw"]]
},
"env": {
"type": "object",
"patternProperties": {
Expand Down
2 changes: 2 additions & 0 deletions pkg/types/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ type BaseMCPServerConfig struct {

// Container-specific fields
Container string `json:"container,omitempty" yaml:"container,omitempty"` // Container image for the MCP server
Entrypoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"` // Optional entrypoint override for container
EntrypointArgs []string `json:"entrypointArgs,omitempty" yaml:"entrypointArgs,omitempty"` // Arguments passed to container entrypoint
Mounts []string `json:"mounts,omitempty" yaml:"mounts,omitempty"` // Volume mounts for container (format: "source:dest:mode")
}
11 changes: 0 additions & 11 deletions pkg/workflow/frontmatter_extraction_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,17 +393,6 @@ func (c *Compiler) extractMCPGatewayConfig(mcpVal any) *MCPGatewayRuntimeConfig
}
}

// Extract mounts (volume mounts)
if mountsVal, hasMounts := mcpObj["mounts"]; hasMounts {
if mountsSlice, ok := mountsVal.([]any); ok {
for _, mount := range mountsSlice {
if mountStr, ok := mount.(string); ok {
mcpConfig.Mounts = append(mcpConfig.Mounts, mountStr)
}
}
}
}

return mcpConfig
}

Expand Down
31 changes: 29 additions & 2 deletions pkg/workflow/mcp-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,9 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
"container": true,
"version": true,
"args": true,
"entrypoint": true,
"entrypointArgs": true,
"mounts": true,
"env": true,
"proxy-args": true,
"url": true,
Expand Down Expand Up @@ -955,9 +957,15 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
if args, hasArgs := config.GetStringArray("args"); hasArgs {
result.Args = args
}
if entrypoint, hasEntrypoint := config.GetString("entrypoint"); hasEntrypoint {
result.Entrypoint = entrypoint
}
if entrypointArgs, hasEntrypointArgs := config.GetStringArray("entrypointArgs"); hasEntrypointArgs {
result.EntrypointArgs = entrypointArgs
}
if mounts, hasMounts := config.GetStringArray("mounts"); hasMounts {
result.Mounts = mounts
}
if env, hasEnv := config.GetStringMap("env"); hasEnv {
result.Env = env
}
Expand Down Expand Up @@ -1006,7 +1014,9 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
if result.Type == "stdio" && result.Container != "" {
// Save user-provided args before transforming
userProvidedArgs := result.Args
entrypoint := result.Entrypoint
entrypointArgs := result.EntrypointArgs
mounts := result.Mounts

// Transform container field to docker command and args
result.Command = "docker"
Expand All @@ -1022,11 +1032,26 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
result.Args = append(result.Args, "-e", envKey)
}

// Insert user-provided args (e.g., volume mounts) before the container image
// Add volume mounts if configured (sorted for deterministic output)
if len(mounts) > 0 {
sortedMounts := make([]string, len(mounts))
copy(sortedMounts, mounts)
sort.Strings(sortedMounts)
for _, mount := range sortedMounts {
result.Args = append(result.Args, "-v", mount)
}
}

// Insert user-provided args (e.g., additional docker flags) before the container image
if len(userProvidedArgs) > 0 {
result.Args = append(result.Args, userProvidedArgs...)
}

// Add entrypoint override if specified
if entrypoint != "" {
result.Args = append(result.Args, "--entrypoint", entrypoint)
}

// Build container image with version if provided
containerImage := result.Container
if result.Version != "" {
Expand All @@ -1041,10 +1066,12 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
result.Args = append(result.Args, entrypointArgs...)
}

// Clear the container, version, and entrypointArgs fields since they're now part of the command
// Clear the container, version, entrypoint, entrypointArgs, and mounts fields since they're now part of the command
result.Container = ""
result.Version = ""
result.Entrypoint = ""
result.EntrypointArgs = nil
result.Mounts = nil
}

return result, nil
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/mcp_config_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ func getRawMCPConfig(toolConfig map[string]any) (map[string]any, error) {
"headers": true,
"version": true,
"args": true,
"entrypoint": true,
"entrypointArgs": true,
"mounts": true,
"proxy-args": true,
"registry": true,
"allowed": true,
Expand Down
Loading