Papyrus.
go-mcp / mcp-server/快速开始.md

快速开始

最后更新 2026-06-24

一个小例子

先别管三七二十一,先写一个简单的 MCP,搞出来看下啥效果。

// MCP 服务端:hello_world 工具,根据 user_name 返回问候语。
package main

import (
    "context"
    "log"

    "github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
    // new 一个 server
    server := mcp.NewServer(&mcp.Implementation{
        Name:    "hello-world",
        Version: "0.1.0",
    }, nil)

    // 注册 hello_world 工具,分为工具元信息和 handler 两个部分
    mcp.AddTool(server, &mcp.Tool{
        Name:        "hello_world", // 工具名称
        Description: "根据 user_name 返回问候语", // 工具描述
    }, helloWorldTool) 

    // 启动 server
    if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
        log.Printf("server stopped: %v", err)
    }
}

// 输入参数结构体
type helloInput struct {
    UserName string `json:"user_name" jsonschema:"调用方用户名"`
}


func helloWorldTool(ctx context.Context, req *mcp.CallToolRequest, in helloInput) (*mcp.CallToolResult, any, error) {
    text := "Hello World, " + in.UserName
    return &mcp.CallToolResult{
        Content: []mcp.Content{
            &mcp.TextContent{Text: text},
        },
    }, nil, nil
}

使用如下命令启动服务,需要安装Node.Js

npx @modelcontextprotocol/inspector go run ./cmd/mcp-hello-world 
# 替换你自己的代码路径

img

img

代码解释

new 一个server

server := mcp.NewServer(&mcp.Implementation{
		Name:    "hello-world",
		Version: "0.1.0",
	}, nil)

这里使用 mcp.Implementation 创建了一个 server。mcp.Implementation字段如下

字段名 含义
Name Mcp 的唯一标识,当title为空时展示该名称
Title 界面和最终用户看的标题。
Version 该 MCP 实现的版本号
WebsiteURL 与该 Server 相关的网站/文档主页链接,没有则可不写。
Icons 在客户端里展示 Server 时用的图标列表(类型为 []Icon),没有则可不写。

注册一个工具

mcp.AddTool(server, &mcp.Tool{
		Name:        "hello_world", // 工具名称
		Description: "根据 user_name 返回问候语", // 工具描述
	}, helloWorldTool) 

给服务添加一个工具。本框架设计时,工具的元信息和工具的能力即handler是分开的。

mcp.Tool是工具的元信息,包含的字段如下

字段名 含义
Annotations 可选补充信息;展示名优先级:Titleannotations.titleName
Description 工具的描述,用来给人看的,也用来给AI理解工具功能的。
InputSchema 可选,输入的json schema。当该字段未设置时,会使用handler的输入结构体作为默认schema
Name 工具唯一标识
OutputSchema 可选,输出的json schema.当该字段未设置时,会使用handler的输出结构体作为默认schema
Title 面向 UI/最终用户的标题,要求好读;
Icons 工具在界面中的图标列表。

工具

func helloWorldTool(ctx context.Context, req *mcp.CallToolRequest, in helloInput) (*mcp.CallToolResult, any, error) {
    text := "Hello World, " + in.UserName
    return &mcp.CallToolResult{
        Content: []mcp.Content{
            &mcp.TextContent{Text: text},
        },
    }, nil, nil
}

就是一个正常的函数,函数的签名支持泛型

func(_ context.Context, request *CallToolRequest, input In) (result *CallToolResult, output Out, _ error)

有必要介绍一下这个函数的入参和出参设计。按照常规理解,入参应该是request,返回值应该是result。但是,还多入参inputoutput。实际上,inputoutput是框架为了方便使用者,从request和result中单独提取出来的。框架会自动将请求json反序列化到input结构体中,响应时将output序列化到resutl的字段中。当output为nil时会则不会执行序列化到result。下边详细说一下每个参数的内容

  1. In 提供默认 input schema SDK 会根据 In 的类型推断/生成 JSON Schema,作为该工具的 inputSchema

    若在 mcp.Tool.InputSchema 里显式传入 schema,可以覆盖这个默认。

  2. req.Params.Arguments 自动反序列化到 In 你不用自己 json.Unmarshal 参数字典,框架会把客户端传来的 arguments 填进 input

  3. 按 input schema 自动校验 参数不合法时,在进你的 handler 之前就会被拒掉(协议层表现为工具调用失败/错误结果,而不是让你自己到处写校验)。

  4. Out 不是 any 时提供默认 output schema 同样可由类型推断出 output schema;也可在 ``mcp.Tool里覆盖。若Out就是any`,则不会用这种「强类型输出 schema」的默认行为。

  5. Out 会写入 result.StructuredOutput 成功路径下,结构化输出会放到结果里对应字段。

  6. 返回的 error 视为「工具执行错误」而非「协议错误」

    例如 handler 里写:

    return nil, nil, fmt.Errorf("数据库连接失败")

    SDK 会调用 SetError,把错误文本放进 Content,并设 IsError: true

    {
      "content": [{ "type": "text", "text": "数据库连接失败" }],
      "isError": true
    }
    • JSON-RPC 层仍是 200 / 正常 result,不是 protocol error
    • 错误信息在 CallToolResult 里,模型/client 能看到并可能自我修正
    • 符合 MCP 规范:业务失败应放在 tool result 里,而不是协议层报错
    客户端 CallTool("hello_world", { user_name: "Alice" })
            │
            ▼
      [SDK: 校验 + unmarshal → helloInput]
            │ 失败 → JSON-RPC protocol error
            ▼
      helloWorldTool(ctx, req, in)   ← 你选中的函数
            │
            ├─ return result, nil, nil          → 成功,Content = "Hello World, Alice"
            ├─ return nil, nil, fmt.Errorf(...) → 工具失败,isError=true,Content=错误文本
            └─ return nil, nil, jsonrpc.Error   → 协议失败,CallTool 的 err != nil
    

多数场景可以完全忽略 request 和返回的 CallToolResult,只关心 inputoutput / error

甚至可以 result nil:只要你只需要返回 outputerror,SDK 会按上面规则自动补全有效结果。

只有在需要读原始请求元数据、自定义 content、或精细控制结果字段时,才需要碰 request / 自己构造 CallToolResult

启动server

if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
	log.Printf("server stopped: %v", err)
}

比较简单,就是server.Run()一下。

需要了解的是第二个参数,这里支持传入的一共有9种结构体对象,对于日常开发,掌握 Transport.md 介绍的两种也就足够了。