Skip to content

Add Model Context Protocol (MCP) support to Serpent #25

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ThomasK33
Copy link
Member

  • Implement MCP server functionality in a new example application.
  • Create README.md to document MCP usage, lifecycle, and methods.
  • Develop main.go to define commands and expose them as MCP tools and resources.
  • Introduce mcp.go to handle MCP server logic, including JSON-RPC request processing.
  • Add tests for MCP server setup, command validation, and JSON schema generation.
  • Update help template to display MCP tool and resource information.

Change-Id: I162ad9d8f1c8d4f5b5bdc37fefb1c99dce179bb2

@ThomasK33 ThomasK33 force-pushed the thomask33/mcp-support branch from 70210af to 06a74d8 Compare April 7, 2025 11:20
mcp.go Outdated
Comment on lines 15 to 21
// JSONRPC2 message types
type JSONRPC2Request struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we shouldn't import the types from a different package apart from "a little copying is better than a little dependency"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no specific reason. I looked at the MCP spec and just carried the types over.

We could introduce a library for that, but if it doesn't provide any abstraction that makes it easier to work with (e.g., a layer for supporting different spec versions with polyfills), I considered the "copy and be done with it" approach more straightforward.

@ThomasK33 ThomasK33 force-pushed the thomask33/mcp-support branch from 06a74d8 to ac46dea Compare April 7, 2025 13:33
Comment on lines +869 to +900
func MCPCommand() *Command {
return &Command{
Use: "mcp [command]",
Short: "Run a command as an MCP server",
Long: `Run a command as a Model Context Protocol (MCP) server over stdio.

This command allows any serpent command to be exposed as an MCP server, which can
provide tools and resources to MCP clients. The server communicates using JSON-RPC 2.0
over stdin/stdout.

If a command name is provided, that specific command will be run as an MCP server.
Otherwise, the root command will be used.

Commands with a Tool field set can be invoked as MCP tools.
Commands with a Resource field set can be accessed as MCP resources.
Commands with neither Tool nor Resource set will not be accessible via MCP.`,
Handler: func(inv *Invocation) error {
rootCmd := inv.Command
if rootCmd.Parent != nil {
// Find the root command
for rootCmd.Parent != nil {
rootCmd = rootCmd.Parent
}
}

// If a command name is provided, use that as the root
if len(inv.Args) > 0 {
cmdName := strings.Join(inv.Args, " ")
cmd := DefaultCommandFinder(rootCmd, cmdName)
if cmd == nil {
return xerrors.Errorf("command not found: %s", cmdName)
}
rootCmd = cmd
}

// Create and run the MCP server
server := NewMCPServer(rootCmd, inv.Stdin, inv.Stdout, inv.Stderr)
return server.Run(inv.Context())
},
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is a nice convenience function, isn't this simple enough to just plumb through as required?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, and this can just be put into the docs.

My goal here was to provide a straightforward way of taking another serpent binary and adding a single line to enable MCP support.

- Implement MCP server functionality in a new example application.
- Create README.md to document MCP usage, lifecycle, and methods.
- Develop main.go to define commands and expose them as MCP tools and resources.
- Introduce mcp.go to handle MCP server logic, including JSON-RPC request processing.
- Add tests for MCP server setup, command validation, and JSON schema generation.
- Update help template to display MCP tool and resource information.

Change-Id: I162ad9d8f1c8d4f5b5bdc37fefb1c99dce179bb2
Signed-off-by: Thomas Kosiewski <[email protected]>
@ThomasK33 ThomasK33 force-pushed the thomask33/mcp-support branch from ac46dea to 2de8fee Compare April 7, 2025 14:40
command.go Outdated
Comment on lines 62 to 71
// Tool is the name of the MCP tool this command provides.
// If set, the command can be invoked via MCP as a tool.
// Tool and Resource are mutually exclusive.
Tool string

// Resource is the URI of the MCP resource this command provides.
// If set, the command can be accessed via MCP as a resource.
// Tool and Resource are mutually exclusive.
Resource string

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest maybe either prefix these with MCP or put them into a struct of MCP-related options.

@Emyrk
Copy link
Member

Emyrk commented Apr 7, 2025

This idea is so cool ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants