Skip to content

1st class support for Docker volumes #191

@peteski22

Description

@peteski22

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
    - workspace

mcpd 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 = false

secrets.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]VolumeEntry
type 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]VolumeExecutionContext
type 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.

Sub-issues

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions