Go SDK로 MCP 클라이언트 기능 만들기

Go SDK로 MCP 클라이언트 기능 만들기

MCP 클라이언트가 서버에 제공할 수 있는 기능들 — 루트, 샘플링, 일렉시테이션 — 을 Go SDK 기준으로 살펴볼게요. 각 기능은 클라이언트 옵션에 핸들러를 설정하면 초기화 핸드셰이크에서 해당 역량이 자동으로 광고됩니다.

출처: MCP Client - MCP Go SDK

루트 (Roots)

참고 — 루트 기능은 프로토콜 버전 2026-07-28부터 deprecated예요(SEP-2577). 폐기 기간(최소 12개월) 동안 완전히 동작하며, SDK는 호환성을 위해 계속 지원해요. 새 코드는 경로를 도구 파라미터, 리소스 URI, 또는 구성으로 전달하세요.

MCP는 클라이언트가 파일시스템 "roots" 집합을 지정할 수 있게 해요. SDK는 이렇게 지원합니다:

클라이언트 쪽: SDK 클라이언트는 항상 roots.listChanged 역량을 가져요. 클라이언트에 루트를 추가하려면 Client.AddRootsClient.RemoveRoots를 사용해요. 이미 연결된 서버가 있으면 AddRoot/RemoveRoots 호출 시 각 연결 서버에 notifications/roots/list_changed 알림이 전송돼요.

서버 쪽: 서버에서 루트를 조회하려면 ServerSession.ListRoots를 사용하고, 루트 변경 통지를 받으려면 ServerOptions.RootsListChangedHandler를 설정해요. 프로토콜 버전 2026-07-28 이상에서는 ListRoots 요청이 멀티 라운드 트립 요청 패턴으로 전달돼요.

func Example_roots() {
    ctx := context.Background()

    // Create a client with two roots.
    c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
    c.AddRoots(&mcp.Root{URI: "file://a"}, &mcp.Root{URI: "file://b"})

    // Create a server with a tool that requests roots via the multi round-trip
    // pattern (SEP-2322): server-to-client requests are no longer sent as
    // standalone JSON-RPC calls on protocol version >= 2026-07-28.
    s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
    mcp.AddTool(s, &mcp.Tool{Name: "roots"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
        if len(req.Params.InputResponses) == 0 {
            return &mcp.CallToolResult{
                InputRequests: mcp.InputRequestMap{"roots": &mcp.ListRootsParams{}},
            }, nil, nil
        }
        rootList := req.Params.InputResponses["roots"].(*mcp.ListRootsResult)
        var roots []string
        for _, root := range rootList.Roots {
            roots = append(roots, root.URI)
        }
        fmt.Println(roots)
        return &mcp.CallToolResult{}, nil, nil
    })

    // Connect the server and client...
    t1, t2 := mcp.NewInMemoryTransports()
    serverSession, err := s.Connect(ctx, t1, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer serverSession.Close()

    clientSession, err := c.Connect(ctx, t2, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer clientSession.Close()

    // ...and call the tool. The client's multi round-trip driver fulfils the
    // embedded roots/list request and retries the call.
    if _, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "roots"}); err != nil {
        log.Fatal(err)
    }
    // Output: [file://a file://b]
}

루트 목록 변경

Client.AddRootsClient.RemoveRoots는 연결된 모든 서버에 목록이 변경됐음을 알려요. 서버는 ServerOptions.RootsListChangedHandler로 이를 관찰하며, 서버 쪽 list-changed 알림처럼 '뭔가 바뀌었다'는 사실만 알려주므로 실제 목록은 ServerSession.ListRoots로 다시 읽어야 해요.

샘플링 (Sampling)

참고 — 샘플링 기능은 프로토콜 버전 2026-07-28부터 deprecated예요(SEP-2577). 폐기 기간 동안 완전히 동작하며, SDK는 호환성을 위해 지원해요. LLM 완성이 필요한 서버는 LLM 프로바이더 API를 직접 호출하세요.

샘플링은 서버가 클라이언트의 AI 능력을 활용하는 방법이에요. SDK에서는 이렇게 구현됩니다:

클라이언트 쪽: 클라이언트에 sampling 역량을 추가하려면 ClientOptions.CreateMessageHandler를 설정해요. 이 함수는 서버가 샘플링을 요청할 때마다 호출됩니다.

서버 쪽: 서버에서 샘플링을 사용하려면 ServerSession.CreateMessage를 호출해요.

프로토콜 버전 2026-07-28 이상에서는 샘플링 요청이 멀티 라운드 트립 요청 패턴으로 전달됩니다.

func Example_sampling() {
    ctx := context.Background()

    // Create a client with a sampling handler.
    c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
        CreateMessageHandler: func(_ context.Context, req *mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
            return &mcp.CreateMessageResult{
                Content: &mcp.TextContent{
                    Text: "would have created a message",
                },
            }, nil
        },
    })

    // Connect the server and client...
    ct, st := mcp.NewInMemoryTransports()
    // Create a server with a tool that requests sampling via the multi
    // round-trip pattern (SEP-2322): server-to-client requests are no longer
    // sent as standalone JSON-RPC calls on protocol version >= 2026-07-28.
    s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
    mcp.AddTool(s, &mcp.Tool{Name: "sample"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
        if len(req.Params.InputResponses) == 0 {
            return &mcp.CallToolResult{
                InputRequests: mcp.InputRequestMap{"msg": &mcp.CreateMessageParams{}},
            }, nil, nil
        }
        msg := req.Params.InputResponses["msg"].(*mcp.CreateMessageWithToolsResult)
        return &mcp.CallToolResult{Content: msg.Content}, nil, nil
    })
    session, err := s.Connect(ctx, st, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    clientSession, err := c.Connect(ctx, ct, nil)
    if err != nil {
        log.Fatal(err)
    }

    res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{Name: "sample"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(res.Content[0].(*mcp.TextContent).Text)
    // Output: would have created a message
}

일렉시테이션 (Elicitation)

일렉시테이션은 서버가 사용자 입력을 요청할 수 있게 해요. SDK에서는 이렇게 구현됩니다:

클라이언트 쪽: 클라이언트에 elicitation 역량을 추가하려면 ClientOptions.ElicitationHandler를 설정해요. 일렉시테이션 핸들러는 요청된 스키마와 일치하는 결과를 반환해야 하며, 그렇지 않으면 일렉시테이션이 에러를 반환해요. URL 모드 일렉시테이션을 지원한다면 그 역량을 명시적으로 선언해야 해요.

서버 쪽: 서버에서 일렉시테이션을 사용하려면 ServerSession.Elicit를 호출해요.

프로토콜 버전 2026-07-28 이상에서는 일렉시테이션 요청이 멀티 라운드 트립 요청 패턴으로 전달됩니다.

스키마 기본값과 Enum

ElicitParams.RequestedSchema는 클라이언트가 폼으로 렌더링하는 원시 필드의 평면 스키마예요. 두 필드 키워드가 그 폼을 구성합니다.

Default(SEP-1034)는 필드를 미리 채워요. 사용자가 값을 주지 않고 수락하면, 결과가 양쪽 호출자에게 도달하기 전에 SDK가 스키마에서 필드를 채워요 — 클라이언트는 일렉시테이션 핸들러가 반환한 뒤에, ServerSession.Elicit는 수신 시 다시 채워요. 기본값이 설정된 필드를 Required로 표시하면 기본값이 무력화돼요: 수락된 콘텐츠는 기본값 적용 전에 스키마로 검증되므로, 필드를 생략한 답변은 기본값으로 채워지는 대신 거부돼요.

Enum(SEP-1330)은 필드를 고정된 값 집합으로 제한하고, 클라이언트는 이를 선택지로 렌더링해요. Enum은 "string" 필드에서만 지원되고, 다른 타입에 선언하면 거부돼요. 선택지에 라벨을 붙이려면 Schema.Extra를 통해 레거시 enumNames 키워드를 enum 값당 정확히 하나의 이름으로 설정해요 — 길이가 맞지 않으면 거부됩니다.

멀티 라운드 트립 요청 (MRTR)

프로토콜 버전 2026-07-28 이상에서는 샘플링, 일렉시테이션, 루트 같은 서버→클라이언트 요청이 더 이상 새 JSON-RPC 요청으로 발행되지 않고, tools/call, prompts/get, resources/read의 진행 중 응답 안에 실려 전달돼요. 클라이언트는 생성된 응답들로 원래 요청을 다시 시도해야 해요.

SDK는 모든 클라이언트에 기본으로 clientMultiRoundTripMiddleware를 설치해요. 이 미들웨어는:

  1. tools/call/prompts/get/resources/read 응답을 검사한다.
  2. 결과의 NeedsInput()이 true면 InputRequests 맵을 동시에 펼쳐 각각(elicit, createMessage/createMessageWithTools, listRoots)에 대해 설정된 핸들러를 호출한다.
  3. 서버가 제공한 불투명 RequestState를 그대로 다시 실어 보낸다.
  4. 결과가 더 이상 input을 필요로 하지 않을 때까지 응답들로 원래 요청을 재시도한다.

미들웨어는 기본으로 켜져 있어요. 끄려면 ClientOptions.MultiRoundTrip.Disabled = true로 설정하면, 클라이언트는 input-required 결과를 직접 호출자에게 표면화해요(반환된 CallToolResult/GetPromptResult/ReadResourceResultNeedsInput() == true를 보고하고 서버의 InputRequests와 불투명 RequestState를 노출). 코드가 각 요청을 처리하고 InputResponses를 설정·RequestState를 다시 실어 원래 호출을 재발급해야 해요.

레거시(<= 2025-11-25) 서버에 대해서는 SDK가 레거시 서버-주도 채널로 투명하게 서버 요청을 보내고, MRTR 기계장치는 그 방향에서 no-op이에요. 레거시 클라이언트가 MRTR 스타일 서버와 통신할 때는 서버 SDK가 역방향 호환 shim을 적용해요.

역량 (Capabilities)

클라이언트 역량은 초기화 핸드셰이크 중에 서버에게 광고돼요. 기본적으로 SDK는 logging 역량을 광고하고, 서버 기능이 추가되거나 ServerOptions 구조체에 핸들러가 설정되면(예: CompletionHandler 설정 → completions 역량) 추가 역량이 자동으로 붙거나 명시적으로 구성될 수 있어요.

역량 추론

ClientOptions에 핸들러가 설정되면(예: CreateMessageHandlerElicitationHandler) 해당 역량이 아직 없을 때 자동으로 기본 구성과 함께 추가돼요. 일렉시테이션의 경우 핸들러가 설정됐는데 Capabilities.Elicitation이 지정되지 않으면 클라이언트는 기본적으로 폼 일렉시테이션으로 동작해요. URL 일렉시테이션이나 두 모드를 모두 쓰려면 Capabilities.Elicitation을 명시적으로 구성하세요.

명시적 역량

ClientOptions.Capabilities를 설정하면 역량을 명시적으로 선언하거나 기본 추론 값을 덮어쓸 수 있어요. 이 값은 핸들러에 기반해 역량이 추가되기 전의 초기 클라이언트 역량을 정해요. 역량이 이미 Capabilities에 있으면 핸들러를 추가해도 구성이 바뀌지 않습니다.

이를 통해:

  • 기본 역량 비활성화: 빈 &ClientCapabilities{}를 넘기면 루트를 포함한 모든 기본값을 끌 수 있어요.
  • listChanged 알림 비활성화: 특정 역량에 ListChanged: false를 설정해 루트 추가/제거 시 list-changed 알림을 막을 수 있어요.
  • 일렉시테이션 모드 구성: 클라이언트가 지원하는 일렉시테이션 모드(form, URL)를 지정할 수 있어요.
// Configure elicitation modes and disable roots.
client := mcp.NewClient(impl, &mcp.ClientOptions{
    Capabilities: &mcp.ClientCapabilities{
        Elicitation: &mcp.ElicitationCapabilities{
            Form: &mcp.FormElicitationCapabilities{},
            URL:  &mcp.URLElicitationCapabilities{},
        },
    },
    ElicitationHandler: handler,
})

확장 (Extensions)

SEP-2133ClientCapabilitiesServerCapabilitiesextensions 맵을 추가해 코어 프로토콜 밖의 선택적 역량을 와이어에 선언할 수 있게 해요. 키는 "{vendor-prefix}/{extension-name}" 형태로 네임스페이스되고, 값은 확장별 설정 객체예요.

더 알아보기 (Learn more)