Documentation Index Fetch the complete documentation index at: https://docs.runalloy.com/llms.txt
Use this file to discover all available pages before exploring further.
What Is User Management?
User Management allows enterprise applications to serve multiple users through a single MCP server instance. Instead of creating separate servers for each user, you can dynamically switch user context using the x-alloy-userid header.
Think of it as a multi-tenant system where:
One server serves all your users
Each request can specify which user it’s for
Data and credentials remain isolated per user
Permissions are enforced at the user level
How User Management Works
When you make a request with the x-alloy-userid header, the system switches context to that specific user:
The user management system operates at multiple levels:
Authentication - Validates the enterprise API key has permission to override users
Context Switching - Loads the specified user’s context, credentials, and permissions
Data Isolation - Ensures each user only sees their own credentials and data
Execution - Runs the request in the user’s context
Response - Returns results specific to that user
Why Use User Management?
Multi-Tenant SaaS Applications
Single Integration : One MCP server for all your customers
Customer Isolation : Each customer’s data and credentials stay separate
Easy Scaling : Add users without creating new servers
Team Collaboration
Department Access : Sales uses CRM tools, Marketing uses email tools
Individual Credentials : Each team member has their own third-party logins
Centralized Control : Manage all users from one place
User Provisioning : Automatically set up new users
Compliance : Track actions per user for audit requirements
Cost Efficiency : One server infrastructure for unlimited users
Visualizing User Management
Without User Management - Multiple Servers:
With User Management - Single Server:
Quick Start
Basic User Override
curl -X POST "https://mcp.runalloy.com/mcp/YOUR_SERVER_ID/YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "x-alloy-userid: user_abc123" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_connectors_alloy",
"arguments": {}
},
"id": "1"
}'
See all 13 lines
How to Implement User Management
Step 1: Create an Enterprise MCP Server
Create a server with your enterprise API key:
curl -X POST https://mcp.runalloy.com/api/servers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "enterprise-multi-user",
"description": "Enterprise server for multiple users",
"restrictions": {
"permissions": [
{
"connector": "slack",
"mode": "allow",
"actions": ["*"]
},
{
"connector": "notion",
"mode": "allow",
"actions": ["*"]
}
]
}
}'
See all 21 lines
Step 2: Execute Requests for Different Users
For User Alice:
curl -X POST "YOUR_MCP_SERVER_URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "x-alloy-userid: user_alice_123" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "execute_action_alloy",
"arguments": {
"connectorId": "slack",
"actionId": "chat_postMessage",
"credentialId": "alice_slack_credential_id",
"parameters": {
"requestBody": {
"channel": "C123456",
"text": "Message from Alice"
}
}
}
},
"id": "1"
}'
See all 23 lines
For User Bob:
curl -X POST "YOUR_MCP_SERVER_URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "x-alloy-userid: user_bob_456" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "execute_action_alloy",
"arguments": {
"connectorId": "slack",
"actionId": "chat_postMessage",
"credentialId": "bob_slack_credential_id",
"parameters": {
"requestBody": {
"channel": "C789012",
"text": "Message from Bob"
}
}
}
},
"id": "1"
}'
See all 23 lines
Understanding the User Flow
Here’s how a typical user management request flows through the system:
Each step ensures:
Authentication - Only enterprise keys can override users
Authorization - User must exist and have permissions
Isolation - User gets only their data
Execution - Actions use user’s specific credentials
Integration Examples
Node.js Client
class MCPClient {
constructor ( serverUrl , token ) {
this . serverUrl = serverUrl ;
this . token = token ;
}
async executeForUser ( userId , method , params ) {
const response = await fetch ( ` ${ this . serverUrl } / ${ this . token } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Accept' : 'application/json, text/event-stream' ,
'x-alloy-userid' : userId // Dynamic user override
},
body: JSON . stringify ({
jsonrpc: '2.0' ,
method: 'tools/call' ,
params: {
name: method ,
arguments: params
},
id: '1'
})
});
return response . json ();
}
}
// Usage
const mcp = new MCPClient (
'https://mcp.runalloy.com/mcp/enterprise-123' ,
'your_token_here'
);
// Execute for different users
await mcp . executeForUser ( 'user_alice_123' , 'list_connectors_alloy' , {});
await mcp . executeForUser ( 'user_bob_456' , 'list_connectors_alloy' , {});
See all 38 lines
Python Client
import requests
class MCPClient :
def __init__ ( self , server_url , token ):
self .server_url = server_url
self .token = token
def execute_for_user ( self , user_id , method , params ):
url = f " { self .server_url } / { self .token } "
headers = {
'Content-Type' : 'application/json' ,
'Accept' : 'application/json, text/event-stream' ,
'x-alloy-userid' : user_id # Dynamic user override
}
payload = {
'jsonrpc' : '2.0' ,
'method' : 'tools/call' ,
'params' : {
'name' : method,
'arguments' : params
},
'id' : '1'
}
response = requests.post(url, json = payload, headers = headers)
return response.json()
# Usage
mcp = MCPClient(
'https://mcp.runalloy.com/mcp/enterprise-123' ,
'your_token_here'
)
# Execute for different users
mcp.execute_for_user( 'user_alice_123' , 'list_connectors_alloy' , {})
mcp.execute_for_user( 'user_bob_456' , 'list_connectors_alloy' , {})
See all 38 lines
Key Concepts
User Context
Each user has their own isolated context containing:
Credentials - OAuth tokens and API keys for third-party services
Permissions - What actions they can perform
Data - Their specific configurations and settings
History - Their action logs and usage
Enterprise Authentication
User management requires enterprise-level authentication:
Standard API Keys - Cannot override user context
Enterprise API Keys - Can specify any valid user ID
Security - Prevents unauthorized user impersonation
Common Use Cases
Serve all your customers through one MCP server:
// When customer Alice uses your platform
await mcpClient . executeForUser ( alice . userId , 'execute_action_alloy' , {
connectorId: 'slack' ,
actionId: 'chat_postMessage' ,
// ... other params
});
// When customer Bob uses your platform
await mcpClient . executeForUser ( bob . userId , 'execute_action_alloy' , {
connectorId: 'notion' ,
actionId: 'pages_create' ,
// ... other params
});
See all 13 lines
2. Multi-Tenant Applications
Isolate data and credentials per tenant:
const tenantUserId = `tenant_ ${ tenantId } _user_ ${ userId } ` ;
await mcpClient . executeForUser ( tenantUserId , method , params );
See all 2 lines
Grant team members access to specific integrations:
// Sales team member
await mcpClient . executeForUser ( 'team_sales_john' , 'execute_action_alloy' , {
connectorId: 'hubspot' ,
actionId: 'contacts_create'
});
// Marketing team member
await mcpClient . executeForUser ( 'team_marketing_jane' , 'execute_action_alloy' , {
connectorId: 'mailchimp' ,
actionId: 'campaigns_create'
});
See all 11 lines
Security Features
Enterprise-Only - Only enterprise API keys can override users
Validation - User IDs are sanitized and validated
Fallback - Invalid users fall back to default context
Logging - All override attempts are logged
Isolation - Users cannot access each other’s data
Best Practices
Use a consistent format for user IDs:
// Good patterns
` ${ companyId } _ ${ userId } `
` ${ environment } _ ${ tenantId } _ ${ userId } `
`team_ ${ department } _ ${ username } `
2. Error Handling
Always handle cases where user override might fail:
try {
const result = await mcpClient . executeForUser ( userId , method , params );
return result ;
} catch ( error ) {
// Fallback logic or error reporting
console . error ( `Failed for user ${ userId } :` , error );
}
3. Rate Limiting
Implement per-user rate limiting in your application:
const userRateLimiter = new Map ();
async function executeWithRateLimit ( userId , method , params ) {
if ( userRateLimiter . get ( userId ) > MAX_REQUESTS_PER_MINUTE ) {
throw new Error ( 'Rate limit exceeded for user' );
}
// Update rate limit counter
userRateLimiter . set ( userId , ( userRateLimiter . get ( userId ) || 0 ) + 1 );
return mcpClient . executeForUser ( userId , method , params );
}
See all 12 lines
4. Credential Management
Each user should have their own credentials:
// Store credentials per user
const userCredentials = {
'user_alice_123' : {
slack: 'alice_slack_credential_id' ,
notion: 'alice_notion_credential_id'
},
'user_bob_456' : {
slack: 'bob_slack_credential_id' ,
hubspot: 'bob_hubspot_credential_id'
}
};
Important Considerations
User Prerequisites
Users must exist in Alloy before you can switch to their context
Create users via Alloy API or dashboard first
Invalid user IDs fall back to default context
Permission Model
Users inherit the server’s restriction settings
Cannot override server restrictions per user
For different permissions, create separate servers
Technical Limits
HTTP header size limit (typically 8KB)
User ID format restrictions (alphanumeric + limited special characters)
Rate limits apply per user context
Troubleshooting
Common Issues
Invalid User ID
Symptom : User override is ignored
{
"warning" : "Invalid x-alloy-userid header" ,
"attempted" : "invalid-user-id" ,
"fallback" : "primary_account_id"
}
Solution : Ensure the user ID exists and is properly formatted.
Missing Credentials
Symptom : Actions fail with credential errors
{
"error" : "No credentials found for user_alice_123 on connector slack"
}
Solution : Each user needs their own credentials for each connector they use.
Permission Denied
Symptom : Actions blocked by restrictions
{
"error" : "Action chat_postMessage is not allowed for connector slack"
}
Solution : Check server restrictions - they apply to all users equally.
Additional Resources