-
Notifications
You must be signed in to change notification settings - Fork 559
feat: refine representation of commands in the API #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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/[email protected]", "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.
5d411ed to
c4e51cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this hybrid approach a lot. named, positional, and template as "types" seem to be a good answer here. Keeps it quite minimalist and clean insofar as the config we're asking devs to write.
Just some small comments. @sridharavinash would be interesting to see if our seed data is able to conform to this without issues.
I'm a 👍 to merge and move forward with this but would appreciate some more eyes on it before proceeding.
ea642de to
a392793
Compare
a392793 to
54609ee
Compare
sandy081
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for this approach.
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:
I am wondering how runtime args work for npm type package? Since we are leaving the decision about which runtime executable to run to the client, should the runtime_args also specify to what exec these args are meant for? For example, how can a client now the given runtime_args are for npx or for someother exec?
I thought about this, perhaps with some For npm packages, I think I it would just be the runtime flags for Node.js itself. |
Yeah something in these lines would be good |
Shouldn't be too much of big lift. Once we're good here and I can finagle the seed data to make the new changes. |
|
This looks great to me! I like the design of the three options. Maybe I am missing something about the intended client logic, but what validations would we imagine for |
In general we have to assume that clients do the right shell escaping for arguments (or don't run the in shell, e.g. |
fcbe59d to
2cbca6c
Compare
2cbca6c to
db7f9e7
Compare
sandy081
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All looks good except for few minor comments.
Approving.
I think we should probably continue this discussion: #41 |
tadasant
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm! Thank you very much for driving this @connor4312 and @sandy081
|
🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This greatly simplifies the command structure from the previous draft of the API specification.
There are a few matters I would like to discuss with the current iteration and get your thoughts on the validity of these matters.
1. Making the explicitly detailed parts of the command implicit:
Example: For the command, docker run --rm -i -e BRAVE_API_KEY mcp/brave-search, this change makes it such that the parts of the command such as run, --rm, -i, -e (possibly) would all have to be implicitly known to be added to the constructed config by the client for the server to work as expected.
I am more in favour of the arguments being a single array wherein a consumer of this API can convert the arguments into the MCP config format through a single statement as follows.
arguments.map((argument: Argument) => { /* some argument handling logic */})The aforementioned approach would differ to looking up each separate type of argument in the configuration object as follows.
arguments.named_arguments((argument: Argument) => { /* some named argument handling logic */ } + arguments.runtime_arguments((argument: Argument) => { /* some named argument handling logic */ } + arguments.package_arguments((argument: Argument) => { /* some named argument handling logic */ } + arguments.template_arguments((argument: Argument) => { /* some named argument handling logic */ }Having sets of different arguments in different places also may complicate ordering (although the effects of this may only be minor, if the arguments are mostly constructed well by all package uploaders).
2. Deleting command causes tight-coupling of "registry_name" and "command":
Deleting the "command" section with "name": "npx" or "name": "docker" then tightly couples the command to the source of the registry.
While this is not a problem if the registries are simply limited to NPM, Docker Hub or PyPI (https://pypi.org) which imply the runtime npx, docker and uvx as the command, a large benefit of this being open-source software is that enterprises may want to deploy their own versions internally for MCP use which may result in package registries being JFrog, GitHub Container Registry (ghcr.io), the artifact registry of the chosen cloud provider (AWS, Azure, GCP) or any variant that obeys the same API as NPM, PyPI or Docker.
This would result in the "registry_name" field being changed to "JFrog", "GHCR" or whatever the respective registry is while the command would still remain npx, docker, or uvx and have to be handled by the client.
I propose de-coupling the run command from the registry name as a good pattern from the previous iteration of this API draft. Please let me know your thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tiovikram for visibility, I think you should open up an Issue for each of these topics, as this PR is closed and will be hard to get eyeballs on.
I'm not quite following the first suggestion, maybe some more explicit examples of what that means for the API shape would be helpful.
For the second one, it does seem worth a discussion whether we want an analog to runtime_hint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example: For the command, docker run --rm -i -e BRAVE_API_KEY mcp/brave-search, this change makes it such that the parts of the command such as run, --rm, -i, -e (possibly) would all have to be implicitly known to be added to the constructed config by the client for the server to work as expected.
If arguments are needed to make the server work as expected, they should be in the runtime_arguemtns. Of those you included, that is only -e BRAVE_API_KEY. --rm is mostly up to the user/client if they want that behavior, -i will be something clients always will add for docker runs that communicate over stdio.
I am more in favour of the arguments being a single array wherein a consumer of this API can convert the arguments into the MCP config format through a single statement as follows.
Please see previous discussion on package versioning here for context on why this was changed #3 (comment)
Deleting the "command" section with "name": "npx" or "name": "docker" then tightly couples the command to the source of the registry.
For that reason in the past I have suggested a shell fallback type, however I didn't add that in this PR to keep things initially scoped
* Update the model to handle new changes to schema based on #33 * Made the import script a little simpler. * Add a new updated seed file * update template mcp.json file for publisher
* Update the model to handle new changes to schema based on #33 * Made the import script a little simpler. * Add a new updated seed file * update template mcp.json file for publisher
| claude_desktop_config.json: | ||
| ```json | ||
| { | ||
| "filesystem": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@connor4312 I just realized I overlooked this diff -- it's meant to be a claude_desktop_config.json file, so I don't think this change is accurate as it prescribes what Claude Desktop should be doing. I think we should have left this as-is? Or am I misreading something?
* Update the model to handle new changes to schema based on #33 * Made the import script a little simpler. * Add a new updated seed file * update template mcp.json file for publisher
* Update the model to handle new changes to schema based on modelcontextprotocol/registry#33 * Made the import script a little simpler. * Add a new updated seed file * update template mcp.json file for publisher
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
npxand friends directly, so a simple package is merely...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_argsin the same format. Say I wanted that for npm, I could: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
propertiesthat follow the same general input schema: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/[email protected]", "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" }, ] } } }Which might generate a CLI:
I think this does a good job of accomplishing the goals we care about:
runtime_arguments, but 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/npx or Python/uvx.)cc @tadasant @sandy081 @digitarald @sridharavinash @toby