Skip to content

Add stateless http transport #59

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ Docs at [https://mcpgolang.com](https://mcpgolang.com)

## Highlights
- 🛡️**Type safety** - Define your tool arguments as native go structs, have mcp-golang handle the rest. Automatic schema generation, deserialization, error handling etc.
- 🚛 **Custom transports** - Use the built-in transports or write your own.
- 🚛 **Custom transports** - Use the built-in transports (stdio for full feature support, HTTP for stateless communication) or write your own.
- ⚡ **Low boilerplate** - mcp-golang generates all the MCP endpoints for you apart from your tools, prompts and resources.
- 🧩 **Modular** - The library is split into three components: transport, protocol and server/client. Use them all or take what you need.
- 🔄 **Bi-directional** - Full support for both server and client implementations.
- 🔄 **Bi-directional** - Full support for both server and client implementations through stdio transport.

## Example Usage

Expand Down Expand Up @@ -92,6 +92,25 @@ func main() {
}
```

### HTTP Server Example

You can also create an HTTP-based server using either the standard HTTP transport or Gin framework:

```go
// Standard HTTP
transport := http.NewHTTPTransport("/mcp")
transport.WithAddr(":8080")
server := mcp_golang.NewServer(transport)

// Or with Gin framework
transport := http.NewGinTransport()
router := gin.Default()
router.POST("/mcp", transport.Handler())
server := mcp_golang.NewServer(transport)
```

Note: HTTP transports are stateless and don't support bidirectional features like notifications. Use stdio transport if you need those features.

### Client Example

Checkout the [examples/client](./examples/client) directory for a more complete example.
Expand Down Expand Up @@ -209,7 +228,9 @@ Open a PR to add your own projects!
- [x] Pagination

### Transports
- [x] Stdio
- [x] Stdio - Full support for all features including bidirectional communication
- [x] HTTP - Stateless transport for simple request-response scenarios (no notifications support)
- [x] Gin - HTTP transport with Gin framework integration (stateless, no notifications support)
- [x] SSE
- [x] Custom transport support
- [ ] HTTPS with custom auth support - in progress. Not currently part of the spec but we'll be adding experimental support for it.
Expand Down
2 changes: 1 addition & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (c *Client) Initialize(ctx context.Context) (*InitializeResponse, error) {
}

// Make initialize request to server
response, err := c.protocol.Request(ctx, "initialize", nil, nil)
response, err := c.protocol.Request(ctx, "initialize", map[string]interface{}{}, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize")
}
Expand Down
58 changes: 57 additions & 1 deletion docs/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,60 @@ if err != nil {

## Complete Example

For a complete working example, check out our [example client implementation](https://github.com/metoro-io/mcp-golang/tree/main/examples/client).
For a complete working example, check out our [example client implementation](https://github.com/metoro-io/mcp-golang/tree/main/examples/client).

## Transport Options

The MCP client supports multiple transport options:

### Standard I/O Transport

For command-line tools that communicate through stdin/stdout:

```go
transport := stdio.NewStdioClientTransport()
client := mcp.NewClient(transport)
```

This transport supports all MCP features including bidirectional communication and notifications.

### HTTP Transport

For web-based tools that communicate over HTTP/HTTPS:

```go
transport := http.NewHTTPClientTransport("/mcp")
transport.WithBaseURL("http://localhost:8080")
client := mcp.NewClient(transport)
```

Note that the HTTP transport is stateless and does not support bidirectional features like notifications. Each request-response cycle is independent, making it suitable for simple tool invocations but not for scenarios requiring real-time updates or persistent connections.

## Context Support

All client operations now support context propagation:

```go
ctx := context.Background()
// With timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

// Call tool with context
response, err := client.CallTool(ctx, "tool-name", args)
if err != nil {
// Handle error
}

// List tools with context
tools, err := client.ListTools(ctx)
if err != nil {
// Handle error
}
```

The context allows you to:
- Set timeouts for operations
- Cancel long-running operations
- Pass request-scoped values
- Implement tracing and monitoring
96 changes: 96 additions & 0 deletions docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,102 @@ Create a file in ~/Library/Application Support/Claude/claude_desktop_config.json
}
```

## HTTP Server Example

You can also create an HTTP-based MCP server. Note that HTTP transport is stateless and doesn't support bidirectional features like notifications - use stdio transport if you need those features.

```go
package main

import (
"context"
"log"
"github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/http"
)

func main() {
// Create an HTTP transport
transport := http.NewHTTPTransport("/mcp")
transport.WithAddr(":8080")

// Create server with the HTTP transport
server := mcp.NewServer(transport)

// Register your tools
server.RegisterTool("hello", &HelloTool{})

// Start the server
if err := server.Serve(); err != nil {
log.Fatal(err)
}
}
```

Or using the Gin framework:

```go
package main

import (
"github.com/gin-gonic/gin"
"github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/http"
)

func main() {
// Create a Gin transport
transport := http.NewGinTransport()

// Create server with the Gin transport
server := mcp.NewServer(transport)

// Register your tools
server.RegisterTool("hello", &HelloTool{})

// Set up Gin router
router := gin.Default()
router.POST("/mcp", transport.Handler())

// Start the server
router.Run(":8080")
}
```

## HTTP Client Example

To connect to an HTTP-based MCP server:

```go
package main

import (
"context"
"log"
"github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/http"
)

func main() {
// Create an HTTP client transport
transport := http.NewHTTPClientTransport("/mcp")
transport.WithBaseURL("http://localhost:8080")

// Create client with the HTTP transport
client := mcp.NewClient(transport)

// Use the client with context
ctx := context.Background()
response, err := client.CallTool(ctx, "hello", map[string]interface{}{
"name": "World",
})
if err != nil {
log.Fatal(err)
}
log.Printf("Response: %v", response)
}
```

## Next Steps

- If you're interested in contributing to mcp-golang, check out [Development Guide](/development) for more detailed information
Expand Down
48 changes: 48 additions & 0 deletions docs/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,52 @@ server: {"id":10,"jsonrpc":"2.0","result":{"content":[{"text":"Hello, claude!","
* **Optional fields** All fields are optional by default. Just don't use the `jsonschema:"required"` tag.
* **Description** Use the `jsonschema:"description"` tag to add a description to the argument.

## HTTP Transport

The MCP SDK now supports HTTP transport for both client and server implementations. This allows you to build MCP tools that communicate over HTTP/HTTPS endpoints.

**Note:** The HTTP transport implementations are stateless, which means they don't support bidirectional communication features like notifications. If you need to send notifications or maintain a persistent connection between client and server, use the stdio transport instead.

### HTTP Server

There are two server implementations available:

1. Standard HTTP Server:
```go
transport := http.NewHTTPTransport("/mcp")
transport.WithAddr(":8080") // Optional, defaults to :8080
```

2. Gin Framework Server:
```go
transport := http.NewGinTransport()
router := gin.Default()
router.POST("/mcp", transport.Handler())
```

### HTTP Client

The HTTP client transport allows you to connect to MCP servers over HTTP:

```go
transport := http.NewHTTPClientTransport("/mcp")
transport.WithBaseURL("http://localhost:8080")
```

### Context Support

All transport implementations now support context propagation. This allows you to pass request-scoped data and handle timeouts/cancellation:

```go
transport.SetMessageHandler(func(ctx context.Context, msg *transport.BaseJsonRpcMessage) {
// Access context values or handle cancellation
if deadline, ok := ctx.Deadline(); ok {
// Handle deadline
}
// Process message
})
```

The context is propagated through all MCP operations, making it easier to implement timeouts, tracing, and other cross-cutting concerns.


49 changes: 49 additions & 0 deletions examples/gin_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Gin Integration Example

This example demonstrates how to integrate the MCP server with a Gin web application. It shows how to:
1. Create an MCP server with a Gin transport
2. Register tools with the server
3. Add the MCP endpoint to a Gin router

## Running the Example

1. Start the server:
```bash
go run main.go
```
This will start a Gin server on port 8081 with an MCP endpoint at `/mcp`.

2. You can test it using the HTTP client example:
```bash
cd ../http_example
go run client/main.go
```

## Understanding the Code

The key components are:

1. `GinTransport`: A transport implementation that works with Gin's router
2. `Handler()`: Returns a Gin handler function that can be used with any Gin router
3. Tool Registration: Shows how to register tools that can be called via the MCP endpoint

## Integration with Existing Gin Applications

To add MCP support to your existing Gin application:

```go
// Create the transport
transport := http.NewGinTransport()

// Create the MCP server
server := mcp_golang.NewServer(transport)

// Register your tools
server.RegisterTool("mytool", "Tool description", myToolHandler)

// Start the server
go server.Serve()

// Add the MCP endpoint to your Gin router
router.POST("/mcp", transport.Handler())
```
58 changes: 58 additions & 0 deletions examples/gin_example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"context"
"fmt"
"log"
"time"

"github.com/gin-gonic/gin"
mcp_golang "github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/http"
)

// TimeArgs defines the arguments for the time tool
type TimeArgs struct {
Format string `json:"format" jsonschema:"description=The time format to use"`
}

func main() {
// Create a Gin transport
transport := http.NewGinTransport()

// Create a new server with the transport
server := mcp_golang.NewServer(transport, mcp_golang.WithName("mcp-golang-gin-example"), mcp_golang.WithVersion("0.0.1"))

// Register a simple tool
err := server.RegisterTool("time", "Returns the current time in the specified format", func(ctx context.Context, args TimeArgs) (*mcp_golang.ToolResponse, error) {
ginCtx, ok := ctx.Value("ginContext").(*gin.Context)
if !ok {
return nil, fmt.Errorf("ginContext not found in context")
}
userAgent := ginCtx.GetHeader("User-Agent")
log.Printf("Request from User-Agent: %s", userAgent)

format := args.Format
if format == "" {
format = time.RFC3339
}
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(time.Now().Format(format))), nil
})
if err != nil {
panic(err)
}

go server.Serve()

// Create a Gin router
r := gin.Default()

// Add the MCP endpoint
r.POST("/mcp", transport.Handler())

// Start the server
log.Println("Starting Gin server on :8081...")
if err := r.Run(":8081"); err != nil {
log.Fatalf("Server error: %v", err)
}
}
Loading
Loading