从源码理解mcp流程

背景

mcp号称ai agent时代的USB接口,本文将从源码角度理解mcp的实现原理.
本文以github.com/modelcontextprotocol/go-sdk的v1.8.0为例
本文以streamable http为例

源码

examples/client/loadtest/main.go中

func main() {
  ...
  构建mcp client
  client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)
  连接mcp服务端
  cs, err := client.Connect(parentCtx, &mcp.StreamableClientTransport{Endpoint: args[0]}, nil)
  ...
  调用工具
  res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: *tool, Arguments: json.RawMessage(*jsonArgs)})
  ...
}

前置相关

mcp/client.go中

构建mcp client
func NewClient(impl *Implementation, options *ClientOptions) *Client {
  ...
  支持的方法
    sendMethods := make(map[string]methodInfo, len(serverMethodInfos))
    maps.Copy(sendMethods, serverMethodInfos)
  ...
    c := &Client{
        impl:                    impl,
        opts:                    opts,
        roots:                   newFeatureSet(func(r *Root) string { return r.URI }),
        sendingMethodHandler_:   defaultSendingMethodHandler,
        receivingMethodHandler_: defaultReceivingMethodHandler[*ClientSession],
        sendMethods:             sendMethods,
    }
  ...
}
### Connect

连接mcp服务端获取tool等信息
func (c *Client) Connect(ctx context.Context, t Transport, opts *ClientSessionOptions) (cs *ClientSession, err error) {
        和服务端建立连接
    cs, err = connect(ctx, t, c, (*clientSessionState)(nil), nil, c.opts.Logger)
    if err != nil {
        return nil, err
    }
  ...
    如果大于2026-07-28这个版本则使用最新server/discover方式
    if protocolVersion >= protocolVersion20260728 {
调用server/discover来发现服务端支持的tool等
            discRes, err := c.discover(discoverCtx, cs)
                return cs, nil

        }
  ...
调用initialize rpc方法

params := &InitializeParams{
        ProtocolVersion: protocolVersion,
        ClientInfo:      c.impl,
        Capabilities:    c.capabilities(protocolVersion),
    }
    req := &InitializeRequest{Session: cs, Params: params}
    res, err := handleSend[*InitializeResult](ctx, methodInitialize, req)
    if err != nil {
        _ = cs.Close()
        return nil, err
    }
    if !slices.Contains(supportedProtocolVersions, res.ProtocolVersion) {
        _ = cs.Close()
        return nil, unsupportedProtocolVersionError{res.ProtocolVersion}
    }
    cs.state.InitializeResult = res
    if hc, ok := cs.mcpConn.(clientConnection); ok {
        hc.sessionUpdated(cs.state)
    }
调用notifications/initialized rpc方法

    req2 := &initializedClientRequest{Session: cs, Params: &InitializedParams{}}
    if err := handleNotify(ctx, notificationInitialized, req2); err != nil {
        _ = cs.Close()
        return nil, err
    }

  ...
}



调用server/discover来发现服务端支持的tool等
func (c *Client) discover(ctx context.Context, cs *ClientSession) (*InitializeResult, error) {
  ...
调用server/discover rpc方法
    params := &DiscoverParams{
        Meta: Meta{
            MetaKeyProtocolVersion:    protocolVersion,
            MetaKeyClientInfo:         c.impl,
            MetaKeyClientCapabilities: caps.toV2(),
        },
    }
    req := &DiscoverRequest{Session: cs, Params: params}
    res, err := handleSend[*DiscoverResult](ctx, methodDiscover, req)
    if err != nil {
        return nil, err
    }
  ...
    var negotiated string
    if slices.Contains(res.SupportedVersions, protocolVersion) {
        negotiated = protocolVersion
    } else {
        negotiated = negotiateMutuallySupportedVersion(res.SupportedVersions)
    }
判断服务端是否支持当前client希望的协议版本,如果协商的版本低于2026-07-28这个版本则也一样抱错
    if negotiated == "" || negotiated < protocolVersion20260728 {
        // If there is no overlap, fall back to initialize so version
        // negotiation can happen via the legacy path.
        return nil, &jsonrpc2.WireError{
            Code:    CodeUnsupportedProtocolVersion,
            Message: "unsupported protocol version",
        }
    }

    var serverInfo *Implementation
    if v, ok := decodeMetaValue[*Implementation](res.GetMeta(), MetaKeyServerInfo); ok {
        serverInfo = v
    }
    return &InitializeResult{
        Capabilities:    res.Capabilities,
        Instructions:    res.Instructions,
        ProtocolVersion: negotiated,
        ServerInfo:      serverInfo,
    }, nil
}


获取方法处理器
func (cs *ClientSession) sendingMethodHandler() MethodHandler {
    cs.client.mu.Lock()
    defer cs.client.mu.Unlock()
    return cs.client.sendingMethodHandler_
}

func defaultSendingMethodHandler(ctx context.Context, method string, req Request) (Result, error) {
获取方法信息
    info, ok := req.GetSession().sendingMethodInfos()[method]
    if !ok {
        // This can be called from user code, with an arbitrary value for method.
        return nil, jsonrpc2.ErrNotHandled
    }
   ...

调用对应rpc方法
        if err := call(ctx, req.GetSession().getConn(), method, params, res); err != nil {
            return nil, err
        }
    
    return res, nil
}

mcp/transport.go中

和服务端建立连接
func connect[H handler, State any](ctx context.Context, t Transport, b binder[H, State], s State, onClose func(), logger *slog.Logger) (H, error) {
  ...
    mcpConn, err := t.Connect(ctx)
    if err != nil {
        return zero, err
    }
  ...
}

mcp/streamable.go中

获取StreamableClientTransport的连接
func (t *StreamableClientTransport) Connect(ctx context.Context) (Connection, error) {
  ...
    conn := &streamableClientConn{
        url:                  t.Endpoint,
        client:               client,
        incoming:             make(chan jsonrpc.Message, 10),
        done:                 make(chan struct{}),
        maxRetries:           maxRetries,
        strict:               t.strict,
        logger:               ensureLogger(t.logger), // must be non-nil for safe logging
        ctx:                  connCtx,
        cancel:               cancel,
        failed:               make(chan struct{}),
        disableStandaloneSSE: t.DisableStandaloneSSE,
        oauthHandler:         t.OAuthHandler,
        maxEventSize:         t.MaxEventSize,
    }
    return conn, nil
  ...
}


以写消息为例
func (c *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) error {
  ...
通过sse发送消息
    doRequest := func() (*http.Request, *http.Response, error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, bytes.NewReader(data))
        if err != nil {
            return nil, nil, err
        }
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Accept", "application/json, text/event-stream")

        if err := c.setMCPHeaders(req, msg); err != nil {

            return nil, nil, fmt.Errorf("%s: %w: %w", requestSummary, jsonrpc2.ErrRejected, err)
        }
        
        setStandardHeaders(ctx, req.Header, msg)
        resp, err := c.client.Do(req)
        if err != nil {

            err = fmt.Errorf("%s: %w: %w", requestSummary, jsonrpc2.ErrRejected, err)
        }
        return req, resp, err
    }

    req, resp, err := doRequest()
    if err != nil {
        return err
    }
  ...  

}

CallTool

mcp/client.go中

func (cs *ClientSession) CallTool(ctx context.Context, params *CallToolParams) (*CallToolResult, error) {
     调用methodCallTool rpc 方法
    return handleSend[*CallToolResult](ctx, methodCallTool, newClientRequest(cs, orZero[Params](params)))
}

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容