# Model Context Protocol (MCP) Implementation

## Overview

This package provides a go-zero integration for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) using the official [go-sdk](https://github.com/modelcontextprotocol/go-sdk). It wraps the official MCP SDK to provide a seamless integration with go-zero's REST server framework.

## Features

- **Official SDK Integration**: Built on top of the official Model Context Protocol Go SDK
- **No SDK Import Required**: Use `mcp.AddTool()` directly without importing the official SDK
- **go-zero Integration**: Seamlessly integrates with go-zero's REST server and configuration system
- **Dual Transport Support**:
  - SSE (Server-Sent Events) transport for 2024-11-05 MCP spec
  - Streamable HTTP transport for 2025-03-26 MCP spec
- **CORS Support**: Configurable CORS settings for cross-origin requests
- **Type-Safe Tool Handlers**: Generic tool handlers with automatic JSON schema generation
- **Prompts and Resources**: Full support for MCP prompts and resources
- **Request Metadata Bridge**: Optional request metadata extraction into handler context

## Quick Start

### 1. Installation

```bash
go get github.com/zeromicro/go-zero
```

**Note**: The official MCP SDK is a transitive dependency and will be installed automatically. You don't need to import it directly in your code.

### 2. Configuration

Create a configuration file `config.yaml`:

```yaml
name: my-mcp-server
host: localhost
port: 8080
mcp:
  name: my-mcp-server
  version: 1.0.0
  useStreamable: false  # Use SSE transport (default), set to true for Streamable HTTP
  sseEndpoint: /sse
  messageEndpoint: /message
  sseTimeout: 24h
  messageTimeout: 30s
  cors:
    - http://localhost:3000
```

### 3. Create Your Server

```go
package main

import (
	"context"
	"log"

	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/mcp"
)

type GreetArgs struct {
	Name string `json:"name" jsonschema:"description=Name of the person to greet"`
}

func main() {
	// Load configuration
	var c mcp.McpConf
	conf.MustLoad("config.yaml", &c)

	// Create MCP server
	server := mcp.NewMcpServer(c)

	// Register a tool with automatic schema generation using the SDK directly
	tool := &mcp.Tool{
		Name:        "greet",
		Description: "Greet someone by name",
	}

	handler := func(ctx context.Context, req *mcp.CallToolRequest, args GreetArgs) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: "Hello, " + args.Name + "!"},
			},
		}, nil, nil
	}

	// Register tool with type-safe generics - no need to import official SDK
	mcp.AddTool(server, tool, handler)

	// Start server
	defer server.Stop()
	server.Start()
}
```

## Adding Tools

Tools are functions that the MCP client can call. The SDK automatically generates JSON schemas from your struct tags. Use `sdkmcp.AddTool` with the server's underlying SDK server:

```go
type CalculateArgs struct {
	Operation string  `json:"operation" jsonschema:"enum=add,enum=subtract,enum=multiply,enum=divide"`
	A         float64 `json:"a" jsonschema:"description=First number"`
	B         float64 `json:"b" jsonschema:"description=Second number"`
}

tool := &mcp.Tool{
	Name:        "calculate",
	Description: "Perform arithmetic operations",
}

handler := func(ctx context.Context, req *mcp.CallToolRequest, args CalculateArgs) (*mcp.CallToolResult, any, error) {
	var result float64
	switch args.Operation {
	case "add":
		result = args.A + args.B
	case "subtract":
		result = args.A - args.B
	case "multiply":
		result = args.A * args.B
	case "divide":
		if args.B == 0 {
			return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("division by zero")
		}
		result = args.A / args.B
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			&mcp.TextContent{Text: fmt.Sprintf("Result: %v", result)},
		},
	}, result, nil
}

// Register tool
mcp.AddTool(server, tool, handler)
```

## Adding Prompts

Prompts provide reusable message templates:

```go
prompt := &mcp.Prompt{
	Name:        "code-review",
	Description: "Review code for best practices",
}

handler := func(ctx context.Context, req *sdkmcp.GetPromptRequest, args map[string]string) (*mcp.GetPromptResult, error) {
	code := args["code"]
	language := args["language"]

	return &mcp.GetPromptResult{
		Messages: []mcp.PromptMessage{
			{
				Role: "user",
				Content: &mcp.TextContent{
					Text: fmt.Sprintf("Please review this %s code:\n\n%s", language, code),
				},
			},
		},
	}, nil
}

server.AddPrompt(prompt, handler)
```

## Adding Resources

Resources provide access to data that the model can read:

```go
resource := &mcp.Resource{
	URI:         "file:///docs/readme.md",
	Name:        "README",
	Description: "Project documentation",
	MimeType:    "text/markdown",
}

handler := func(ctx context.Context, req *sdkmcp.ReadResourceRequest, uri string) (*mcp.ReadResourceResult, error) {
	content, err := os.ReadFile("README.md")
	if err != nil {
		return nil, err
	}

	return &mcp.ReadResourceResult{
		Contents: []mcp.ResourceContents{
			{
				URI:      uri,
				MimeType: "text/markdown",
				Text:     string(content),
			},
		},
	}, nil
}

server.AddResource(resource, handler)
```

## Transport Options

### SSE Transport (Default)

The SSE (Server-Sent Events) transport is the original MCP transport from the 2024-11-05 specification:

```yaml
mcp:
  useStreamable: false
  sseEndpoint: /sse
```

### Streamable HTTP Transport

The newer Streamable HTTP transport from the 2025-03-26 specification provides better connection management:

```yaml
mcp:
  useStreamable: true
  messageEndpoint: /message
```

## Request Metadata Bridge

For multi-tenant or request-context-aware tools, you can extract selected HTTP request metadata once at the transport boundary and read it from `context.Context` in handlers.

```go
server := mcp.NewMcpServerWithOptions(c,
	mcp.WithRequestMetadataExtractor(mcp.DefaultRequestMetadataExtractor),
)

handler := func(ctx context.Context, req *mcp.CallToolRequest, args SomeArgs) (*mcp.CallToolResult, any, error) {
	tenant, _ := mcp.HeaderFromContext(ctx, "X-Tenant-Id")
	traceID, _ := mcp.QueryFromContext(ctx, "trace")
	scope, _ := mcp.PathFromContext(ctx, "scope")

	_ = tenant
	_ = traceID
	_ = scope

	return &mcp.CallToolResult{}, nil, nil
}
```

Available helpers:

- `RequestMetadataFromContext(ctx)`
- `HeaderFromContext(ctx, key)`
- `QueryFromContext(ctx, key)`
- `PathFromContext(ctx, key)`

## Configuration Options

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | | Server name (required, from RestConf) |
| `host` | string | | Server host (required, from RestConf) |
| `port` | int | | Server port (required, from RestConf) |
| `mcp.name` | string | | MCP server name (defaults to `name`) |
| `mcp.version` | string | `1.0.0` | Server version |
| `mcp.useStreamable` | bool | `false` | Use Streamable HTTP transport instead of SSE |
| `mcp.sseEndpoint` | string | `/sse` | SSE endpoint path |
| `mcp.messageEndpoint` | string | `/message` | Message endpoint path |
| `mcp.sseTimeout` | duration | `24h` | SSE connection timeout |
| `mcp.messageTimeout` | duration | `30s` | Message processing timeout |
| `mcp.cors` | []string | | Allowed CORS origins |

## Examples

See the `adhoc/mcp` directory for a complete working example.

## Official SDK Documentation

For more details on the underlying MCP SDK, see:
- [Official Go SDK](https://github.com/modelcontextprotocol/go-sdk)
- [MCP Specification](https://modelcontextprotocol.io/)
- [SDK Documentation](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp)

## License

This implementation follows the go-zero project license (MIT).
