Skip to content

How to use MCP client in a multi-user application? #243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mehdi-sp opened this issue Mar 30, 2025 · 4 comments
Open

How to use MCP client in a multi-user application? #243

mehdi-sp opened this issue Mar 30, 2025 · 4 comments

Comments

@mehdi-sp
Copy link

mehdi-sp commented Mar 30, 2025

I'm working on a web based chat application. Unlike desktop apps (Claude Desktop, Cursor IDE, ...), at each moment multiple users might be working with my app. What is the recommended way of using MCP client in this scenario?

  • Should I reuse one transport/client for all of the users? As I see in the code, the auth function does not support more than one user
  • Should I create a new client/transport for each of the active users? the overhead of Initialization flow is too much in this case. also it is unclear how long the transport will be alive and when it should be terminated
@csmoakpax8
Copy link

What Ive found is you need a new instance of MCPServer per user connecting. Docs are misleading here: https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#http-with-sse

FastMcp also does this as a "FastMCPSession": https://github.com/punkpeye/fastmcp/blob/main/src/FastMCP.ts#L424

But to your point, with Streamable HTTP transport in the spec, how long do we need to support SSE

@mehdi-sp
Copy link
Author

mehdi-sp commented Apr 2, 2025

Thanks for the reference to FastMCP! I was not aware of it.

I see two related issues here:

  1. A MCP server cannot handle connections from multiple MCP clients. the clients can be different applications or multiple replicas of the same application.
    this is being discussed in Multiple clients to the same SSE connection? #204.
    as you said the best solution available for now is to create a new MCP server for each client.
  2. A MCP client cannot be used for more than one user
    this is what I am trying to discuss here.
    again it can be handled by creating a new MCP client & server for each user. but it is too much overhead. the client and server will negotiate capabilities and other unnecessary stuff per user connecting.

@lloydzhou
Copy link

i have write a package to deploy mcp server for multi-user.

https://github.com/ConechoAI/nchan-mcp-transport/tree/main/typescript

  1. using nchan as transport and gateway.
  2. deploy mcp server as simple web server (every JSONRPCRequest will translate to HTTP Request, manage user session in web server is sample...)

you can see the flow here: https://github.com/ConechoAI/nchan-mcp-transport/blob/main/docs/MCP%20Communication%20Flow.md

Pizzaface pushed a commit to RewstApp/mcp-inspector that referenced this issue May 2, 2025
…roxy_config

Add MCP proxy address config support, better error messages, redesigned Config panel
@xblaauw
Copy link

xblaauw commented May 19, 2025

Just going to leave this here:

server.py

from fastmcp import FastMCP, Context
import logging

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-server")

# Create MCP server
mcp = FastMCP("Echo Context Server")

@mcp.tool()
async def echo_context(message: str, ctx: Context) -> dict:
    """
    Echo back the message and user context from request headers.
    
    Args:
        message: A message to echo back
    
    Returns:
        A dictionary with the message and user context
    """
    # Get HTTP request
    request = ctx.get_http_request()
    
    # Extract user context from headers
    user_id = request.headers.get("X-User-ID", "unknown")
    auth_token = request.headers.get("Authorization", "").replace("Bearer ", "")
    
    logger.info(f"Request from user: {user_id}")
    
    # Return the message and user context
    return {
        "message": message,
        "user_context": {
            "user_id": user_id,
            "auth_token": auth_token[:5] + "..." if auth_token else "none"
        }
    }

if __name__ == "__main__":
    logger.info("Starting MCP server on port 3000...")
    # Use the streamable-http transport with correct parameters
    mcp.run(transport="streamable-http", host="0.0.0.0", port=3000)

client.py:

import asyncio
import json
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

async def list_tools():
    """List all available tools and their schemas"""
    print("\n--- Listing Available Tools ---")
    
    # Create a transport without any specific user context
    transport = StreamableHttpTransport(
        url="http://localhost:3000/mcp"
    )
    
    # Create client with the configured transport
    client = Client(transport)
    
    try:
        # Use the client to list all tools
        async with client:
            tools = await client.list_tools()
            print("Available tools:")
            for tool in tools:
                print(f"\nTool: {tool.name}")
                print(f"Description: {tool.description}")
                print("Parameters schema:")
                # Pretty print the input schema
                print(json.dumps(tool.inputSchema, indent=2))
    except Exception as e:
        print(f"Error: {e}")

async def call_with_user(user_id, auth_token, message):
    """Call echo_context tool with specific user context in headers"""
    print(f"\n--- Calling as user: {user_id} ---")
    
    # Set up headers with user context
    headers = {
        "X-User-ID": user_id,
        "Authorization": f"Bearer {auth_token}",
        "Accept": "application/json, text/event-stream"
    }
    
    # Skip Authorization header if token is empty
    if not auth_token:
        headers.pop("Authorization")
    
    # Create a StreamableHttpTransport with headers
    transport = StreamableHttpTransport(
        url="http://localhost:3000/mcp",
        headers=headers
    )
    
    # Create client with the configured transport
    client = Client(transport)
    
    try:
        # Use the client to call the tool
        async with client:
            result = await client.call_tool("echo_context", {"message": message})
            print(f"Result: {result}")
    except Exception as e:
        print(f"Error: {e}")

async def main():
    # First, list all tools to inspect their schemas
    await list_tools()
    
    # Then call with different user contexts
    await call_with_user("alice", "alice-token-123", "Hello from Alice!")
    await call_with_user("bob", "bob-token-456", "Hello from Bob!")
    await call_with_user("anonymous", "", "Hello without auth!")

if __name__ == "__main__":
    asyncio.run(main())

Using the request headers and ctx object you can inject the user's id or name or whatever you want on behalf of whom you are executing a toolcall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants