Connecting MCP Clients
After deploying your MCP server to AWS Lambda, you need to connect clients to it. This lesson covers connecting Claude Desktop, Claude.ai, and custom applications to your remote MCP server.
Connection Overview
Remote MCP servers use HTTP transport instead of stdio:
┌─────────────────────────────────────────────────────────────────────────┐
│ MCP CLIENT CONNECTION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ LOCAL SERVER (stdio) REMOTE SERVER (HTTP) │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ MCP Client │ │ MCP Client │ │
│ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ stdin/stdout │ HTTPS │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Local │ │ API Gateway │ │
│ │ Process │ │ + Lambda │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ Config: Config: │
│ { { │
│ "command": "my-server" "url": "https://...", │
│ } "transport": "streamable-http" │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Getting Your Server URL
After deployment, get your MCP endpoint:
cargo pmcp deploy outputs
# Output:
# ┌────────────────────────────────────────────────────────────────────┐
# │ Deployment Outputs │
# ├────────────────────────────────────────────────────────────────────┤
# │ ApiEndpoint: https://abc123.execute-api.us-east-1.amazonaws.com │
# │ McpEndpoint: https://abc123.execute-api.us-east-1.amazonaws.com/mcp│
# │ OAuthUrl: https://auth.abc123.amazoncognito.com │
# │ ClientId: 1234567890abcdef │
# └────────────────────────────────────────────────────────────────────┘
Connecting Claude Desktop
Without Authentication
For internal servers without OAuth (not recommended for production):
Edit ~/.config/claude/claude_desktop_config.json (macOS/Linux) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"my-remote-server": {
"transport": "streamable-http",
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/mcp"
}
}
}
With OAuth Authentication
For production servers with Cognito authentication:
{
"mcpServers": {
"my-remote-server": {
"transport": "streamable-http",
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/mcp",
"oauth": {
"client_id": "1234567890abcdef",
"authorization_url": "https://auth.abc123.amazoncognito.com/oauth2/authorize",
"token_url": "https://auth.abc123.amazoncognito.com/oauth2/token",
"scopes": ["openid", "mcp:read", "mcp:write"]
}
}
}
}
When you start Claude Desktop:
- It detects the OAuth configuration
- Opens your browser to the Cognito login page
- You authenticate (username/password or SSO)
- Browser redirects back with authorization code
- Claude Desktop exchanges code for access token
- All MCP requests include the access token
With API Key Authentication
For simpler authentication using API keys:
{
"mcpServers": {
"my-remote-server": {
"transport": "streamable-http",
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/mcp",
"headers": {
"Authorization": "Bearer your-api-key-here"
}
}
}
}
Security note: Store API keys securely. Consider using environment variables:
{
"mcpServers": {
"my-remote-server": {
"transport": "streamable-http",
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_API_KEY}"
}
}
}
}
Then set the environment variable before starting Claude Desktop:
export MCP_API_KEY="your-api-key-here"
open -a "Claude"
Connecting Claude.ai (Web)
Claude.ai supports connecting to remote MCP servers through the Integrations settings.
Step 1: Register Your Server
In Claude.ai settings, navigate to Integrations → Add MCP Server:
Server Name: My Data Server
Server URL: https://abc123.execute-api.us-east-1.amazonaws.com/mcp
Auth Type: OAuth 2.0
OAuth Settings:
Client ID: 1234567890abcdef
Authorization URL: https://auth.abc123.amazoncognito.com/oauth2/authorize
Token URL: https://auth.abc123.amazoncognito.com/oauth2/token
Scopes: openid mcp:read mcp:write
Step 2: Authorize
Click Connect to initiate the OAuth flow:
- Redirects to your Cognito login page
- Enter credentials or use SSO
- Grant permission to Claude.ai
- Redirected back to Claude.ai with connection established
Step 3: Verify Connection
Start a new conversation and verify the server is connected:
You: What tools do you have available from my data server?
Claude: I have access to the following tools from "My Data Server":
- query_users: Search for users by name or email
- get_user_details: Get detailed information about a specific user
- list_departments: List all departments in the organization
OAuth Flow Details
Understanding the OAuth flow helps debug connection issues:
┌─────────────────────────────────────────────────────────────────────────┐
│ OAUTH 2.0 FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. USER INITIATES CONNECTION │
│ Claude Desktop/Claude.ai detects OAuth config │
│ │
│ 2. AUTHORIZATION REQUEST │
│ Browser opens: │
│ https://auth.abc123.amazoncognito.com/oauth2/authorize │
│ ?client_id=1234567890abcdef │
│ &response_type=code │
│ &redirect_uri=http://localhost:8765/callback │
│ &scope=openid%20mcp:read%20mcp:write │
│ &state=random_state_value │
│ │
│ 3. USER AUTHENTICATES │
│ - Username/password │
│ - Or federated SSO (Google, SAML, etc.) │
│ │
│ 4. AUTHORIZATION CODE RETURNED │
│ Browser redirects to: │
│ http://localhost:8765/callback?code=AUTH_CODE&state=random_state │
│ │
│ 5. TOKEN EXCHANGE │
│ Client POSTs to token endpoint: │
│ POST https://auth.abc123.amazoncognito.com/oauth2/token │
│ grant_type=authorization_code │
│ &code=AUTH_CODE │
│ &client_id=1234567890abcdef │
│ &redirect_uri=http://localhost:8765/callback │
│ │
│ Response: │
│ { │
│ "access_token": "eyJhbGciOi...", │
│ "refresh_token": "eyJjdHki...", │
│ "expires_in": 3600 │
│ } │
│ │
│ 6. MCP REQUESTS WITH TOKEN │
│ POST https://abc123.execute-api.us-east-1.amazonaws.com/mcp │
│ Authorization: Bearer eyJhbGciOi... │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Token Refresh
Access tokens expire (typically after 1 hour). Clients automatically refresh:
┌─────────────────────────────────────────────────────────────────────────┐
│ TOKEN REFRESH FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Access token expires (401 Unauthorized) │
│ │
│ 2. Client uses refresh token: │
│ POST https://auth.abc123.amazoncognito.com/oauth2/token │
│ grant_type=refresh_token │
│ &refresh_token=eyJjdHki... │
│ &client_id=1234567890abcdef │
│ │
│ 3. New tokens returned │
│ │
│ 4. Retry original request with new access token │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Cognito User Management
Creating Users
Create users in the Cognito console or via CLI:
# Create a user
aws cognito-idp admin-create-user \
--user-pool-id us-east-1_ABC123 \
--username alice@company.com \
--user-attributes Name=email,Value=alice@company.com \
--temporary-password "TempPass123!"
# Set permanent password (skip temporary)
aws cognito-idp admin-set-user-password \
--user-pool-id us-east-1_ABC123 \
--username alice@company.com \
--password "SecurePass456!" \
--permanent
Configuring Scopes
Define custom scopes in Cognito for fine-grained access:
# Create resource server with scopes
aws cognito-idp create-resource-server \
--user-pool-id us-east-1_ABC123 \
--identifier "mcp" \
--name "MCP API" \
--scopes ScopeName=read,ScopeDescription="Read access" \
ScopeName=write,ScopeDescription="Write access" \
ScopeName=admin,ScopeDescription="Admin access"
Update your app client to include scopes:
aws cognito-idp update-user-pool-client \
--user-pool-id us-east-1_ABC123 \
--client-id 1234567890abcdef \
--allowed-oauth-scopes openid mcp/read mcp/write
Federated Identity (SSO)
Connect Cognito to your identity provider:
// In CDK stack
const userPool = new cognito.UserPool(this, 'McpUserPool', {
// ...
});
// Add Google SSO
const googleProvider = new cognito.UserPoolIdentityProviderGoogle(
this, 'Google',
{
userPool,
clientId: 'google-client-id',
clientSecretValue: SecretValue.secretsManager('google-client-secret'),
scopes: ['email', 'profile'],
attributeMapping: {
email: cognito.ProviderAttribute.GOOGLE_EMAIL,
fullname: cognito.ProviderAttribute.GOOGLE_NAME,
},
}
);
// Add SAML provider for enterprise SSO
const samlProvider = new cognito.UserPoolIdentityProviderSaml(
this, 'Okta',
{
userPool,
metadata: cognito.UserPoolIdentityProviderSamlMetadata.url(
'https://company.okta.com/app/metadata'
),
attributeMapping: {
email: cognito.ProviderAttribute.other('email'),
},
}
);
Custom MCP Clients
Build your own application that connects to the remote MCP server:
Rust Client
use pmcp::client::{Client, HttpTransport}; use pmcp::types::CallToolParams; #[tokio::main] async fn main() -> Result<()> { // Create HTTP transport with OAuth token let transport = HttpTransport::new("https://abc123.execute-api.us-east-1.amazonaws.com/mcp") .with_bearer_token("eyJhbGciOi...") .build()?; // Connect to server let client = Client::connect(transport).await?; // Initialize let server_info = client.initialize().await?; println!("Connected to: {}", server_info.name); // List available tools let tools = client.list_tools().await?; for tool in &tools { println!("Tool: {} - {}", tool.name, tool.description.as_deref().unwrap_or("")); } // Call a tool let result = client.call_tool(CallToolParams { name: "query_users".to_string(), arguments: serde_json::json!({ "department": "Engineering" }), }).await?; println!("Result: {}", serde_json::to_string_pretty(&result)?); Ok(()) }
TypeScript/JavaScript Client
import { Client, HttpTransport } from '@anthropic/mcp-sdk';
async function main() {
// Create transport with authentication
const transport = new HttpTransport({
url: 'https://abc123.execute-api.us-east-1.amazonaws.com/mcp',
headers: {
'Authorization': `Bearer ${process.env.MCP_TOKEN}`,
},
});
// Connect
const client = new Client({ transport });
await client.connect();
// Initialize
const serverInfo = await client.initialize({
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: { name: 'my-app', version: '1.0.0' },
});
console.log(`Connected to: ${serverInfo.serverInfo.name}`);
// List tools
const tools = await client.listTools();
console.log('Available tools:', tools.tools.map(t => t.name));
// Call a tool
const result = await client.callTool({
name: 'query_users',
arguments: { department: 'Engineering' },
});
console.log('Result:', result);
}
main().catch(console.error);
Python Client
import asyncio
from mcp import Client, HttpTransport
async def main():
# Create transport with authentication
transport = HttpTransport(
url="https://abc123.execute-api.us-east-1.amazonaws.com/mcp",
headers={"Authorization": f"Bearer {os.environ['MCP_TOKEN']}"}
)
# Connect
async with Client(transport) as client:
# Initialize
server_info = await client.initialize()
print(f"Connected to: {server_info.name}")
# List tools
tools = await client.list_tools()
print(f"Available tools: {[t.name for t in tools]}")
# Call a tool
result = await client.call_tool(
name="query_users",
arguments={"department": "Engineering"}
)
print(f"Result: {result}")
asyncio.run(main())
Testing the Connection
Using curl
Test your endpoint directly:
# Initialize
curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "curl", "version": "1.0"}
},
"id": 1
}'
# List tools
curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 2
}'
# Call a tool
curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "query_users",
"arguments": {"department": "Engineering"}
},
"id": 3
}'
Using cargo pmcp deploy test
PMCP provides a built-in test command:
# Run integration tests against deployed server
cargo pmcp deploy test
# Output:
# Testing connection to https://abc123.execute-api.us-east-1.amazonaws.com/mcp
# ✓ Initialize: 45ms
# ✓ List Tools: 23ms (found 5 tools)
# ✓ Call 'query_users': 156ms
# ✓ Call 'get_user_details': 89ms
#
# All tests passed!
Troubleshooting
"401 Unauthorized"
Cause: Invalid or expired token.
Solution:
- Check token is included in Authorization header
- Verify token hasn't expired
- Re-authenticate to get fresh token
"403 Forbidden"
Cause: Token valid but missing required scopes.
Solution:
- Check Cognito app client has required scopes
- Ensure user has permission for requested scopes
- Re-authorize with correct scope request
"CORS Error" (Browser)
Cause: API Gateway CORS not configured for your origin.
Solution: Update CDK to allow your origin:
corsPreflight: {
allowOrigins: ['https://your-app.com'],
// ...
}
"Connection Timeout"
Cause: Lambda in VPC without NAT gateway, or cold start too slow.
Solution:
- Ensure VPC has NAT gateway for outbound traffic
- Check Lambda timeout is sufficient
- Consider provisioned concurrency
"Invalid Redirect URI"
Cause: Callback URL doesn't match Cognito configuration.
Solution: Add the redirect URI to Cognito app client:
aws cognito-idp update-user-pool-client \
--user-pool-id us-east-1_ABC123 \
--client-id 1234567890abcdef \
--callback-urls "http://localhost:8765/callback" "https://claude.ai/callback"
Summary
Connecting clients to your remote MCP server:
- Get your endpoint URL:
cargo pmcp deploy outputs - Configure authentication: OAuth (recommended) or API keys
- Set up client configuration: Claude Desktop config or Claude.ai integration
- Test the connection: curl, built-in test, or your application
Key configuration patterns:
// Claude Desktop with OAuth
{
"mcpServers": {
"my-server": {
"transport": "streamable-http",
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/mcp",
"oauth": {
"client_id": "...",
"authorization_url": "...",
"token_url": "...",
"scopes": ["openid", "mcp:read"]
}
}
}
}
Your MCP server is now accessible to anyone with proper credentials, from anywhere in the world.