Getting Started with opinionated_mcp¶
opinionated_mcp is a zero-config OAuth framework for MCP (Model Context Protocol) servers that makes authentication simple and secure. This guide will get you to a working MCP server with user-specific data storage in just a few steps.
What You’ll Build¶
By the end of this guide, you’ll have a working MCP server with two tools:
write_name(name)- Store the user’s nameread_name()- Retrieve the user’s stored name
Each user’s data is automatically isolated by their Google account - no need to handle user identification in your tool code.
Prerequisites¶
Before starting, you’ll need:
Python 3.8+ installed
A Google account
5 minutes to set up Google OAuth
Step 1: Installation¶
Install opinionated_mcp:
pip install opinionated_mcp
Step 2: Google OAuth Setup¶
You need a Google OAuth Client ID (no client secret required):
Go to the Google Cloud Console
Create a new project or select an existing one
Enable the Google+ API (or Google Identity API)
Navigate to “Credentials” → “Create Credentials” → “OAuth 2.0 Client ID”
Set application type to “Web application”
Add authorized redirect URI:
http://localhost:8000/callbackCopy the Client ID (looks like:
123456789-abc.apps.googleusercontent.com)
Step 3: Create Your Server¶
Create a new file called server.py:
from opinionated_mcp import OpinionatedMCP, generate_session_key
# In-memory storage for user data (use a database in production)
user_data = {}
# Create the MCP server
server = OpinionatedMCP(
name="User Name Manager",
google_client_id="YOUR_GOOGLE_CLIENT_ID_HERE", # Replace with your Client ID
session_key=generate_session_key(),
base_url="http://localhost:8000"
)
@server.tool(name="write_name", description="Store your name")
@server.require_auth
async def write_name(request, user_id, name: str):
"""Store the user's name. The user_id is automatically provided by authentication."""
user_data[user_id] = name
return f"Stored name '{name}' for user {user_id}"
@server.tool(name="read_name", description="Get your stored name")
@server.require_auth
async def read_name(request, user_id):
"""Retrieve the user's stored name. The user_id is automatically provided by authentication."""
name = user_data.get(user_id, "No name stored")
return f"Your stored name is: {name}"
if __name__ == "__main__":
server.run(debug=True)
Important: Replace YOUR_GOOGLE_CLIENT_ID_HERE with your actual Google Client ID from step 2.
Step 4: Run Your Server¶
Start your server:
python server.py
You should see output like:
🚀 Starting User Name Manager
📡 Server: http://localhost:8000
🔗 Base URL: http://localhost:8000
🔐 Login: http://localhost:8000/login
🤖 MCP: http://localhost:8000/mcp
Step 5: Test Your Server¶
Visit the server: Open http://localhost:8000 in your browser
Log in: Click the login link or visit http://localhost:8000/login
Authenticate: Complete the Google OAuth flow
Test the MCP endpoint: Your MCP tools are available at http://localhost:8000/mcp
Testing with MCP Client¶
If you have an MCP client, connect it to http://localhost:8000/mcp. You’ll see two available tools:
write_name- Takes a name parameterread_name- Returns your stored name
Each user who authenticates will have their own isolated data storage.
Understanding the Code¶
Let’s break down what’s happening:
Server Setup:
server = OpinionatedMCP(
name="User Name Manager", # Display name
google_client_id="YOUR_CLIENT_ID", # Google OAuth Client ID
session_key=generate_session_key(), # Encryption key for sessions
base_url="http://localhost:8000" # Where your server runs
)
Tool Definition:
@server.tool(name="write_name", description="Store your name")
@server.require_auth
async def write_name(request, user_id, name: str):
# user_id is automatically the authenticated user's email
user_data[user_id] = name
return f"Stored name '{name}' for user {user_id}"
Key points:
@server.tool()registers the function as an MCP tool@server.require_authensures only authenticated users can call ituser_idparameter is automatically injected with the authenticated user’s emailThe tool signature (
name: str) becomes the MCP tool’s parameter schema
Authentication Flow:
User calls an MCP tool
Server checks if user is authenticated
If not authenticated, returns authentication error
If authenticated, calls your tool function with
user_idautomatically setYour tool code works with per-user data without worrying about authentication
What Makes This “Opinionated”¶
This framework makes several decisions for you:
Google OAuth only - No configuration for multiple providers
PKCE without client secrets - More secure, easier deployment
Automatic user ID injection - Your tools automatically get the authenticated user
Session-based authentication - Uses encrypted cookies
FastAPI integration - Modern, async Python web framework
Production Considerations¶
For production use, consider these improvements:
Persistent Storage:
# Replace in-memory dict with a database
import sqlite3
def get_user_name(user_id):
conn = sqlite3.connect('users.db')
cursor = conn.execute('SELECT name FROM users WHERE email = ?', (user_id,))
result = cursor.fetchone()
conn.close()
return result[0] if result else None
def set_user_name(user_id, name):
conn = sqlite3.connect('users.db')
conn.execute('INSERT OR REPLACE INTO users (email, name) VALUES (?, ?)', (user_id, name))
conn.commit()
conn.close()
Environment Variables:
import os
server = OpinionatedMCP(
name=os.getenv("SERVER_NAME", "User Name Manager"),
google_client_id=os.getenv("GOOGLE_CLIENT_ID"),
session_key=os.getenv("SESSION_KEY", generate_session_key()),
base_url=os.getenv("BASE_URL", "http://localhost:8000")
)
HTTPS and Domain:
# Update Google OAuth redirect URI to:
# https://yourdomain.com/callback
server = OpinionatedMCP(
name="User Name Manager",
google_client_id="YOUR_CLIENT_ID",
session_key=os.getenv("SESSION_KEY"),
base_url="https://yourdomain.com",
host="0.0.0.0", # Listen on all interfaces
port=int(os.getenv("PORT", "8000"))
)
Next Steps¶
Now that you have a working authenticated MCP server, you can:
Add more tools with different functionality
Integrate with databases or external APIs
Deploy to production with proper HTTPS
Add web endpoints alongside your MCP tools
Scale to handle multiple users
The key insight is that user_id is automatically provided to all your @server.require_auth decorated tools, so you can focus on your business logic rather than authentication plumbing.
Troubleshooting¶
- “OAuth error” messages:
Check that your Google Client ID is correct and the redirect URI (
http://localhost:8000/callback) is configured in Google Cloud Console.- “No name stored” always returned:
Make sure you’re testing with the same Google account that you used to store the name.
- MCP client can’t connect:
Ensure your server is running and accessible at
http://localhost:8000/mcp.- Server won’t start:
Check that port 8000 isn’t already in use, or change the port in your server configuration.