Skip to main content
Glama
victor-velazquez-ai

Enterprise MCP Template

Enterprise MCP Template

A production-ready template for building enterprise-grade MCP (Model Context Protocol) servers with OAuth 2.0 authentication, based on battle-tested patterns from the Luxsant NetSuite MCP project.

What is MCP? MCP is a standard protocol that lets AI assistants (Claude, Copilot, etc.) call "tools" (functions) on remote servers. Think of it as a standardized API that AI models know how to use.


Table of Contents


Related MCP server: production-grade-mcp-agentic-system

Quick Start

1. Clone and rename

git clone https://github.com/YOUR_USER/enterprise-mcp-template.git my-cool-mcp
cd my-cool-mcp

2. Rename the package

# Rename the source directory
mv src/my_mcp_server src/my_cool_mcp

# Find and replace all occurrences:
#   "my_mcp_server"  -> "my_cool_mcp"
#   "my-mcp-server"  -> "my-cool-mcp"
#   "{{PROJECT_NAME}}" -> "My Cool MCP"
#   "{{AUTHOR}}"       -> "Your Name"

3. Configure environment

cp .env.example .env
# Edit .env with your upstream API credentials

4. Install and run

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

# Install dependencies
pip install -e ".[dev]"

# Run locally (stdio mode for Claude Desktop)
python -m my_cool_mcp

# Run as HTTP server
python -m my_cool_mcp http

# Run tests
pytest

5. Deploy

# Docker build
docker compose up --build

# Or deploy to Azure Web App
az webapp up --name my-cool-mcp --runtime PYTHON:3.11

Architecture Overview

AI Client (Claude Desktop / VS Code / Custom)
    |
    | MCP Protocol (stdio / SSE / HTTP)
    |
+---v----------------------------------------------+
|  MCP Server (server.py)                          |
|  +--------------------------------------------+  |
|  | OAuth 2.0 Proxy (OAuthProxy)               |  |
|  | - Handles user authentication               |  |
|  | - Manages proxy tokens                      |  |
|  | - Token exchange with upstream              |  |
|  +--------------------------------------------+  |
|  +--------------------------------------------+  |
|  | MCP Tools (@mcp.tool() functions)           |  |
|  | - create_record()                           |  |
|  | - get_record()                              |  |
|  | - update_record()                           |  |
|  | - delete_record()                           |  |
|  | - execute_query()                           |  |
|  +--------------------------------------------+  |
|  +--------------------------------------------+  |
|  | HTTP Routes (/health, /debug/*)             |  |
|  +--------------------------------------------+  |
+--------------------------------------------------+
    |
    | HTTPS + Bearer Token
    |
+---v----------------------------------------------+
|  API Client (api_client.py)                      |
|  - HTTP requests with retry logic                |
|  - Response parsing                              |
|  - Error handling                                |
+--------------------------------------------------+
    |
    | REST API calls
    |
+---v----------------------------------------------+
|  Upstream Service (NetSuite, Salesforce, etc.)   |
+--------------------------------------------------+

Module Dependency Flow

__main__.py / wsgi.py
    -> server.py      (main server, tools, OAuth, routes)
       -> api_client.py   (HTTP client for upstream API)
          -> config.py     (environment configuration)
          -> models.py     (Pydantic data models)
          -> exceptions.py (error hierarchy)
       -> auth.py          (token caching & refresh)
          -> config.py
          -> exceptions.py
       -> utils.py         (logging, sanitization, helpers)

Project Structure

enterprise-mcp-template/
|-- .env.example              # Environment variable template
|-- .gitignore                # Git ignore rules
|-- docker-compose.yml        # Docker Compose for local dev
|-- Dockerfile                # Multi-stage production Docker build
|-- LICENSE                   # MIT License
|-- main.py                   # Root smoke test (not the entry point)
|-- pyproject.toml            # Python project configuration
|-- README.md                 # This file
|-- CLAUDE.md                 # AI agent instructions
|-- requirements.txt          # Production dependencies
|-- startup.sh                # Azure Web App startup script
|
|-- docs/                     # Documentation
|   |-- guide.pdf             # PDF version of this guide
|
|-- samples/                  # Example payloads
|   |-- example_payload.json  # Sample API request payload
|
|-- src/
|   |-- my_mcp_server/        # Main package (RENAME THIS)
|       |-- __init__.py       # Package init with lazy imports
|       |-- __main__.py       # CLI entry point (python -m my_mcp_server)
|       |-- server.py         # *** MAIN FILE *** MCP server + tools + OAuth
|       |-- api_client.py     # HTTP client for upstream API
|       |-- auth.py           # Token management (LRU cache + refresh)
|       |-- config.py         # Environment-based configuration
|       |-- models.py         # Pydantic data models
|       |-- exceptions.py     # Exception hierarchy
|       |-- utils.py          # Utility functions
|       |-- wsgi.py           # ASGI entry point for production
|       |-- static/
|           |-- index.html    # Browser-friendly status page
|
|-- tests/                    # Test suite
    |-- __init__.py
    |-- test_config.py        # Config tests
    |-- test_models.py        # Model tests
    |-- test_auth.py          # Auth/token tests

How to Create a New MCP Server

Step 1: Global Find & Replace

Find

Replace With

Example

my_mcp_server

Your package name (snake_case)

salesforce_mcp

my-mcp-server

Your package name (kebab-case)

salesforce-mcp

{{PROJECT_NAME}}

Display name

Salesforce MCP Enterprise

{{AUTHOR}}

Your name/org

El Paso Labs

UPSTREAM_

Your service prefix

SALESFORCE_

example.com

Your API domain

salesforce.com

Step 2: Update OAuth Endpoints (server.py)

In _build_auth_provider(), update:

# BEFORE (template):
auth_endpoint = f"https://{account_id}.app.example.com/oauth2/authorize"
token_endpoint = f"https://{account_id}.api.example.com/oauth2/token"
api_scopes = ["api_access"]

# AFTER (example for NetSuite):
auth_endpoint = f"https://{account_id}.app.netsuite.com/app/login/oauth2/authorize.nl"
token_endpoint = f"https://{account_id}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token"
api_scopes = ["rest_webservices"]

Step 3: Update API URL Patterns (config.py, api_client.py)

In config.py UpstreamAPIConfig.build_api_base_url():

# BEFORE:
return f"https://{self.account_id}.api.example.com/v1"

# AFTER (NetSuite):
return f"https://{self.account_id}.suitetalk.api.netsuite.com/services/rest/record/v1"

Step 4: Define Your MCP Tools (server.py)

Replace the generic CRUD tools with domain-specific ones:

@mcp.tool()
async def create_customer(
    customer_data: Dict[str, Any],
    account_id: Optional[str] = None,
) -> Dict[str, Any]:
    """
    Create a new customer in Salesforce.
    
    Args:
        customer_data: Customer fields (Name, Email, Phone, etc.)
        account_id: Salesforce org ID
    
    Returns:
        Structured response with the created customer's ID.
    """
    token = _get_oauth_token()
    async with _get_client(account_id=account_id) as client:
        response = await client.create_record(
            access_token=token,
            record_type="customer",
            payload=customer_data,
        )
        return _serialize_response(response)

Step 5: Update Models (models.py)

Replace example models with your domain entities:

class CustomerPayload(BaseModel):
    name: str = Field(..., description="Customer name")
    email: Optional[str] = Field(default=None)
    phone: Optional[str] = Field(default=None)
    # ... your fields

Step 6: Test and Deploy

# Run tests
pytest

# Local HTTP test
python -m your_package http
# Visit http://localhost:8000/health

# Docker
docker compose up --build

OAuth 2.0 Authentication Deep Dive

How OAuth Works in This Template

1. AI Client connects to MCP server
   |
2. MCP server redirects user to upstream login page
   |  (via OAuthProxy)
   |
3. User logs in at upstream service (NetSuite, Salesforce, etc.)
   |
4. Upstream redirects back with authorization code
   |  -> https://your-server.com/auth/callback?code=ABC123
   |
5. OAuthProxy exchanges code for access token (server-to-server)
   |  POST to token endpoint with client_id + client_secret
   |
6. OAuthProxy stores the real token, gives client a proxy token
   |
7. Client sends proxy token with each MCP tool call
   |
8. OAuthProxy looks up real token, passes to tool function
   |
9. Tool function uses real token to call upstream API

Critical OAuth Configuration

auth = OAuthProxy(
    # WHERE users log in
    upstream_authorization_endpoint=auth_endpoint,
    
    # WHERE we exchange codes for tokens
    upstream_token_endpoint=token_endpoint,
    
    # OUR app's credentials
    upstream_client_id=client_id,
    upstream_client_secret=client_secret,
    
    # HOW we verify proxy tokens
    token_verifier=token_verifier,
    
    # PUBLIC URL for callbacks
    base_url=base_url,
    
    # HOW we send credentials to token endpoint
    # "client_secret_basic" = Authorization header (most APIs)
    # "client_secret_post"  = POST body parameters
    token_endpoint_auth_method="client_secret_basic",
    
    # PKCE handling - CRITICAL!
    # Set to False if upstream handles PKCE with browser directly
    # Set to True if you need to forward PKCE params
    forward_pkce=False,
    
    # OAuth scopes
    valid_scopes=api_scopes,
    
    # Accept any MCP client redirect URI
    allowed_client_redirect_uris=None,
    
    # Sign proxy JWTs with a stable key (set MCP_JWT_SIGNING_KEY in prod!)
    jwt_signing_key=jwt_signing_key,
    
    # Skip our consent screen (upstream has its own)
    require_authorization_consent=False,
    
    # In-memory client storage (resets on restart - intentional)
    client_storage=client_storage,
)

OAuth Gotchas (Lessons Learned)

  1. forward_pkce=False: If your upstream API handles PKCE between itself and the browser, do NOT forward your own PKCE parameters. Your server's code_verifier won't match the browser's code_challenge, causing invalid_grant errors.

  2. required_scopes on DebugTokenVerifier: Without this, clients registered via DCR get scope="" and ALL scope requests are rejected with invalid_scope before reaching the upstream.

  3. MCP_JWT_SIGNING_KEY: Without a stable key, the OAuthProxy generates a random key on each startup. Container restarts invalidate ALL proxy tokens. Always set in production.

  4. MemoryStore for client storage: Resets on restart. This is actually GOOD - prevents stale client registrations from previous deployments.

  5. token_endpoint_auth_method: Test both "client_secret_basic" and "client_secret_post" using the /debug/token-test endpoint. The wrong method gives invalid_client instead of invalid_grant.


Libraries & Dependencies

Library

Version

Purpose

Why This Library

fastmcp

>=3.0.0b2

MCP framework

Only production-grade MCP framework. Handles protocol, OAuth, transport.

httpx

>=0.27.0

HTTP client

Async HTTP client with connection pooling. Superior to requests for async.

pydantic

>=2.0.0

Data validation

Industry standard. Auto-validation, serialization, IDE support.

pydantic-settings

>=2.1.0

Settings management

Pydantic extension for env var parsing.

python-dotenv

>=1.0.0

.env file loading

Loads .env files for local development.

loguru

>=0.7.2

Logging

Enhanced logging (optional, can use stdlib).

gunicorn

>=21.2.0

Process manager

Production WSGI/ASGI server. Multi-worker, graceful restarts.

uvicorn

>=0.27.0

ASGI server

High-performance async HTTP server. Used as gunicorn worker class.

Why FastMCP 3.0?

FastMCP 3.0 is the only production-grade MCP framework available. Key features:

  • Native host/port support in .run()

  • Built-in OAuthProxy for OAuth 2.0 authentication

  • DebugTokenVerifier for development/testing

  • get_access_token() dependency injection

  • Support for three transports: stdio, SSE, HTTP

  • @mcp.tool() decorator for registering tools

  • @mcp.custom_route() for HTTP endpoints

  • Stateless HTTP mode for cloud load balancers

Why httpx over requests?

  • Async support: httpx.AsyncClient works natively with async/await

  • Connection pooling: Reuses TCP connections automatically

  • Timeout control: Granular timeout settings per request

  • HTTP/2 support: Optional HTTP/2 for better performance

  • requests-compatible API: Easy to migrate from requests


Configuration System

All configuration uses environment variables following the 12-Factor App methodology.

Configuration Hierarchy

AppConfig
├── UpstreamAPIConfig   (API connection: URL, credentials, timeouts)
├── TokenStoreConfig    (Token caching: LRU size, expiry buffer)
└── ServerConfig        (Server: name, transport, host, port)

Key Environment Variables

Variable

Required

Default

Description

UPSTREAM_ACCOUNT_ID

Yes*

-

Account/tenant identifier

UPSTREAM_OAUTH_CLIENT_ID

Yes*

-

OAuth client ID

UPSTREAM_OAUTH_CLIENT_SECRET

Yes*

-

OAuth client secret

MCP_SERVER_BASE_URL

Yes*

-

Public URL for OAuth callbacks

MCP_TRANSPORT

No

stdio

Transport: stdio/sse/http

MCP_PORT

No

8000

Server port

MCP_HOST

No

0.0.0.0

Server host binding

TOKEN_CACHE_ENABLED

No

true

Enable token LRU cache

TOKEN_EXPIRY_BUFFER_SECS

No

300

Refresh buffer (seconds)

MCP_JWT_SIGNING_KEY

No

random

Stable JWT key for production

LOG_LEVEL

No

INFO

DEBUG/INFO/WARNING/ERROR

DEBUG

No

false

Enable debug mode

*Required for OAuth authentication. Server runs without auth if missing.

Singleton Pattern

from config import get_config, set_config, reset_config

# Normal usage (reads env vars once, caches globally)
config = get_config()
base_url = config.upstream.build_api_base_url()

# Testing (override with custom config)
set_config(AppConfig(server=ServerConfig(port=9999)))

# Reset (force re-read from env)
reset_config()

MCP Tools Pattern

Every MCP tool follows this exact pattern:

@mcp.tool()
async def my_tool(
    required_param: str,
    optional_param: Optional[str] = None,
    account_id: Optional[str] = None,
    base_url: Optional[str] = None,
) -> Dict[str, Any]:
    """
    Tool description (AI reads this to decide when to use the tool).
    
    Args:
        required_param: Description for AI
        optional_param: Description for AI
        account_id: Account ID (if not preconfigured)
        base_url: Override API URL
    
    Returns:
        Structured response dict with ok, status_code, data, errors.
    """
    # 1. Get OAuth token from MCP session
    token = _get_oauth_token()
    
    # 2. Create API client (async context manager for cleanup)
    async with _get_client(base_url, account_id) as client:
        # 3. Call the appropriate client method
        response = await client.some_method(
            access_token=token,
            ...
        )
        # 4. Serialize and return
        return _serialize_response(response)

Rules for MCP Tools

  1. Return simple Python objects (dict, list, str, number). They're serialized to JSON.

  2. Docstrings matter: AI reads them to decide when/how to use the tool.

  3. Parameter types matter: FastMCP generates JSON Schema from type hints.

  4. Always use _serialize_response(): Provides consistent response format.

  5. Always use async with: Ensures HTTP client cleanup on error.

  6. Add account_id and base_url params: Lets AI clients specify targets dynamically.


API Client Pattern

The API client (api_client.py) handles all HTTP communication:

async with APIClient(base_url="https://api.example.com/v1") as client:
    # Generic CRUD
    response = await client.create_record(token, "customer", payload)
    response = await client.get_record(token, "customer", "123")
    response = await client.update_record(token, "customer", "123", updates)
    response = await client.delete_record(token, "customer", "123")
    
    # Query (if your API supports it)
    response = await client.execute_query(token, "SELECT * FROM Customer")

Retry Logic

Attempt 1: Immediate
Attempt 2: Wait 0.5s  (backoff_factor * 2^0)
Attempt 3: Wait 1.0s  (backoff_factor * 2^1)
Attempt 4: Wait 2.0s  (backoff_factor * 2^2)

Retries on: 429, 500, 502, 503, 504, timeouts, connection errors. Does NOT retry: 400, 401, 403, 404.


Token Management

LRU Token Cache

Token Cache (max 100 entries)
+---------+------------------+-----------+
| Key     | Token            | Expires   |
+---------+------------------+-----------+
| sha256  | eyJhbG...        | 1hr       | <- Most recently used
| sha256  | eyJxyz...        | 45min     |
| sha256  | eyJabc...        | 30min     |
| ...     | ...              | ...       |
| sha256  | eyJold...        | 10min     | <- Least recently used (evicted first)
+---------+------------------+-----------+

Token Lifecycle

1. User authenticates -> access_token + refresh_token
2. Token cached with SHA-256 key
3. On each API call: check if cached token is still valid
4. If expired (with 5-min buffer): attempt refresh
5. If refresh succeeds: cache new token
6. If refresh fails: user must re-authenticate

Exception Hierarchy

MCPServerError (catch-all)
├── ConfigurationError
│   ├── MissingConfigurationError
│   └── InvalidConfigurationError
├── AuthenticationError
│   ├── TokenError
│   │   ├── TokenExpiredError
│   │   ├── TokenRefreshError
│   │   └── TokenValidationError
│   └── InvalidCredentialsError
├── APIError
│   ├── ConnectionError
│   ├── TimeoutError
│   ├── RateLimitError
│   ├── NotFoundError
│   ├── ValidationError
│   ├── PermissionError
│   └── ServerError
└── RecordError
    ├── RecordNotFoundError
    ├── RecordValidationError
    └── DuplicateRecordError

Every exception has to_dict() for JSON serialization and a machine-readable code field.


Deployment Guide

Local Development (stdio)

python -m my_mcp_server
# Communicates via stdin/stdout - used by Claude Desktop

Local HTTP Server

python -m my_mcp_server http
# Available at http://localhost:8000
# Health: http://localhost:8000/health
# MCP: http://localhost:8000/mcp

Docker

# Build and run
docker compose up --build

# Or standalone
docker build -t my-mcp .
docker run -p 8000:8000 --env-file .env my-mcp

Azure Web App

# Option 1: Container deployment
az webapp create --name my-mcp --plan my-plan --deployment-container-image-name my-mcp:latest

# Option 2: Source deployment
az webapp up --name my-mcp --runtime PYTHON:3.11

# Set environment variables in Azure Portal:
# Settings -> Configuration -> Application settings

Required Azure settings:

  • All UPSTREAM_* env vars

  • MCP_SERVER_BASE_URL=https://my-mcp.azurewebsites.net

  • MCP_TRANSPORT=http

  • MCP_JWT_SIGNING_KEY=<generate with: python -c "import secrets; print(secrets.token_hex(32))">

Claude Desktop Configuration

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.azurewebsites.net/mcp"
    }
  }
}

Testing

# Run all tests
pytest

# With coverage
pytest --cov=my_mcp_server --cov-report=html

# Specific test file
pytest tests/test_config.py -v

# Run with verbose output
pytest -v -s

Test Structure

  • test_config.py - Environment parsing, config validation, singleton

  • test_models.py - Pydantic model validation, serialization, factories

  • test_auth.py - Token caching, expiry checking, LRU eviction


Best Practices & Gotchas

DO

  • Always use async with for API clients - ensures HTTP connection cleanup

  • Always sanitize sensitive data before logging - use sanitize_for_logging()

  • Always return APIResponse from tools - consistent interface for AI clients

  • Set MCP_JWT_SIGNING_KEY in production - prevents token invalidation on restart

  • Log to stderr, not stdout - stdout is reserved for MCP protocol in stdio mode

  • Use UTC for all timestamps - datetime.now(timezone.utc)

  • Add account_id parameter to tools - lets AI specify targets dynamically

  • Write descriptive docstrings - AI reads them to decide tool usage

  • Use environment variables for ALL config - never hardcode credentials

DON'T

  • Don't log raw tokens - use mask_token() helper

  • Don't hardcode API URLs - use config.py and env vars

  • Don't catch bare Exception - use the exception hierarchy

  • Don't use requests library - use httpx for async support

  • Don't run on stdout in stdio mode - it corrupts MCP protocol

  • Don't skip the token expiry buffer - tokens can expire mid-request

  • Don't use functools.lru_cache for tokens - need expiry-aware eviction

  • Don't forward PKCE if upstream handles it - causes invalid_grant


Troubleshooting

OAuth Issues

  1. Visit /health - shows if OAuth is configured and which env vars are set

  2. Visit /debug/logs?filter=oauth - shows OAuth flow logs

  3. Visit /debug/token-test - tests both auth methods against upstream

  4. Visit /debug/server-info - shows if container restarted (lost OAuth state)

Common Errors

Error

Cause

Fix

invalid_grant

PKCE mismatch or expired code

Set forward_pkce=False

invalid_client

Wrong auth method or credentials

Try both auth methods via /debug/token-test

invalid_scope

Missing required_scopes on verifier

Add required_scopes to DebugTokenVerifier

No authenticated session

User not logged in

Connect via MCP client with OAuth support

Token invalidated on restart

No stable JWT key

Set MCP_JWT_SIGNING_KEY env var

Debug Endpoints

Endpoint

Purpose

GET /health

Server status, config, OAuth info

GET /debug/logs

Recent server logs (in-memory buffer)

GET /debug/logs?filter=oauth

OAuth-specific logs

GET /debug/server-info

Instance ID, uptime, OAuth state counts

GET /debug/token-test

Test token exchange with upstream


License

MIT License - See LICENSE for details.

Available Tools

8 tools
create_recordC

Create a new record in the upstream API.

TEMPLATE: Replace this docstring with your domain-specific documentation. The docstring is shown to AI clients to help them understand when and how to use this tool.

Args: record_type: The type of record to create (e.g., "customer", "invoice") payload: JSON payload for the record. account_id: Account ID (required if not configured on server). base_url: Optional full API URL (overrides account_id).

Returns: Structured response with ok, status_code, data, errors, request_id.

ParametersJSON Schema
NameRequiredDescriptionDefault
record_typeYes
payloadYes
account_idNo
base_urlNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but omits critical safety context: no idempotency guarantees, no conflict behavior (what if record exists?), no rate limits, and no auth scope requirements. The Returns section adds value by describing the response structure beyond the existence of an output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description wastes prime real estate with meta-commentary ('Replace this docstring with your domain-specific documentation') intended for developers rather than AI agents. While the Args/Returns structure is logical, the template noise undermines front-loading of essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The presence of the TEMPLATE warning indicates incomplete documentation. While the Args section covers parameters and Returns covers output, the description lacks domain specificity (what kind of records? what API?) and operational context (error scenarios, validation rules) expected for a 4-parameter mutation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Despite 0% schema description coverage, the Args section compensates effectively by documenting all 4 parameters with types, optionality logic (account_id 'required if not configured on server'), and usage examples (record_type examples: 'customer', 'invoice'). This is strong compensation for the schema deficiency.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a clear verb-resource pair ('Create a new record'), but the description is polluted with template placeholder text ('TEMPLATE: Replace this docstring...') that signals incomplete implementation. It fails to distinguish from sibling update_record or clarify record creation scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides no explicit guidance on when to use create_record versus update_record or execute_query. The only contextual clue is in the account_id parameter description mentioning server configuration, but this is implicit rather than prescriptive guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_recordA

Delete a record from the upstream API.

Args: record_type: The type of record (e.g., "customer", "invoice") record_id: Internal ID of the record. account_id: Account ID (required if not configured on server). base_url: Optional full API URL (overrides account_id).

Returns: Structured response with deletion result.

ParametersJSON Schema
NameRequiredDescriptionDefault
record_typeYes
record_idYes
account_idNo
base_urlNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, description carries full burden. It successfully discloses conditional auth requirements (account_id only required if not server-configured) and parameter precedence (base_url overrides account_id). However, it fails to state that deletion is permanent/irreversible or describe error conditions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Uses structured docstring format (Args/Returns) that is front-loaded with the core action. Slightly verbose given the Returns section repeats what the output schema already provides, but every line conveys useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 4 parameters and 0% schema coverage, the description successfully documents all inputs. Acknowledges return value existence without duplicating the output schema. Only gap is missing warning about destructive nature given lack of destructiveHint annotation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema has 0% description coverage, but the description comprehensively documents all 4 parameters: record_type (with examples), record_id (semantics), account_id (conditional requirement), and base_url (override behavior). Fully compensates for schema deficiency.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States specific verb (Delete) and resource (record) and distinguishes from siblings create_record/update_record/get_record. 'Upstream API' is slightly vague context, but the core purpose is unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides no guidance on when to use this versus update_record or create_record, nor does it warn that deletion is permanent. The Args section explains parameter mechanics but not usage strategy.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

execute_queryC

Execute a query against the upstream API.

TEMPLATE: Replace with your API's query language documentation. Examples: SuiteQL (NetSuite), SOQL (Salesforce), GraphQL, OData, etc.

Args: query: Query string in the upstream API's query language. account_id: Account ID (required if not configured on server). base_url: Optional full API URL (overrides account_id). limit: Maximum number of results. offset: Starting offset for pagination.

Returns: Structured response with query results.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
account_idNo
base_urlNo
limitNo
offsetNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, leaving full burden to description. States it returns 'Structured response with query results' but fails to disclose if this is read-only (implied by name but not explicit), rate limits, authentication requirements, or error handling behavior. Template nature suggests incomplete behavioral documentation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with clear Args/Returns sections, but contains inappropriate template placeholder text ('TEMPLATE: Replace with...') that wastes space and reduces clarity. Otherwise efficiently organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Describes the 5 parameters and mentions return structure, adequate for a query tool. However, lacks safety disclosures (despite being likely read-only) and edge case handling given zero annotations and incomplete template status.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the Args section effectively compensates by documenting all 5 parameters: query language syntax, account_id conditional requirements, base_url override behavior, and pagination controls (limit/offset). Adds meaning beyond the raw schema types.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the tool executes queries against an upstream API and lists example query languages (SuiteQL, SOQL, GraphQL), but contains placeholder template text ('TEMPLATE: Replace with...') indicating incomplete customization. Distinguishes from CRUD siblings (create_record, delete_record) by nature of being a query operation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus alternatives like get_record (which likely retrieves single records by ID). The Args section explains parameter mechanics but not selection criteria or prerequisites for using this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_recordA

Fetch a record from the upstream API by ID.

Args: record_type: The type of record (e.g., "customer", "invoice") record_id: Internal ID of the record. account_id: Account ID (required if not configured on server). base_url: Optional full API URL (overrides account_id). expand_sub_resources: Include full sublist/related data.

Returns: Structured response with record data.

ParametersJSON Schema
NameRequiredDescriptionDefault
record_typeYes
record_idYes
account_idNo
base_urlNo
expand_sub_resourcesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the return value structure ('Structured response with record data') but omits error handling (404 behavior), authentication requirements, rate limits, or caching behavior that would help an agent understand operational constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The docstring-style format with Args and Returns sections is structured and readable. Every sentence provides value, though the formatting is slightly unconventional for MCP descriptions. Given the zero schema coverage, the verbosity is necessary and appropriate.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Considering the 5 parameters with zero schema coverage, the description successfully documents all inputs. Since an output schema exists, the brief Returns statement is sufficient. It lacks only error scenario documentation to be fully complete for a retrieval operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage, making the description's Args section critical. It excellently compensates by documenting all 5 parameters: record_type includes examples ('customer', 'invoice'), account_id explains conditional requirement logic, and base_url clarifies the override relationship to account_id.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool fetches a record 'by ID' from an upstream API, using specific verb (Fetch) and resource (record). The 'by ID' qualifier helps distinguish it from sibling execute_query, though it doesn't explicitly name the alternative.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance on when to use this tool versus alternatives like execute_query (for queries) or get_record_schema (for metadata). The description only implies usage through the parameter documentation without stating when-not-to-use or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_record_schemaC

Get the schema/structure for record payloads. Returns documentation about expected payload formats.

TEMPLATE: Replace with your domain-specific schema documentation. This helps AI clients construct correct payloads.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It correctly indicates this is a read operation that returns documentation, but lacks details on the format of returned schema (JSON Schema?, OpenAPI?), caching behavior, or whether the schema is static or dynamic.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description contains obvious template placeholder text ('TEMPLATE: Replace with your domain-specific schema documentation') that serves no purpose for the AI agent and indicates an unfinished implementation. This meta-text wastes tokens and creates confusion.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

While the context signals indicate an output schema exists (reducing the description's burden to explain return values), the presence of template text suggests the tool definition is incomplete. For a schema introspection tool with no parameters, the description should specify what format the schema documentation takes (e.g., JSON Schema, field listings).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema correctly reflects this with 'additionalProperties: false'. Per evaluation guidelines, zero-parameter tools receive a baseline score of 4 since there are no parameter semantics to describe beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first two sentences clearly state the tool retrieves schema/structure information for record payloads. However, the inclusion of 'TEMPLATE: Replace with your domain-specific schema documentation' indicates placeholder content that pollutes the description and creates ambiguity about whether this is a fully implemented tool or a stub.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides implied usage guidance by stating it 'helps AI clients construct correct payloads,' suggesting it should be used when preparing data for create/update operations. However, it lacks explicit guidance on when to prefer this over siblings (e.g., 'Call this before create_record').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

healthA

Health check for the MCP server. Returns server status, configuration validity, and version information.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden. It successfully discloses return payload contents (server status, configuration validity, version information). Could improve by explicitly stating it's read-only/safe, but adequately covers the key behavioral trait for a health endpoint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with zero waste. First establishes purpose, second details return values. Perfectly front-loaded and appropriately sized for tool complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 0 parameters and existence of output schema (per context signals), description provides sufficient context by disclosing the nature of returned information. Complete for a simple health-check tool, though mentioning read-only nature would provide full coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters present. Per rubric, 0 params = baseline 4. The description correctly avoids inventing parameter semantics where none exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ('Health check') + resource ('MCP server') clearly stated. The return value details (status, configuration validity, version) effectively distinguish it from sibling 'ping' (likely a simple connectivity check), establishing a distinct purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit 'when to use' or comparison to sibling alternatives provided. While 'health check' is self-explanatory, the description doesn't state prerequisites or when to prefer this over 'ping' for connectivity checks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

pingA

Simple ping/pong health check. Returns 'pong' if the server is responsive.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the return value ('pong') and the basic operation, but lacks details on failure modes, timeout behavior, authentication requirements, or side effects that would be expected for a complete behavioral profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence of 11 words that immediately communicates the tool's purpose without waste. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has zero parameters, an output schema exists (reducing the need to document return values), and the operation is trivial, the description is adequate. It would benefit from clarifying the distinction from the 'health' sibling tool, but is otherwise complete for its complexity level.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters and 100% coverage. Per the rubric, zero-parameter tools receive a baseline score of 4 since no parameter explanation is required in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states this is a 'ping/pong health check' that returns 'pong' if responsive, using specific verbs and identifying the resource. However, it does not differentiate from the sibling 'health' tool, leaving ambiguity about which health-checking tool to use.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by stating it checks if 'the server is responsive,' but provides no explicit guidance on when to prefer this over the sibling 'health' tool, nor any prerequisites or conditions for use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_recordB

Update an existing record in the upstream API.

Args: record_type: The type of record (e.g., "customer", "invoice") record_id: Internal ID of the record. updates: Dictionary of fields to update. account_id: Account ID (required if not configured on server). base_url: Optional full API URL (overrides account_id).

Returns: Structured response with update result.

ParametersJSON Schema
NameRequiredDescriptionDefault
record_typeYes
record_idYes
updatesYes
account_idNo
base_urlNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden of behavioral disclosure. Fails to specify critical mutation traits: whether updates are partial (PATCH) or full replacement (PUT), what happens if record_id doesn't exist, idempotency guarantees, or error handling behavior. Only mentions 'Structured response' without behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-organized docstring format with clear Args and Returns sections. Front-loads the core purpose in the first sentence. No redundant or filler text, though 'upstream API' could be more specific.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Adequate for parameter documentation given the input schema lacks descriptions, and output schema exists so return values need minimal explanation. However, as a mutation tool with zero annotations, it lacks important operational context regarding failure modes, authentication requirements, and update semantics that would be necessary for safe invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Excellent compensation for 0% schema description coverage. The Args section documents all 5 parameters including helpful examples (e.g., 'customer', 'invoice' for record_type), conditional requirement logic for account_id, and the override relationship between base_url and account_id. Only minor gap is lack of detail on valid values within the updates dictionary.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States 'Update an existing record' with clear verb and resource type. The phrase 'existing record' usefully distinguishes from sibling create_record. However, 'upstream API' is vague jargon that doesn't specify the domain or system being interacted with.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides no explicit guidance on when to select this tool versus create_record or other alternatives. Missing crucial prerequisite context such as 'use only when record exists' or guidance on how to check existence via get_record first.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 8 tool updatesv0.1.0
    • First observedcreate_record
    • First observeddelete_record
    • First observedexecute_query
    • First observedget_record
    • First observedget_record_schema
    • First observedhealth
    • First observedping
    • First observedupdate_record

TDQS

A3.5/5.0
Disambiguation5/5

Every tool has a clearly distinct purpose with no ambiguity. The CRUD operations (create_record, get_record, update_record, delete_record) are well-separated from query execution (execute_query), schema retrieval (get_record_schema), and health checks (health, ping). The descriptions clearly differentiate each tool's function, making misselection unlikely.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern with snake_case throughout. The naming is predictable and readable, with verbs like create, delete, execute, get, and update paired with appropriate nouns like record, query, schema, or simple actions like ping/health.

Tool Count5/5

The 8 tools are well-scoped for an enterprise API template server. This includes complete CRUD operations, query execution, schema retrieval, and health checks, which is appropriate for interfacing with various upstream APIs. Each tool earns its place without feeling excessive or insufficient.

Completeness5/5

The tool surface provides complete CRUD/lifecycle coverage for record management (create, get, update, delete), plus essential utilities like query execution, schema documentation, and health checks. There are no obvious gaps for the server's purpose of interacting with enterprise APIs, and agents can perform all expected operations without dead ends.

Maintenance

ActivityInactive
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    C
    maintenance
    A professional Python framework and template for building robust Model Context Protocol (MCP) servers with modular architecture, enterprise features like authentication and rate limiting, and comprehensive tooling. Provides easy-to-use APIs for registering tools, resources, and prompts with full type safety and multiple transport support.
    4
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Easiest framework for building MCP servers with automatic discovery of tools, prompts, and resources, plus enterprise-grade authentication and telemetry.
    839
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    A production-ready MCP server template that connects LLMs and AI agents to external data, tools, and services with built-in OAuth 2.1 authentication, Redis-backed session management, and a modular tools engine.
    1
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/victor-velazquez-ai/enterprise-mcp-template'

If you have feedback or need assistance with the MCP directory API, please join our Discord server