Skip to content

Conversation

AkihiroSuda
Copy link
Member

@AkihiroSuda AkihiroSuda commented Jul 17, 2025

This PR allows AI agents such as Gemini CLI to wrap local file operations (read/write/execute) inside Lima VM.

It should work with Claude Code, Codex, etc. too, but they might need a modification to disable their built-in local file operation tools. (Help wanted for testing)

This feature will be available in Lima v2.0

Preview of the documentation: https://deploy-preview-3744--lima-vm.netlify.app/docs/config/ai/


Interface

pkg/mcp/msi defines "MCP Sandbox Interface" (tentative) that should be reusable for other projects too.

MCP Sandbox Interface defines MCP (Model Context Protocol) tools that can be used for reading, writing, and executing local files with an appropriate sandboxing technology. The sandboxing technology can be more secure and/or efficient than the default tools provided by an AI agent.

MCP Sandbox Interface was inspired by Gemini CLI's built-in tools. https://github.com/google-gemini/gemini-cli/tree/v0.1.12/docs/tools

Implementation

limactl mcp serve INSTANCE launches an MCP server that implements the MCP Sandbox Interface.

Use https://github.com/modelcontextprotocol/inspector to play around with the server.

limactl start default
brew install mcp-inspector
mcp-inspector

In the web browser,

  • Set Command to limactl
  • Set Arguments to mcp serve default
  • Click ▶️Connect

Usage with Gemni CLI

  1. Create .gemini/extensions/lima/gemini-extension.json as follows:
{
  "name": "lima",
  "version": "2.0.0",
  "mcpServers": {
    "lima": {
      "command": "limactl",
      "args": [
        "mcp",
        "serve",
        "default"
      ]
    }
  }
}
  1. Modify .gemini/settings.json so as to disable Gemini CLI's built-in tools except ones that do not relate to local command execution and file I/O:
{
  "coreTools": ["WebFetchTool", "WebSearchTool", "MemoryTool"]
}

TODOs

  • Support writing files
  • Test

// - [RunShellCommandParams].Directory must not be empty
//
// Eventually, this package may be split to a separate repository.
package msi
Copy link
Member Author

Choose a reason for hiding this comment

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

RFC: "MCP Sandbox Interface" might be a misnomer; it might rather sound like an interface for sandboxing MCP servers, e.g., docker run -i --rm example.com/some-mcp-server /usr/bin/some-mcp-server
https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#docker-based-mcp-server

Alternative names:

  • AI Sandbox Interface
  • Agent Sandbox Interface
  • AI Protection Interface
  • ...

Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

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

cc @lima-vm/maintainers

Copy link
Member

Choose a reason for hiding this comment

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

I think the name is fine. It is an interface for MCP to tools that run in a sandbox.

@AkihiroSuda AkihiroSuda force-pushed the mcp branch 3 times, most recently from dadfb7c to ab6c5dc Compare July 17, 2025 15:36
@AkihiroSuda AkihiroSuda force-pushed the mcp branch 2 times, most recently from 0892d60 to 2967ffe Compare September 2, 2025 05:34
@jandubois
Copy link
Member

I've only browsed the code, but if I understand this correctly, the "protection" relies just on the fact that the VM has mounted the filesystem readonly. AFAICT there is no sandboxing of the filesystem, so the ReadFile tool could still read e.g. credentials from ~/.aws/config on the host and is not restricted to subdirectories of the current directory. And since you run arbitrary commands, this would need some kind of chroot jail, and not a pre-filtering of the arguments.

Beyond that, I wonder if this isn't outside the scope of Lima itself. It is an application built on top of Lima, but for some reason compiled into it. It could just as well be built as a separate tool. Or as a limactl-mcp plugin.

So I wonder if it wouldn't be good to include it as an example on how to create plugins for Lima? The reason to keep it in the Lima repo would be that MCP is a hot topic, and having it bundled makes it easier to discover.

@AkihiroSuda
Copy link
Member Author

I've only browsed the code, but if I understand this correctly, the "protection" relies just on the fact that the VM has mounted the filesystem readonly. AFAICT there is no sandboxing of the filesystem, so the ReadFile tool could still read e.g. credentials from ~/.aws/config on the host and is not restricted to subdirectories of the current directory. And since you run arbitrary commands, this would need some kind of chroot jail, and not a pre-filtering of the arguments.

The home directory except the pwd can be hidden with:

limactl start --mount-only "$(pwd)"

Beyond that, I wonder if this isn't outside the scope of Lima itself. It is an application built on top of Lima, but for some reason compiled into it. It could just as well be built as a separate tool. Or as a limactl-mcp plugin.

Maybe lima-mcp (or lima-mcpserver) so as to keep limac[TAB] shell completion working ?

@jandubois
Copy link
Member

jandubois commented Sep 3, 2025

Maybe lima-mcp (or lima-mcpserver) so as to keep limac[TAB] shell completion working ?

limactl-mcp would be the naming scheme we have already implemented. Tab completion still works, but you have to type the space yourself:

$ cat ~/bin/limactl-hello
#!/bin/bash
echo "Hello world from $BASH_SOURCE"

$ limactl hello
Hello world from /Users/jan/bin/limactl-hello

We could also look for plugins in /usr/local/share/libexec/lima to have an option to install them outside the PATH by default (we already look for external drivers in that directory).

@AkihiroSuda
Copy link
Member Author

Reimplemented as limactl-mcp CLI plugin

@AkihiroSuda AkihiroSuda force-pushed the mcp branch 2 times, most recently from 42eb5c5 to 206003b Compare September 9, 2025 08:21
@AkihiroSuda AkihiroSuda marked this pull request as ready for review September 9, 2025 08:21
@jandubois
Copy link
Member

It doesn't seem to be working for me:

l mcp -v
limactl-mcp version 2.0.0-alpha.0-39-gf71031e6l ls default qemu
NAME       STATUS     SSH                VMTYPE    ARCH       CPUS    MEMORY    DISK      DIR
default    Stopped    127.0.0.1:0        vz        aarch64    4       4GiB      100GiB    ~/.lima/default
qemu       Running    127.0.0.1:63754    qemu      aarch64    4       4GiB      100GiB    ~/.lima/qemul mcp serve default
FATA[0000] expected status of instance "default" to be "Running", got ""l mcp serve qemu
FATA[0000] expected status of instance "qemu" to be "Running", got ""l shell qemu uname -a
Linux lima-qemu 6.14.0-23-generic #23-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 13 22:12:54 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux

@AkihiroSuda
Copy link
Member Author

It doesn't seem to be working for me:

Works for me.
Maybe you have some "non-standard" config? (e.g., custom LIMA_HOME)

@jandubois
Copy link
Member

Maybe you have some "non-standard" config? (e.g., custom LIMA_HOME)

No, I have the default setup. My suspicion is that store.Inspect does not work properly because limactl-mcp doesn't have the builtin drivers, and I don't have the drivers installed as external drivers. So loading of the driver-specific fields does not work.

I would have expected to see something like vmType "vz" is not registered or something like that, so don't know if the theory is correct.

Are you sure you don't have external qemu and vz drivers installed on your system, that might be responsible for making it work on your machine.

I think as a plugin the code has to call limactl ls INSTANCE --json as a subprocess instead of using store.Inspect.

@jandubois
Copy link
Member

Confirmed: The error goes away after running

ADDITIONAL_DRIVERS="qemu vz" make native additional-drivers limactl-plugins install

@jandubois
Copy link
Member

I would have expected to see something like vmType "vz" is not registered or something like that,

And it turns out to be #2668 once again wasting time. We really should fix this non-idiomatic interface.

When I add

@@ -151,6 +152,9 @@ func mcpServeAction(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
+	if len(inst.Errors) != 0 {
+		return errors.Join(inst.Errors...)
+	}
 	if err = ts.RegisterInstance(ctx, inst); err != nil {
 		return err
 	}

then I get a more meaningful error:

l mcp serve default
FATA[0000] failed to resolve vm for "/Users/jan/.lima/default/lima.yaml": vmType "vz" is not a registered driver

@jandubois
Copy link
Member

Is it a bug that make uninstall does not remove external drivers?

@unsuman
Copy link
Contributor

unsuman commented Sep 15, 2025

Is it a bug that make uninstall does not remove external drivers?

I guess for a dev env, make clean should remove the external drivers. Not sure how make uninstall works though?

@jandubois
Copy link
Member

I guess for a dev env, make clean should remove the external drivers. Not sure how make uninstall works though?

You can just look it up in the Makefile. 😄

make clean just deletes _output with everything in it.

The install target actually runs uninstall first, to make sure it installs a consistent set of files. It tries do delete anything that has potentially been installed by make install before. I think we just forgot to add the external drivers to the uninstall target.

@unsuman
Copy link
Contributor

unsuman commented Sep 15, 2025

Oh, got it, thanks! I've raised a fix for the same #4034

@AkihiroSuda AkihiroSuda reopened this Sep 16, 2025
@AkihiroSuda AkihiroSuda marked this pull request as draft September 17, 2025 03:33
@AkihiroSuda AkihiroSuda marked this pull request as ready for review September 18, 2025 04:18
@AkihiroSuda
Copy link
Member Author

Fixed the incompatibility with built-in drivers

=== Interface ===

`pkg/mcp/msi` defines "MCP Sandbox Interface" (tentative)
that should be reusable for other projects too.

MCP Sandbox Interface defines MCP (Model Context Protocol) tools
that can be used for reading, writing, and executing local files
with an appropriate sandboxing technology. The sandboxing technology
can be more secure and/or efficient than the default tools provided
by an AI agent.

MCP Sandbox Interface was inspired by Gemini CLI's built-in tools.
https://github.com/google-gemini/gemini-cli/tree/v0.1.12/docs/tools

=== Implementation ===

`limactl mcp serve INSTANCE` launches an MCP server that implements the MCP
Sandbox Interface.

Use <https://github.com/modelcontextprotocol/inspector>
to play around with the server.

```bash
limactl start default
brew install mcp-inspector
mcp-inspector
```
In the web browser,
- Set `Command` to `limactl`
- Set `Arguments` to `mcp serve default`
- Click `▶️Connect`

=== Usage with Gemni CLI ===

1. Create `.gemini/extensions/lima/gemini-extension.json` as follows:
```json
{
  "name": "lima",
  "version": "2.0.0",
  "mcpServers": {
    "lima": {
      "command": "limactl",
      "args": [
        "mcp",
        "serve",
        "default"
      ]
    }
  }
}
```

2. Modify `.gemini/settings.json` so as to disable Gemini CLI's built-in tools
except ones that do not relate to local command execution and file I/O:
```json
{
  "coreTools": ["WebFetchTool", "WebSearchTool", "MemoryTool"]
}
```

Signed-off-by: Akihiro Suda <[email protected]>
Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM

@@ -112,6 +112,8 @@ help-targets:
@echo '- limactl : Build limactl, and lima'
@echo '- lima : Copy lima, and lima.bat'
@echo '- helpers : Copy nerdctl.lima, apptainer.lima, docker.lima, podman.lima, and kubectl.lima'
# TODO: move CLI plugins to _output/libexec/lima/
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't have to be in this PR, but should be done before 2.0 release.

cmd.AddCommand(
newMcpInfoCommand(),
newMcpServeCommand(),
// TODO: `limactl-mcp install-gemini` ?
Copy link
Member

Choose a reason for hiding this comment

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

I feel that is out-of-scope for an MCP server. And gemini-cli seems to be available in package managers already anyways.


// Path returns the path to the `limactl` executable.
func Path() (string, error) {
limactl := cmp.Or(os.Getenv("LIMACTL"), "limactl")
Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed? It needs to be documented, if we do need it. And then it should be a pattern for all plugins.

Right now we go to extra effort that the version of limactl that was used to invoke the plugin is first on the PATH before the plugin is started.

If this is purely for development/testing, then the variable should start with an underscore.

// Inspect runs `limactl list --json INST` and parses the output.
func Inspect(ctx context.Context, limactl, instName string) (*limatype.Instance, error) {
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, limactl, "list", "--json", instName)
Copy link
Member

Choose a reason for hiding this comment

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

Currently we document --json as a legacy flag that is equal to --format json.

I know "legacy" does not necessarily mean "deprecated", but it is pointing that way.

I'm in favour of keeping --json as a shorthand, and not calling it "legacy". But if it is considered legacy, then we should use the modern form here.

Comment on lines +47 to +48
// Offset *int `json:"offset,omitempty" jsonschema:"For text files, the 0-based line number to start reading from. Requires limit to be set."`
// Limit *int `json:"limit,omitempty" jsonschema:"For text files, the maximum number of lines to read. If omitted, reads a default maximum (e.g., 2000 lines) or the entire file if feasible."`
Copy link
Member

Choose a reason for hiding this comment

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

We should not have commented out code unless it also has a TODO explanation.


var SearchFileContent = &mcp.Tool{
Name: "search_file_content",
Description: `Searches for a regular expression pattern within the content of files in a specified directory. Internally calls 'git grep -n --no-index'.`,
Copy link
Member

Choose a reason for hiding this comment

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

Should the server test that git is available in the instance and not provide the search tool if it isn't?

This should be documented in the limactl mcp serve --help that the instance needs to have git installed.

// - the output format is JSON, not a plain text
// - the output of [SearchFileContent] always corresponds to `git grep -n --no-index`
// - [RunShellCommandParams].Command is a string slice, not a string
// - [RunShellCommandParams].Directory is a absolute path, not a relative path
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// - [RunShellCommandParams].Directory is a absolute path, not a relative path
// - [RunShellCommandParams].Directory is an absolute path, not a relative path

// - [RunShellCommandParams].Directory must not be empty
//
// Eventually, this package may be split to a separate repository.
package msi
Copy link
Member

Choose a reason for hiding this comment

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

I think the name is fine. It is an interface for MCP to tools that run in a sandbox.

if err != nil {
return nil, nil, err
}
return &mcp.CallToolResult{
Copy link
Member

Choose a reason for hiding this comment

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

Should there be some post-processing for binary files?

Either reject them, or show a hexdump style output for the beginning of the file?

It may be better to reject binary output and provide a separate hexdump tool. The ReadFile tool could explain why the output was not shown and recommend to use the hexdump tool if the binary content really is needed.

There are two kinds of scenario to use Lima with AI:

- [AI in Lima](./ai-in-lima): running an AI agent inside a VM
- [Lima in AI](./lima-in-ai): calling Lima's MCP tools from an AI agent running outside a VM
Copy link
Member

Choose a reason for hiding this comment

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

I would call this "MCP tools in Lima".

Because "Lima in AI" means to me that the AI tool itself will run limactl commands on the host, and not via MCP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants