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:
parent
b005e9c09a
commit
8e5cf14a42
2 changed files with 194 additions and 306 deletions
252
api/README.md
252
api/README.md
|
|
@ -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:
|
|||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
|||
254
api/openapi.yaml
254
api/openapi.yaml
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in a new issue