Skip to content

Connect: ADK Go

Recipe · through our MCP server · their docs checked 2026-10-30

ADK Go's mcptoolset turns an MCP server into a tool.Toolset for an llmagent. It uses the official MCP Go SDK for the connection.

Our side

def public_app(tools: Registry, host: str, agent_token: str, reviewer_token: str) -> Any:
    """REST at /, MCP at /mcp, one bearer check for both: for a host on the internet."""
    from mcp.server.transport_security import TransportSecuritySettings

    from agent_adapter.server import create_app

    tokens = {
        agent_token: Principal("hosted-agent"),  # the token the remote framework sends
        reviewer_token: Principal("reviewer@example.com", scopes=frozenset({APPROVER_SCOPE})),
    }
    security = TransportSecuritySettings(allowed_hosts=[host], allowed_origins=[f"https://{host}"])
    return create_app(
        tools,
        authenticate=bearer_auth(tokens),
        host="0.0.0.0",  # a public bind: the public-host checks apply
        allowed_hosts=[host],  # REST: any other Host header gets 421
        mcp_options={"transport_security": security},  # MCP: the same rule
    )

In development on one machine, local_app() and http://127.0.0.1:8000/mcp are enough.

Their side

Module google.golang.org/adk/v2 (v2.6.0) and github.com/modelcontextprotocol/go-sdk:

import (
    "net/http"
    "os"

    "github.com/modelcontextprotocol/go-sdk/mcp"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/tool/mcptoolset"
)

type bearer struct{ token string }

func (b bearer) RoundTrip(r *http.Request) (*http.Response, error) {
    r = r.Clone(r.Context())
    r.Header.Set("Authorization", "Bearer "+b.token)
    return http.DefaultTransport.RoundTrip(r)
}

shopTools, err := mcptoolset.New(mcptoolset.Config{
    Transport: &mcp.StreamableClientTransport{
        Endpoint:   "https://tools.example.com/mcp",
        HTTPClient: &http.Client{Transport: bearer{os.Getenv("AA_AGENT_TOKEN")}},
    },
    RequireConfirmationProvider: func(name string, input any) bool {
        return name == "send_invoice" // ADK asks a person first: an extra gate
    },
})
// ...
agent, err := llmagent.New(llmagent.Config{ /* Name, Model, Instruction */ Toolsets: []tool.Toolset{shopTools}})
Field Meaning
mcptoolset.Config.Transport how to reach us: a *mcp.StreamableClientTransport
StreamableClientTransport.Endpoint / HTTPClient our /mcp URL / the client that adds the header
Config.Endpoint, Config.Auth v2 shortcut: a URL and an auth.CredentialProvider instead of Transport
RequireConfirmation, RequireConfirmationProvider ADK's own confirmation, for all tools or per tool
llmagent.Config.Toolsets where the toolset goes

Checklist

Check Why
ADK's confirmation is extra it adds a gate; our executor still decides confirm tools
filter confirm tools out (tool.FilterToolset), or serve hosted ADK does not send our resume keys back
the header goes only to our host a custom RoundTripper on its own http.Client, never on http.DefaultClient
everything on the public-host checklist

Sources