feat: refine representation of commands in the API

Refs #28. After some thinking, this tries for a 'best of both worlds'
approach. Simple arguments can be present essentially as the original
spec recommended. As discussed, we don't specify the args for `npx` and
friends directly, so a simple package is merely...

```yaml
- name: my-api-mcp
  # ...
  packages:
    - type: npm
      name: "@c4312/my-api-mcp"
      version: "0.0.1"
      arguments:
        # Named inputs in 'arguments' generate a `--name=<value>`
        - type: named
          name: "api-key"
          is_required: true
          is_secret: true
```

However, I think we need to be able to specify runtime args in some
cases, otherwise the registry eventually mirrors all docker configs and
options which is not a great situation to be in. So, I suggest a
`runtime_args` in the same format. Say I wanted that for npm, I could:

```yaml
- name: my-api-mcp
  # ...
  packages:
    - type: npm
      name: "@c4312/my-api-mcp"
      version: "0.0.1"
      runtime_arguments:
        # Arguments with a pre-specified `value` don't get prompted for input
        - type: positional
          value: "--experimental-eventsource"
```

But Docker mounts are... complex, and ultimately we may deal with cases
that need multiple inputs in an argument like that. So for that I suggest
the third kind of "templated" input that does basic replacements and has
`properties` that follow the same general input schema:

```yaml
- name: server-filesystem
  # ...
  packages:
    - type: docker
      name: "mcp/filesystem"
      version: "0.0.1"
      runtime_arguments:
        - type: template
          description: Mount paths to access.
          required: true
          repated: true
          template: "--mount=type=bind,src={host_path},dst={container_path}"
          values:
            host_path:
              description: Path to access on the local machine
              required: true
              format: filepath
            container_path:
              description: Path to mount in the container. It should be rooted in `/project` directory.
              required: true
              format: string
              default: "/project"

      arguments:
        - type: positional
          value: "/project"
```

And then client config ends up being pretty simple, for the above case
for example a client could have this config:

```
{
  "filesystem": {
    "server": "@modelcontextprotocol/servers/src/filesystem@1.0.2",
    "package": "docker",
    "settings": {
      "mount_config": [
        { "source_path": "/Users/username/Desktop", "target_path": "/project/desktop" },
        { "source_path": "/path/to/other/allowed/dir", "target_path": "//projects/other/allowed/dir,ro" },
      ]
    }
  }
}
```

Which might generate a CLI:

```
docker run \
  --mount=type=bind,src=/Users/username/Desktop,dst=/project/desktop \
  --mount=type=bind,src=/path/to/other/allowed/dir,dst=/project/other/allowed/dir,ro \
  mcp/filesystem:1.0.2 \
  /project
```

I think this does a good job of accomplishing the goals we care about:

- We get nicely defined inputs that are easy to extend, easy to process,
  and easy for clients to configure.
- Versioning is enforced as we don't require the package to specify the
  identifier to npx/uvx/etc.
- We _usually_ can break away from a dependency on a specific runtime.
  This promise is broken a bit with `runtime_arguments`, most packages
  should not need this and those that do are those inherently more
  tightly coupled to a specific runtime like Docker (e.g. they would be
  pretty uncommon for Node or Python.)
- We don't have a suple complex template system with loops and fallbacks
  and conditionals, like we would if we tried to have a single template
  string for a command.
This commit is contained in:
Connor Peet 2025-05-09 18:37:08 -07:00
commit 8e5cf14a42
2 changed files with 194 additions and 306 deletions

View file

@ -65,122 +65,61 @@ GET /v0/servers/a5e8a7f0-d4e4-4a1d-b12f-2896a23fd4f1?version=0.0.3
"registry_name": "npm",
"name": "@modelcontextprotocol/server-filesystem",
"version": "1.0.2",
"command": {
"name": "npx",
"subcommands": [],
"positional_arguments": [
{
"position": 0,
"name": "package",
"description": "NPM package name",
"default_value": "@modelcontextprotocol/server-filesystem",
"is_required": true,
"is_editable": false,
"is_repeatable": false,
"choices": []
},
{
"position": 1,
"name": "path",
"description": "Path to access",
"default_value": "/Users/username/Desktop",
"is_required": true,
"is_editable": true,
"is_repeatable": true,
"choices": []
}
],
"named_arguments": [
{
"short_flag": "-y",
"requires_value": false,
"is_required": false,
"is_editable": false,
"description": "Skip prompts and automatically answer yes",
"choices": []
}
]
},
"arguments": [
{
"type": "positional",
"description": "Path to access",
"default": "/Users/username/Desktop",
"is_required": true,
"is_repeated": true
}
],
"environment_variables": [
{
"type": "named",
"name": "LOG_LEVEL",
"description": "Logging level (debug, info, warn, error)",
"required": false,
"default_value": "info"
"default": "info"
}
]
},
{
"name": "docker",
"package_name": "mcp/filesystem",
"registry_name": "docker",
"name": "mcp/filesystem",
"version": "1.0.2",
"command": {
"name": "docker",
"subcommands": [
{
"name": "run",
"description": "Run the Docker container",
"is_required": true,
"subcommands": [],
"positional_arguments": [],
"named_arguments": [
{
"short_flag": "-i",
"requires_value": false,
"is_required": true,
"is_editable": false,
"description": "Run in interactive mode"
},
{
"long_flag": "--rm",
"requires_value": false,
"is_required": true,
"is_editable": false,
"description": "Remove container when it exits"
},
{
"long_flag": "--mount",
"requires_value": true,
"is_required": true,
"is_repeatable": true,
"is_editable": true,
"description": "Mount a volume into the container",
"default_value": "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop",
"choices": []
}
]
"runtime_arguments": [
{
"type": "template",
"description": "Mount a volume into the container",
"template": "--mount=type=bind,src={source_path},dst={target_path}",
"is_required": true,
"is_repeated": true,
"properties": {
"source_path": {
"description": "Source path on host",
"format": "filepath",
"is_required": true
},
"target_path": {
"description": "Path to mount in the container. It should be rooted in `/project` directory.",
"is_required": true,
"default": "/project",
}
}
],
"positional_arguments": [
{
"position": 0,
"name": "image",
"description": "Docker image name",
"default_value": "mcp/filesystem",
"is_required": true,
"is_editable": false,
"is_repeatable": false,
"choices": []
},
{
"position": 1,
"name": "root_path",
"description": "Root path for filesystem access",
"default_value": "/projects",
"is_required": true,
"is_editable": false,
"is_repeatable": false,
"choices": []
}
],
"named_arguments": []
},
}
],
"arguments": [
{
"type": "positional",
"value": "/project",
}
]
"environment_variables": [
{
"type": "named",
"name": "LOG_LEVEL",
"description": "Logging level (debug, info, warn, error)",
"required": false,
"default_value": "info"
"default": "info"
}
]
}
@ -220,25 +159,13 @@ API Response:
"registry_name": "npm",
"name": "@modelcontextprotocol/server-brave-search",
"version": "1.0.2",
"command": {
"name": "npx",
"subcommands": [],
"positional_arguments": [],
"named_arguments": [
{
"short_flag": "-y",
"requires_value": false,
"is_required": false,
"description": "Skip prompts"
}
]
},
"environment_variables": [
{
"type": "named",
"name": "BRAVE_API_KEY",
"description": "Brave Search API Key",
"required": true,
"default_value": ""
"is_required": true,
"is_secret": true
}
]
}
@ -286,51 +213,33 @@ API Response:
"registry_name": "docker",
"name": "mcp/filesystem",
"version": "1.0.2",
"command": {
"name": "docker",
"subcommands": [
{
"name": "run",
"description": "Run the Docker container",
"is_required": true,
"named_arguments": [
{
"short_flag": "-i",
"requires_value": false,
"is_required": true,
"description": "Run in interactive mode"
},
{
"long_flag": "--rm",
"requires_value": false,
"is_required": true,
"description": "Remove container when it exits"
},
{
"long_flag": "--mount",
"requires_value": true,
"is_required": true,
"is_repeatable": true,
"description": "Mount a volume into the container"
}
]
"runtime_arguments": [
{
"type": "template",
"description": "Mount a volume into the container",
"template": "--mount=type=bind,src={source_path},dst={target_path}",
"is_required": true,
"is_repeated": true,
"properties": {
"source_path": {
"description": "Source path on host",
"format": "filepath",
"is_required": true
},
"target_path": {
"description": "Path to mount in the container. It should be rooted in `/project` directory.",
"is_required": true,
"default": "/project",
}
}
],
"positional_arguments": [
{
"position": 0,
"name": "image",
"description": "Docker image name",
"default_value": "mcp/filesystem"
},
{
"position": 1,
"name": "root_path",
"description": "Root path for filesystem access",
"default_value": "/projects"
}
]
}
}
],
"arguments": [
{
"type": "positional",
"value": "/project",
}
]
}
]
}
@ -340,17 +249,14 @@ claude_desktop_config.json:
```json
{
"filesystem": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop",
"--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro",
"--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt",
"mcp/filesystem",
"/projects"
]
"server": "@modelcontextprotocol/servers/src/filesystem@1.0.2",
"package": "docker",
"settings": {
"mount_config": [
{ "source_path": "/Users/username/Desktop", "target_path": "/project/desktop" },
{ "source_path": "/path/to/other/allowed/dir", "target_path": "/project/other/allowed/dir,ro" },
]
}
}
}
```
@ -380,4 +286,4 @@ API Response:
}
]
}
```
```

View file

@ -3,8 +3,8 @@ info:
title: MCP Server Registry API
summary: API for discovering and accessing MCP servers metadata
description: |
REST API that centralizes metadata about publicly available MCP servers by allowing server creators to submit
and maintain metadata about their servers in a standardized format. This API enables MCP client
REST API that centralizes metadata about publicly available MCP servers by allowing server creators to submit
and maintain metadata about their servers in a standardized format. This API enables MCP client
applications and "server aggregator" type consumers to discover and install MCP servers.
version: 0.0.1
contact:
@ -82,7 +82,7 @@ paths:
example: "Server not found"
components:
schemas:
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"
Repository:
type: object
required:
@ -101,7 +101,7 @@ components:
id:
type: string
example: "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9"
Server:
type: object
required:
@ -144,7 +144,7 @@ components:
example: true
description: Whether the MCP server version is the latest version available in the registry.
$schema: "https://json-schema.org/draft/2020-12/schema"
ServerList:
type: object
required:
@ -162,131 +162,7 @@ components:
total_count:
type: integer
example: 1
Argument:
type: object
required:
- name
- description
- is_required
properties:
name:
type: string
example: "path"
description:
type: string
example: "Path to access"
is_required:
type: boolean
example: true
is_repeatable:
type: boolean
example: false
is_editable:
type: boolean
example: true
choices:
type: array
items:
type: string
example: []
default_value:
type: string
example: "/Users/username/Desktop"
PositionalArgument:
allOf:
- $ref: '#/components/schemas/Argument'
- type: object
required:
- position
properties:
position:
type: integer
example: 1
NamedArgument:
allOf:
- $ref: '#/components/schemas/Argument'
- type: object
properties:
short_flag:
type: string
example: "-y"
long_flag:
type: string
example: "--mount"
requires_value:
type: boolean
example: true
Subcommand:
type: object
required:
- name
- description
properties:
name:
type: string
example: "run"
description:
type: string
example: "Run the Docker container"
is_required:
type: boolean
example: true
subcommands:
type: array
items:
$ref: '#/components/schemas/Subcommand'
positional_arguments:
type: array
items:
$ref: '#/components/schemas/PositionalArgument'
named_arguments:
type: array
items:
$ref: '#/components/schemas/NamedArgument'
Command:
type: object
properties:
name:
type: string
enum: [npx, docker, pypi, uvx] # TODO: Add all supported commands as a whitelist
example: "npx"
subcommands:
type: array
items:
$ref: '#/components/schemas/Subcommand'
positional_arguments:
type: array
items:
$ref: '#/components/schemas/PositionalArgument'
named_arguments:
type: array
items:
$ref: '#/components/schemas/NamedArgument'
EnvironmentVariable:
type: object
required:
- name
- description
properties:
name:
type: string
example: "LOG_LEVEL"
description:
type: string
example: "Logging level (debug, info, warn, error)"
required:
type: boolean
example: false
default_value:
type: string
example: "info"
Package:
type: object
required:
@ -304,13 +180,115 @@ components:
version:
type: string
example: "1.0.2"
command:
$ref: '#/components/schemas/Command'
runtime_arguments:
type: array
description: List of arguments passed to the package's runtime command (such as docker or npx).
items:
$ref: '#/components/schemas/Input'
arguments:
type: array
description: List of arguments passed to the package's binary.
items:
$ref: '#/components/schemas/Input'
environment_variables:
type: array
items:
$ref: '#/components/schemas/EnvironmentVariable'
$ref: '#/components/schemas/NamedInput'
UserInput:
type: object
properties:
description:
type: string
is_required:
type: boolean
default: false
is_repeated:
type: boolean
description: Whether the input can be repeated multiple times in the command line.
default: false
format:
type: string
description: Input format. Includes a variety of standardized formats, including `filepath`, which should be interpreted as a file on the user's filesystem.
enum: [string, number, boolean, array, filepath]
default: string
value:
type: string
description: The default value for the input. If this is not set, the user will be asked to provide a value.
is_secret:
type: boolean
description: Whether the input is a secret value (e.g., password, token). If true, clients should handle the value securely.
default: false
default:
type: string
description: The default value for the input.
PositionalInput:
description: A positional input is a value inserted verbatim into the command line.
allOf:
- $ref: '#/components/schemas/UserInput'
- type: object
required:
- type
properties:
type:
type: string
enum: [positional]
example: "positional"
NamedInput:
description: A command-line `--flag={value}`, named environment variable or header.
allOf:
- $ref: '#/components/schemas/UserInput'
- type: object
required:
- type
- name
properties:
type:
type: string
enum: [named]
example: "named"
name:
type: string
example: "--port"
TemplateInput:
- type: object
required:
- type
- template
- name
properties:
type:
type: string
enum: [template]
name:
type: string
example: mount_config
description: The name of the input, which can be used by clients as an identifier to reference the input. It is not present in the generated command line.
description:
type: string
is_required:
type: boolean
is_repeated:
type: boolean
description: Whether the input can be repeated multiple times in the command line.
template:
type: string
description: The template string that will be used to generate the value. Values wrapped in `{curly_braces}` will be replaced with the corresponding properties.
example: "--mount=type=bind,src={host_path},dst={container_path}"
properties:
type: object
additionalProperties:
$ref: '#/components/schemas/UserInput'
Input:
anyOf:
- $ref: '#/components/schemas/PositionalInput'
- $ref: '#/components/schemas/NamedInput'
- $ref: '#/components/schemas/TemplateInput'
Remote:
type: object
required:
@ -325,7 +303,11 @@ components:
type: string
format: uri
example: "https://mcp-fs.example.com/sse"
headers:
type: array
items:
$ref: '#/components/schemas/NamedInput'
ServerDetail:
allOf:
- $ref: '#/components/schemas/Server'
@ -342,4 +324,4 @@ components:
remotes:
type: array
items:
$ref: '#/components/schemas/Remote'
$ref: '#/components/schemas/Remote'