Skip to content

feat: Add mcp server command - #554

Open
dragosgheorghioiu wants to merge 19 commits into
dragosg/tool-1688-rename-cli-resources-to-modelsfrom
dragosg/feat/mcp-server
Open

dragosgheorghioiu wants to merge 19 commits into
dragosg/tool-1688-rename-cli-resources-to-modelsfrom
dragosg/feat/mcp-server

Conversation

@dragosgheorghioiu

Copy link
Copy Markdown
Contributor

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

@jedevc

jedevc commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Can you stack this on top of #534? I know we split it out of there.

@jedevc jedevc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 🎉

Comment thread internal/resource/partition.go Resolved
Comment thread internal/mcpserver/server.go Resolved
Comment thread internal/mcpserver/server.go Resolved
Comment thread internal/mcpserver/server.go Resolved
Comment thread internal/cmd/mcp.go Outdated
Comment on lines +220 to +223
srv.AddTools[ServiceGroup](ctx),
srv.AddListTool[Certificate](ctx),
srv.AddCreateTool[Certificate](ctx),
srv.AddDeleteTool[Certificate](ctx),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@jedevc jedevc Oct 30, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i would remove AddTools completely and just have one method for every interface (like AddCreateTool)

Comment thread internal/mcpserver/docs.go Outdated
Comment on lines +178 to +184
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))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause weird things with permission denied and other similar errors.

Comment thread internal/mcpserver/http_test.go Resolved
Comment thread internal/mcp/instances.go Resolved
Comment thread internal/cmd/mcp.go Resolved

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: review this file more, I haven't looked at it.

@jedevc

jedevc commented Oct 20, 2026

Copy link
Copy Markdown
Member

I would also update the README.md with instructions as to how to configure the MCP server.

@dragosgheorghioiu
dragosgheorghioiu force-pushed the dragosg/feat/mcp-server branch 3 times, most recently from eb1d7c9 to 31b3723 Compare October 26, 2026 14:04
@dragosgheorghioiu
dragosgheorghioiu removed this pull request from stack #556 October 27, 2026 15:10
@dragosgheorghioiu
dragosgheorghioiu changed the base branch from prod-staging to dragosg/tool-1688-rename-cli-resources-to-models October 27, 2026 15:11
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants