|
#!/bin/bash |
|
set -euo pipefail |
|
|
|
# |
|
# Usage: |
|
# ./mcp-auth.sh <mcp-server-url> [client-id] |
|
# |
|
# Examples: |
|
# ./mcp-auth.sh http://localhost:3001/mcp | tee creds.json |
|
# ./mcp-auth.sh https://example-server.modelcontextprotocol.io/mcp | tee creds.json |
|
# |
|
# Implements MCP OAuth 2.1 flow per specification (2025-06-18): |
|
# - RFC 8414: OAuth Authorization Server Metadata |
|
# - RFC 8707: Resource Indicators |
|
# - PKCE required for all clients |
|
# |
|
|
|
MCP_SERVER="$1" |
|
CLIENT_ID="${2:-}" # Will be set via registration if empty |
|
|
|
# Extract base URL (protocol + host + optional port) |
|
if [[ $MCP_SERVER =~ ^(https?://[^/]+) ]]; then |
|
BASE_URL="${BASH_REMATCH[1]}" |
|
else |
|
echo "Error: Invalid MCP server URL: $MCP_SERVER" >&2 |
|
exit 1 |
|
fi |
|
|
|
echo "==> Step 1: Fetching OAuth Protected Resource metadata" >&2 |
|
RESOURCE_METADATA_URL="${BASE_URL}/.well-known/oauth-protected-resource" |
|
echo " From: $RESOURCE_METADATA_URL" >&2 |
|
|
|
if ! RESOURCE_METADATA="$(curl --silent --fail "$RESOURCE_METADATA_URL")"; then |
|
echo "Error: Failed to fetch OAuth Protected Resource metadata" >&2 |
|
# echo "The server may not support OAuth, or the well-known endpoint is not available." >&2 |
|
# exit 1 |
|
RESOURCE_URI="${MCP_SERVER}" |
|
AUTH_SERVER_URL="${BASE_URL}/" |
|
else |
|
# Extract resource URI (RFC 8707 requirement) |
|
RESOURCE_URI="$(echo "$RESOURCE_METADATA" | jq -r '.resource')" |
|
if [[ "$RESOURCE_URI" == "null" ]]; then |
|
echo "Error: No resource URI found in protected resource metadata" >&2 |
|
exit 1 |
|
fi |
|
# Extract authorization server URL |
|
AUTH_SERVER_URL="$(echo "$RESOURCE_METADATA" | jq -r '.authorization_servers[0]')" |
|
if [[ "$AUTH_SERVER_URL" == "null" ]]; then |
|
echo "Error: No authorization server found in protected resource metadata" >&2 |
|
exit 1 |
|
fi |
|
fi |
|
echo " Resource URI: $RESOURCE_URI" >&2 |
|
|
|
echo " Authorization server URL: $AUTH_SERVER_URL" >&2 |
|
|
|
echo "" >&2 |
|
echo "==> Step 2: Fetching OAuth Authorization Server metadata" >&2 |
|
AUTH_SERVER_METADATA_URL="${AUTH_SERVER_URL}.well-known/oauth-authorization-server" |
|
echo " From: $AUTH_SERVER_METADATA_URL" >&2 |
|
|
|
if ! AUTH_SERVER_METADATA="$(curl --silent --fail "$AUTH_SERVER_METADATA_URL")"; then |
|
echo "Error: Failed to fetch OAuth Authorization Server metadata" >&2 |
|
exit 1 |
|
fi |
|
|
|
# Extract authorization endpoint (RFC 8414) |
|
AUTH_ENDPOINT="$(echo "$AUTH_SERVER_METADATA" | jq -r '.authorization_endpoint')" |
|
if [[ "$AUTH_ENDPOINT" == "null" ]]; then |
|
echo "Error: No authorization endpoint found in authorization server metadata" >&2 |
|
exit 1 |
|
fi |
|
echo " Authorization endpoint: $AUTH_ENDPOINT" >&2 |
|
|
|
# Extract token endpoint |
|
TOKEN_ENDPOINT="$(echo "$AUTH_SERVER_METADATA" | jq -r '.token_endpoint')" |
|
if [[ "$TOKEN_ENDPOINT" == "null" ]]; then |
|
echo "Error: No token endpoint found in authorization server metadata" >&2 |
|
exit 1 |
|
fi |
|
echo " Token endpoint: $TOKEN_ENDPOINT" >&2 |
|
|
|
# Scopes are optional in authorization server metadata |
|
SCOPES="$(echo "$AUTH_SERVER_METADATA" | jq -r 'if .scopes_supported then .scopes_supported | join(" ") else "" end')" |
|
if [[ -n "$SCOPES" ]]; then |
|
echo " Scopes supported: $SCOPES" >&2 |
|
fi |
|
|
|
echo "" >&2 |
|
echo "==> Step 3: Dynamic Client Registration (RFC 7591)" >&2 |
|
|
|
# Check if registration endpoint is available |
|
REGISTRATION_ENDPOINT="$(echo "$AUTH_SERVER_METADATA" | jq -r '.registration_endpoint')" |
|
|
|
if [[ "$REGISTRATION_ENDPOINT" != "null" && -z "$CLIENT_ID" ]]; then |
|
echo " Registration endpoint: $REGISTRATION_ENDPOINT" >&2 |
|
echo " Registering client dynamically..." >&2 |
|
|
|
# Register a new OAuth client |
|
REGISTRATION_REQUEST='{ |
|
"client_name": "mcp-cli", |
|
"grant_types": ["authorization_code", "refresh_token"], |
|
"response_types": ["code"], |
|
"redirect_uris": ["http://localhost:9876/callback"], |
|
"token_endpoint_auth_method": "none" |
|
}' |
|
|
|
if REGISTRATION_RESPONSE="$(curl --silent --fail -X POST "$REGISTRATION_ENDPOINT" \ |
|
-H "Content-Type: application/json" \ |
|
-d "$REGISTRATION_REQUEST")"; then |
|
|
|
CLIENT_ID="$(echo "$REGISTRATION_RESPONSE" | jq -r '.client_id')" |
|
if [[ "$CLIENT_ID" == "null" || -z "$CLIENT_ID" ]]; then |
|
echo "Error: Failed to extract client_id from registration response" >&2 |
|
echo "Response: $REGISTRATION_RESPONSE" >&2 |
|
exit 1 |
|
fi |
|
echo " ✓ Client registered with ID: $CLIENT_ID" >&2 |
|
else |
|
echo "Error: Failed to register client dynamically" >&2 |
|
echo "You may need to provide a pre-registered client ID as the second argument" >&2 |
|
exit 1 |
|
fi |
|
else |
|
if [[ "$REGISTRATION_ENDPOINT" == "null" ]]; then |
|
echo " No registration endpoint available" >&2 |
|
if [[ -z "$CLIENT_ID" ]]; then |
|
echo "Error: No client ID provided and dynamic registration not available" >&2 |
|
echo "Usage: $0 <mcp-server-url> <client-id>" >&2 |
|
exit 1 |
|
fi |
|
fi |
|
echo " Client ID: $CLIENT_ID" >&2 |
|
fi |
|
|
|
# Ensure we have a client ID at this point |
|
if [[ -z "$CLIENT_ID" ]]; then |
|
echo "Error: No client ID available" >&2 |
|
exit 1 |
|
fi |
|
|
|
echo "" >&2 |
|
echo "==> Step 4: Starting OAuth authorization flow" >&2 |
|
echo " Client ID: $CLIENT_ID" >&2 |
|
echo " Resource: $RESOURCE_URI (RFC 8707)" >&2 |
|
echo "" >&2 |
|
|
|
# Build oauth2c command |
|
# NOTE: oauth2c v1.17.2 does not support the --resource flag required by RFC 8707 |
|
# MCP spec (2025-06-18) REQUIRES the resource parameter in authorization requests |
|
# This is a known limitation - see https://github.com/cloudentity/oauth2c/issues |
|
echo "WARNING: oauth2c does not support RFC 8707 resource parameter" >&2 |
|
echo " MCP spec compliance requires --resource '$RESOURCE_URI'" >&2 |
|
echo " Proceeding without resource parameter (may fail with spec-compliant servers)" >&2 |
|
echo "" >&2 |
|
|
|
OAUTH_ARGS=( |
|
"$MCP_SERVER" |
|
--authorization-endpoint "$AUTH_ENDPOINT" |
|
--token-endpoint "$TOKEN_ENDPOINT" |
|
--grant-type authorization_code |
|
--response-types code |
|
--response-mode query |
|
--client-id "$CLIENT_ID" |
|
--auth-method none |
|
--pkce |
|
) |
|
|
|
# Add scopes if present |
|
if [[ -n "$SCOPES" ]]; then |
|
OAUTH_ARGS+=(--scopes "$SCOPES") |
|
fi |
|
|
|
# TODO: Need to add resource parameter manually to authorization URL |
|
# The resource parameter should be: resource=$RESOURCE_URI |
|
# This requires either: |
|
# 1. Patching oauth2c to support --resource flag |
|
# 2. Using a different OAuth client tool |
|
# 3. Manually constructing the authorization URL with the resource parameter |
|
|
|
|
|
oauth2c "${OAUTH_ARGS[@]}" | \ |
|
jq --arg mcp_server "$MCP_SERVER" '. + {mcp_server: $mcp_server}' |