MCP Servers with per-user credentials with LibreChat
Introduction
MCP servers started as 1:1 stdio processes—Claude App spawns a server, uses it, kills it. This doesn’t scale for non-tech people as they need to clone projects locally and deal with .env
files or variables to authenticate. We need N:1 servers where multiple users connect to the same instance. Problem: each user needs their own credentials. Here’s how to build an MCP server that handles per-user auth, using Salesforce + LibreChat as an example.
A Salesforce MCP server
The key insight: grab user credentials from HTTP headers. If you follow along you need:
"httpx>=0.28.1",
"mcp[cli]>=1.5.0",
"pandas>=2.2.3",
"simple-salesforce>=1.12.6",
"tabulate>=0.9.0",
Here is the implementation:
import pandas as pd
from simple_salesforce import Salesforce
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.server import Context
# Initialize FastMCP server
mcp = FastMCP("salesforce",
port=8050,
auth_required=False,
host="0.0.0.0")
def initialize_client(username: str, password: str, token: str) -> Salesforce:
"""Initialize and return a Salesforce client."""
sf = Salesforce(
username=username,
password=password,
security_token=token,
domain='login')
return sf
@mcp.tool()
def query_salesforce(soql_query: str, ctx: Context) -> str:
"""Issues a REST query to salesforce using simple_salesforce in the background.
Select only the minimum set of columns needed and apply appropriate filters if needed.
Parameters
----------
soql_query : str
SOQL query to execute against Salesforce
Returns
-------
str
Markdown formatted table of results
Examples
--------
Get the id, subject and status from my open salesforce cases:
query_salesforce("Select id, subject, status from Case WHERE Status!= 'Closed'")
"""
headers_info = {}
if ctx.request_context.request:
headers_info = dict(ctx.request_context.request.headers)
salesforce_client = initialize_client(username=headers_info['x-auth-username'],
password=headers_info['x-auth-password'],
token=headers_info['x-auth-token'])
results = salesforce_client.query(soql_query)
return pd.DataFrame(results['records']).to_markdown()
if __name__ == "__main__":
mcp.run(transport='sse')
When LibreChat calls query_salesforce
, the server pulls x-auth-username
, x-auth-password
, and x-auth-token
from the request headers, creates a fresh Salesforce client, and executes the SOQL query. Each user’s credentials stay isolated.
This is a bare-bones example to show the mechanism whose gist is capturing the headers.
Production code would need error handling, input sanitization, and broader Salesforce API coverage.
LibreChat Support
LibreChat passes user variables as HTTP headers. Configure it in librechat.yaml
per the documentation:
mcpServers:
per-user-credentials-example:
type: streamable-http
url: "https://example.com/api/"
headers:
X-Auth-Token: ""
customUserVars:
MY_SERVICE_API_KEY:
title: "My Service API Key"
description: "Enter your personal API key for the service. You can generate one at <a href='https://myservice.example.com/developer/keys' target='_blank'>Service Developer Portal</a>."
Users enter their credentials once in the UI. LibreChat automatically includes them as headers in every MCP request.
For our Salesforce server:
mcpServers:
salesforce:
type: sse
url: "https://example.com/api/"
headers:
X-Auth-Username: ""
X-Auth-Password: ""
X-Auth-Token: ""
customUserVars:
SF_USERNAME:
title: "SF USERNAME" # This is the label shown above the input field
description: "Get your API key <a href='https://example.com/api-keys' target='_blank'>here</a>." # This description appears below the input
SF_PASSWORD:
title: "SF PASSWORD" # This is the label shown above the input field
description: "Get your API key <a href='https://example.com/api-keys' target='_blank'>here</a>." # This description appears below the input
SF_TOKEN:
title: "SF TOKEN" # This is the label shown above the input field
description: "Get your API key <a href='https://example.com/api-keys' target='_blank'>here</a>." # This description appears below the input
The header names (X-Auth-Username
, etc.) match what our Python server expects. The customUserVars
section creates input fields in LibreChat’s UI.
This is how this looks like in the UI once you click on “MCP Servers” and on the toggle buttons of the salesforce
server: