模型上下文协议服务器

一个Go语言实现的模型上下文协议(MCP)服务器,用于连接LLM应用与外部数据源和工具。

  • by shaneholloman
  • Go
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 shaneholloman/mcp-server-go

使用说明

MCP服务器启动

[!提示] 模型上下文协议(MCP)的Go实现,实现了LLM应用程序与外部数据源和工具之间的无缝集成。

package main

import (
    "context"
    "fmt"

    "github.com/shaneholloman/mcp-server-go/mcp"
    "github.com/shaneholloman/mcp-server-go/server"
)

func main() {
    // Create MCP server
    s := server.NewMCPServer(
        "Demo",
        "1.0.0",
    )

    // Add tool
    tool := mcp.NewTool("hello_world",
        mcp.WithDescription("Say hello to someone"),
        mcp.WithString("name",
            mcp.Required(),
            mcp.Description("Name of the person to greet"),
        ),
    )

    // Add tool handler
    s.AddTool(tool, helloHandler)

    // Start the stdio server
    if err := server.ServeStdio(s); err != nil {
        fmt.Printf("Server error: %v\n", err)
    }
}

func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    name, ok := request.Params.Arguments["name"].(string)
    if !ok {
        return mcp.NewToolResultError("name must be a string"), nil
    }

    return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}

就是这样!

MCP Go处理所有复杂的协议细节和服务器管理,因此您可以专注于构建出色的工具。它的目标是高水平和易于使用。

主要特点

  • 快速:高级接口意味着更少的代码和更快的开发
  • 简单:使用最少的样板构建MCP服务器
  • 完成*:MCP Go旨在提供核心MCP规范的完整实现

[!重要] MCP Go正在积极开发中,MCP规范本身也是如此。核心功能正在发挥作用,但一些高级功能仍在开发中。

目录

安装

go get github.com/shaneholloman/mcp-server-go

快速启动

让我们创建一个简单的MCP服务器,它公开了一个计算器工具和一些数据:

package main

import (
    "context"
    "fmt"

    "github.com/shaneholloman/mcp-server-go/mcp"
    "github.com/shaneholloman/mcp-server-go/server"
)

func main() {
    // Create a new MCP server
    s := server.NewMCPServer(
        "Calculator Demo",
        "1.0.0",
        server.WithResourceCapabilities(true, true),
        server.WithLogging(),
    )

    // Add a calculator tool
    calculatorTool := mcp.NewTool("calculate",
        mcp.WithDescription("Perform basic arithmetic operations"),
        mcp.WithString("operation",
            mcp.Required(),
            mcp.Description("The operation to perform (add, subtract, multiply, divide)"),
            mcp.Enum("add", "subtract", "multiply", "divide"),
        ),
        mcp.WithNumber("x",
            mcp.Required(),
            mcp.Description("First number"),
        ),
        mcp.WithNumber("y",
            mcp.Required(),
            mcp.Description("Second number"),
        ),
    )

    // Add the calculator handler
    s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        op := request.Params.Arguments["operation"].(string)
        x := request.Params.Arguments["x"].(float64)
        y := request.Params.Arguments["y"].(float64)

        var result float64
        switch op {
        case "add":
            result = x + y
        case "subtract":
            result = x - y
        case "multiply":
            result = x * y
        case "divide":
            if y == 0 {
                return mcp.NewToolResultError("Cannot divide by zero"), nil
            }
            result = x / y
        }

        return mcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil
    })

    // Start the server
    if err := server.ServeStdio(s); err != nil {
        fmt.Printf("Server error: %v\n", err)
    }
}

什么是MCP?

这 模型上下文协议(MCP) 允许您构建以安全、标准化的方式向LLM应用程序公开数据和功能的服务器。将其视为一个web API,但它是专门为LLM交互而设计的。MCP服务器可以:

  • 通过以下方式公开数据 资源 (将这些类似于GET端点;它们用于将信息加载到LLM的上下文中)
  • 通过以下方式提供功能 工具 (有点像POST端点;它们用于执行代码或产生副作用)
  • 通过以下方式定义交互模式 提示 (LLM交互的可重用模板)
  • 还有更多!

核心概念

服务器

Show Server Examples

服务器是MCP协议的核心接口。它处理连接管理、协议合规性和消息路由:

// Create a basic server
s := server.NewMCPServer(
    "My Server",  // Server name
    "1.0.0",     // Version
)

// Start the server using stdio
if err := server.ServeStdio(s); err != nil {
    log.Fatalf("Server error: %v", err)
}

资源

Show Resource Examples Resources are how you expose data to LLMs. They can be anything - files, API responses, database queries, system information, etc. Resources can be:
  • 静态(固定URI)
  • 动态(使用URI模板)

下面是一个静态资源的简单示例:

// Static resource example - exposing a README file
resource := mcp.NewResource(
    "docs://readme",
    "Project README",
    mcp.WithResourceDescription("The project's README file"),
    mcp.WithMIMEType("text/markdown"),
    mcp.WithAnnotations([]mcp.Role{mcp.RoleAssistant}, 0.8),
)

// Add resource with its handler
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]interface{}, error) {
    content, err := os.ReadFile("README.md")
    if err != nil {
        return nil, err
    }

    return []interface{}{
        mcp.TextResourceContents{
            ResourceContents: mcp.ResourceContents{
                URI:      "docs://readme",
                MIMEType: "text/markdown",
            },
            Text: string(content),
        },
    }, nil
})

下面是一个使用模板的动态资源的示例:

// Dynamic resource example - user profiles by ID
template := mcp.NewResourceTemplate(
    "users://{id}/profile",
    "User Profile",
    mcp.WithTemplateDescription("Returns user profile information"),
    mcp.WithTemplateMIMEType("application/json"),
    mcp.WithTemplateAnnotations([]mcp.Role{mcp.RoleAssistant, mcp.RoleUser}, 0.5),
)

// Add template with its handler
s.AddResourceTemplate(template, func(ctx context.Context, request mcp.ReadResourceRequest) ([]interface{}, error) {
    userID := request.Params.URI // Extract ID from the full URI

    profile, err := getUserProfile(userID)  // Your DB/API call here
    if err != nil {
        return nil, err
    }

    return []interface{}{
        mcp.TextResourceContents{
            ResourceContents: mcp.ResourceContents{
                URI:      fmt.Sprintf("users://%s/profile", userID),
                MIMEType: "application/json",
            },
            Text: profile,
        },
    }, nil
})

这些例子很简单,但展示了核心概念。资源可以更复杂——提供多个内容、使用注释、与数据库或外部API集成等。

工具

Show Tool Examples

工具允许LLM通过您的服务器执行操作。与资源不同,工具被期望执行计算并具有副作用。它们类似于REST API中的POST端点。

简单计算示例:

calculatorTool := mcp.NewTool("calculate",
    mcp.WithDescription("Perform basic arithmetic calculations"),
    mcp.WithString("operation",
        mcp.Required(),
        mcp.Description("The arithmetic operation to perform"),
        mcp.Enum("add", "subtract", "multiply", "divide"),
    ),
    mcp.WithNumber("x",
        mcp.Required(),
        mcp.Description("First number"),
    ),
    mcp.WithNumber("y",
        mcp.Required(),
        mcp.Description("Second number"),
    ),
)

s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    op := request.Params.Arguments["operation"].(string)
    x := request.Params.Arguments["x"].(float64)
    y := request.Params.Arguments["y"].(float64)

    var result float64
    switch op {
    case "add":
        result = x + y
    case "subtract":
        result = x - y
    case "multiply":
        result = x * y
    case "divide":
        if y == 0 {
            return mcp.NewToolResultError("Division by zero is not allowed"), nil
        }
        result = x / y
    }

    return mcp.FormatNumberResult(result), nil
})

HTTP请求示例:

httpTool := mcp.NewTool("http_request",
    mcp.WithDescription("Make HTTP requests to external APIs"),
    mcp.WithString("method",
        mcp.Required(),
        mcp.Description("HTTP method to use"),
        mcp.Enum("GET", "POST", "PUT", "DELETE"),
    ),
    mcp.WithString("url",
        mcp.Required(),
        mcp.Description("URL to send the request to"),
        mcp.Pattern("^https?://.*"),
    ),
    mcp.WithString("body",
        mcp.Description("Request body (for POST/PUT)"),
    ),
)

s.AddTool(httpTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    method := request.Params.Arguments["method"].(string)
    url := request.Params.Arguments["url"].(string)
    body := ""
    if b, ok := request.Params.Arguments["body"].(string); ok {
        body = b
    }

    // Create and send request
    var req *http.Request
    var err error
    if body != "" {
        req, err = http.NewRequest(method, url, strings.NewReader(body))
    } else {
        req, err = http.NewRequest(method, url, nil)
    }
    if err != nil {
        return mcp.NewToolResultError(fmt.Sprintf("Failed to create request: %v", err)), nil
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return mcp.NewToolResultError(fmt.Sprintf("Request failed: %v", err)), nil
    }
    defer resp.Body.Close()

    // Return response
    respBody, err := io.ReadAll(resp.Body)
    if err != nil {
        return mcp.NewToolResultError(fmt.Sprintf("Failed to read response: %v", err)), nil
    }

    return mcp.NewToolResultText(fmt.Sprintf("Status: %d\nBody: %s", resp.StatusCode, string(respBody))), nil
})

工具可用于任何类型的计算或副作用:

  • 数据库查询
  • 文件 操作
  • 外部API调用
  • 计算
  • 系统操作

每个工具都应该:

  • 有清晰的描述
  • 验证输入
  • 优雅地处理错误
  • 返回结构化响应
  • 使用适当的结果类型

提示

Show Prompt Examples

提示是可重用的模板,可帮助LLM有效地与服务器交互。它们就像编码到服务器中的“最佳实践”。以下是一些示例:

// Simple greeting prompt
s.AddPrompt(mcp.NewPrompt("greeting",
    mcp.WithPromptDescription("A friendly greeting prompt"),
    mcp.WithArgument("name",
        mcp.ArgumentDescription("Name of the person to greet"),
    ),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
    name := request.Params.Arguments["name"].(string)
    if name == "" {
        name = "friend"
    }

    return mcp.NewGetPromptResult(
        "A friendly greeting",
        []mcp.PromptMessage{
            mcp.NewPromptMessage(
                mcp.RoleAssistant,
                mcp.NewTextContent(fmt.Sprintf("Hello, %s! How can I help you today?", name)),
            ),
        },
    ), nil
})

// Code review prompt with embedded resource
s.AddPrompt(mcp.NewPrompt("code_review",
    mcp.WithPromptDescription("Code review assistance"),
    mcp.WithArgument("pr_number",
        mcp.ArgumentDescription("Pull request number to review"),
        mcp.RequiredArgument(),
    ),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
    prNumber := request.Params.Arguments["pr_number"].(string)
    if prNumber == "" {
        return nil, fmt.Errorf("pr_number is required")
    }

    return mcp.NewGetPromptResult(
        "Code review assistance",
        []mcp.PromptMessage{
            mcp.NewPromptMessage(
                mcp.RoleSystem,
                mcp.NewTextContent("You are a helpful code reviewer. Review the changes and provide constructive feedback."),
            ),
            mcp.NewPromptMessage(
                mcp.RoleAssistant,
                mcp.NewEmbeddedResource(mcp.ResourceContents{
                    URI: fmt.Sprintf("git://pulls/%s/diff", prNumber),
                    MIMEType: "text/x-diff",
                }),
            ),
        },
    ), nil
})

// Database query builder prompt
s.AddPrompt(mcp.NewPrompt("query_builder",
    mcp.WithPromptDescription("SQL query builder assistance"),
    mcp.WithArgument("table",
        mcp.ArgumentDescription("Name of the table to query"),
        mcp.RequiredArgument(),
    ),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
    tableName := request.Params.Arguments["table"].(string)
    if tableName == "" {
        return nil, fmt.Errorf("table name is required")
    }

    return mcp.NewGetPromptResult(
        "SQL query builder assistance",
        []mcp.PromptMessage{
            mcp.NewPromptMessage(
                mcp.RoleSystem,
                mcp.NewTextContent("You are a SQL expert. Help construct efficient and safe queries."),
            ),
            mcp.NewPromptMessage(
                mcp.RoleAssistant,
                mcp.NewEmbeddedResource(mcp.ResourceContents{
                    URI: fmt.Sprintf("db://schema/%s", tableName),
                    MIMEType: "application/json",
                }),
            ),
        },
    ), nil
})

提示可能包括:

  • 系统说明
  • 必需参数
  • 嵌入资源
  • 多条消息
  • 不同的内容类型(文本、图像等)
  • 自定义URI方案

例子

有关示例,请参阅 examples/ 目录。

贡献

Open Developer Guide

先决条件

Go版本>=1.23

开发安装

创建此存储库的分支,然后克隆它:

git clone https://github.com/shaneholloman/mcp-server-go.git
cd mcp-server-go

测试

请确保测试任何新功能。你的测试应该简单而原子化,并预测变化,而不是巩固复杂的模式。

从根目录运行测试:

go test -v './...'

打开拉取请求

分叉存储库并创建一个新分支:

git checkout -b my-branch

进行更改并提交:

git add . && git commit -m "My changes"

将您的更改推到叉子上:

git push origin my-branch

如果您有任何问题,请随时联系GitHub问题或讨论!