Skip to content

Repository files navigation

mcp-composition-demo

A production-grade reference project demonstrating how to compose multiple MCP servers into a multi-server architecture orchestrated by a LangGraph agent.

Built by Chandra Mohan Busam | Principal Engineer and AI Engineer

Architecture Notes: docs/ARCHITECTURE.md Design decisions behind the mount pattern, why LangGraph instead of LangChain ReAct, and how servers coordinate without calling each other.


What This Project Demonstrates

Pattern Implementation
FastMCP Mount Pattern Four sub-servers on one port via main.mount()
LangGraph Orchestration Four nodes, one per server, guaranteed execution order
Redis Session Memory Mock for local dev, real Redis interface for production
TTL Caching Config cached 60s, transparent to orchestrator
Structured JSON Logging Stdout + rotating file, shared across all servers
Bearer Token Auth Single token for all mounted sub-servers
ngrok Public Access One tunnel exposes all four servers
Session Isolation session:{id}:{server}:{key} pattern prevents data leaks

Architecture

User Request
     |
     v
LangGraph Orchestrator (orchestrator/agent.py)
     |
     +-- Node 1 --> /config  DeploymentConfigServer  (get_deployment_config)
     |                       Redis cache: 60s TTL
     |                       Writes config to LangGraph State
     |
     +-- Node 2 --> /agent   DeploymentAgentServer   (download, transfer, deploy, restart)
     |                       Reads config from LangGraph State
     |                       Retries transfer up to 3x on checksum failure
     |
     +-- Node 3 --> /audit   DeploymentAuditServer   (record_start, steps, complete)
     |                       Runs whether deployment succeeds or fails
     |                       Writes audit_id to session memory
     |
     +-- Node 4 --> /notify  NotificationServer      (send_deployment_notification)
                             Reads audit_id from session memory
                             Posts to Slack and Teams

Key principle: Servers never call other servers. LangGraph State is the coordination layer. Redis is the caching and session layer inside individual servers.


Project Structure

mcp-composition-demo/
│
├── shared/                         # Shared infrastructure across all servers
│   ├── __init__.py
│   ├── logger.py                   # Structured JSON + rotating file logger
│   ├── session_manager.py          # Redis session memory (mock + real)
│   ├── cache.py                    # TTL caching (mock + real)
│   └── auth.py                     # Bearer token validation
│
├── servers/                        # Four MCP sub-servers
│   ├── config_server.py            # DeploymentConfigServer (4 tools)
│   ├── agent_server.py             # DeploymentAgentServer (5 tools)
│   ├── audit_server.py             # DeploymentAuditServer (4 tools)
│   └── notify_server.py            # NotificationServer (3 tools)
│
├── orchestrator/
│   └── agent.py                    # LangGraph graph, nodes, state, run helper
│
├── docs/
│   └── ARCHITECTURE.md             # Design decisions and reasoning
│
├── main.py                         # Mounts all 4 servers, runs on port 8000
├── .env.example                    # Copy to .env and fill in your values
├── requirements.txt
├── LICENSE
└── README.md

1. Installation

# Clone the repo
git clone https://github.com/ChandraMohanBusam/mcp-composition-demo.git
cd mcp-composition-demo

# Create a virtual environment
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Set up environment variables
cp .env.example .env
# Edit .env with your values (see sections below)

2. Running the Demo

Start the MCP server

python main.py

Expected output:

MCP Composition Demo
==================================================
Transport:  SSE
Port:       8000
Auth:       enabled
Session:    mock backend
Cache:      mock backend

Mounted servers:
  /config  -> DeploymentConfigServer
  /agent   -> DeploymentAgentServer
  /audit   -> DeploymentAuditServer
  /notify  -> NotificationServer

SSE endpoint: http://localhost:8000/sse
==================================================

Run the LangGraph orchestrator

python orchestrator/agent.py

This runs a full deployment pipeline:

  1. Fetches config for staging environment
  2. Resolves latest build version
  3. Downloads, transfers, deploys, restarts
  4. Records audit trail
  5. Sends Slack and Teams notification

3. MCP Tool Reference

/config - DeploymentConfigServer

Tool Description
get_deployment_config(environment, app_name, session_id) Returns server IP, deploy path, service config. Cached 60s.
get_environment_list() Lists available environments with health status.
validate_environment(environment) Checks if environment is healthy before deployment.
get_health_check_url(environment, app_name) Returns URL to verify deployment succeeded.

/agent - DeploymentAgentServer

Tool Description
get_latest_build(pipeline_id, session_id) Resolves latest successful build version.
download_build(version, environment, session_id) Downloads build artifact.
transfer_to_server(version, server_ip, deploy_path, session_id) SCP transfer with checksum verification.
deploy_on_server(version, server_ip, deploy_path, service_name, session_id) Unpacks and deploys.
restart_services(server_ip, service_name, session_id) Restarts application services.

/audit - DeploymentAuditServer

Tool Description
record_deployment_start(version, environment, triggered_by, session_id) Creates audit record, returns deployment_id.
record_deployment_step(deployment_id, step_name, step_status, details, session_id) Records a single step.
record_deployment_complete(deployment_id, final_status, duration_seconds, session_id) Closes the audit record.
get_last_successful_deployment(environment, session_id) Returns rollback target version.

/notify - NotificationServer

Tool Description
send_deployment_notification(message, status, deployment_id, environment, version, session_id) Posts to Slack and Teams.
send_slack_alert(message, severity, session_id) Direct Slack alert.
send_teams_alert(message, severity, session_id) Direct Teams alert.

4. Session Memory and Caching

Session Memory

Controls short-term context within a deployment run. Set in .env:

SESSION_BACKEND=mock    # in-memory, no Redis needed (default)
SESSION_BACKEND=redis   # real Redis, requires Redis running

Key pattern for session isolation:

session:{session_id}:{server_name}:{data_type}

Every session key expires after 1 hour automatically.

Caching

Controls config cache TTL. Set in .env:

CACHE_BACKEND=mock      # in-memory (default)
CACHE_BACKEND=redis     # real Redis

Cache TTL values:

  • Deployment config: 60 seconds
  • Environment list: 300 seconds
  • Health check: 30 seconds

Switching to Real Redis

# .env
SESSION_BACKEND=redis
CACHE_BACKEND=redis
REDIS_HOST=localhost
REDIS_PORT=6379

Start Redis locally:

# Windows (WSL or Docker)
docker run -p 6379:6379 redis:latest

# Mac
brew install redis && brew services start redis

5. Bearer Token Setup

All four sub-servers share one Bearer token.

Generate a secure token:

python -c "import secrets; print(secrets.token_hex(32))"

Add to .env:

MCP_BEARER_TOKEN=your_generated_token_here

Include in Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "mcp-composition-demo": {
      "url": "http://localhost:8000/sse",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_HERE"
      }
    }
  }
}

6. Running with ngrok (Public Access)

ngrok creates a secure tunnel from a public URL to your local server. This allows Claude Desktop, Claude Code, or claude.ai to connect to your locally running MCP server over the internet.

Why one tunnel works for four servers: The mount pattern runs all four sub-servers on a single port (8000). One ngrok tunnel exposes all four servers simultaneously. This is the main advantage of the mount pattern for local development.

Step 1: Install ngrok

Download from ngrok.com/download and authenticate:

ngrok config add-authtoken YOUR_NGROK_AUTH_TOKEN

Step 2: Start the MCP server

# Terminal 1
python main.py

Step 3: Start the ngrok tunnel

# Terminal 2
ngrok http 8000

Copy the public URL shown by ngrok:

Forwarding https://abc123.ngrok-free.app -> http://localhost:8000

Step 4: Update Claude Desktop config

{
  "mcpServers": {
    "mcp-composition-demo": {
      "url": "https://abc123.ngrok-free.app/sse",
      "headers": {
        "Authorization": "Bearer YOUR_BEARER_TOKEN_HERE"
      }
    }
  }
}

Fully quit Claude Desktop (right-click system tray, Quit) and reopen.

Step 5: Add to Claude Code

claude mcp add mcp-composition-demo \
  --url https://abc123.ngrok-free.app/sse \
  --header "Authorization: Bearer YOUR_BEARER_TOKEN_HERE"

ngrok Troubleshooting

Issue Cause Fix
401 Unauthorized Token mismatch Check Bearer token in .env matches Claude Desktop config
Tunnel not found Session expired Restart ngrok, update URL in Claude Desktop config
URL changes each restart Free plan limitation Upgrade ngrok for static subdomain, or use paid plan
Server not responding MCP server not running Start python main.py before starting ngrok

Free plan note: The free ngrok plan generates a new URL on every restart. Update your Claude Desktop config with the new URL each time you restart ngrok. Upgrade to a paid plan for a fixed static subdomain.


7. Logging

Three logging layers are active by default:

Layer 1: Structured JSON to stdout Every log line is a parseable JSON object. AWS ECS and Azure Container Apps pick this up automatically for CloudWatch and Azure Monitor.

Layer 2: Rotating file logger Writes to logs/{server_name}.log. Rotates daily at midnight. Keeps 7 days.

Useful log queries after a run:

# All tool calls for a session
grep "sess_abc123" logs/orchestrator.log

# All errors across all servers
grep '"level":"ERROR"' logs/*.log

# Config cache hits
grep "cache_hit" logs/config_server.log

# Notification delivery results
grep "send_deployment_notification" logs/notify_server.log

8. Debugging Philosophy

This project demonstrates two debugging approaches side by side.

Code-level debugging (this repo):

  • Structured JSON logs per server
  • session_id traces a full deployment run across all four servers
  • LoopDetectionHandler equivalent: transfer retry is explicit in orchestrator code

LangGraph-specific debugging:

  • stream_mode="debug" in build_deployment_graph() for real-time node events
  • get_state() after run for full State snapshot
  • get_state_history() for Time Travel: replay from any checkpoint

See github.com/ChandraMohanBusam/langchain-debug-demo for the companion project covering LangChain and LangGraph debugging in depth.


9. Related Projects

This project is part of a connected MCP portfolio:

Project Transport Role
ai-deployment-agent HTTP/SSE Original deployment agent (LangChain, V1 + V2 with ADO)
mcp-notification-server HTTP/SSE Shared notification server (Slack + Teams)
mcp-queue-monitor stdio Local database monitoring (Claude Desktop)
langchain-debug-demo N/A LangChain + LangGraph debugging reference
mcp-composition-demo (this) HTTP/SSE Multi-server composition with LangGraph orchestration

Author

Chandra Mohan Busam Principal Engineer | AI Engineer GitHub | LinkedIn

About

Production-grade MCP composition pattern: four specialized servers orchestrated by LangGraph with config-driven notification routing, health checks, auto-rollback, Redis session memory, structured logging, and Bearer token auth. Mount pattern for local dev, distributed ports for production.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages