-
Notifications
You must be signed in to change notification settings - Fork 14
Added state-mcp #3671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Added state-mcp #3671
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
1e30204
Merge pull request #3629 from ActiveState/version/0-47-0-RC2
Naatan 44f50ff
Merge pull request #3639 from ActiveState/version/0-47-0-RC3
Naatan abb5937
Merge pull request #3642 from ActiveState/version/0-47-0-RC3
Naatan cde656c
Merge pull request #3645 from ActiveState/version/0-47-1-RC1
Naatan 5a667f6
Merge branch 'version/0-47-1-RC1' into beta
Naatan e5d7281
Merge remote-tracking branch 'origin/master' into mcp
Naatan 1271598
Captain: fix SetArgs, sort of
Naatan e8d1d28
Don't include root command (state) in NameRecursive
Naatan 94c8042
Added BaseCommand and AllChildren to Captain
Naatan 48850be
Remove dependency on HOME and TEMPDIR env vars
Naatan 7114a62
Support debugging via go-build
Naatan 0d405d8
Fix nil panic
Naatan d614458
Fix simple output oddly formatted
Naatan e35097b
Drop useless debug entry
Naatan 3a78c3c
Work around vscode syntax parsing issue
Naatan ed26dc9
Add state-mcp
Naatan 7f33790
Fix AI deciding to indent something that didn't need it
Naatan 993a88b
Fix prime being stale
Naatan 0a062a3
Drop unused import
Naatan c11c5c4
Disable tests
Naatan c8dc1b3
Add missing comma
Naatan af5769a
Update test now that we work without HOME set
Naatan 93a5529
Include state-mcp in update
Naatan 5e29acd
Windows specific build target
Naatan c714877
Always print what we're building
Naatan 5cc036d
Debug copy error
Naatan 6286ad5
Re-org by AI
Naatan 1d20586
Fix window target not being used
Naatan 0063d6f
Implemented flawed script runner that doesn't pass back stdout as sub…
Naatan 121eb84
Cleanup state-mcp
Naatan cac3faf
Merge branch 'master' into mcp
Naatan 397c278
Handle cast library returning nil values if source is nil
Naatan 6d41be7
Drop debugging code
Naatan 38e51d5
Fix test not constructing outputter properly
Naatan c97f5d8
Fix tests
Naatan 8ffadb2
Drop donotshipme package; we can in fact ship this now
Naatan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package mcpserver | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/ActiveState/cli/internal/constants" | ||
| "github.com/ActiveState/cli/internal/errs" | ||
| "github.com/ActiveState/cli/internal/logging" | ||
| "github.com/ActiveState/cli/internal/primer" | ||
| "github.com/mark3labs/mcp-go/mcp" | ||
| "github.com/mark3labs/mcp-go/server" | ||
| ) | ||
|
|
||
| type ToolHandlerFunc func(context.Context, *primer.Values, mcp.CallToolRequest) (*mcp.CallToolResult, error) | ||
|
|
||
| // Handler wraps the MCP server and provides methods for adding tools and resources | ||
| type Handler struct { | ||
| Server *server.MCPServer | ||
| primeGetter func() (*primer.Values, func() error, error) | ||
| } | ||
|
|
||
| func New(primeGetter func() (*primer.Values, func() error, error)) *Handler { | ||
| s := server.NewMCPServer( | ||
| constants.StateMCPCmd, | ||
| constants.VersionNumber, | ||
| ) | ||
|
|
||
| mcpHandler := &Handler{ | ||
| Server: s, | ||
| primeGetter: primeGetter, | ||
| } | ||
|
|
||
| return mcpHandler | ||
| } | ||
|
|
||
| func (m Handler) ServeStdio() error { | ||
| if err := server.ServeStdio(m.Server); err != nil { | ||
| logging.Error("Server error: %v\n", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // addResource adds a resource to the MCP server with error handling and logging | ||
| func (m *Handler) AddResource(resource mcp.Resource, handler server.ResourceHandlerFunc) { | ||
| m.Server.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { | ||
| r, err := handler(ctx, request) | ||
| if err != nil { | ||
| logging.Error("%s: Error handling resource request: %v", resource.Name, err) | ||
| return nil, errs.Wrap(err, "Failed to handle resource request") | ||
| } | ||
| return r, nil | ||
| }) | ||
| } | ||
|
|
||
| // addTool adds a tool to the MCP server with error handling and logging | ||
| func (m *Handler) AddTool(tool mcp.Tool, handler ToolHandlerFunc) { | ||
| m.Server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (r *mcp.CallToolResult, rerr error) { | ||
| p, closer, err := m.primeGetter() | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Failed to get primer") | ||
| } | ||
| defer closer() | ||
| r, err = handler(ctx, p, request) | ||
| if err != nil { | ||
| logging.Error("%s: Error handling tool request: %v", tool.Name, errs.JoinMessage(err)) | ||
| // Format all errors as a single string, so the client gets the full context | ||
| return nil, fmt.Errorf("%s: %s", tool.Name, errs.JoinMessage(err)) | ||
| } | ||
| return r, nil | ||
| }) | ||
| } | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package toolregistry | ||
|
|
||
| type ToolCategory string | ||
|
|
||
| const ( | ||
| ToolCategoryDebug ToolCategory = "debug" | ||
| ) | ||
|
|
||
| type ToolCategories []ToolCategory | ||
|
|
||
| func (c ToolCategories) String() []string { | ||
| result := []string{} | ||
| for _, category := range c { | ||
| result = append(result, string(category)) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| func Categories() ToolCategories { | ||
| return ToolCategories{ | ||
| ToolCategoryDebug, | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package toolregistry | ||
|
|
||
| import ( | ||
| "context" | ||
| "slices" | ||
|
|
||
| "github.com/ActiveState/cli/internal/primer" | ||
| "github.com/mark3labs/mcp-go/mcp" | ||
| ) | ||
|
|
||
| type Tool struct { | ||
| mcp.Tool | ||
| Category ToolCategory | ||
| Handler func(context.Context, *primer.Values, mcp.CallToolRequest) (*mcp.CallToolResult, error) | ||
| } | ||
|
|
||
| type Registry struct { | ||
| tools map[ToolCategory][]Tool | ||
| } | ||
|
|
||
| func New() *Registry { | ||
| r := &Registry{ | ||
| tools: make(map[ToolCategory][]Tool), | ||
| } | ||
|
|
||
| r.RegisterTool(HelloWorldTool()) | ||
|
|
||
| return r | ||
| } | ||
|
|
||
| func (r *Registry) RegisterTool(tool Tool) { | ||
| if _, ok := r.tools[tool.Category]; !ok { | ||
| r.tools[tool.Category] = []Tool{} | ||
| } | ||
| r.tools[tool.Category] = append(r.tools[tool.Category], tool) | ||
| } | ||
|
|
||
| func (r *Registry) GetTools(requestCategories ...string) []Tool { | ||
| if len(requestCategories) == 0 { | ||
| for _, category := range Categories() { | ||
| if category == ToolCategoryDebug { | ||
| // Debug must be explicitly requested | ||
| continue | ||
| } | ||
| requestCategories = append(requestCategories, string(category)) | ||
| } | ||
| } | ||
| categories := Categories() | ||
| result := []Tool{} | ||
| for _, category := range categories { | ||
| if slices.Contains(requestCategories, string(category)) { | ||
| result = append(result, r.tools[category]...) | ||
| } | ||
| } | ||
| return result | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package toolregistry | ||
|
|
||
| import ( | ||
| "context" | ||
| "strings" | ||
|
|
||
| "github.com/ActiveState/cli/internal/primer" | ||
| "github.com/ActiveState/cli/internal/runners/hello" | ||
| "github.com/mark3labs/mcp-go/mcp" | ||
| ) | ||
|
|
||
| func HelloWorldTool() Tool { | ||
| return Tool{ | ||
| Category: ToolCategoryDebug, | ||
| Tool: mcp.NewTool( | ||
| "hello", | ||
| mcp.WithDescription("Hello world tool"), | ||
| mcp.WithString("name", mcp.Required(), mcp.Description("The name to say hello to")), | ||
| ), | ||
| Handler: func(ctx context.Context, p *primer.Values, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||
| name, err := request.RequireString("name") | ||
| if err != nil { | ||
| return mcp.NewToolResultError(err.Error()), nil | ||
| } | ||
|
|
||
| runner := hello.New(p) | ||
| params := hello.NewParams() | ||
| params.Name = name | ||
|
|
||
| err = runner.Run(params) | ||
| if err != nil { | ||
| return mcp.NewToolResultError(err.Error()), nil | ||
| } | ||
|
|
||
| return mcp.NewToolResultText( | ||
| strings.Join(p.Output().History().Print, "\n"), | ||
| ), nil | ||
| }, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "flag" | ||
| "fmt" | ||
| "os" | ||
| "runtime/debug" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/ActiveState/cli/cmd/state-mcp/internal/mcpserver" | ||
| "github.com/ActiveState/cli/cmd/state-mcp/internal/toolregistry" | ||
| "github.com/ActiveState/cli/internal/events" | ||
| "github.com/ActiveState/cli/internal/logging" | ||
| ) | ||
|
|
||
| func main() { | ||
| defer func() { | ||
| logging.Debug("Exiting") | ||
| if r := recover(); r != nil { | ||
| logging.Error("Recovered from panic: %v", r) | ||
| fmt.Printf("Recovered from panic: %v, stack: %s\n", r, string(debug.Stack())) | ||
| os.Exit(1) | ||
| } | ||
| }() | ||
| defer func() { | ||
| if err := events.WaitForEvents(5*time.Second, logging.Close); err != nil { | ||
| logging.Warning("Failed waiting for events: %v", err) | ||
| } | ||
| }() | ||
|
|
||
| // Parse command line flags | ||
| rawFlag := flag.String("categories", "", "Comma separated list of categories to register tools for") | ||
| flag.Parse() | ||
|
|
||
| mcps := setupServer(strings.Split(*rawFlag, ",")...) | ||
|
|
||
| // Start the stdio server | ||
| logging.Info("Starting MCP server") | ||
| if err := mcps.ServeStdio(); err != nil { | ||
| logging.Error("Server error: %v\n", err) | ||
| } | ||
| } | ||
|
|
||
| func setupServer(categories ...string) *mcpserver.Handler { | ||
| mcps := mcpserver.New(newPrimer) | ||
|
|
||
| registry := toolregistry.New() | ||
| tools := registry.GetTools(categories...) | ||
| for _, tool := range tools { | ||
| mcps.AddTool(tool.Tool, tool.Handler) | ||
| } | ||
|
|
||
| return mcps | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Usually we like to return pointers to avoid copies each time this function is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gotten into the habit of only using pointers when I intend to track the underlying type across different areas of responsibility. I don't believe we ever had any rule around this.
I'd have preferred to make this a constant, but Go is a little weird about what things can be constants.