Skip to content

helmforgedev/fastmcp-server

Repository files navigation

fastmcp-server

SafeSkill 50/100 Production-ready FastMCP server image with dynamic loading of tools, resources, prompts, and knowledge bases from multiple sources.

Run your own MCP server anywhere — Docker, Compose, Swarm, Kubernetes, or any container runtime. Define tools in Python, mount them, and your server is live.

Quick Start

# Create a tool
mkdir -p tools
cat > tools/hello.py << 'EOF'
def greet(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"
EOF

# Run the server
docker run -d \
  -p 8000:8000 \
  -v $(pwd)/tools:/app/inline/tools \
  docker.io/helmforge/fastmcp-server:0.4.0

Your MCP server is now available at http://localhost:8000/mcp. The built-in Web UI is at http://localhost:8000/ui.

How It Works

The server loads MCP components (tools, resources, prompts, knowledge) from a workspace directory. On startup, it syncs files from one or more sources into the workspace, registers everything with FastMCP, and exposes the server over HTTP.

Sources (Inline, S3, Git)
        │
        ▼
  /app/workspace/
  ├── tools/*.py         → registered as MCP tools
  ├── resources/*.py     → registered as MCP resources
  ├── prompts/*.py       → registered as MCP prompts
  └── knowledge/*        → served as knowledge:// resources
        │
        ▼
  FastMCP Server (:8000/mcp)
  ├── /ui               → Web dashboard
  ├── /healthz          → Liveness probe
  ├── /readyz           → Readiness probe
  ├── /startupz         → Startup probe
  ├── /debug/info       → Server diagnostics
  ├── /metrics          → Prometheus metrics (opt-in)
  └── /api/*            → JSON API for UI

Sources

The image supports three data sources with merge precedence (highest first):

Source Best For Limit
Inline (volume mount at /app/inline/) Quick setup, small configs Host filesystem
S3 (AWS S3, MinIO, Cloudflare R2) Teams, CI/CD pipelines, large knowledge bases Unlimited
Git (any HTTPS repo) Version-controlled tools, collaboration Repo size

All sources can be combined. Inline always wins on conflicts.

Running with Docker Compose

services:
  mcp-server:
    image: docker.io/helmforge/fastmcp-server:0.4.0
    ports:
      - "8000:8000"
    volumes:
      - ./tools:/app/inline/tools
      - ./resources:/app/inline/resources
      - ./prompts:/app/inline/prompts
      - ./knowledge:/app/inline/knowledge
    environment:
      MCP_SERVER_NAME: my-mcp-server
      MCP_AUTH_TYPE: bearer
      MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN}
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8000/healthz"]
      interval: 30s
      timeout: 5s
      retries: 3

Running with S3 (MinIO example)

services:
  mcp-server:
    image: docker.io/helmforge/fastmcp-server:0.4.0
    ports:
      - "8000:8000"
    environment:
      SOURCE_S3_ENABLED: "true"
      SOURCE_S3_ENDPOINT: http://minio:9000
      SOURCE_S3_BUCKET: mcp-tools
      SOURCE_S3_ACCESS_KEY: minioadmin
      SOURCE_S3_SECRET_KEY: minioadmin

  minio:
    image: docker.io/minio/minio:RELEASE.2025-04-03T14-56-28Z
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin

Running with Git Source

docker run -d \
  -p 8000:8000 \
  -e SOURCE_GIT_ENABLED=true \
  -e SOURCE_GIT_REPOSITORY=https://github.com/myorg/mcp-tools.git \
  -e SOURCE_GIT_BRANCH=main \
  docker.io/helmforge/fastmcp-server:0.4.0

For private repos, set SOURCE_GIT_TOKEN with a personal access token.

Writing Tools

Create a .py file in tools/. By default, every public function defined in that module becomes an MCP tool:

def get_weather(city: str) -> str:
    """Get current weather for a city."""
    import httpx
    return httpx.get(f"https://wttr.in/{city}?format=3").text

def roll_dice(sides: int = 6) -> int:
    """Roll a die with the given number of sides."""
    import random
    return random.randint(1, sides)

Utility modules are supported too. Files named like *_helpers.py are skipped by default. For any other utility module, keep helpers private (_helper) or disable auto-registration explicitly:

__mcp_auto_register__ = False

def evidence_true(data, *keys):
    return True

If you want exact control over exports, declare a TOOLS allowlist:

TOOLS = ["deploy"]

def deploy(service: str, version: str) -> str:
    """Deploy a service."""
    return f"Deployed {service}@{version}"

def helper() -> str:
    return "not registered"

Tool Metadata

Add optional module-level variables to control tool registration:

__tags__ = {"devops", "production"}        # Categorization tags
__timeout__ = 30.0                          # Execution timeout (seconds)
__annotations_mcp__ = {                     # MCP behavior hints
    "destructiveHint": True,
    "title": "Deploy Service"
}

def deploy(service: str, version: str) -> str:
    """Deploy a service to production."""
    return f"Deployed {service}@{version}"

Async Tools

Both async def and def functions work. Async is preferred for I/O-bound operations:

async def fetch_url(url: str) -> str:
    """Fetch content from a URL."""
    import httpx
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.text

Structured Output

Return ToolResult for full control over response format:

from fastmcp.tools.tool import ToolResult

def analyze(data: str) -> ToolResult:
    """Analyze data with structured output."""
    return ToolResult(
        content="Analysis complete",
        structured_content={"word_count": len(data.split())},
        meta={"version": "1.0"}
    )

Writing Resources

Create a .py file in resources/ with a RESOURCE_URI constant and a handler function:

import json

RESOURCE_URI = "config://app"

def get_config() -> str:
    """Application configuration."""
    return json.dumps({"version": "1.0", "env": "production"}, indent=2)

Important: Resource handlers must return str, bytes, or list[ResourceContent]. Returning a dict directly will cause a serialization error.

Resource Templates

Use {param} placeholders in URIs for parameterized resources:

import json

RESOURCE_URI = "users://{user_id}/profile"

def get_profile(user_id: str) -> str:
    """Get user profile by ID."""
    return json.dumps({"user_id": user_id, "name": f"User {user_id}"}, indent=2)

Multiple Resources per File

Use a RESOURCES dict to register multiple resources from one file:

import json

RESOURCES = {
    "status://health": "get_health",
    "status://version": "get_version",
}

def get_health() -> str:
    return json.dumps({"status": "ok"}, indent=2)

def get_version() -> str:
    return "1.0.0"

Writing Prompts

Create a .py file in prompts/. Every public function becomes an MCP prompt:

def summarize(text: str) -> str:
    """Summarize the provided text."""
    return f"Please provide a concise summary of:\n\n{text}"

Knowledge Base

Any file placed in knowledge/ is served as a knowledge:// resource. Supports text, markdown, JSON, YAML, or any UTF-8 file.

knowledge/
├── product-overview.md
├── api-reference.json
└── troubleshooting/
    ├── common-errors.md
    └── faq.md

These become accessible as knowledge://product-overview.md, knowledge://troubleshooting/common-errors.md, etc.

Authentication

Type Variables Use Case
None MCP_AUTH_TYPE=none Development, internal networks
Bearer MCP_AUTH_TYPE=bearer + MCP_AUTH_TOKEN API keys, service accounts
JWT MCP_AUTH_TYPE=jwt + MCP_AUTH_JWT_* OAuth/OIDC, enterprise SSO

Web UI

The embedded dashboard at /ui provides:

  • Dashboard — Server name, version, uptime, component counts, source status
  • Tools Explorer — All registered tools with descriptions, parameters, tags, timeout
  • Resources Explorer — Resources and templates with URIs, MIME types
  • Prompts Explorer — All prompts with descriptions

Auto-refreshes every 15 seconds. Disable with MCP_UI_ENABLED=false.

Observability

Health Endpoints

Endpoint Purpose When 200
GET /healthz Liveness Always (process is running)
GET /readyz Readiness Sources synced + components loaded
GET /startupz Startup Full initialization complete

Diagnostics

GET /debug/info returns full server diagnostics: version, FastMCP version, uptime, component details, source status, auth type, and configuration.

Prometheus Metrics

Enable with MCP_METRICS_ENABLED=true. Exposes at /metrics:

  • mcp_tools_total — Number of registered tools
  • mcp_resources_total — Number of registered resources
  • mcp_prompts_total — Number of registered prompts
  • mcp_knowledge_total — Number of knowledge files
  • mcp_tool_calls_total{tool} — Tool invocation counter
  • mcp_tool_duration_seconds{tool} — Tool execution duration histogram
  • mcp_tool_errors_total{tool} — Tool error counter
  • mcp_sources_sync_total{source,status} — Source sync operations
  • mcp_auth_requests_total{result} — Auth attempt counter

Structured Logging

Set LOG_FORMAT=json for JSON-structured logs compatible with Loki, ELK, CloudWatch, and Datadog:

{
  "timestamp": "2026-04-05T10:30:00+00:00",
  "level": "INFO",
  "logger": "fastmcp-server.builder",
  "message": "Registered tool: greet"
}

Environment Variables

Server

Variable Default Description
MCP_SERVER_NAME fastmcp-server Server display name
MCP_HOST 0.0.0.0 Listen address
MCP_PORT 8000 Listen port
MCP_PATH /mcp HTTP endpoint path
MCP_WORKSPACE /app/workspace Workspace directory
LOG_LEVEL INFO Logging level
LOG_FORMAT text Log format: text or json
MCP_MASK_ERROR_DETAILS false Hide internal error details from clients
MCP_ON_DUPLICATE_TOOLS warn Duplicate handling: warn, error, replace, ignore
MCP_STRICT_LOADING false Fail on boot if any tool/resource has errors
MCP_UI_ENABLED true Enable built-in Web UI at /ui
MCP_METRICS_ENABLED false Enable Prometheus metrics at /metrics
EXTRA_PIP_PACKAGES Comma-separated pip packages to install at startup

Authentication

Variable Default Description
MCP_AUTH_TYPE none bearer, jwt, or none
MCP_AUTH_TOKEN Bearer token value
MCP_AUTH_JWT_ISSUER JWT issuer
MCP_AUTH_JWT_AUDIENCE JWT audience
MCP_AUTH_JWT_JWKS_URI JWKS endpoint URL

S3 Source

Variable Default Description
SOURCE_S3_ENABLED false Enable S3 sync
SOURCE_S3_ENDPOINT S3-compatible endpoint URL
SOURCE_S3_BUCKET Bucket name
SOURCE_S3_REGION us-east-1 AWS region
SOURCE_S3_PREFIX Key prefix filter
SOURCE_S3_ACCESS_KEY Access key ID
SOURCE_S3_SECRET_KEY Secret access key

Git Source

Variable Default Description
SOURCE_GIT_ENABLED false Enable Git sync
SOURCE_GIT_REPOSITORY Repository HTTPS URL
SOURCE_GIT_BRANCH main Branch to clone
SOURCE_GIT_PATH Subdirectory within the repo
SOURCE_GIT_TOKEN Auth token for private repos

Init Container Pattern

For Kubernetes, separate source syncing from server startup using sync_only.py:

python /app/sync_only.py

This script runs sync_sources() and exits. Use it as a Kubernetes init container to pre-populate the workspace volume before the main server starts.

Deployment Options

Method Docs
Docker / Docker Compose / Swarm This README
Kubernetes (Helm) helmforge/charts — fastmcp-server

Connecting MCP Clients

Once your FastMCP server is running and accessible, connect AI assistants to it as an MCP server.

Claude Code

Add the server to your Claude Code settings (~/.claude/settings.json or project .claude/settings.json):

{
  "mcpServers": {
    "my-mcp-server": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

With bearer authentication:

{
  "mcpServers": {
    "my-mcp-server": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

Codex (VS Code Extension)

Add to your Codex configuration file (~/.codex/config.toml):

[mcp_servers.my-mcp-server]
enabled = true
url = "https://mcp.example.com/mcp"

[mcp_servers.my-mcp-server.http_headers]
Authorization = "Bearer <your-token>"

Without authentication (development):

[mcp_servers.my-mcp-server]
enabled = true
url = "https://mcp.example.com/mcp"

Local Development

When running locally with Docker, use http://localhost:8000/mcp as the URL and omit authentication headers if MCP_AUTH_TYPE=none.

License

Apache-2.0 — see LICENSE.

About

Production-ready FastMCP server with dynamic tool loading from inline, S3, and Git sources

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors