|
4 | 4 | package main |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "context" |
7 | 8 | "encoding/json" |
8 | 9 | "errors" |
9 | 10 | "fmt" |
| 11 | + "os" |
| 12 | + "path/filepath" |
10 | 13 | "runtime" |
11 | 14 | "strings" |
12 | 15 |
|
@@ -38,6 +41,7 @@ func newApp() *cobra.Command { |
38 | 41 | cmd.AddCommand( |
39 | 42 | newMcpInfoCommand(), |
40 | 43 | newMcpServeCommand(), |
| 44 | + newMcpGenDocCommand(), |
41 | 45 | // TODO: `limactl-mcp configure gemini` ? |
42 | 46 | ) |
43 | 47 | return cmd |
@@ -77,47 +81,51 @@ func newMcpInfoCommand() *cobra.Command { |
77 | 81 |
|
78 | 82 | func mcpInfoAction(cmd *cobra.Command, _ []string) error { |
79 | 83 | ctx := cmd.Context() |
80 | | - limactl, err := limactlutil.Path() |
| 84 | + info, err := inspectInfo(ctx) |
81 | 85 | if err != nil { |
82 | 86 | return err |
83 | 87 | } |
84 | | - ts, err := toolset.New(limactl) |
| 88 | + j, err := json.MarshalIndent(info, "", " ") |
85 | 89 | if err != nil { |
86 | 90 | return err |
87 | 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 | + } |
88 | 101 | server := newServer() |
89 | 102 | if err = ts.RegisterServer(server); err != nil { |
90 | | - return err |
| 103 | + return nil, err |
91 | 104 | } |
92 | 105 | serverTransport, clientTransport := mcp.NewInMemoryTransports() |
93 | 106 | serverSession, err := server.Connect(ctx, serverTransport, nil) |
94 | 107 | if err != nil { |
95 | | - return err |
| 108 | + return nil, err |
96 | 109 | } |
97 | 110 | client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil) |
98 | 111 | clientSession, err := client.Connect(ctx, clientTransport, nil) |
99 | 112 | if err != nil { |
100 | | - return err |
| 113 | + return nil, err |
101 | 114 | } |
102 | 115 | toolsResult, err := clientSession.ListTools(ctx, &mcp.ListToolsParams{}) |
103 | 116 | if err != nil { |
104 | | - return err |
| 117 | + return nil, err |
105 | 118 | } |
106 | 119 | if err = clientSession.Close(); err != nil { |
107 | | - return err |
| 120 | + return nil, err |
108 | 121 | } |
109 | 122 | if err = serverSession.Wait(); err != nil { |
110 | | - return err |
| 123 | + return nil, err |
111 | 124 | } |
112 | 125 | info := &Info{ |
113 | 126 | Tools: toolsResult.Tools, |
114 | 127 | } |
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 |
| 128 | + return info, nil |
121 | 129 | } |
122 | 130 |
|
123 | 131 | type Info struct { |
@@ -170,3 +178,76 @@ func mcpServeAction(cmd *cobra.Command, args []string) error { |
170 | 178 | transport := &mcp.StdioTransport{} |
171 | 179 | return server.Run(ctx, transport) |
172 | 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 | +} |
0 commit comments