Repository navigation
feat: Add mcp server command - #554
dragosgheorghioiu wants to merge 19 commits into
Conversation
|
Can you stack this on top of #534? I know we split it out of there. |
jedevc
left a comment
There was a problem hiding this comment.
A by hand review 🎉 very old fashioned.
It's neat, I think it's mostly about code structure + some places that I need more clarification on 🎉
| srv.AddTools[ServiceGroup](ctx), | ||
| srv.AddListTool[Certificate](ctx), | ||
| srv.AddCreateTool[Certificate](ctx), | ||
| srv.AddDeleteTool[Certificate](ctx), |
There was a problem hiding this comment.
Hm, I see. I would like to be able to do AddTools[Certificate].
Is there any way we could dynamically add tools, instead of having the strict constraint?
There was a problem hiding this comment.
at compile time all resorces that call AddTools should have all the interfaces already implemented but i can't dynamically call each function from here
this can be done at runtime but it would still compile and a model missing a method would only show up at runtime
There was a problem hiding this comment.
this can be done at runtime but it would still compile and a model missing a method would only show up at runtime
I think that's okay 🙂 It just wouldn't add that method right?
There was a problem hiding this comment.
yeah i would remove AddTools completely and just have one method for every interface (like AddCreateTool)
| switch { | ||
| case resp.StatusCode == http.StatusNotFound: | ||
| return "", false, fmt.Errorf("%w: %s", errDocsNotFound, target) | ||
| case resp.StatusCode != http.StatusOK: | ||
| return "", false, fmt.Errorf("fetching %s: %s", target, resp.Status) | ||
| } | ||
| data, err := io.ReadAll(io.LimitReader(resp.Body, docsMaxBytes)) |
There was a problem hiding this comment.
This will cause weird things with permission denied and other similar errors.
There was a problem hiding this comment.
note to self: review this file more, I haven't looked at it.
|
I would also update the README.md with instructions as to how to configure the MCP server. |
eb1d7c9 to
31b3723
Compare
Add `unikraft mcp`, which serves the CLI's resources over the Model Context Protocol on stdio. Each resource type gets list, create, edit and delete tools, with JSON schemas derived from its fields, plus instance control and logs. The documentation is exposed as `unikraft://docs` resources so agents can read it on demand. HTTP transport is left as a follow-up. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
`unikraft mcp` only spoke the Model Context Protocol over stdio, so HTTP-only clients and hosted deployments could not reach it. Add `--listen host:port`, serving the same tools at /mcp over the SDK's stateless streamable HTTP transport with the process's profile, plus /healthz. Non-loopback addresses need --allow-remote since there is no authentication yet. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Tool descriptions buried their purpose under field path lists and had no worked examples, so a model had little to go on when picking tools or filling them. Rewrite the tool texts task first, move field paths to the filter parameter, and derive an example set per create tool from the help and example tags of the model fields. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Wrong or missing tool arguments were silently dropped, instance_logs and instance_control named their targets unlike every other tool, del entries copied the add help, and models had no guidance on sandboxes, autostart or when instance edits take effect. Reject unknown and missing arguments against the declared schema, take key and keys on the instance tools, describe del entries by name or key, start created instances unless autostart is false, and explain sandboxes and edit timing in the tool texts. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
The sandbox instructions told models to attach a volume at /data, which reads as a separate volume_create step, although volumes accepts a nameless entry with a size that creates the volume. Point the hint at ":/data:size=1GiB", which creates the volume and attaches it in the same instance_create call. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
RunRaw and StartBackground each built the child command, its working directory, stdin and environment on their own, so the two copies drifted apart on every change. Build the command in one place and expose it as Command, so a test can start the CLI itself with the same environment as Run. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Every client loaded all 34 tools, about 65 KB of schemas on each model request, even when an assistant only needed a few of them; the agreed answer was an allowlist on the one server, not a split. Serve only the tools of the resource types named in --resources, using the tool-name prefixes. Unknown names are rejected at startup with the known types, and the instructions list the types served. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
An edit tool's add properties carried the same help text as its set properties, about 1.6 KB of repetition across the tool list, and the instructions claimed create takes add and del objects when it only takes set. Point an add property at set whenever the text would be identical, keep the full help where add differs, and say that create takes set only. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Commands and files inside a sandbox were only reachable through the CLI, so an assistant that created one could not use it; copy would need local paths, which over HTTP are the server host's. sandbox_exec runs argv or a shell line with env, dir, stdin and a timeout, sandbox_read returns a text file and sandbox_write creates or appends to one; read stays available in read-only mode, and the adapter skips the interactive attach flow in favour of tool hints. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
The resource registrars each spelled out the argument handling the command helper already had, the sandbox exec schema was built by hand, and the tests repeated the server setup, client connect and tool lookup across five files. Register every tool through the command helper, infer the exec schema from struct tags, move the session helpers to a mcptest package, and share the integration env and unique-name setup with the eval suite. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Tool descriptions, argument logging and the --resources check were spread over one-use helpers on the server, and several tests only repeated what others already covered. Inline the one-use helpers, check --resources against a fixed list of types in the command, and drop the redundant tests. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
The MCP server package mixed the tools for any model type, the protocol and HTTP plumbing, and the Unikraft instance and sandbox tools, so none of them could be reused without the others. Move the model tools to internal/model/mcp, keep the server, HTTP and docs in internal/mcpserver, and move the Unikraft tools and instructions to internal/mcp. The mcp command wires them together. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Registering a tool for a model type went through two nested closures, wrote the inferred schema back into the caller's tool, and the key descriptions left out metro-prefixed UUIDs. Look the type up before registering its tool, register a copy with a cloned schema, and describe keys as names or UUIDs with an optional metro/ prefix. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Tool arguments were only checked for unknown and missing top-level names, so a value of the wrong type reached the command and failed there with a decoding error. Validate the arguments against the tool's resolved schema, with null members treated as absent and the fields required in set left to the command, which fills in defaults first. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
The CLI and MCP server names, the product title and the website, docs and issues URLs were string literals spread over the root command and the MCP server. Keep them as constants in internal/branding and use those, passing the docs URL to the --docs-url default through a kong variable. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
With --listen the server only spoke plain HTTP, so a deployment without a TLS-terminating proxy in front sent its traffic in the clear. Add --tls-cert and --tls-key, which make the server use HTTPS, and merge the HTTP tests into one that serves both over a real listener. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Docs pages were registered one by one from a hard-coded list of site sections, and an error page served with a 200 status or a page cut off at the size limit was cached and returned as documentation. Serve the table of contents and the page template only, and reject pages that are not markdown or text, or that exceed the limit. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
The README did not mention unikraft mcp, so setting it up for an assistant meant reading its --help. Add a section on registering the server over stdio and HTTP, the --resources and --read-only flags and TLS, and list the command and UNIKRAFT_DOCS_URL in the tables. Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
Signed-off-by: Dragos Gheorghioiu <dragosg@unikraft.com>
31b3723 to
9e21db8
Compare
Add
unikraft mcp, which serves the CLI's resources over the ModelContext Protocol on stdio. Each resource type gets list, create, edit
and delete tools, with JSON schemas derived from its fields, plus
instance control and logs.
The documentation is exposed as
unikraft://docsresources so agentscan read it on demand. HTTP transport is left as a follow-up