go-sdk/examples/auth/client/main.go
Pranav RK 2d43865b97
Enterprise managed authorization (#770)
`auth`, `oauthex`: implement Enterprise Managed Authorization (SEP-990)
This PR implements Enterprise Managed Authorization (SEP-990) for the Go
MCP SDK, enabling MCP Clients and Servers to leverage enterprise
Identity Providers for seamless authorization without requiring users to
authenticate separately to each MCP Server.
 Overview
Enterprise Managed Authorization follows the Identity Assertion
Authorization Grant specification
(draft-ietf-oauth-identity-assertion-authz-grant), implementing a
three-step flow:
1. **Single Sign-On (SSO)**: User authenticates to the MCP Client via
enterprise IdP (Okta, Auth0, Azure AD, etc.)
2. **Token Exchange (RFC 8693)**: Client exchanges ID Token for Identity
Assertion JWT Authorization Grant (ID-JAG) at the IdP
3. **JWT Bearer Grant (RFC 7523)**: Client exchanges ID-JAG for Access
Token at the MCP Server
This enables:
- **For end users**: Single sign-on across MCP Clients and Servers—no
manual connection/authorization per server
- **For enterprise admins**: Centralized visibility and control over
which MCP Servers can be used within the organization
- **For MCP clients**: Automatic token acquisition without user
interaction for each server

Closes:
[#628](https://github.com/modelcontextprotocol/go-sdk/issues/628)

---------

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-03-30 16:29:30 +02:00

134 lines
3.6 KiB
Go

// Copyright 2026 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.
//go:build mcp_go_client_oauth
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"github.com/modelcontextprotocol/go-sdk/auth"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
var (
// URL of the MCP server.
serverURL = flag.String("server_url", "http://localhost:8000/mcp", "URL of the MCP server.")
// Port for the local HTTP server that will receive the authorization code.
callbackPort = flag.Int("callback_port", 3142, "Port for the local HTTP server that will receive the authorization code.")
)
type codeReceiver struct {
authChan chan *auth.AuthorizationResult
errChan chan error
listener net.Listener
server *http.Server
}
func (r *codeReceiver) serveRedirectHandler(listener net.Listener) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
r.authChan <- &auth.AuthorizationResult{
Code: req.URL.Query().Get("code"),
State: req.URL.Query().Get("state"),
}
fmt.Fprint(w, "Authentication successful. You can close this window.")
})
r.server = &http.Server{
Addr: fmt.Sprintf("localhost:%d", *callbackPort),
Handler: mux,
}
if err := r.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
r.errChan <- err
}
}
func (r *codeReceiver) getAuthorizationCode(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
fmt.Printf("Please open the following URL in your browser: %s\n", args.URL)
select {
case authRes := <-r.authChan:
return authRes, nil
case err := <-r.errChan:
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (r *codeReceiver) close() {
if r.server != nil {
r.server.Close()
}
}
func main() {
flag.Parse()
receiver := &codeReceiver{
authChan: make(chan *auth.AuthorizationResult),
errChan: make(chan error),
}
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *callbackPort))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
go receiver.serveRedirectHandler(listener)
defer receiver.close()
authHandler, err := auth.NewAuthorizationCodeHandler(&auth.AuthorizationCodeHandlerConfig{
RedirectURL: fmt.Sprintf("http://localhost:%d", *callbackPort),
AuthorizationCodeFetcher: receiver.getAuthorizationCode,
// Uncomment the client configuration you want to use.
// PreregisteredClient: &oauthex.ClientCredentials{
// ClientID: "",
// ClientSecretAuth: &oauthex.ClientSecretAuth{
// ClientSecret: "",
// },
// },
// },
// DynamicClientRegistrationConfig: &auth.DynamicClientRegistrationConfig{
// Metadata: &oauthex.ClientRegistrationMetadata{
// ClientName: "Dynamically registered MCP client",
// RedirectURIs: []string{fmt.Sprintf("http://localhost:%d", *callbackPort)},
// Scope: "read",
// },
// },
})
if err != nil {
log.Fatalf("failed to create auth handler: %v", err)
}
transport := &mcp.StreamableClientTransport{
Endpoint: *serverURL,
OAuthHandler: authHandler,
}
ctx := context.Background()
client := mcp.NewClient(&mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
}, nil)
session, err := client.Connect(ctx, transport, nil)
if err != nil {
log.Fatalf("client.Connect(): %v", err)
}
defer session.Close()
tools, err := session.ListTools(ctx, nil)
if err != nil {
log.Fatalf("session.ListTools(): %v", err)
}
log.Println("Tools:")
for _, tool := range tools.Tools {
log.Printf("- %q", tool.Name)
}
}