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.