Development Guide

This document provides a step-by-step guide for contributing to the mcp-golang project. By no means is it complete, but it should help you get started.

Development Setup

To set up your development environment, follow these steps:

Prerequisites

  • Go 1.19 or higher
  • Git

Local Development

  1. Clone the repository:
git clone https://github.com/metoro-io/mcp-golang.git
cd mcp-golang
  1. Install dependencies:
go mod download
  1. Run tests:
go test ./...

Project Structure

The project is organized into several key packages:

  • server/: Core server implementation
  • transport/: Transport layer implementations (stdio, SSE)
  • protocol/: MCP protocol implementation
  • examples/: Example implementations
  • internal/: Internal utilities and helpers

Implementation Guidelines

Creating a Custom Transport

To implement a custom transport, create a struct that implements the Transport interface. If your transport is not part of the spec then you can add it as an experimental feature. Before you implement the transport, you should have a good understanding of the MCP protocol. Take a look at https://spec.modelcontextprotocol.io/specification/

Testing

Unit Tests

All new functions should have unit tests where possible. We currently use testify for this. Each test should explain its purpose and expected behavior. E.g.


// TestProtocol_Request tests the core request-response functionality of the protocol.
// This is the most important test as it covers the primary use case of the protocol.
// It includes subtests for:
// 1. Successful request/response with proper correlation
// 2. Request timeout handling
// 3. Request cancellation via context
// These scenarios ensure the protocol can handle both successful and error cases
// while maintaining proper message correlation and resource cleanup.
func TestProtocol_Request(t *testing.T) {
	p := NewProtocol(nil)
	transport := mcp.newMockTransport()

	if err := p.Connect(transport); err != nil {
		t.Fatalf("Connect failed: %v", err)
	}

	// Test successful request
	t.Run("Successful request", func(t *testing.T) {
		ctx := context.Background()
		go func() {
			// Simulate response after a short delay
			time.Sleep(10 * time.Millisecond)
			msgs := transport.getMessages()
			if len(msgs) == 0 {
				t.Error("No messages sent")
				return
			}

			lastMsg := msgs[len(msgs)-1]
			req, ok := lastMsg.(map[string]interface{})
			if !ok {
				t.Error("Last message is not a request")
				return
			}

			// Simulate response
			transport.simulateMessage(map[string]interface{}{
				"jsonrpc": "2.0",
				"id":      req["id"],
				"result":  "test result",
			})
		}()

		result, err := p.Request(ctx, "test_method", map[string]string{"key": "value"}, nil)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}

		if result != "test result" {
			t.Errorf("Expected result 'test result', got %v", result)
		}
	})

	// Test request timeout
	t.Run("Request timeout", func(t *testing.T) {
		ctx := context.Background()
		opts := &RequestOptions{
			Timeout: 50 * time.Millisecond,
		}

		_, err := p.Request(ctx, "test_method", nil, opts)
		if err == nil {
			t.Fatal("Expected timeout error, got nil")
		}
	})

	// Test request cancellation
	t.Run("Request cancellation", func(t *testing.T) {
		ctx, cancel := context.WithCancel(context.Background())

		go func() {
			time.Sleep(10 * time.Millisecond)
			cancel()
		}()

		_, err := p.Request(ctx, "test_method", nil, nil)
		if !errors.Is(err, context.Canceled) {
			t.Fatalf("Expected context.Canceled error, got %v", err)
		}
	})
}
}

Integration Tests

Run integration tests that use the actual transport layers:

func TestWithStdioTransport(t *testing.T) {
    transport := stdio.NewStdioServerTransport()
    server := server.NewServer(transport)
    
    // Test server with real transport
}

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run existing tests
  6. Submit a pull request

Pull Request Guidelines

  • Keep changes focused and atomic
  • Follow existing code style
  • Include tests for new functionality
  • Update documentation as needed
  • Add yourself to CONTRIBUTORS.md

Adding docs

Prerequisite: Please install Node.js (version 19 or higher) before proceeding.

Follow these steps to install and run Mintlify on your operating system:

Step 1: Install Mintlify:

Step 2: Navigate to the docs directory (where the mint.json file is located) and execute the following command:

mintlify dev

A local preview of your documentation will be available at http://localhost:3000.

When your PR merges into the main branch, it will be deployed automatically.

Getting Help