zijiren 183fcda446 feat: patch plugin (#346) 4 mesi fa
..
hosted 826677ff89 feat: add some mcp server (#315) 6 mesi fa
local 826677ff89 feat: add some mcp server (#315) 6 mesi fa
mcpregister 826677ff89 feat: add some mcp server (#315) 6 mesi fa
.golangci.yml 183fcda446 feat: patch plugin (#346) 4 mesi fa
README.md 763a880556 feat: host mcp support (#231) 6 mesi fa
go.mod 555f39f5f3 feat: smark update group and token api to select field (#341) 4 mesi fa
go.sum 555f39f5f3 feat: smark update group and token api to select field (#341) 4 mesi fa
mcp.go 826677ff89 feat: add some mcp server (#315) 6 mesi fa
register.go d78adfd3d4 feat: add wsl v5 linter (#314) 6 mesi fa
server.go c2e492408f feat: add timeout plugin to control response header timeout (#338) 4 mesi fa
tools.go 1857282156 feat: list embed mcp tools (#282) 6 mesi fa

README.md

Embed MCP Servers

This directory contains the implementation of embedded Model Context Protocol (MCP) servers for AI Proxy. Embed MCP allows native Go implementations to be registered directly within the application, providing high-performance, type-safe MCP servers without external dependencies.

Overview

Embed MCP servers are native Go implementations that run within the AI Proxy process. They offer several advantages over external MCP servers:

  • Performance: No network overhead, direct function calls
  • Type Safety: Compile-time validation of server implementations
  • Resource Efficiency: Shared memory and resources with the main application
  • Hot Reloading: Dynamic configuration without server restart
  • Integrated Monitoring: Built-in metrics and logging

Architecture

Registration System

Embed MCP servers use a simple registration pattern during application startup:

func init() {
    mcpservers.Register(mcpservers.EmbedMcp{
        ID:              "my-mcp-server",
        Name:            "My MCP Server", 
        NewServer:       NewServer,
        ConfigTemplates: configTemplates,
        Tags:            []string{"example", "demo"},
        Readme:          "Description of the server functionality",
    })
}

Configuration Templates

Configuration templates define the parameters required by your MCP server:

var configTemplates = map[string]mcpservers.ConfigTemplate{
    "api_key": {
        Name:        "API Key",
        Required:    mcpservers.ConfigRequiredTypeInitOnly,
        Example:     "sk-example-key",
        Description: "The API key for authentication",
        Validator:   validateAPIKey,
    },
    "endpoint": {
        Name:        "Endpoint URL", 
        Required:    mcpservers.ConfigRequiredTypeReusingOptional,
        Example:     "https://api.example.com",
        Description: "The base URL for the API",
        Validator:   validateURL,
    },
}

Configuration Types

  • ConfigRequiredTypeInitOnly: Required during server initialization, set once globally
  • ConfigRequiredTypeReusingOnly: Required as reusing parameter, can vary by group
  • ConfigRequiredTypeInitOrReusingOnly: Required in either init or reusing config (mutually exclusive)
  • ConfigRequiredTypeInitOptional: Optional during initialization
  • ConfigRequiredTypeReusingOptional: Optional as reusing parameter

Server Implementation

func NewServer(config map[string]string, reusingConfig map[string]string) (*server.MCPServer, error) {
    // Access configuration
    apiKey := config["api_key"]           // From init config
    endpoint := reusingConfig["endpoint"] // From reusing config
    
    // Create and configure your MCP server
    mcpServer := server.NewMCPServer("my-server", server.WithMCPCapabilities(
        server.MCPServerCapabilities{
            Tools:     &server.MCPCapabilitiesTools{},
            Resources: &server.MCPCapabilitiesResources{},
        },
    ))
    
    // Register tools, resources, etc.
    mcpServer.AddTool(server.Tool{
        Name:        "example_tool",
        Description: "An example tool",
        // ... tool implementation
    })
    
    return mcpServer, nil
}

API Reference

Management Endpoints

List Available Embed MCP Servers

GET /api/embedmcp/

Returns all registered embed MCP servers with their configuration templates and enabled status.

Response:

[
  {
    "id": "aiproxy-openapi",
    "enabled": true,
    "name": "AI Proxy OpenAPI",
    "readme": "Exposes AI Proxy API as MCP tools",
    "tags": ["openapi", "admin"],
    "config_templates": {
      "host": {
        "name": "Host",
        "required": true,
        "example": "http://localhost:3000",
        "description": "The host of the OpenAPI server"
      },
      "authorization": {
        "name": "Authorization", 
        "required": false,
        "example": "admin-key",
        "description": "The admin key for authentication"
      }
    }
  }
]

Save/Configure Embed MCP Server

POST /api/embedmcp/

Configure and enable/disable an embed MCP server.

Request:

{
  "id": "aiproxy-openapi",
  "enabled": true,
  "init_config": {
    "host": "http://localhost:3000"
  }
}

Testing Endpoints

Test SSE Connection

GET /api/test-embedmcp/{id}/sse?key=adminkey&config[key]=value&reusing[key]=value

Establishes a Server-Sent Events connection for testing the embed MCP server.

Query Parameters:

  • config[key]=value: Initial configuration parameters
  • reusing[key]=value: Reusing configuration parameters

Test Streamable Connection

GET|POST|DELETE /api/test-embedmcp/{id}?key=adminkey&config[key]=value&reusing[key]=value

HTTP-based request/response interface for testing.

Send Test Message

POST /api/test-embedmcp/message?key=adminkey&sessionId={session}

Send messages to active SSE test sessions.

Request Body: JSON-RPC message

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

Usage Examples

Testing an Embed MCP Server

1. Start SSE Connection

http://localhost:3000/api/test-embedmcp/aiproxy-openapi/sse?key=adminkey&config[host]=http://localhost:3000&reusing[authorization]=admin-key

This will establish an SSE connection and return:

event: endpoint
data: /api/test-embedmcp/message?sessionId=abc123&key=your-token

event: message  
data: {"jsonrpc":"2.0","id":1,"method":"ping"}

2. Send Messages (in another terminal)

http://localhost:3000/api/test-embedmcp/message?key=adminkey&sessionId=abc123

3. Test Streamable Interface

http://localhost:3000/api/test-embedmcp/aiproxy-openapi?key=adminkey&config[host]=http://localhost:3000&reusing[authorization]=admin-key

Configuration Examples

Complex Configuration

# Multiple configuration parameters
curl "http://localhost:3000/api/test-embedmcp/my-server/sse?key=adminkey&config[api_key]=sk-123&config[timeout]=30&reusing[endpoint]=https://api.example.com&reusing[region]=us-east-1"

Optional Parameters

# Only required parameters
curl "http://localhost:3000/api/test-embedmcp/my-server/sse?key=adminkey&config[api_key]=sk-123"

Development Guide

Creating a New Embed MCP Server

1. Create Server Directory

core/mcpservers/my-server/
├── server.go
└── README.md

2. Implement the Server

server.go:

package myserver

import (
    "fmt"
    "net/url"
    
    "github.com/labring/aiproxy/core/mcpservers"
    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
)

// Configuration templates define required parameters
var configTemplates = map[string]mcpservers.ConfigTemplate{
    "api_endpoint": {
        Name:        "API Endpoint",
        Required:    mcpservers.ConfigRequiredTypeInitOnly,
        Example:     "https://api.example.com",
        Description: "The API endpoint URL",
        Validator:   validateURL,
    },
    "api_key": {
        Name:        "API Key",
        Required:    mcpservers.ConfigRequiredTypeReusingOnly,
        Example:     "sk-example-key",
        Description: "Authentication key for the API",
        Validator:   validateAPIKey,
    },
    "timeout": {
        Name:        "Timeout",
        Required:    mcpservers.ConfigRequiredTypeInitOptional,
        Example:     "30",
        Description: "Request timeout in seconds",
        Validator:   validateTimeout,
    },
}

// Validation functions
func validateURL(value string) error {
    u, err := url.Parse(value)
    if err != nil {
        return err
    }
    if u.Scheme != "http" && u.Scheme != "https" {
        return fmt.Errorf("invalid scheme: %s", u.Scheme)
    }
    return nil
}

func validateAPIKey(value string) error {
    if len(value) < 10 {
        return fmt.Errorf("API key too short")
    }
    return nil
}

func validateTimeout(value string) error {
    // Validation logic here
    return nil
}

// NewServer creates a new instance of the MCP server
func NewServer(config map[string]string, reusingConfig map[string]string) (*server.MCPServer, error) {
    endpoint := config["api_endpoint"]
    apiKey := reusingConfig["api_key"]
    
    // Create MCP server with capabilities
    mcpServer := server.NewMCPServer("my-server", server.WithMCPCapabilities(
        server.MCPServerCapabilities{
            Tools:     &server.MCPCapabilitiesTools{},
            Resources: &server.MCPCapabilitiesResources{},
        },
    ))
    
    // Add tools
    mcpServer.AddTool(server.Tool{
        Name:        "get_data",
        Description: "Retrieve data from the API",
        InputSchema: mcp.ToolInputSchema{
            Type: "object",
            Properties: map[string]any{
                "query": {
                    "type":        "string",
                    "description": "Search query",
                },
            },
            Required: []string{"query"},
        },
    }, func(arguments map[string]any) (*server.ToolResult, error) {
        query := arguments["query"].(string)
        
        // Implement your tool logic here
        // Use endpoint and apiKey for API calls
        
        return &server.ToolResult{
            Content: []any{
                server.TextContent{
                    Type: "text",
                    Text: fmt.Sprintf("Retrieved data for query: %s", query),
                },
            },
        }, nil
    })
    
    // Add resources if needed
    mcpServer.AddResource(server.Resource{
        URI:         "file:///data.json",
        Name:        "Data File",
        Description: "Sample data resource",
        MimeType:    "application/json",
    }, func() ([]byte, error) {
        // Return resource content
        return []byte(`{"example": "data"}`), nil
    })
    
    return mcpServer, nil
}

// Register the server during package initialization
func init() {
    mcpservers.Register(mcpservers.EmbedMcp{
        ID:              "my-server",
        Name:            "My Custom Server",
        NewServer:       NewServer,
        ConfigTemplates: configTemplates,
        Tags:            []string{"example", "custom"},
        Readme:          "A custom MCP server implementation for demonstration",
    })
}

3. Register in Init System

Add import to core/mcpservers/mcpregister/init.go:

package mcpregister

import (
    // register embed mcp
    _ "github.com/labring/aiproxy/core/mcpservers/aiproxy-openapi"
    _ "github.com/labring/aiproxy/core/mcpservers/my-server"  // Add this line
)

4. Test Your Server

http://localhost:3000/api/test-embedmcp/my-server/sse?key=adminkey&config[api_endpoint]=https://api.example.com&reusing[api_key]=sk-test-key

Advanced Features

Server Caching

For servers without configuration requirements, the system automatically caches instances:

// No configuration required - server will be cached
var configTemplates = map[string]mcpservers.ConfigTemplate{}

func NewServer(config map[string]string, reusingConfig map[string]string) (*server.MCPServer, error) {
    // This server instance will be cached and reused
    return server.NewMCPServer("static-server"), nil
}

Dynamic Configuration

Servers can access both init and reusing configuration:

func NewServer(config map[string]string, reusingConfig map[string]string) (*server.MCPServer, error) {
    // Init config: Set once per server deployment  
    baseURL := config["base_url"]
    
    // Reusing config: Can vary per group/user
    apiKey := reusingConfig["api_key"]
    region := reusingConfig["region"]
    
    // Create server with group-specific configuration
    return createServerWithConfig(baseURL, apiKey, region)
}

Error Handling

Implement robust error handling:

func NewServer(config map[string]string, reusingConfig map[string]string) (*server.MCPServer, error) {
    endpoint := config["endpoint"]
    if endpoint == "" {
        return nil, fmt.Errorf("endpoint is required")
    }
    
    // Validate endpoint accessibility
    if err := validateEndpointConnectivity(endpoint); err != nil {
        return nil, fmt.Errorf("endpoint validation failed: %w", err)
    }
    
    return createServer(endpoint)
}

Built-in Servers

AI Proxy OpenAPI Server

The aiproxy-openapi server demonstrates a complete embed MCP implementation that exposes AI Proxy's REST API as MCP tools.

Features:

  • Automatic OpenAPI to MCP conversion
  • Admin authentication support
  • Full CRUD operations for channels, tokens, groups
  • Resource management capabilities

Configuration:

  • host (required): AI Proxy server URL
  • authorization (optional): Admin authentication key

Example Tools:

  • get_channels: List all channels
  • create_channel: Create a new channel
  • get_tokens: List tokens
  • create_token: Create authentication tokens
  • get_groups: List user groups

This server serves as both a practical tool and a reference implementation for creating embed MCP servers.