Skip to content

Commit b6797b9

Browse files
committed
feat(mcp): support running in mcp server mode
1 parent 54b271c commit b6797b9

File tree

11 files changed

+267
-454
lines changed

11 files changed

+267
-454
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ all: install lint test clean
1010
build:
1111
mkdir -p bin
1212
$(GO) build $(GO_FLAGS) -o ./bin/g
13-
$(GO) build $(GO_FLAGS) -o ./bin/g-mcp-server ./cmd/mcp-server/*.go
1413

1514
install: build
1615
$(GO) install $(GO_FLAGS)

cli/commands.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ var (
124124
},
125125
},
126126
},
127+
{
128+
Name: "mcp",
129+
Usage: "Run in mcp server mode",
130+
UsageText: "g mcp",
131+
Action: runMcpServer,
132+
},
127133
}
128134
)
129135

cli/mcp.go

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// Copyright (c) 2025 voidint <[email protected]>
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
// this software and associated documentation files (the "Software"), to deal in
5+
// the Software without restriction, including without limitation the rights to
6+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
// the Software, and to permit persons to whom the Software is furnished to do so,
8+
// subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
20+
package cli
21+
22+
import (
23+
"context"
24+
"encoding/json"
25+
"fmt"
26+
"os/exec"
27+
"strings"
28+
29+
"github.com/ThinkInAIXYZ/go-mcp/protocol"
30+
"github.com/ThinkInAIXYZ/go-mcp/server"
31+
"github.com/ThinkInAIXYZ/go-mcp/transport"
32+
"github.com/pkg/errors"
33+
"github.com/urfave/cli/v2"
34+
"github.com/voidint/g/build"
35+
)
36+
37+
func runMcpServer(_ *cli.Context) (err error) {
38+
transportServer := transport.NewStdioServerTransport()
39+
40+
mcpServer, err := server.NewServer(transportServer, server.WithServerInfo(protocol.Implementation{
41+
Name: "g",
42+
Version: build.ShortVersion,
43+
}))
44+
if err != nil {
45+
return cli.Exit(errstring(err), 1)
46+
}
47+
48+
envTool, err := protocol.NewTool("env", "Show environment variables of g", struct{}{})
49+
if err != nil {
50+
return cli.Exit(errstring(err), 1)
51+
}
52+
53+
cleanTool, err := protocol.NewTool("clean", "Delete the cached installation package files", struct{}{})
54+
if err != nil {
55+
return cli.Exit(errstring(err), 1)
56+
}
57+
58+
lsTool, err := protocol.NewTool("ls", "List installed go sdk versions", struct{}{})
59+
if err != nil {
60+
return cli.Exit(errstring(err), 1)
61+
}
62+
63+
lsRemoteTool, err := protocol.NewTool("ls-remote", "List remote go sdk versions available for install", struct{}{})
64+
if err != nil {
65+
return cli.Exit(errstring(err), 1)
66+
}
67+
68+
installTool, err := protocol.NewTool("install", "Download and install a go sdk version", InstallReq{})
69+
if err != nil {
70+
return cli.Exit(errstring(err), 1)
71+
}
72+
73+
uninstallTool, err := protocol.NewTool("uninstall", "Uninstall a go sdk version", UninstallReq{})
74+
if err != nil {
75+
return cli.Exit(errstring(err), 1)
76+
}
77+
78+
useTool, err := protocol.NewTool("use", "Switch to specified go sdk version", UseReq{})
79+
if err != nil {
80+
return cli.Exit(errstring(err), 1)
81+
}
82+
83+
mcpServer.RegisterTool(envTool, envHandler)
84+
mcpServer.RegisterTool(cleanTool, cleanHandler)
85+
mcpServer.RegisterTool(lsTool, lsHandler)
86+
mcpServer.RegisterTool(lsRemoteTool, lsRemoteHandler)
87+
mcpServer.RegisterTool(installTool, installHandler)
88+
mcpServer.RegisterTool(uninstallTool, uninstallHandler)
89+
mcpServer.RegisterTool(useTool, useHandler)
90+
91+
if err = mcpServer.Run(); err != nil {
92+
return cli.Exit(errstring(err), 1)
93+
}
94+
return err
95+
}
96+
97+
func envHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
98+
cmd := exec.CommandContext(ctx, "g", "env")
99+
output, err := cmd.Output()
100+
if err != nil {
101+
return nil, errors.WithStack(err)
102+
}
103+
104+
return &protocol.CallToolResult{
105+
Content: []protocol.Content{
106+
&protocol.TextContent{
107+
Type: "text",
108+
Text: string(output),
109+
},
110+
},
111+
}, nil
112+
}
113+
114+
func cleanHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
115+
cmd := exec.CommandContext(ctx, "g", "clean")
116+
output, err := cmd.Output()
117+
if err != nil {
118+
return nil, errors.WithStack(err)
119+
}
120+
121+
return &protocol.CallToolResult{
122+
Content: []protocol.Content{
123+
&protocol.TextContent{
124+
Type: "text",
125+
Text: string(output),
126+
},
127+
},
128+
}, nil
129+
}
130+
131+
func lsHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
132+
cmd := exec.CommandContext(ctx, "g", "ls", "-o", "json")
133+
output, err := cmd.Output()
134+
if err != nil {
135+
return nil, errors.WithStack(err)
136+
}
137+
138+
return &protocol.CallToolResult{
139+
Content: []protocol.Content{
140+
&protocol.TextContent{
141+
Type: "text",
142+
Text: string(output),
143+
},
144+
},
145+
}, nil
146+
}
147+
148+
func lsRemoteHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
149+
cmd := exec.CommandContext(ctx, "g", "ls-remote", "-o", "json")
150+
output, err := cmd.Output()
151+
if err != nil {
152+
return nil, errors.WithStack(err)
153+
}
154+
155+
var items []struct {
156+
Version string `json:"version"`
157+
InUse bool `json:"inUse"`
158+
Installed bool `json:"installed"`
159+
}
160+
if err = json.Unmarshal([]byte(output), &items); err != nil {
161+
return nil, errors.WithStack(err)
162+
}
163+
164+
if output, err = json.Marshal(items); err != nil {
165+
return nil, errors.WithStack(err)
166+
}
167+
168+
return &protocol.CallToolResult{
169+
Content: []protocol.Content{
170+
&protocol.TextContent{
171+
Type: "text",
172+
Text: string(output),
173+
},
174+
},
175+
}, nil
176+
}
177+
178+
type InstallReq struct {
179+
Version string `json:"version" description:"go sdk version keywords" required:"true"`
180+
Nouse bool `json:"nouse" description:"don't use the version after installed" required:"false"`
181+
}
182+
183+
func installHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
184+
var installReq InstallReq
185+
if err := protocol.VerifyAndUnmarshal(req.RawArguments, &installReq); err != nil {
186+
return nil, err
187+
}
188+
189+
cmd := exec.CommandContext(ctx, "g", "install", fmt.Sprintf("--nouse=%t", installReq.Nouse), installReq.Version)
190+
output, err := cmd.CombinedOutput()
191+
if err != nil {
192+
return nil, errors.WithStack(err)
193+
}
194+
195+
if !strings.Contains(string(output), "installed") {
196+
if output, err = exec.CommandContext(ctx, "go", "version").Output(); err != nil {
197+
return nil, errors.WithStack(err)
198+
}
199+
}
200+
201+
return &protocol.CallToolResult{
202+
Content: []protocol.Content{
203+
&protocol.TextContent{
204+
Type: "text",
205+
Text: string(output),
206+
},
207+
},
208+
}, nil
209+
}
210+
211+
type UninstallReq struct {
212+
Version string `json:"version" description:"go sdk version" required:"true"`
213+
}
214+
215+
func uninstallHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
216+
var uninstallReq UninstallReq
217+
if err := protocol.VerifyAndUnmarshal(req.RawArguments, &uninstallReq); err != nil {
218+
return nil, err
219+
}
220+
221+
cmd := exec.CommandContext(ctx, "g", "uninstall", uninstallReq.Version)
222+
output, err := cmd.CombinedOutput()
223+
if err != nil {
224+
return nil, errors.WithStack(err)
225+
}
226+
227+
return &protocol.CallToolResult{
228+
Content: []protocol.Content{
229+
&protocol.TextContent{
230+
Type: "text",
231+
Text: string(output),
232+
},
233+
},
234+
}, nil
235+
}
236+
237+
type UseReq struct {
238+
Version string `json:"version" description:"go sdk version" required:"true"`
239+
}
240+
241+
func useHandler(ctx context.Context, req *protocol.CallToolRequest) (*protocol.CallToolResult, error) {
242+
var useReq UseReq
243+
if err := protocol.VerifyAndUnmarshal(req.RawArguments, &useReq); err != nil {
244+
return nil, err
245+
}
246+
247+
cmd := exec.CommandContext(ctx, "g", "use", useReq.Version)
248+
output, err := cmd.Output()
249+
if err != nil {
250+
return nil, errors.WithStack(err)
251+
}
252+
253+
return &protocol.CallToolResult{
254+
Content: []protocol.Content{
255+
&protocol.TextContent{
256+
Type: "text",
257+
Text: string(output),
258+
},
259+
},
260+
}, nil
261+
}

cmd/mcp-server/clean.go

Lines changed: 0 additions & 45 deletions
This file was deleted.

cmd/mcp-server/env.go

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)