Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building MCP (Model Context Protocol) with Golang

Avatar for Bo-Yi Wu Bo-Yi Wu
July 02, 2025
4

Building MCP (Model Context Protocol) with Golang

# Building MCP (Model Context Protocol) with Golang for Seamless Integration of LLM Applications with External Data Sources and Tools

## Time

2025/07/03 Cloud Summit 09:30 - 11:00

## Topic Overview

Model Context Protocol (MCP) is a protocol that allows chat interfaces to integrate with various tools and systems, enabling seamless command execution and management of repositories, users, and various resources. MCP enables you to expose data and functionality to Large Language Model (LLM) applications in a secure, standardized manner. You can think of MCP as a web API specifically designed for LLM interactions.

MCP servers can:

- Expose data through Resources (similar to GET endpoints; used to load information into LLM context)
- Provide functionality through Tools (similar to POST endpoints; used to execute code or generate other side effects)
- Define interaction patterns through Prompts (LLM interaction templates)

## Course Objectives

- Gain deep understanding of Model Context Protocol (MCP)
- Learn to configure and operate resource, tool, and prompt endpoints in LLMs
- Enhance cloud and automation development capabilities through practical experience
- Design secure configurations to protect data and functionality

## Course Outline

1. MCP Concept Introduction: Understanding MCP and its purpose in LLMs, server configuration and basic operations
2. Resources and Data Exposure: Implementing resource endpoints to load data into LLM context
3. Tools and Functionality Provision: Designing POST endpoints to trigger operations
4. Prompts: Designing and applying reusable prompt templates to guide LLM tasks
5. MCP Security Design: Configuring security measures to protect data and functionality
6. Practical Application Cases: Comprehensive implementation of MCP systems using resources, tools, and prompts

## More Information

https://cloudsummit.ithome.com.tw/2025/lab-page/3721

Avatar for Bo-Yi Wu

Bo-Yi Wu

July 02, 2025
Tweet

Transcript

  1. MCP in Golang (Model Context Protocol) Bo-Yi Wu @ Mediatek

    https://blog.wu-boy.com/ 2025/07/03 09:30 - 11:00
  2. Preparation before workshop • GitHub Account • GitHub Copilot (or

    Cline ….) • VSCode Editor • Install Golang https://go.dev/dl/
  3. What is MCP? (Model Context Protocol) MCP is an open

    source protocol that standardizes how applications provide context to LLMs.
  4. // MCPServer struct encapsulates the MCP server instance. type MCPServer

    struct { server *server.MCPServer // The internal MCPServer instance } // NewMCPServer creates and initializes an MCPServer instance. func NewMCPServer() *MCPServer { mcpServer := server.NewMCPServer( "mcp-with-gin-example", // Server name "1.0.0", // Version server.WithToolCapabilities(true), // Enable tool capabilities server.WithLogging(), // Enable logging server.WithRecovery(), // Enable error recovery ) // Register Tool operation.RegisterCommonTool(mcpServer) return &MCPServer{ server: mcpServer, } } // ServeHTTP produces a streamable HTTP server // wrapping the MCPServer as an HTTP handler. func (s *MCPServer) ServeHTTP() *server.StreamableHTTPServer { return server.NewStreamableHTTPServer(s.server, server.WithHeartbeatInterval(30*time.Second), ) } .$14FSWFS$PEF#BTF
  5. var EchoMessageTool = mcp.NewTool("echo_message", mcp.WithDescription("Returns the input message prefixed with

    'Echo: '"), mcp.WithString("message", // Tool argument name mcp.Description("The message to echo back in the response."), // Argument description mcp.Required(), // Argument is required ), ) func HandleEchoMessageTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Retrieve the "message" argument from arguments and check its type message, ok := req.GetArguments()["message"].(string) if !ok { return nil, fmt.Errorf("invalid message argument") // Argument type error } // Return the formatted message return mcp.NewToolResultText(fmt.Sprintf("Echo: %s", message)), nil } .$15PPM$PEF#BTF
  6. var AddNumbersTool = mcp.NewTool("add_numbers", mcp.WithDescription("Calculates the sum of two numbers."),

    mcp.WithNumber("num1", mcp.Description("The first number to add"), mcp.Required(), ), mcp.WithNumber("num2", mcp.Description("The second number to add"), mcp.Required(), ), ) func HandleAddNumbersTool( ctx context.Context, req mcp.CallToolRequest, ) (*mcp.CallToolResult, error) { // Retrieve num1 and num2 arguments and check their types num1Val, ok1 := req.GetArguments()["num1"].(float64) num2Val, ok2 := req.GetArguments()["num2"].(float64) if !ok1 || !ok2 { return nil, fmt.Errorf("invalid num1 or num2 argument") } sum := num1Val + num2Val return mcp.NewToolResultText(fmt.Sprintf("Sum: %.0f", sum)), nil } .$15PPM$PEF#BTF
  7. // ServeHTTP returns a streamable HTTP server that injects the

    auth token // from HTTP requests into the context. func (s *MCPServer) ServeHTTP() *server.StreamableHTTPServer { return server.NewStreamableHTTPServer(s.server, server.WithHeartbeatInterval(30*time.Second), server.WithHTTPContextFunc(func( ctx context.Context, r *http.Request, ) context.Context { ctx = core.AuthFromRequest(ctx, r) return core.WithRequestID(ctx) }), ) } (FU"VUIPSJ[BUJPO5PLFOGSPNDPOUFYU
  8. type AuthKey struct{} type RequestIDKey struct{} // WithRequestID returns a

    new context with a generated request ID set. func WithRequestID(ctx context.Context) context.Context { reqID := uuid.New().String() return context.WithValue(ctx, RequestIDKey{}, reqID) } // withAuthKey returns a new context with the provided auth token set. func withAuthKey(ctx context.Context, auth string) context.Context { return context.WithValue(ctx, AuthKey{}, auth) } // authFromRequest extracts the Authorization header from the HTTP request // and stores it in the context. Used for HTTP transport. func AuthFromRequest(ctx context.Context, r *http.Request) context.Context { return withAuthKey(ctx, r.Header.Get("Authorization")) } (FU"VUIPSJ[BUJPO5PLFOGSPNDPOUFYU
  9. // HandleShowAuthTokenTool is an MCP tool handler // that returns

    the current auth token from context as a string. func HandleShowAuthTokenTool( ctx context.Context, _ mcp.CallToolRequest, ) (*mcp.CallToolResult, error) { token, err := core.TokenFromContext(ctx) if err != nil { return nil, fmt.Errorf("missing token: %v", err) } masked := token if len(token) > 8 { masked = token[:6] + "****" + token[len(token)-2:] } else if len(token) > 0 { masked = "****" } return mcp.NewToolResultText(masked), nil } (FU5PLFOGSPNDPOUFYU
  10. func MCPToolHandlerMiddleware() server.ToolHandlerMiddleware { return func(next server.ToolHandlerFunc) server.ToolHandlerFunc { return

    func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { start := time.Now() // Record tool name and all parameters for observability AddRequestAttributes( ctx, attribute.String("mcp.tool", req.Params.Name), attribute.String("mcp.params", fmt.Sprintf("%+v", req.Params)), ) res, err := next(ctx, req) durationMs := float64(time.Since(start).Microseconds()) / 1000.0 // Record execution status and duration for observability status, errMsg := extractStatusAndError(res, err) attrs := []attribute.KeyValue{ attribute.String("mcp.status", status), attribute.Float64("mcp.duration_ms", durationMs), } if errMsg != "" { attrs = append(attrs, attribute.String("mcp.error", errMsg)) } // Add status, duration, and error attributes to the trace or log AddRequestAttributes(ctx, attrs...) return res, err } } }
  11. func composeLogAttrs(span trace.Span, attrs ...attribute.KeyValue) []slog.Attr { logAttrs := make([]slog.Attr,

    0, len(attrs)+4) for _, attr := range attrs { logAttrs = append(logAttrs, slog.Any(string(attr.Key), attr.Value.AsInterface())) } logAttrs = append(logAttrs, slog.Bool("observability.fallback", true)) if span != nil { sc := span.SpanContext() if sc.HasTraceID() { logAttrs = append(logAttrs, slog.String("trace_id", sc.TraceID().String())) } if sc.HasSpanID() { logAttrs = append(logAttrs, slog.String("span_id", sc.SpanID().String())) } } else { logAttrs = append(logAttrs, slog.String("trace_id", "none")) logAttrs = append(logAttrs, slog.String("span_id", "none")) } return logAttrs }
  12. Standards Compliance in OAuth • OAuth 2.1 IETF DRAFT (draft-ietf-oauth-v2-1-12)

    • OAuth 2.0 Authorization Server Metadata (RFC8414) • OAuth 2.0 Dynamic Client Registration Protocol (RFC7591) • OAuth 2.0 Protected Resource Metadata (RFC9728)
  13. Authorization Flow (Roles) • MCP Server (OAuth 2.1 Resource Server)

    • MCP Client (OAuth 2.1 Client) • Authorization Server
  14. MCP Server • Serve protected resource metadata (RFC9728) • Must

    include the authorization_servers fi eld • Must return a 401 Unauthorized and include a WWW-Authenticate header
  15. MCP Client • Parse WWW-Authenticate headers on HTTP 401 Unauthorized

    response. • Process the protected resource metadata, including available authorization_servers. • Initiate the correct OAuth 2.0 fl ow to obtain tokens form authorization server.
  16. Authorization Server • Support OAuth 2.0 token endpoint(s) as indicated

    in the resource metadata • Issue tokens as de fi ned in the OAuth 2.0 speci fi cation • SHOULD be discoverable via the MCP server’s metadata document.
  17. Dynamic Client Registration • MCP Clients and authorization servers SHOULD

    support the OAuth 2.0 Dynamic Client Registration Protocol (RFC7591) • MCP Clients to automatically obtain OAuth Client IDs (an Secrets) from authorization.
  18. 

  19. // Middleware to check Authorization header authMiddleware := func(c *gin.Context)

    { if c.GetHeader("Authorization") == "" { c.AbortWithStatus(http.StatusUnauthorized) return } c.Next() } router.POST("/mcp", authMiddleware, gin.WrapH(mcpServer.ServeHTTP())) router.GET("/mcp", authMiddleware, gin.WrapH(mcpServer.ServeHTTP())) router.DELETE("/mcp", authMiddleware, gin.WrapH(mcpServer.ServeHTTP())) "VUINJEEMFXBSFGPS6OBVUIPSJ[FEXJUIUPLFO $IFDLBVUIPSJ[FEJO1045 (&5BOE%FMFUFSPVUF
  20. 

  21. // Create OAuth configuration oauthConfig := client.OAuthConfig{ // Client ID

    can be empty if using dynamic registration ClientID: os.Getenv("MCP_CLIENT_ID"), ClientSecret: os.Getenv("MCP_CLIENT_SECRET"), RedirectURI: redirectURI, Scopes: []string{"mcp.read", "mcp.write"}, TokenStore: tokenStore, PKCEEnabled: true, // Enable PKCE for public clients } // Try to initialize the client result, err := c.Initialize(context.Background(), mcp.InitializeRequest{ Params: mcp.InitializeParams{ ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION, ClientInfo: mcp.Implementation{ Name: "mcp-oauth-client-example", Version: "1.0.0", }, }, }) $SFBUF0"VUI$PO fi HGPS.$1$MJFOU *OJUJBM.$1$POOFDUJPOGPS0"VUI$MJFOU
  22. router.GET("/.well-known/oauth-protected-resource", corsMiddleware(), func(c *gin.Context) { metadata := &transport.OAuthProtectedResource{ AuthorizationServers: []string{"http://localhost"

    + addr}, Resource: "Example OAuth Protected Resource", ResourceName: "Example OAuth Protected Resource", } c.JSON(http.StatusOK, metadata) }) IUUQTEBUBUSBDLFSJFUGPSHEPDIUNMSGD
  23. 

  24. router.GET("/.well-known/oauth-authorization-server", corsMiddleware(), func(c *gin.Context) { metadata := transport.AuthServerMetadata{ Issuer: "http://localhost"

    + addr, AuthorizationEndpoint: "http://localhost" + addr + "/authorize", TokenEndpoint: "http://localhost" + addr + "/token", RegistrationEndpoint: "http://localhost" + addr + "/register", ScopesSupported: []string{"openid", "profile", "email"}, ResponseTypesSupported: []string{"code"}, GrantTypesSupported: []string{"authorization_code", "refresh_token"}, TokenEndpointAuthMethodsSupported: []string{"none", "client_secret_basic", "client_secret_post"}, CodeChallengeMethodsSupported: []string{"S256"}, // for inspector } c.JSON(http.StatusOK, metadata) }) IUUQTEBUBUSBDLFSJFUGPSHEPDIUNMSGD "VUIPSJ[BUJPO4FSWFS.FUBEBUB
  25. IUUQTMPHJONJDSPTPGUPOMJOFDPNDPNNPOWXFMMLOPXOPQFOJEDPO fi HVSBUJPO { "token_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token", "token_endpoint_auth_methods_supported": [ "client_secret_post", "private_key_jwt",

    "client_secret_basic" ], "jwks_uri": "https://login.microsoftonline.com/common/discovery/v2.0/keys", "subject_types_supported": [ "pairwise" ], "id_token_signing_alg_values_supported": [ "RS256" ], "response_types_supported": [ "code", "id_token token" ], "scopes_supported": [ "openid", "profile", "email" ], "issuer": "https://login.microsoftonline.com/{tenantid}/v2.0", "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", "authorization_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", "claims_supported": [ "sub", "iss", "at_hash", "c_hash", "email" ] }
  26. // Add /register endpoint: echoes back the JSON body router.POST("/register",

    corsMiddleware("Authorization", "Content-Type"), func(c *gin.Context) { var body map[string]interface{} if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } body["client_id"] = clientID body["client_secret"] = clientSecret c.JSON(http.StatusOK, body) }) IUUQTEBUBUSBDLFSJFUGPSHEPDIUNMSGD %ZOBNJD$MJFOU3FHJTUSBUJPO1SPUPDPM 1MFBTFJNQMFNFOUUIFSFHJTUSBUJPO fl PX
  27. 

  28. OAuth PKCE Flow • Generate a code veri fi er

    for PKCE (Proof Key for Code Exchange) • Generates a code challenge from a code veri fi er (SHA256 + Base64URL encoding) • Generate anti-CSRF state (32 chars)
  29. 

  30. // Try to initialize again with the token result, err

    = c.Initialize(context.Background(), mcp.InitializeRequest{ Params: mcp.InitializeParams{ ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION, ClientInfo: mcp.Implementation{ Name: "mcp-go-oauth-example", Version: "0.1.0", }, }, }) if result.Capabilities.Tools != nil { tools, err := c.ListTools(context.Background(), mcp.ListToolsRequest{ PaginatedRequest: mcp.PaginatedRequest{ Params: mcp.PaginatedParams{ Cursor: "", }, }, }) for _, tool := range tools.Tools { slog.Info("Available Tool", "name", tool.Name) } } *OJUJBMJ[F.$1$MJFOU"HBJO -JTUBWBJMBCMF.$1UPPMMJTU
  31. { "servers": { "default-stdio-server": { "type": "stdio", "command": "mcp-server", "args":

    ["-t", "stdio"] }, "default-http-server": { "type": "http", "url": "http://localhost:8080/mcp", "headers": { "Authorization": "Bearer 1234567890" } }, "default-oauth-server": { "type": "http", "url": "http://localhost:9093/mcp" } } }