MCP Servers with per-user credentials with LibreChat

2 minute read

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: LibreChat UI