Skip to content

Commit cb4c187

Browse files
authored
Merge branch 'master' into show-available-plugins-for-info-and-exec-cmd
Signed-off-by: Jan Dubois <jan.dubois@suse.com>
2 parents 6bc9696 + 689b928 commit cb4c187

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1680
-387
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
# Apparently, a binary built on a newer version of macOS can still run on
2727
# an older release of macOS without an error.
2828
# This is quite different from Linux and glibc.
29-
runs-on: macos-15
29+
runs-on: macos-26
3030
timeout-minutes: 20
3131
steps:
3232
- name: "Show xcode and SDK version"

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
timeout-minutes: 30
7070
strategy:
7171
matrix:
72-
runs-on: [ubuntu-24.04, macos-15, windows-2025]
72+
runs-on: [ubuntu-24.04, macos-26, windows-2025]
7373
runs-on: ${{ matrix.runs-on }}
7474
steps:
7575
- name: Force git to use LF

Makefile

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,38 @@ PACKAGE := github.com/lima-vm/lima/v2
4343
VERSION := $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
4444
VERSION_TRIMMED := $(VERSION:v%=%)
4545

46+
# `DEBUG` flag to build binaries with debug information for use by `dlv exec`.
47+
# This implies KEEP_DWARF=1 and KEEP_SYMBOLS=1.
48+
DEBUG ?=
49+
GO_BUILD_GCFLAGS ?=
50+
KEEP_DWARF ?=
4651
KEEP_SYMBOLS ?=
52+
ifeq ($(DEBUG),1)
53+
# Disable optimizations and inlining to make debugging easier.
54+
GO_BUILD_GCFLAGS = -gcflags="all=-N -l"
55+
# Keep the symbol table
56+
KEEP_DWARF = 1
57+
# Enable DWARF generation
58+
KEEP_SYMBOLS = 1
59+
endif
60+
61+
GO_BUILD_LDFLAGS_W := true
62+
ifeq ($(KEEP_DWARF),1)
63+
GO_BUILD_LDFLAGS_W = false
64+
endif
65+
4766
GO_BUILD_LDFLAGS_S := true
4867
ifeq ($(KEEP_SYMBOLS),1)
4968
GO_BUILD_LDFLAGS_S = false
5069
endif
51-
GO_BUILD_LDFLAGS := -ldflags="-s=$(GO_BUILD_LDFLAGS_S) -w -X $(PACKAGE)/pkg/version.Version=$(VERSION)"
70+
# `-s`: Strip the symbol table according to the KEEP_SYMBOLS config
71+
# `-w`: Disable DWARF generation according to the KEEP_DWARF config
72+
# `-X`: Embed version information.
73+
GO_BUILD_LDFLAGS := -ldflags="-s=$(GO_BUILD_LDFLAGS_S) -w=$(GO_BUILD_LDFLAGS_W) -X $(PACKAGE)/pkg/version.Version=$(VERSION)"
5274
# `go -version -m` returns -tags with comma-separated list, because space-separated list is deprecated in go1.13.
5375
# converting to comma-separated list is useful for comparing with the output of `go version -m`.
5476
GO_BUILD_FLAG_TAGS := $(addprefix -tags=,$(shell echo "$(GO_BUILDTAGS)"|tr " " "\n"|paste -sd "," -))
55-
GO_BUILD := $(GO) build $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS)
77+
GO_BUILD := $(strip $(GO) build $(GO_BUILD_GCFLAGS) $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS))
5678

5779
################################################################################
5880
# Features
@@ -78,7 +100,9 @@ help-variables:
78100
@echo '# Variables that can be overridden.'
79101
@echo
80102
@echo '- PREFIX (directory) : Installation prefix (default: /usr/local)'
103+
@echo '- KEEP_DWARF (1 or 0) : Whether to keep DWARF information (default: 0)'
81104
@echo '- KEEP_SYMBOLS (1 or 0) : Whether to keep symbols (default: 0)'
105+
@echo '- DEBUG (1 or 0) : Whether to build with debug information (default: 0)'
82106

83107
.PHONY: help-targets
84108
help-targets:
@@ -88,6 +112,8 @@ help-targets:
88112
@echo '- limactl : Build limactl, and lima'
89113
@echo '- lima : Copy lima, and lima.bat'
90114
@echo '- helpers : Copy nerdctl.lima, apptainer.lima, docker.lima, podman.lima, and kubectl.lima'
115+
# TODO: move CLI plugins to _output/libexec/lima/
116+
@echo '- limactl-plugins : Build limactl-* CLI plugins'
91117
@echo
92118
@echo 'Targets for files in _output/share/lima/:'
93119
@echo '- guestagents : Build guestagents'
@@ -150,7 +176,7 @@ CONFIG_GUESTAGENT_COMPRESS=y
150176

151177
################################################################################
152178
.PHONY: binaries
153-
binaries: limactl helpers guestagents \
179+
binaries: limactl helpers limactl-plugins guestagents \
154180
templates template_experimentals \
155181
documentation create-links-in-doc-dir
156182

@@ -177,7 +203,7 @@ dependencies_for_cmd = go.mod $(call find_files_excluding_dir_and_test, ./cmd/$(
177203
# $(1): target binary
178204
extract_build_vars = $(shell \
179205
($(GO) version -m $(1) 2>&- || echo $(1):) | \
180-
awk 'FNR==1{print "GOVERSION="$$2}$$2~/^(CGO|GO|-ldflags|-tags).*=.+$$/{sub("^.*"$$2,$$2); print $$0}' \
206+
awk 'FNR==1{print "GOVERSION="$$2}$$2~/^(CGO|GO|-gcflags|-ldflags|-tags).*=.+$$/{sub("^.*"$$2,$$2); print $$0}' \
181207
)
182208

183209
# a list of keys from the GO build variables to be used for calling `go env`.
@@ -192,7 +218,7 @@ go_build_vars = $(shell \
192218
$(ENVS_$(1)) $(GO) env $(2) | \
193219
awk '/ /{print "\""$$0"\""; next}{print}' | \
194220
for k in $(2); do read -r v && echo "$$k=$${v}"; done \
195-
) $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS)
221+
) $(GO_BUILD_GCFLAGS) $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS)
196222

197223
# returns the difference between $(1) and $(2).
198224
diff = $(filter-out $(2),$(1))$(filter-out $(1),$(2))
@@ -256,6 +282,11 @@ ifeq ($(GOOS),darwin)
256282
codesign -f -v --entitlements vz.entitlements -s - $@
257283
endif
258284

285+
limactl-plugins: _output/bin/limactl-mcp$(exe)
286+
287+
_output/bin/limactl-mcp$(exe): $(call dependencies_for_cmd,limactl-mcp) $$(call force_build,$$@)
288+
$(ENVS_$@) $(GO_BUILD) -o $@ ./cmd/limactl-mcp
289+
259290
DRIVER_INSTALL_DIR := _output/libexec/lima
260291

261292
.PHONY: additional-drivers
@@ -356,16 +387,20 @@ MKDIR_TARGETS += _output/share/lima
356387
################################################################################
357388
# _output/share/lima/templates
358389
TEMPLATES = $(addprefix _output/share/lima/templates/,$(filter-out experimental,$(notdir $(wildcard templates/*))))
390+
TEMPLATE_DEFAULTS = ${addprefix _output/share/lima/templates/_default/,$(notdir $(wildcard templates/_default/*))}
391+
TEMPLATE_IMAGES = $(addprefix _output/share/lima/templates/_images/,$(notdir $(wildcard templates/_images/*)))
359392
TEMPLATE_EXPERIMENTALS = $(addprefix _output/share/lima/templates/experimental/,$(notdir $(wildcard templates/experimental/*)))
360393

361394
.PHONY: default_template templates template_experimentals
362395
default_template: _output/share/lima/templates/default.yaml
363-
templates: $(TEMPLATES)
396+
templates: $(TEMPLATES) $(TEMPLATE_DEFAULTS) $(TEMPLATE_IMAGES)
364397
template_experimentals: $(TEMPLATE_EXPERIMENTALS)
365398

366399
$(TEMPLATES): | _output/share/lima/templates
400+
$(TEMPLATE_DEFAULTS): | _output/share/lima/templates/_default
401+
$(TEMPLATE_IMAGES): | _output/share/lima/templates/_images
367402
$(TEMPLATE_EXPERIMENTALS): | _output/share/lima/templates/experimental
368-
MKDIR_TARGETS += _output/share/lima/templates _output/share/lima/templates/experimental
403+
MKDIR_TARGETS += _output/share/lima/templates _output/share/lima/templates/_default _output/share/lima/templates/_images _output/share/lima/templates/experimental
369404

370405
_output/share/lima/templates/%: templates/%
371406
cp -aL $< $@
@@ -488,14 +523,19 @@ uninstall:
488523
"$(DEST)/bin/lima" \
489524
"$(DEST)/bin/lima$(bat)" \
490525
"$(DEST)/bin/limactl$(exe)" \
526+
"$(DEST)/bin/limactl-mcp$(exe)" \
491527
"$(DEST)/bin/nerdctl.lima" \
492528
"$(DEST)/bin/apptainer.lima" \
493529
"$(DEST)/bin/docker.lima" \
494530
"$(DEST)/bin/podman.lima" \
495531
"$(DEST)/bin/kubectl.lima" \
496532
"$(DEST)/share/man/man1/lima.1" \
497533
"$(DEST)/share/man/man1/limactl"*".1" \
498-
"$(DEST)/share/lima" "$(DEST)/share/doc/lima"
534+
"$(DEST)/share/lima" \
535+
"$(DEST)/share/doc/lima" \
536+
"$(DEST)/libexec/lima/lima-driver-qemu$(exe)" \
537+
"$(DEST)/libexec/lima/lima-driver-vz$(exe)" \
538+
"$(DEST)/libexec/lima/lima-driver-wsl2$(exe)"
499539
if [ "$$(readlink "$(DEST)/bin/nerdctl")" = "nerdctl.lima" ]; then rm "$(DEST)/bin/nerdctl"; fi
500540
if [ "$$(readlink "$(DEST)/bin/apptainer")" = "apptainer.lima" ]; then rm "$(DEST)/bin/apptainer"; fi
501541

cmd/limactl-mcp/main.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"runtime"
11+
"strings"
12+
13+
"github.com/modelcontextprotocol/go-sdk/mcp"
14+
"github.com/sirupsen/logrus"
15+
"github.com/spf13/cobra"
16+
"golang.org/x/text/cases"
17+
"golang.org/x/text/language"
18+
19+
"github.com/lima-vm/lima/v2/pkg/limactlutil"
20+
"github.com/lima-vm/lima/v2/pkg/mcp/toolset"
21+
"github.com/lima-vm/lima/v2/pkg/version"
22+
)
23+
24+
func main() {
25+
if err := newApp().Execute(); err != nil {
26+
logrus.Fatal(err)
27+
}
28+
}
29+
30+
func newApp() *cobra.Command {
31+
cmd := &cobra.Command{
32+
Use: "limactl-mcp",
33+
Short: "Model Context Protocol plugin for Lima (EXPERIMENTAL)",
34+
Version: strings.TrimPrefix(version.Version, "v"),
35+
SilenceUsage: true,
36+
SilenceErrors: true,
37+
}
38+
cmd.AddCommand(
39+
newMcpInfoCommand(),
40+
newMcpServeCommand(),
41+
// TODO: `limactl-mcp configure gemini` ?
42+
)
43+
return cmd
44+
}
45+
46+
func newServer() *mcp.Server {
47+
impl := &mcp.Implementation{
48+
Name: "lima",
49+
Title: "Lima VM, for sandboxing local command executions and file I/O operations",
50+
Version: version.Version,
51+
}
52+
serverOpts := &mcp.ServerOptions{
53+
Instructions: `This MCP server provides tools for sandboxing local command executions and file I/O operations,
54+
by wrapping them in Lima VM (https://lima-vm.io).
55+
56+
Use these tools to avoid accidentally executing malicious codes directly on the host.
57+
`,
58+
}
59+
if runtime.GOOS != "linux" {
60+
serverOpts.Instructions += fmt.Sprintf(`
61+
62+
NOTE: the guest OS of the VM is Linux, while the host OS is %s.
63+
`, cases.Title(language.English).String(runtime.GOOS))
64+
}
65+
return mcp.NewServer(impl, serverOpts)
66+
}
67+
68+
func newMcpInfoCommand() *cobra.Command {
69+
cmd := &cobra.Command{
70+
Use: "info",
71+
Short: "Show information about the MCP server",
72+
Args: cobra.NoArgs,
73+
RunE: mcpInfoAction,
74+
}
75+
return cmd
76+
}
77+
78+
func mcpInfoAction(cmd *cobra.Command, _ []string) error {
79+
ctx := cmd.Context()
80+
limactl, err := limactlutil.Path()
81+
if err != nil {
82+
return err
83+
}
84+
ts, err := toolset.New(limactl)
85+
if err != nil {
86+
return err
87+
}
88+
server := newServer()
89+
if err = ts.RegisterServer(server); err != nil {
90+
return err
91+
}
92+
serverTransport, clientTransport := mcp.NewInMemoryTransports()
93+
serverSession, err := server.Connect(ctx, serverTransport, nil)
94+
if err != nil {
95+
return err
96+
}
97+
client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil)
98+
clientSession, err := client.Connect(ctx, clientTransport, nil)
99+
if err != nil {
100+
return err
101+
}
102+
toolsResult, err := clientSession.ListTools(ctx, &mcp.ListToolsParams{})
103+
if err != nil {
104+
return err
105+
}
106+
if err = clientSession.Close(); err != nil {
107+
return err
108+
}
109+
if err = serverSession.Wait(); err != nil {
110+
return err
111+
}
112+
info := &Info{
113+
Tools: toolsResult.Tools,
114+
}
115+
j, err := json.MarshalIndent(info, "", " ")
116+
if err != nil {
117+
return err
118+
}
119+
_, err = fmt.Fprint(cmd.OutOrStdout(), string(j))
120+
return err
121+
}
122+
123+
type Info struct {
124+
Tools []*mcp.Tool `json:"tools"`
125+
}
126+
127+
func newMcpServeCommand() *cobra.Command {
128+
cmd := &cobra.Command{
129+
Use: "serve INSTANCE",
130+
Short: "Serve MCP over stdio",
131+
Long: `Serve MCP over stdio.
132+
133+
Expected to be executed via an AI agent, not by a human`,
134+
Args: cobra.MaximumNArgs(1),
135+
RunE: mcpServeAction,
136+
}
137+
return cmd
138+
}
139+
140+
func mcpServeAction(cmd *cobra.Command, args []string) error {
141+
ctx := cmd.Context()
142+
instName := "default"
143+
if len(args) > 0 {
144+
instName = args[0]
145+
}
146+
limactl, err := limactlutil.Path()
147+
if err != nil {
148+
return err
149+
}
150+
// FIXME: We can not use store.Inspect() here because it requires VM drivers to be compiled in.
151+
// https://github.com/lima-vm/lima/pull/3744#issuecomment-3289274347
152+
inst, err := limactlutil.Inspect(ctx, limactl, instName)
153+
if err != nil {
154+
return err
155+
}
156+
if len(inst.Errors) != 0 {
157+
return errors.Join(inst.Errors...)
158+
}
159+
ts, err := toolset.New(limactl)
160+
if err != nil {
161+
return err
162+
}
163+
server := newServer()
164+
if err = ts.RegisterServer(server); err != nil {
165+
return err
166+
}
167+
if err = ts.RegisterInstance(ctx, inst); err != nil {
168+
return err
169+
}
170+
transport := &mcp.StdioTransport{}
171+
return server.Run(ctx, transport)
172+
}

0 commit comments

Comments
 (0)