Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl-mcp/main.go
2621 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"context"
8
"encoding/json"
9
"errors"
10
"fmt"
11
"os"
12
"path/filepath"
13
"runtime"
14
"strings"
15
16
"github.com/modelcontextprotocol/go-sdk/mcp"
17
"github.com/sirupsen/logrus"
18
"github.com/spf13/cobra"
19
"golang.org/x/text/cases"
20
"golang.org/x/text/language"
21
22
"github.com/lima-vm/lima/v2/pkg/limactlutil"
23
"github.com/lima-vm/lima/v2/pkg/mcp/toolset"
24
"github.com/lima-vm/lima/v2/pkg/version"
25
)
26
27
func main() {
28
if err := newApp().Execute(); err != nil {
29
logrus.Fatal(err)
30
}
31
}
32
33
func newApp() *cobra.Command {
34
cmd := &cobra.Command{
35
Use: "limactl-mcp",
36
Short: "Model Context Protocol plugin for Lima (EXPERIMENTAL)",
37
Version: strings.TrimPrefix(version.Version, "v"),
38
SilenceUsage: true,
39
SilenceErrors: true,
40
}
41
cmd.AddCommand(
42
newMcpInfoCommand(),
43
newMcpServeCommand(),
44
newMcpGenDocCommand(),
45
// TODO: `limactl-mcp configure gemini` ?
46
)
47
return cmd
48
}
49
50
func newServer() *mcp.Server {
51
impl := &mcp.Implementation{
52
Name: "lima",
53
Title: "Lima VM, for sandboxing local command executions and file I/O operations",
54
Version: version.Version,
55
}
56
serverOpts := &mcp.ServerOptions{
57
Instructions: `This MCP server provides tools for sandboxing local command executions and file I/O operations,
58
by wrapping them in Lima VM (https://lima-vm.io).
59
60
Use these tools to avoid accidentally executing malicious codes directly on the host.
61
`,
62
}
63
if runtime.GOOS != "linux" {
64
serverOpts.Instructions += fmt.Sprintf(`
65
66
NOTE: the guest OS of the VM is Linux, while the host OS is %s.
67
`, cases.Title(language.English).String(runtime.GOOS))
68
}
69
return mcp.NewServer(impl, serverOpts)
70
}
71
72
func newMcpInfoCommand() *cobra.Command {
73
cmd := &cobra.Command{
74
Use: "info",
75
Short: "Show information about the MCP server",
76
Args: cobra.NoArgs,
77
RunE: mcpInfoAction,
78
}
79
return cmd
80
}
81
82
func mcpInfoAction(cmd *cobra.Command, _ []string) error {
83
ctx := cmd.Context()
84
info, err := inspectInfo(ctx)
85
if err != nil {
86
return err
87
}
88
j, err := json.MarshalIndent(info, "", " ")
89
if err != nil {
90
return err
91
}
92
_, err = fmt.Fprint(cmd.OutOrStdout(), string(j))
93
return err
94
}
95
96
func inspectInfo(ctx context.Context) (*Info, error) {
97
ts, err := toolset.New("")
98
if err != nil {
99
return nil, err
100
}
101
server := newServer()
102
if err = ts.RegisterServer(server); err != nil {
103
return nil, err
104
}
105
serverTransport, clientTransport := mcp.NewInMemoryTransports()
106
serverSession, err := server.Connect(ctx, serverTransport, nil)
107
if err != nil {
108
return nil, err
109
}
110
client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil)
111
clientSession, err := client.Connect(ctx, clientTransport, nil)
112
if err != nil {
113
return nil, err
114
}
115
toolsResult, err := clientSession.ListTools(ctx, &mcp.ListToolsParams{})
116
if err != nil {
117
return nil, err
118
}
119
if err = clientSession.Close(); err != nil {
120
return nil, err
121
}
122
if err = serverSession.Wait(); err != nil {
123
return nil, err
124
}
125
info := &Info{
126
Tools: toolsResult.Tools,
127
}
128
return info, nil
129
}
130
131
type Info struct {
132
Tools []*mcp.Tool `json:"tools"`
133
}
134
135
func newMcpServeCommand() *cobra.Command {
136
cmd := &cobra.Command{
137
Use: "serve INSTANCE",
138
Short: "Serve MCP over stdio",
139
Long: `Serve MCP over stdio.
140
141
Expected to be executed via an AI agent, not by a human`,
142
Args: cobra.MaximumNArgs(1),
143
RunE: mcpServeAction,
144
}
145
return cmd
146
}
147
148
func mcpServeAction(cmd *cobra.Command, args []string) error {
149
ctx := cmd.Context()
150
instName := "default"
151
if len(args) > 0 {
152
instName = args[0]
153
}
154
limactl, err := limactlutil.Path()
155
if err != nil {
156
return err
157
}
158
// FIXME: We can not use store.Inspect() here because it requires VM drivers to be compiled in.
159
// https://github.com/lima-vm/lima/pull/3744#issuecomment-3289274347
160
inst, err := limactlutil.Inspect(ctx, limactl, instName)
161
if err != nil {
162
return err
163
}
164
if len(inst.Errors) != 0 {
165
return errors.Join(inst.Errors...)
166
}
167
ts, err := toolset.New(limactl)
168
if err != nil {
169
return err
170
}
171
server := newServer()
172
if err = ts.RegisterServer(server); err != nil {
173
return err
174
}
175
if err = ts.RegisterInstance(ctx, inst); err != nil {
176
return err
177
}
178
transport := &mcp.StdioTransport{}
179
return server.Run(ctx, transport)
180
}
181
182
func newMcpGenDocCommand() *cobra.Command {
183
cmd := &cobra.Command{
184
Use: "generate-doc DIR",
185
Short: "Generate documentation pages",
186
Args: cobra.MinimumNArgs(1),
187
RunE: mcpGenDocAction,
188
Hidden: true,
189
}
190
return cmd
191
}
192
193
func mcpGenDocAction(cmd *cobra.Command, args []string) error {
194
ctx := cmd.Context()
195
dir := args[0]
196
if err := os.MkdirAll(dir, 0o755); err != nil {
197
return err
198
}
199
fName := filepath.Join(dir, "mcp.md")
200
f, err := os.Create(fName)
201
if err != nil {
202
return err
203
}
204
defer f.Close()
205
fmt.Fprint(f, `---
206
title: MCP tools
207
weight: 99
208
---
209
Lima implements the "MCP Sandbox Interface" (tentative name):
210
https://pkg.go.dev/github.com/lima-vm/lima/v2/pkg/mcp/msi
211
212
MCP Sandbox Interface defines MCP (Model Context Protocol) tools
213
that can be used for reading, writing, and executing local files
214
with an appropriate sandboxing technology, such as Lima.
215
216
The sandboxing technology can be more secure and/or efficient than
217
the default tools provided by an AI agent.
218
219
MCP Sandbox Interface was inspired by
220
[Google Gemini CLI's built-in tools](https://github.com/google-gemini/gemini-cli/tree/main/docs/tools).
221
222
`)
223
info, err := inspectInfo(ctx)
224
if err != nil {
225
return err
226
}
227
for _, tool := range info.Tools {
228
fmt.Fprintf(f, "## `%s`\n\n", tool.Name)
229
if tool.Title != "" {
230
fmt.Fprintf(f, "### Title\n\n%s\n\n", tool.Title)
231
}
232
if tool.Description != "" {
233
fmt.Fprintf(f, "### Description\n\n%s\n\n", tool.Description)
234
}
235
if tool.InputSchema != nil {
236
fmt.Fprint(f, "### Input Schema\n\n")
237
schema, err := json.MarshalIndent(tool.InputSchema, "", " ")
238
if err != nil {
239
return err
240
}
241
fmt.Fprintf(f, "```json\n%s\n```\n\n", string(schema))
242
}
243
if tool.OutputSchema != nil {
244
fmt.Fprint(f, "### Output Schema\n\n")
245
schema, err := json.MarshalIndent(tool.OutputSchema, "", " ")
246
if err != nil {
247
return err
248
}
249
fmt.Fprintf(f, "```json\n%s\n```\n\n", string(schema))
250
}
251
}
252
return f.Close()
253
}
254
255