-
Notifications
You must be signed in to change notification settings - Fork 12
Description
Is your feature request related to a problem? Please describe.
Although we recently added support in #185 for the Docker runtime with mcpd (via docker in docker, with stdio), we don't support volumes.
We should update the mozilla-ai registry and mcpd to support configuring and supplying volumes to containers as a 1st class citizen.
Describe the solution you'd like
The following sections explain the user-facing or edges of mcpd that could provide the required support.
mcpd search output
Support the addition of volumes to the search results:
🗂 Volumes (Docker)
app /app Application folder
calendar /calendar-server Calendar folder inside the container
dist /app/dist Application dist folder
gdrive /gdrive-server GDrive folder inside the container
kubeconfig /home/nonroot/.kube/config Kubernetes config file
workspace /workspace Workspace folder inside the container
❗Required volumes (Docker)
- kubeconfig
- workspacemcpd config commands
Support the addition of a new set of config commands under volumes:
# Set host paths for one or multiple volumes
mcpd config volumes set filesystem -- --workspace="/Users/foo/repos/mcpd" --gdrive="/mcp/gdrive"
# List all user-supplied volumes for a server
mcpd config volumes list filesystem
# Clear all configured volumes for a server
mcpd config volumes clear filesystem --force
# Remove a single user-supplied volume entry for a server
mcpd config volumes remove filesystem --workspace.mcpd.toml support
Support volumes in declarative config file, this includes path ('to' mapping), and if the volume is required or not.
[[servers]]
name = "filesystem"
package = "docker::mcp/filesystem:latest"
tools = ["read_file", "write_file", "list_directory", "create_directory"]
[servers.filesystem.volumes.workspace]
path = "/workspace"
required = true
[servers.filesystem.volumes.kubeconfig]
path = "/home/nonroot/.kube/config"
required = true
[servers.filesystem.volumes.gdrive]
path = "/gdrive-server"
required = false
[servers.filesystem.volumes.calendar]
path = "/calendar-server"
required = false
[servers.filesystem.volumes.dist]
path = "/app/dist"
required = false
[servers.filesystem.volumes.app]
path = "/app"
required = falsesecrets.dev.toml support
[servers.filesystem.volumes]
workspace = "/Users/foo/repos/mcpd"
gdrive = "mcp-gdrive"
calendar = "mcp-calendar"
dist = "claude-memory"
app = "."
kubeconfig = "~/.kube/config"
[servers.jira]
args = ["--confluence-token=foo", "--confluence-url=http://mozilla.ai"]
[servers.mcp-discord.env]
DISCORD_TOKEN = "qwerty123"
[servers.notion.env]
NOTION_API_KEY = "xyz123abc090"
[servers.obsidian-mcp]
args = ["/Users/peter/foo.vault"]Registry (mozilla-ai) schema update
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Argument",
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"required": { "type": "boolean", "default": false },
"type": {
"type": "string",
"enum": [
"environment",
"argument",
"argument_bool",
"argument_positional",
"volume"
]
},
"example": { "type": "string" },
"position": { "type": "integer", "minimum": 1 },
"path": { "type": "string" },
"from": { "type": "string" }
},
"required": ["name", "description", "type"],
"additionalProperties": false,
"allOf": [
{
"if": { "properties": { "type": { "const": "argument_positional" } } },
"then": { "required": ["position"] }
},
{
"if": { "properties": { "type": { "const": "volume" } } },
"then": { "required": ["path"] }
}
]
}Registry (mozilla-ai) data sample
Currently we only have the redis server which doesn't require volumes, but here's an example of how the registry JSON would look:
{
"filesystem": {
"arguments": {
"workspace": {
"name": "workspace",
"description": "Workspace folder inside the container",
"type": "volume",
"path": "/workspace",
"required": true
},
"kubeconfig": {
"name": "kubeconfig",
"description": "Kubernetes config file inside the container",
"type": "volume",
"path": "/home/nonroot/.kube/config",
"required": true
},
"gdrive": {
"name": "gdrive",
"description": "GDrive folder inside the container",
"type": "volume",
"path": "/gdrive-server",
"required": false
},
"calendar": {
"name": "calendar",
"description": "Calendar folder inside the container",
"type": "volume",
"path": "/calendar-server",
"required": false
},
"dist": {
"name": "dist",
"description": "Application dist folder",
"type": "volume",
"path": "/app/dist",
"required": false
},
"app": {
"name": "app",
"description": "Application folder",
"type": "volume",
"path": "/app",
"required": false
}
},
"tools": ["read_file", "write_file", "list_directory", "create_directory"]
}
}Describe alternatives you've considered
.
Additional Context
config.ServerEntry(AKA .mcpd.toml)
type VolumesEntry map[string]VolumeEntrytype VolumeEntry struct {
// Path is the container mount path.
// e.g., "/workspace", "/home/nonroot/.kube/config".
Path string `toml:"path"`
// Required indicates whether the volume must be configured by the user.
Required bool `toml:"required"`
}context.ServerExecutionContext (AKA secrets.dev.toml)
type VolumesContext map[string]VolumeExecutionContexttype VolumeExecutionContext struct {
// From is the host path or named volume supplied by the user.
// e.g., "/Users/foo/repos/mcpd" or "mcp-gdrive".
From string `toml:"from,omitempty"`
}runtime.Server
Can be aggregated into Volume used by the Server type:
type Volume struct {
// Name is the user-facing argument name.
// e.g., "workspace", "kubeconfig", "gdrive".
Name string `toml:"-"` // reconstructed from map key
VolumeEntry
VolumeExecutionContext
}func (s *Server) Volumes() (map[string]Volume, error) {
vols := make(map[string]Volume)
for name, entry := range s.ServerEntry.Volumes {
// Look up runtime configuration for this volume.
runtimeVol, exists := s.ServerExecutionContext.Volumes[name]
// Skip optional volumes that aren't configured.
if !entry.Required && !exists {
continue
}
// Return error if required volume is missing
if entry.Required && !exists {
return nil, fmt.Errorf("required volume '%s' not configured", name)
}
vols[name] = Volume{
Name: name,
VolumeEntry: entry,
VolumeExecutionContext: runtimeVol,
}
}
return vols, nil
}We only need to care about volumes if we're using the Docker runtime for an MCP server
if server.Runtime() == RuntimeDocker {
vols, err := server.Volumes()
...Self-Checklist
- I have read the Contributing Guidelines.
- I have searched the existing issues and found no duplicate.
- I have provided a clear and concise description of the problem.
- I have provided a clear and concise description of the proposed solution.