Deploy an MCP agent with end-user OAuth so each user authenticates with their own identity and the agent accesses only resources the user has authorized. Default MCP deployments use a single service account, which means all users share the same permissions and rate limits. End-user OAuth gives each user their own token, enabling per-user rate limiting, audit trails, and scoped data access. This is required for multi-tenant agents that access user-specific data like email, calendar, or private repos.
Prerequisites
- Python 3.8+ installed
- An MCP server implementation (FastAPI, Express, or similar)
- An OAuth 2.0 provider (Google, GitHub, or custom)
- A Scavio API key from scavio.dev for the search tool
Walkthrough
Step 1: Configure OAuth scopes
Define the OAuth scopes your agent needs and map them to MCP tool permissions.
import os, json
# OAuth configuration
OAUTH_CONFIG = {
'provider': 'google', # or github, custom
'client_id': os.environ.get('OAUTH_CLIENT_ID', ''),
'client_secret': os.environ.get('OAUTH_CLIENT_SECRET', ''),
'redirect_uri': 'http://localhost:3000/callback',
'scopes': [
'openid',
'profile',
'email',
],
}
# Map scopes to tool permissions
TOOL_PERMISSIONS = {
'web_search': {'required_scopes': ['openid']},
'email_search': {'required_scopes': ['openid', 'email']},
'calendar_read': {'required_scopes': ['openid', 'https://www.googleapis.com/auth/calendar.readonly']},
}
print(f'Configured {len(TOOL_PERMISSIONS)} tools with scope requirements')Step 2: Set connection type to end-user
Configure the MCP server to use end-user connection type instead of service account.
# MCP server configuration for end-user OAuth
mcp_config = {
'mcpServers': {
'search-agent': {
'url': 'http://localhost:3000/mcp',
'connection': {
'type': 'end_user',
'oauth': {
'provider': OAUTH_CONFIG['provider'],
'client_id': OAUTH_CONFIG['client_id'],
'scopes': OAUTH_CONFIG['scopes'],
},
},
'tools': list(TOOL_PERMISSIONS.keys()),
}
}
}
# Write config
with open('.mcp.json', 'w') as f:
json.dump(mcp_config, f, indent=2)
print('MCP config written with end-user OAuth')Step 3: Deploy the agent with token validation
Build the agent endpoint that validates the user's OAuth token before executing tools.
import requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def validate_token(token: str) -> dict:
"""Validate OAuth token and extract user info."""
resp = requests.get('https://www.googleapis.com/oauth2/v3/userinfo',
headers={'Authorization': f'Bearer {token}'}, timeout=10)
if resp.status_code != 200:
return {'valid': False, 'error': 'invalid_token'}
user = resp.json()
return {'valid': True, 'email': user.get('email', ''), 'sub': user.get('sub', '')}
def execute_tool(tool_name: str, params: dict, user_token: str) -> dict:
user = validate_token(user_token)
if not user['valid']:
return {'error': 'unauthorized', 'message': 'Invalid or expired token'}
if tool_name == 'web_search':
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY},
json={'platform': 'google', 'query': params.get('query', '')}, timeout=10)
return {'results': resp.json().get('organic_results', [])[:5], 'user': user['email']}
return {'error': f'Unknown tool: {tool_name}'}
print('Agent endpoint ready with token validation')Step 4: Test with end-user identity
Simulate an end-user request and verify the agent correctly uses the user's identity.
def test_end_user_flow():
# Simulate with a mock token (in production, this comes from OAuth flow)
mock_token = 'mock_user_token_for_testing'
# Test the search tool
result = execute_tool('web_search', {'query': 'python best practices 2026'}, mock_token)
if 'error' in result and result['error'] == 'unauthorized':
print('Token validation working (mock token rejected as expected)')
else:
print(f'Tool executed for user: {result.get("user", "unknown")}')
# Test with Scavio directly (no OAuth needed)
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY},
json={'platform': 'google', 'query': 'oauth 2.0 best practices'}, timeout=10)
results = resp.json().get('organic_results', [])
print(f'Direct API test: {len(results)} results')
test_end_user_flow()Step 5: Verify per-user identity isolation
Confirm that each user's requests are isolated and tracked separately.
from collections import defaultdict
user_audit = defaultdict(list)
def audited_tool(tool_name: str, params: dict, user_email: str) -> dict:
"""Execute tool with per-user audit logging."""
user_audit[user_email].append({
'tool': tool_name,
'params': params,
'timestamp': __import__('time').time(),
})
if tool_name == 'web_search':
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY},
json={'platform': 'google', 'query': params.get('query', '')}, timeout=10)
return {'results': resp.json().get('organic_results', [])[:3]}
return {}
# Simulate multiple users
audited_tool('web_search', {'query': 'react tutorial'}, 'alice@example.com')
audited_tool('web_search', {'query': 'vue tutorial'}, 'bob@example.com')
audited_tool('web_search', {'query': 'angular tutorial'}, 'alice@example.com')
for user, actions in user_audit.items():
print(f'{user}: {len(actions)} tool calls')
print('Per-user isolation verified')Python Example
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def user_search(query, user_email):
data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': query}).json()
results = data.get('organic_results', [])[:3]
print(f'[{user_email}] {query}: {len(results)} results')
return results
user_search('python oauth tutorial', 'user@example.com')JavaScript Example
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
async function userSearch(query, userEmail) {
const r = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: H, body: JSON.stringify({platform: 'google', query})
});
const results = (await r.json()).organic_results || [];
console.log(`[${userEmail}] ${query}: ${results.length} results`);
return results.slice(0, 3);
}
userSearch('oauth best practices', 'user@example.com');Expected Output
An MCP agent deployed with end-user OAuth where each user authenticates independently, tool calls are scoped to user permissions, and all actions are audited per user.