mcp-server-demo

thakorneyp11/mcp-server-demo

3.2

If you are the rightful owner of mcp-server-demo and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

This is a production-ready MCP server for accessing Thai stock market data, featuring OAuth 2.1 authorization and designed for integration with Claude.ai Custom Connector.

Tools
3
Resources
0
Prompts
0

Thailand Stock Market MCP Server with OAuth 2.1

Production-ready MCP (Model Context Protocol) server for Thai stock market data with OAuth 2.1 authorization, designed for Claude.ai Custom Connector integration.

CategoryFeatures
MCP Transport/mcp (Streamable HTTP) and /sse (SSE) - both OAuth protected
Stock DataReal-time prices, company info, search from Thailand SET
OAuth 2.1PKCE, JWT (RS256), dynamic client registration, audience validation
ProductionDocker, nginx, rate limiting, CORS, health checks, structured logging
Claude.ai Custom Connector

🚀 Quick Start

# Clone and configure
git clone <your-repo-url> && cd mcp-server-demo
cp .env.example .env

# Start production server (OAuth mode by default)
docker-compose -f docker-compose.prod.yml up --build -d

# Server running at http://localhost (nginx) → http://localhost:8000 (MCP server)

# Verify
curl http://localhost/health
curl http://localhost/.well-known/oauth-protected-resource

Table of Contents

📖 Project Overview

This MCP server is built with Python 3.10 and FastMCP to provide HTTP/SSE transport for remote access. It uses the yfinance library to fetch stock data from the Thailand Stock Exchange (SET) and exposes 3 tools for MCP clients

Key Capabilities:

  • Real-time stock prices, company information, and search for Thai stocks
  • OAuth 2.1 authorization for secure Claude.ai integration
  • Production-ready deployment with Docker, nginx, and Cloudflare SSL support

Deployment Options:

  • Development: Docker Compose with hot-reload
  • Testing: Docker Compose for automated testing
  • Production: Docker + nginx + Cloudflare SSL for cloud deployment

🔧 Technical Features

Stock Data Features

  • Current Price: Price in THB, change amount, percentage change, volume
  • Company Info: Full name, sector, industry, market cap, description
  • Stock Search: Search by symbol or company name (case-insensitive)

Performance Features

  • Caching: 60-second TTL cache to minimize API calls
  • Async Support: Built with asyncio for concurrent requests
  • Error Handling: Graceful error messages for invalid symbols

Data Source

  • Uses yfinance library with .BK suffix for Thai stocks
  • Example: AOTAOT.BK (Airports of Thailand)
  • Data is delayed by 15-20 minutes (yfinance limitation)

🔧 MCP Features

OAuth 2.1 Authorization

  • PKCE Enforcement: Proof Key for Code Exchange (RFC 7636)
  • JWT RS256 Tokens: Signed access tokens with configurable lifetime
  • Dynamic Client Registration: RFC 7591 compliant
  • Audience Validation: Prevents token passthrough attacks (RFC 8707)
  • Token Refresh: 30-day refresh tokens with rotation

Dual Transport Support

  • Streamable HTTP: /mcp endpoint for modern MCP clients
  • Server-Sent Events: /sse endpoint for legacy compatibility
  • Both share the same FastMCP tools and OAuth protection

OAuth Endpoints

EndpointDescription
POST /oauth/registerDynamic client registration
GET /oauth/authorizeAuthorization endpoint (PKCE required)
POST /oauth/tokenToken exchange
POST /oauth/revokeToken revocation
GET /.well-known/oauth-protected-resourceResource metadata (RFC 9728)
GET /.well-known/oauth-authorization-serverAS metadata (RFC 8414)
GET /.well-known/jwks.jsonPublic keys for JWT verification

🔧 Production Features

  • API Key Auth: Legacy mode with X-API-Key header
  • Rate Limiting: 100 requests/minute per IP (configurable)
  • CORS: Configurable cross-origin resource sharing
  • Health Checks: /health endpoint for monitoring
  • Metrics: /metrics endpoint for request tracking
  • Structured Logging: JSON logs with request IDs and duration
  • Security Headers: X-Frame-Options, X-Content-Type-Options, etc.
  • Non-root User: Runs as unprivileged mcpuser in Docker

📊 Architecture

Production Stack

┌──────────────┐
│  Cloudflare  │ (SSL Termination, DNS, DDoS Protection)
└──────┬───────┘
       │ HTTPS
┌──────▼───────┐
│    nginx     │ (Reverse Proxy, Port 80)
│              │ - Security headers
│              │ - SSE optimization
│              │ - Health checks
└──────┬───────┘
       │ HTTP
┌──────▼───────┐
│  MCP Server  │ (FastMCP + uvicorn, Port 8000)
│              │ - OAuth 2.1 / API Key auth
│              │ - Rate limiting & CORS
│              │ - Logging & metrics
│              │ - 3 MCP tools
└──────┬───────┘
       │
┌──────▼───────┐
│   yfinance   │ (Stock data API)
└──────────────┘

⚙️ Setup and Installation

Prerequisites

  • Docker and Docker Compose
  • Python 3.10+ (for local development)

Development (with hot-reload)

# Start development server
docker-compose -f docker-compose.dev.yml up --build

# Server at http://localhost:8000 with auto-reload on code changes

Testing

# Run full test suite in Docker
docker-compose -f docker-compose.test.yml up --build

# Or run locally with pytest
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
pytest -v

Production

# Configure environment
cp .env.example .env
# Edit .env: Set SERVER_URL, CORS_ORIGINS for your domain

# Start production stack (OAuth mode by default)
docker-compose -f docker-compose.prod.yml up --build -d
# optional: run `ngrok http 8000` to get a public https URL to test with Claude.ai

# Verify deployment
curl http://localhost/health
curl http://localhost/metrics

Authentication Modes (via AUTH_MODE env var):

ModeDescription
oauthOAuth 2.1 Bearer tokens (default, recommended)
apikeyLegacy API key via X-API-Key header
dualBoth OAuth and API key supported
Full Environment Variables Reference

Authentication

VariableDefaultDescription
AUTH_MODEoauthAuthentication mode: oauth, apikey, or dual
SERVER_URLhttp://localhost:8000OAuth issuer and audience URL
MCP_API_KEY-API key (required for apikey/dual modes)

OAuth Tokens

VariableDefaultDescription
ACCESS_TOKEN_LIFETIME900Access token lifetime in seconds (15 min)
REFRESH_TOKEN_LIFETIME2592000Refresh token lifetime in seconds (30 days)
OAUTH_SCOPESmcp:read,mcp:toolsSupported OAuth scopes

JWT Keys (auto-generated if empty)

VariableDescription
JWT_PRIVATE_KEYRSA private key for signing
JWT_PUBLIC_KEYRSA public key for verification

Server Configuration

VariableDefaultDescription
HOST0.0.0.0Bind address
PORT8000Server port
LOG_LEVELINFOLogging level
RELOADfalseAuto-reload (dev only)
RATE_LIMIT_PER_MINUTE100Rate limit per IP
CORS_ORIGINS*Allowed origins (comma-separated)

🧩 API Endpoints

EndpointAuthDescription
/mcpOAuthMCP Streamable HTTP transport
/sseOAuthMCP SSE transport
/healthNoneHealth check
/metricsNoneServer metrics
/oauth/*NoneOAuth endpoints
/.well-known/*NoneOAuth metadata

For detailed API documentation, see .

👉🏻 MCP Tools

get_stock_price

Get current stock price for a Thai stock symbol.

{
  "symbol": "AOT",
  "price": 73.50,
  "change": 0.50,
  "change_percent": 0.68,
  "volume": 12345678,
  "timestamp": "2025-01-15T10:30:00"
}

get_stock_info

Get detailed company information.

{
  "symbol": "AOT",
  "name": "Airports of Thailand PCL",
  "sector": "Industrials",
  "industry": "Airport Services",
  "market_cap": 500000000000,
  "description": "..."
}

search_stocks

Search for stocks by symbol or company name.

{
  "query": "bank",
  "results": [
    {"symbol": "KBANK", "name": "Kasikornbank PCL"},
    {"symbol": "BBL", "name": "Bangkok Bank PCL"},
    {"symbol": "SCB", "name": "Siam Commercial Bank PCL"}
  ]
}

🤖 Claude Desktop Integration

Local Development (stdio)

For direct Python execution with Claude Desktop:

{
  "mcpServers": {
    "thailand-stocks": {
      "command": "/path/to/venv/bin/python",
      "args": ["/path/to/mcp-server-demo/main.py"],
      "env": {}
    }
  }
}

Production (HTTP/SSE with OAuth)

For remote access to deployed server:

{
  "mcpServers": {
    "thailand-stocks": {
      "url": "https://your-domain.com/sse",
      "headers": {
        "Authorization": "Bearer <your-access-token>"
      }
    }
  }
}

Claude.ai Custom Connector

  1. Deploy server to cloud with public HTTPS URL
  2. In Claude.ai: Settings → Custom Connectors → Add https://your-domain.com/mcp
  3. Claude.ai auto-discovers OAuth metadata and handles authorization
Configure for Claude.ai

Full guide:

📚 Documentation

GuideDescription
Complete documentation directory
Docker configuration and deployment
Custom Connector setup
OAuth 2.1 technical deep dive
Complete API documentation
Common issues and fixes

🔒 Security

This implementation follows the MCP Authorization Specification:

  • PKCE Enforcement: All authorization flows require PKCE
  • JWT RS256 Signing: Tokens signed with RSA keys
  • Audience Validation: Prevents token passthrough attacks
  • WWW-Authenticate Headers: RFC 9728 compliant
  • Rate Limiting: Per-IP request limits
  • CORS: Configurable origin restrictions

Production Status: ✅ READY FOR DEPLOYMENT

🛠️ Adding New MCP Tools

To add new MCP tools, follow this pattern in src/server_oauth.py:

1. Define the Tool Function

from typing import Annotated

@mcp.tool()
async def your_new_tool(
    param1: Annotated[str, "Description of parameter 1"],
    param2: Annotated[int, "Description of parameter 2 (optional)"] = 10
) -> dict:
    """Brief description of what the tool does.

    This docstring becomes the tool description visible to MCP clients.
    """
    logger.info(f"your_new_tool called with param1: {param1}")

    # Call your business logic (keep tool handlers thin)
    result = your_service.do_something(param1, param2)

    # Log outcome
    if result.get("success"):
        logger.info(f"Success: {result}")
    else:
        logger.warning(f"Failed: {result.get('error')}")

    return result

2. Key Requirements

RequirementDescription
DecoratorUse @mcp.tool() to register the function
AsyncFunction must be async def
Annotated paramsUse Annotated[type, "description"] for auto-generated schemas
DocstringFirst line becomes the tool description for clients
Return dictReturn {"success": True/False, ...} pattern
LoggingLog inputs and outcomes for debugging

3. Business Logic Separation

Keep tool handlers thin - delegate to service modules:

src/
├── server_oauth.py      # Tool handlers (thin layer)
├── stock_service.py     # Business logic for stock tools
└── your_service.py      # Business logic for your new tools

4. Testing

Add tests in tests/test_server.py:

@pytest.mark.asyncio
async def test_your_new_tool():
    result = await your_new_tool("test_param")
    assert result["success"] is True