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

What’s MCP && Authorization?

Avatar for Bo-Yi Wu Bo-Yi Wu
October 13, 2025

What’s MCP && Authorization?

This workshop provides a comprehensive guide to building MCP (Model Context Protocol) servers and clients using the Go programming language. You will learn how to leverage MCP to streamline your workflow and enhance your development environment.

This project demonstrates an OAuth 2.1 protected Model Context Protocol (MCP) server written in Go. It supports multiple OAuth providers (GitHub, GitLab, Gitea) and showcases authenticated MCP tool execution with token context propagation.

The implementation includes both an OAuth authorization server (oauth-server/) and an example OAuth client (oauth-client/), demonstrating the complete OAuth 2.1 flow with PKCE support and flexible storage options.

The server provides:

* Multi-Provider OAuth Integration: Supports GitHub, GitLab, and Gitea as OAuth 2.0 providers for user authentication
* Flexible Storage Backends: Choose between in-memory or Redis-backed storage for OAuth data persistence
* MCP Server with Authentication: Requires valid OAuth tokens for all MCP endpoint access
* Context-based Token Propagation: Injects and propagates authentication tokens through Go's context.Context

Two Authenticated MCP Tools:

* make_authenticated_request: Makes authenticated HTTP requests to external APIs using the user's token
* show_auth_token: Displays a masked version of the current authorization token
* OAuth 2.0 Endpoints: Implements required OAuth endpoints with provider integration
* Dynamic Client Registration: Supports automatic client registration for MCP clients

Conference URL: https://hwdc.ithome.com.tw/2025/lab-page/3994

Avatar for Bo-Yi Wu

Bo-Yi Wu

October 13, 2025
Tweet

More Decks by Bo-Yi Wu

Other Decks in Technology

Transcript

  1. Preparation before workshop • GitHub Account • GitHub Copilot •

    VSCode Editor • Claude Code • Install Golang
  2. What is MCP? (Model Context Protocol) MCP is an open-source

    standard for connecting AI applications to external systems.
  3. { "mcpServers": { "default-stdio-server": { "type": "stdio", "command": "mcp-server", "args":

    ["-t", "stdio"] }, "default-http-server": { "type": "http", "url": "http://localhost:8080/mcp", "headers": { "Authorization": "xxxxxx" } } } } .$1$PO fi HVSBUJPO
  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. 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)
  14. Authorization Flow (Roles) • MCP Server (OAuth 2.1 Resource Server)

    • MCP Client (OAuth 2.1 Client) • Authorization Server • Resource Owner # " $ %
  15. 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)
  16. 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)
  17. 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)
  18. 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
  19. 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.
  20. 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.
  21. 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.
  22. 

  23. // 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
  24. 

  25. // Create OAuth configuration oauthConfig := client.OAuthConfig{ 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
  26. 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
  27. 

  28. 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
  29. 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" ] }
  30. router.POST("/register", corsMiddleware("Authorization", "Content-Type"), func(c *gin.Context) { var registration ClientRegistrationMetadata if

    err := c.ShouldBindJSON(&registration); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Create response using RegisterResponse struct response := &ClientRegistrationResponse{ ClientID: clientID, ClientSecret: "", ClientRegistrationMetadata: registration, ClientIDIssuedAt: time.Now(), ClientSecretExpiresAt: time.Now().Add(1 * time.Minute), } c.JSON(http.StatusCreated, response) }) IUUQTEBUBUSBDLFSJFUGPSHEPDIUNMSGD %ZOBNJD$MJFOU3FHJTUSBUJPO1SPUPDPM 1MFBTFJNQMFNFOUUIFSFHJTUSBUJPO fl PX
  31. 

  32. 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)
  33. 

  34. // 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
  35. { "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" } } }