Skip to content

Commit

Permalink
✨ Polish new protocol structure
Browse files Browse the repository at this point in the history
  • Loading branch information
wesen committed Feb 16, 2025
1 parent 874715b commit c0637c7
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 97 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ Start the server with either stdio or SSE transport:

```bash
# Start with stdio transport (default)
go-go-mcp start --transport stdio
go-go-mcp server start --transport stdio

# Start with SSE transport
go-go-mcp start --transport sse --port 3001
go-go-mcp server start --transport sse --port 3001
```

The server automatically watches configured repositories and files for changes, reloading tools when:
Expand Down Expand Up @@ -142,7 +142,7 @@ go-go-mcp server tools list --profile data
Use the client subcommand to interact with an MCP server:

```bash
# List available prompts (uses default server: go-go-mcp start --transport stdio)
# List available prompts (uses default server: go-go-mcp server start --transport stdio)
go-go-mcp client prompts list

# List available tools
Expand Down Expand Up @@ -171,7 +171,7 @@ go-go-mcp can be used as a bridge to expose an SSE server as a stdio server. Thi

```bash
# Start an SSE server on port 3000
go-go-mcp start --transport sse --port 3000
go-go-mcp server start --transport sse --port 3000

# In another terminal, start the bridge to expose the SSE server as stdio
go-go-mcp bridge --sse-url http://localhost:3000 --log-level debug
Expand All @@ -186,7 +186,7 @@ This is particularly useful when integrating with tools that only support stdio
Add the `--debug` flag to enable detailed logging:

```bash
go-go-mcp start --debug
go-go-mcp server start --debug
```

### Version Information
Expand Down
61 changes: 60 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1063,4 +1063,63 @@ Updated cobra command handling to support both full and minimal Glazed command l
- Added support for GlazedMinimalCommandLayer in cobra command processing
- Unified handling of common flags (print-yaml, print-parsed-parameters, etc.) between both layers
- Maintained backward compatibility with full GlazedCommandLayer features
- Added placeholder for schema printing functionality
- Added placeholder for schema printing functionality

# Transport Layer Refactoring

Implemented new transport layer architecture as described in RFC-01. This change:
- Creates a clean interface for different transport mechanisms
- Separates transport concerns from business logic
- Provides consistent error handling across transports
- Adds support for transport-specific options and capabilities

- Created new transport package with core interfaces and types
- Implemented SSE transport using new architecture
- Added transport options system
- Added standardized error handling

# Transport Layer Implementation

Added stdio transport implementation using new transport layer architecture:
- Implemented stdio transport with proper signal handling and graceful shutdown
- Added support for configurable buffer sizes and logging
- Added proper error handling and JSON-RPC message processing
- Added context-based cancellation and cleanup

# Server Layer Updates

Updated server implementation to use new transport layer:
- Refactored Server struct to use transport interface
- Added RequestHandler to implement transport.RequestHandler interface
- Updated server command to support multiple transport types
- Improved error handling and logging throughout server layer

# Enhanced SSE Transport

Added support for integrating SSE transport with existing HTTP servers:
- Added standalone and integrated modes for SSE transport
- Added GetHandlers method to get SSE endpoint handlers
- Added RegisterHandlers method for router integration
- Added support for path prefixes and middleware
- Improved configuration options for HTTP server integration

# Transport Interface Refactoring

Simplified transport interface to use protocol types directly instead of custom types.
- Removed duplicate type definitions from transport package
- Use protocol.Request/Response/Notification types directly
- Improved type safety by removing interface{} usage

# Transport Request ID Handling

Added proper request ID handling to transport package:
- Added IsNotification helper to check for empty/null request IDs
- Improved notification detection for JSON-RPC messages
- Consistent handling of request IDs across transports

# Transport ID Type Conversion

Added helper functions for converting between string and JSON-RPC ID types:
- Added StringToID to convert string to json.RawMessage
- Added IDToString to convert json.RawMessage to string
- Improved type safety in ID handling across transports
4 changes: 3 additions & 1 deletion cmd/go-go-mcp/cmds/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ func (c *StartCommand) Run(
// Start file watcher
g.Go(func() error {
if err := toolProvider.Watch(gctx); err != nil {
logger.Error().Err(err).Msg("failed to start file watcher")
if !errors.Is(err, context.Canceled) {
logger.Error().Err(err).Msg("failed to run file watcher")
}
return err
}
return nil
Expand Down
9 changes: 8 additions & 1 deletion pkg/protocol/base.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package protocol

import "encoding/json"
import (
"encoding/json"
"fmt"
)

// Request represents a JSON-RPC 2.0 request.
type Request struct {
Expand All @@ -25,6 +28,10 @@ type Error struct {
Data json.RawMessage `json:"data,omitempty"`
}

func (e *Error) Error() string {
return fmt.Sprintf("code: %d, message: %s, data: %s", e.Code, e.Message, e.Data)
}

// Notification represents a JSON-RPC 2.0 notification.
type Notification struct {
JSONRPC string `json:"jsonrpc"`
Expand Down
26 changes: 13 additions & 13 deletions pkg/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ func NewRequestHandler(s *Server) *RequestHandler {
}

// HandleRequest processes a request and returns a response
func (h *RequestHandler) HandleRequest(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) HandleRequest(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
// Validate JSON-RPC version
if req.Headers["jsonrpc"] != "2.0" {
if req.JSONRPC != "2.0" {
return nil, transport.NewInvalidRequestError("invalid JSON-RPC version")
}

Expand All @@ -50,7 +50,7 @@ func (h *RequestHandler) HandleRequest(ctx context.Context, req *transport.Reque
}

// HandleNotification processes a notification (no response expected)
func (h *RequestHandler) HandleNotification(ctx context.Context, notif *transport.Notification) error {
func (h *RequestHandler) HandleNotification(ctx context.Context, notif *protocol.Notification) error {
switch notif.Method {
case "notifications/initialized":
h.server.logger.Info().Msg("Client initialized")
Expand All @@ -62,20 +62,20 @@ func (h *RequestHandler) HandleNotification(ctx context.Context, notif *transpor
}

// Helper method to create success response
func (h *RequestHandler) newSuccessResponse(id string, result interface{}) (*transport.Response, error) {
func (h *RequestHandler) newSuccessResponse(id json.RawMessage, result interface{}) (*protocol.Response, error) {
resultJSON, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to marshal result: %w", err)
}

return &transport.Response{
return &protocol.Response{
ID: id,
Result: resultJSON,
}, nil
}

// Individual request handlers
func (h *RequestHandler) handleInitialize(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handleInitialize(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params protocol.InitializeParams
if err := json.Unmarshal(req.Params, &params); err != nil {
return nil, transport.NewInvalidParamsError(err.Error())
Expand Down Expand Up @@ -120,11 +120,11 @@ func (h *RequestHandler) handleInitialize(ctx context.Context, req *transport.Re
return h.newSuccessResponse(req.ID, result)
}

func (h *RequestHandler) handlePing(_ context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handlePing(_ context.Context, req *protocol.Request) (*protocol.Response, error) {
return h.newSuccessResponse(req.ID, struct{}{})
}

func (h *RequestHandler) handlePromptsList(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handlePromptsList(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Cursor string `json:"cursor"`
}
Expand All @@ -147,7 +147,7 @@ func (h *RequestHandler) handlePromptsList(ctx context.Context, req *transport.R
})
}

func (h *RequestHandler) handlePromptsGet(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handlePromptsGet(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Name string `json:"name"`
Arguments map[string]string `json:"arguments"`
Expand All @@ -164,7 +164,7 @@ func (h *RequestHandler) handlePromptsGet(ctx context.Context, req *transport.Re
return h.newSuccessResponse(req.ID, prompt)
}

func (h *RequestHandler) handleResourcesList(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handleResourcesList(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Cursor string `json:"cursor"`
}
Expand All @@ -187,7 +187,7 @@ func (h *RequestHandler) handleResourcesList(ctx context.Context, req *transport
})
}

func (h *RequestHandler) handleResourcesRead(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handleResourcesRead(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Name string `json:"name"`
}
Expand All @@ -205,7 +205,7 @@ func (h *RequestHandler) handleResourcesRead(ctx context.Context, req *transport
})
}

func (h *RequestHandler) handleToolsList(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handleToolsList(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Cursor string `json:"cursor"`
}
Expand All @@ -228,7 +228,7 @@ func (h *RequestHandler) handleToolsList(ctx context.Context, req *transport.Req
})
}

func (h *RequestHandler) handleToolsCall(ctx context.Context, req *transport.Request) (*transport.Response, error) {
func (h *RequestHandler) handleToolsCall(ctx context.Context, req *protocol.Request) (*protocol.Response, error) {
var params struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
Expand Down
34 changes: 19 additions & 15 deletions pkg/transport/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package transport

import "fmt"
import (
"fmt"

"github.com/go-go-golems/go-go-mcp/pkg/protocol"
)

// Common error codes
const (
Expand All @@ -14,50 +18,50 @@ const (
)

// Error constructors
func NewParseError(msg string) *ResponseError {
return &ResponseError{
func NewParseError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeParse,
Message: fmt.Sprintf("Parse error: %s", msg),
}
}

func NewInvalidRequestError(msg string) *ResponseError {
return &ResponseError{
func NewInvalidRequestError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeInvalidRequest,
Message: fmt.Sprintf("Invalid request: %s", msg),
}
}

func NewMethodNotFoundError(msg string) *ResponseError {
return &ResponseError{
func NewMethodNotFoundError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeMethodNotFound,
Message: fmt.Sprintf("Method not found: %s", msg),
}
}

func NewInvalidParamsError(msg string) *ResponseError {
return &ResponseError{
func NewInvalidParamsError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeInvalidParams,
Message: fmt.Sprintf("Invalid params: %s", msg),
}
}

func NewInternalError(msg string) *ResponseError {
return &ResponseError{
func NewInternalError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeInternal,
Message: fmt.Sprintf("Internal error: %s", msg),
}
}

func NewTransportError(msg string) *ResponseError {
return &ResponseError{
func NewTransportError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeTransport,
Message: fmt.Sprintf("Transport error: %s", msg),
}
}

func NewTimeoutError(msg string) *ResponseError {
return &ResponseError{
func NewTimeoutError(msg string) *protocol.Error {
return &protocol.Error{
Code: ErrCodeTimeout,
Message: fmt.Sprintf("Timeout error: %s", msg),
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/transport/sse/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sync"
"time"

"github.com/go-go-golems/go-go-mcp/pkg/protocol"
"github.com/go-go-golems/go-go-mcp/pkg/transport"
"github.com/google/uuid"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -45,7 +46,7 @@ type SSETransport struct {
type SSEClient struct {
id string
sessionID string
messageChan chan *transport.Response
messageChan chan *protocol.Response
createdAt time.Time
remoteAddr string
userAgent string
Expand Down Expand Up @@ -140,7 +141,7 @@ func (s *SSETransport) Listen(ctx context.Context, handler transport.RequestHand
}
}

func (s *SSETransport) Send(ctx context.Context, response *transport.Response) error {
func (s *SSETransport) Send(ctx context.Context, response *protocol.Response) error {
s.mu.RLock()
defer s.mu.RUnlock()

Expand Down Expand Up @@ -255,7 +256,7 @@ func (s *SSETransport) handleSSE(w http.ResponseWriter, r *http.Request) {
client := &SSEClient{
id: clientID,
sessionID: sessionID,
messageChan: make(chan *transport.Response, 100),
messageChan: make(chan *protocol.Response, 100),
createdAt: time.Now(),
remoteAddr: r.RemoteAddr,
userAgent: r.UserAgent(),
Expand Down Expand Up @@ -318,7 +319,7 @@ func (s *SSETransport) handleMessages(w http.ResponseWriter, r *http.Request) {

ctx := context.WithValue(r.Context(), sessionIDKey, sessionID)

var request transport.Request
var request protocol.Request
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
s.logger.Error().Err(err).Msg("Failed to decode request")
w.WriteHeader(http.StatusBadRequest)
Expand Down
Loading

0 comments on commit c0637c7

Please sign in to comment.