-
Notifications
You must be signed in to change notification settings - Fork 4
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
ThomasK33
wants to merge
7
commits into
main
Choose a base branch
from
thomask33/mcp-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2de8fee
Add Model Context Protocol (MCP) support to Serpent
ThomasK33 6b1e3a5
fix: add string-array pflag type
johnstcn 3ae3a40
fix: actually initialize server
johnstcn 106faeb
fix: ensure stdin reads always fail for mcp invocations
johnstcn 3d86fd7
feat: allow overriding flags at a tool level
johnstcn 119c807
feat: add positional arguments as pseudo-arguments
johnstcn ca22b36
fixup! feat: add positional arguments as pseudo-arguments
johnstcn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# Serpent MCP Server Example | ||
|
||
This example demonstrates how to use the Model Context Protocol (MCP) functionality in Serpent to create a command-line tool that can also be used as an MCP server. | ||
|
||
## What is MCP? | ||
|
||
The Model Context Protocol (MCP) is a protocol for communication between AI models and external tools or resources. It allows AI models to invoke tools and access resources provided by MCP servers. | ||
|
||
## How to Use | ||
|
||
### Running as a CLI Tool | ||
|
||
You can run the example as a normal CLI tool: | ||
|
||
```bash | ||
# Echo a message | ||
go run main.go echo "Hello, World!" | ||
|
||
# Get version information | ||
go run main.go version | ||
|
||
# Show help | ||
go run main.go --help | ||
``` | ||
|
||
### Running as an MCP Server | ||
|
||
You can run the example as an MCP server using the `mcp` subcommand: | ||
|
||
```bash | ||
go run main.go mcp | ||
``` | ||
|
||
This will start an MCP server that listens on stdin/stdout for JSON-RPC 2.0 requests. | ||
|
||
## MCP Protocol | ||
|
||
### Lifecycle | ||
|
||
The MCP server follows the standard MCP lifecycle: | ||
|
||
1. The client sends an `initialize` request to the server | ||
2. The server responds with its capabilities | ||
3. The client sends an `initialized` notification | ||
4. After this, normal message exchange can begin | ||
|
||
All MCP methods will return an error if called before the initialization process is complete. | ||
|
||
### Methods | ||
|
||
The MCP server implements the following JSON-RPC 2.0 methods: | ||
|
||
- `initialize`: Initializes the MCP server and returns its capabilities | ||
- `notifications/initialized`: Notifies the server that initialization is complete | ||
- `ping`: Simple ping method to check server availability | ||
- `tools/list`: Lists all available tools | ||
- `tools/call`: Invokes a tool with the given arguments | ||
- `resources/list`: Lists all available resources | ||
- `resources/templates/list`: Lists all available resource templates | ||
- `resources/read`: Accesses a resource with the given URI | ||
|
||
### Example Requests | ||
|
||
Here are some example JSON-RPC 2.0 requests you can send to the MCP server: | ||
|
||
#### Initialize | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"manual-test-client","version":"1.0.0"},"capabilities":{}}} | ||
``` | ||
|
||
Response: | ||
```json | ||
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":true,"resources":true}}} | ||
``` | ||
|
||
#### Initialized | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"} | ||
``` | ||
|
||
#### List Tools | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}} | ||
``` | ||
|
||
#### List Resources | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":4,"method":"resources/list","params":{}} | ||
``` | ||
|
||
#### Invoke Tool | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"echo","arguments":{"_":"Hello from MCP!"}}} | ||
``` | ||
|
||
#### Access Resource | ||
|
||
```json | ||
{"jsonrpc":"2.0","id":6,"method":"resources/read","params":{"uri":"version"}} | ||
``` | ||
|
||
### Complete Initialization Example | ||
|
||
Here's a complete example of the initialization process: | ||
|
||
```json | ||
// Client sends initialize request | ||
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"manual-test-client","version":"1.0.0"},"capabilities":{}}} | ||
|
||
// Server responds with capabilities | ||
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":true,"resources":true}}} | ||
|
||
// Client sends initialized notification | ||
{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"} | ||
|
||
// Server acknowledges (optional, since initialized is technically a notification) | ||
{"jsonrpc":"2.0","id":2,"result":{}} | ||
|
||
// Now client can use MCP methods | ||
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}} | ||
``` | ||
|
||
## How to Implement MCP in Your Own Commands | ||
|
||
To implement MCP in your own Serpent commands: | ||
|
||
1. Add the `Tool` field to commands that should be invokable as MCP tools | ||
2. Add the `Resource` field to commands that should be accessible as MCP resources | ||
3. Add the MCP command to your root command using `root.AddMCPCommand()` | ||
|
||
Example: | ||
|
||
```go | ||
// Create a command that will be exposed as an MCP tool | ||
echoCmd := &serpent.Command{ | ||
Use: "echo [message]", | ||
Short: "Echo a message", | ||
Tool: "echo", // This makes the command available as an MCP tool | ||
Handler: func(inv *serpent.Invocation) error { | ||
// Command implementation | ||
}, | ||
} | ||
|
||
// Create a command that will be exposed as an MCP resource | ||
versionCmd := &serpent.Command{ | ||
Use: "version", | ||
Short: "Get version information", | ||
Resource: "version", // This makes the command available as an MCP resource | ||
Handler: func(inv *serpent.Invocation) error { | ||
// Command implementation | ||
}, | ||
} | ||
|
||
// Add the MCP command to the root command | ||
root.AddSubcommands(serpent.MCPCommand()) | ||
``` | ||
|
||
## Notes | ||
|
||
- A command can have either a `Tool` field or a `Resource` field, but not both | ||
- Commands with neither `Tool` nor `Resource` set will not be accessible via MCP | ||
- The MCP server communicates using JSON-RPC 2.0 over stdin/stdout |
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,87 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/coder/serpent" | ||
) | ||
|
||
func main() { | ||
// Create a root command | ||
root := &serpent.Command{ | ||
Use: "mcp-example", | ||
Short: "Example MCP server", | ||
Long: "An example of how to use the MCP functionality in serpent.", | ||
} | ||
|
||
var repeats int64 = 2 | ||
|
||
// Add a command that will be exposed as an MCP tool | ||
echoCmd := &serpent.Command{ | ||
Use: "echo [message]", | ||
Short: "Echo a message", | ||
Tool: "echo", // This makes the command available as an MCP tool | ||
Options: []serpent.Option{ | ||
{ | ||
Name: "repeat", | ||
Flag: "repeat", // Add the Flag field so it's exposed in JSON Schema | ||
Description: "Number of times to repeat the message.", | ||
Default: "2", | ||
Value: serpent.Int64Of(&repeats), | ||
}, | ||
}, | ||
Handler: func(inv *serpent.Invocation) error { | ||
message := "Hello, World!" | ||
if len(inv.Args) > 0 { | ||
message = strings.Join(inv.Args, " ") | ||
} | ||
for i := int64(0); i < repeats; i++ { | ||
if _, err := fmt.Fprintln(inv.Stdout, message); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
}, | ||
} | ||
root.AddSubcommands(echoCmd) | ||
|
||
// Add a command that will be exposed as an MCP resource | ||
versionCmd := &serpent.Command{ | ||
Use: "version", | ||
Short: "Get version information", | ||
Resource: "version", // This makes the command available as an MCP resource | ||
Handler: func(inv *serpent.Invocation) error { | ||
version := map[string]string{ | ||
"version": "1.0.0", | ||
"name": "serpent-mcp-example", | ||
"author": "Coder", | ||
} | ||
encoder := json.NewEncoder(inv.Stdout) | ||
return encoder.Encode(version) | ||
}, | ||
} | ||
root.AddSubcommands(versionCmd) | ||
|
||
// Add a command that will not be exposed via MCP | ||
hiddenCmd := &serpent.Command{ | ||
Use: "hidden", | ||
Short: "This command is not exposed via MCP", | ||
Handler: func(inv *serpent.Invocation) error { | ||
_, err := fmt.Fprintln(inv.Stdout, "This command is not exposed via MCP") | ||
return err | ||
}, | ||
} | ||
root.AddSubcommands(hiddenCmd) | ||
|
||
// Add the MCP command to the root command | ||
root.AddSubcommands(serpent.MCPCommand()) | ||
|
||
// Run the command | ||
if err := root.Invoke(os.Args[1:]...).WithOS().Run(); err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %v\n", err) | ||
os.Exit(1) | ||
} | ||
} |
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
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.
I'd suggest maybe either prefix these with
MCP
or put them into a struct of MCP-related options.