The Architect’s Blueprint: Mastering MCP Tool Registration in 2026

Published: 7/15/2026 by Harry Holoway
The Architect’s Blueprint: Mastering MCP Tool Registration in 2026

 



Introduction: The End of the "Black Box" Era

In the early days of Large Language Model (LLM) integration, roughly between 2023 and 2024, connecting an AI to external systems was akin to performing surgery with a blunt instrument. Developers would wrap API calls in loose Python functions, describe them in natural language within a prompt, and hope the model guessed the parameters correctly. It was fragile. It was unscalable. It was a "black box" where inputs went in and hoped-for outputs came out, but the mechanics were opaque, inconsistent, and prone to catastrophic failure when context windows filled up or schemas drifted.

Fast forward to July 2026. The landscape has matured. The Model Context Protocol (MCP) has emerged not just as a standard, but as the foundational bedrock of the composable AI enterprise. We have moved from "prompt engineering" to "context engineering." And at the heart of context engineering lies one critical, often underestimated component: Tool Registration.

Tool registration is the act of exposing a function, a script, or a service to an AI agent via the MCP protocol. It is the bridge between the deterministic world of code and the probabilistic world of intelligence. If this bridge is poorly built, the agent falls into the abyss of hallucination, infinite loops, and security breaches. If it is built well, the agent becomes a powerful, autonomous extension of your software stack.

This guide is not a superficial overview. It is a deep, technical, and practical exploration of MCP Tool Registration best practices for advanced developers. We will dissect the anatomy of a tool, explore the nuances of schema design, tackle the complexities of asynchronous execution, secure your endpoints against adversarial prompts, and optimize for latency and cost. We will write real, production-ready code in Python and TypeScript, the two dominant languages of the MCP ecosystem in 2026. We will solve actual problems: how to handle large payloads, how to manage stateful tools, how to implement retry logic, and how to debug the invisible interactions between agents and servers.

By the end of this guide, you will not just know how to register a tool; you will understand how to architect a tool ecosystem that is robust, scalable, and intelligent. You will be equipped to build the next generation of AI-native applications.

Part I: The Philosophy of Tool Registration

Before we write a single line of code, we must align on the philosophy. Why does tool registration matter so much? Why can’t we just let the LLM call any API it wants?

1. The Contract of Trust

When you register a tool with an MCP server, you are creating a contract. You are telling the AI agent: "Here is a capability I possess. Here is exactly what I need from you to use it. Here is exactly what I will give you in return."

This contract is defined by the Tool Schema. The schema is the single source of truth. The LLM does not "know" your database. It does not "understand" your CRM. It only understands the JSON schema you provide. If the schema is vague, the LLM’s usage will be vague. If the schema is strict, the LLM’s usage will be precise.

In 2026, we recognize that the quality of the AI’s output is directly proportional to the quality of the tool registration. Garbage In, Garbage Out still applies, but now it’s "Bad Schema, Bad Action."

2. Decoupling Intelligence from Execution

MCP enforces a strict separation of concerns. The Client (the AI agent) handles reasoning, planning, and decision-making. The Server (your application) handles execution, data access, and business logic.

Tool registration is the interface between these two worlds. By keeping this interface clean and standardized, we achieve several critical goals:

  • Interoperability: Any MCP-compliant client (Gemini, Claude, Llama, etc.) can use your tools.

  • Maintainability: You can update your backend logic without changing the AI’s prompt, as long as the schema remains stable.

  • Security: You can enforce authentication and authorization at the server level, independent of the AI’s internal state.

3. The Shift from "Functions" to "Tools"

In traditional programming, a function is a block of code. In MCP, a Tool is a first-class citizen. It has metadata, descriptions, examples, and error handling strategies. It is not just code; it is a service offered to the AI.

This shift requires a change in mindset. You are no longer just writing code for humans to call. You are writing code for an AI to discover, understand, and invoke. This means your documentation (descriptions) is as important as your implementation. Your error messages are as important as your success responses. Your parameter names are as important as your logic.

Part II: Anatomy of an MCP Tool

Let’s break down the components of an MCP Tool. According to the MCP specification (v1.2 as of 2026), a tool consists of three main parts:

  1. Metadata: Name, description, and tags.

  2. Input Schema: A JSON Schema object defining the required and optional parameters.

  3. Execution Logic: The code that runs when the tool is called.

1. Metadata: The First Impression

The Name should be concise, unique, and action-oriented. Avoid generic names like get_data. Use specific names like get_customer_order_history.

The Description is the most critical piece of metadata. This is what the LLM reads to decide if and how to use the tool. It should be written in clear, natural language. It should explain:

  • What the tool does.

  • When to use it.

  • What the parameters mean.

  • Any side effects or constraints.

Bad Description: "Gets user info." Good Description: "Retrieves detailed profile information for a specific user by their unique UUID. Use this tool when you need to verify a user's email address, subscription status, or account creation date. Do not use this tool to list all users; use list_users for that purpose."

Notice the difference? The good description provides context, usage guidelines, and even tells the AI when not to use it. This reduces hallucinations and incorrect tool selection.

2. Input Schema: The Blueprint

The Input Schema is a JSON Schema object. It defines the structure of the arguments the AI must provide. In 2026, we use JSON Schema Draft 2020-12 or later, which supports rich validation features.

Key elements of a robust schema:

  • Type Safety: Explicitly define types (string, integer, boolean, array, object).

  • Required Fields: Clearly mark which fields are mandatory.

  • Enums: Use enums for fixed sets of values (e.g., status: ["active", "inactive", "pending"]). This prevents the AI from inventing invalid statuses.

  • Patterns: Use regex patterns for strings (e.g., email formats, phone numbers).

  • Descriptions for Each Parameter: Every parameter should have its own description. This helps the AI understand what each field represents.

3. Execution Logic: The Engine

This is your actual code. It takes the validated arguments, performs the action, and returns a result. The result should be structured and predictable. Avoid returning raw HTML or unstructured text if possible. Return JSON objects that the AI can easily parse.

Part III: Setting Up the Development Environment

Before we dive into code, let’s set up a robust development environment. In 2026, the standard stack for MCP development includes:

  • Python 3.12+: For backend services and data-heavy tools.

  • TypeScript 5.0+: For web-integrated tools and frontend-facing agents.

  • MCP SDKs: The official mcp library for Python and @modelcontextprotocol/sdk for TypeScript.

  • Pydantic V2: For data validation and schema generation in Python.

  • Zod: For schema validation in TypeScript.

  • Docker: For containerizing MCP servers.

  • Postman/Insomnia: For testing HTTP-based MCP transports.

Installing the Python MCP SDK

pip install mcp pydantic uvicorn

Installing the TypeScript MCP SDK

npm install @modelcontextprotocol/sdk zod

Part IV: Best Practice #1 – Precision in Schema Design

The most common mistake developers make is being too lazy with their schemas. They use type: "object" and leave it at that. This is a recipe for disaster.

The Problem with Loose Schemas

If you define a parameter as just string, the AI might pass a JSON string, a CSV, a paragraph of text, or a SQL injection attempt. If you define an array as items: {}, the AI might mix types, causing your backend to crash.

The Solution: Strict Typing and Validation

Let’s look at a concrete example. Suppose we are building a tool to create a new project in a project management system.

Bad Schema (Python/Pydantic)

from pydantic import BaseModel

class CreateProjectArgs(BaseModel):
    name: str
    description: str
    members: list

This schema is weak. name could be empty. description could be 10,000 words. members could be a list of integers, strings, or nested objects. The AI will guess, and it will often guess wrong.

Good Schema (Python/Pydantic)

from pydantic import BaseModel, Field, EmailStr
from typing import List, Optional
import re

class Member(BaseModel):
    email: EmailStr
    role: str = Field(..., description="Role of the member: 'admin', 'editor', or 'viewer'")
    
    @field_validator('role')
    @classmethod
    def validate_role(cls, v):
        if v not in ['admin', 'editor', 'viewer']:
            raise ValueError('Role must be one of: admin, editor, viewer')
        return v

class CreateProjectArgs(BaseModel):
    name: str = Field(..., min_length=3, max_length=100, description="Name of the project. Must be unique.")
    description: Optional[str] = Field(None, max_length=500, description="Brief description of the project.")
    members: List[Member] = Field(..., min_items=1, max_items=10, description="List of initial team members.")
    start_date: str = Field(..., pattern=r'^\d{4}-\d{2}-\d{2}
    

    
    
  


, description="Start date in YYYY-MM-DD format.")

Notice the improvements:

  1. Constraints: min_length, max_length, min_items, max_items. These prevent abuse and ensure data integrity.

  2. Nested Objects: The Member class is a separate Pydantic model, ensuring that each member has a valid email and role.

  3. Validators: Custom validators enforce business logic (e.g., valid roles).

  4. Patterns: The start_date is enforced to be in a specific format.

  5. Descriptions: Every field has a clear description.

When this Pydantic model is converted to JSON Schema for MCP, it generates a highly detailed and restrictive schema. The LLM will see this and know exactly what to provide.

TypeScript Equivalent with Zod

import { z } from 'zod';

const MemberSchema = z.object({
  email: z.string().email(),
  role: z.enum(['admin', 'editor', 'viewer']).describe("Role of the member: 'admin', 'editor', or 'viewer'")
});

const CreateProjectArgsSchema = z.object({
  name: z.string().min(3).max(100).describe("Name of the project. Must be unique."),
  description: z.string().max(500).optional().describe("Brief description of the project."),
  members: z.array(MemberSchema).min(1).max(10).describe("List of initial team members."),
  start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Start date in YYYY-MM-DD format.")
});

Zod makes it easy to generate JSON Schema from TypeScript types, ensuring consistency between your code and your MCP definition.

Handling Complex Data Types

What if you need to pass a file? Or a large JSON object?

Files: Do not pass raw binary data in the tool arguments. Instead, pass a reference (e.g., a URI or a file ID). The tool should then fetch the file from storage. This keeps the context window small and the transmission efficient.

Large JSON Objects: If you need to pass a large configuration object, consider breaking it down into smaller, manageable chunks or using a reference ID. Alternatively, use MCP Resources to store the large object and pass the resource URI to the tool.

Part V: Best Practice #2 – Descriptive Power and Natural Language

As mentioned earlier, the description is key. But how do you write good descriptions?

1. Be Specific, Not Generic

Avoid: "Searches for items." Use: "Searches the product catalog for items matching the given query. Returns up to 10 results sorted by relevance. Use this tool when the user asks for product recommendations or specific item details."

2. Explain the "Why" and "When"

Tell the AI when to use the tool.

  • "Use this tool only after verifying the user's identity."

  • "Use this tool if the user asks for historical data older than 30 days."

  • "Do not use this tool for real-time stock prices; use get_live_stock_price instead."

3. Provide Examples

Some MCP implementations support providing examples in the tool definition. Even if they don’t, you can include examples in the description.

"Example usage: To find books by 'J.K. Rowling', set author='J.K. Rowling' and category='books'."

4. Describe the Output

Tell the AI what to expect back. "Returns a JSON object containing id, title, price, and availability. If no items are found, returns an empty list."

This helps the AI plan its next steps. If it knows the output format, it can prepare to parse it.

Part VI: Best Practice #3 – Error Handling and Resilience

Tools fail. APIs go down. Parameters are invalid. Networks time out. How your tool handles errors determines whether the agent recovers or crashes.

1. Structured Error Responses

Never return a raw stack trace or a generic "Error 500" message to the AI. The AI cannot interpret a stack trace. It needs a structured error response.

Define a standard error format:

{
  "success": false,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "The 'email' field is not a valid email address.",
    "details": {
      "field": "email",
      "value": "not-an-email"
    }
  }
}

2. Categorize Errors

Categorize errors so the AI can decide how to respond.

  • User Error (4xx): The AI provided bad input. The AI should correct the input and retry.

  • System Error (5xx): The server failed. The AI should wait and retry, or notify the user.

  • Business Logic Error: The action is not allowed (e.g., insufficient permissions). The AI should inform the user.

3. Implement Retry Logic

For transient errors (network timeouts, rate limits), implement automatic retry logic in your tool execution code. Use exponential backoff.

import time
import requests

def call_external_api(url, params, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, params=params, timeout=10)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff
                time.sleep(wait_time)
            else:
                raise Exception(f"Failed after {max_retries} attempts: {str(e)}")

4. Graceful Degradation

If a tool fails, can you provide a partial result? For example, if a weather API fails, can you return cached data? Inform the AI that the data is stale.

def get_weather(city):
    try:
        return fetch_live_weather(city)
    except Exception:
        cached_data = get_cached_weather(city)
        if cached_data:
            return {
                "data": cached_data,
                "warning": "Live data unavailable. Returning cached data from 1 hour ago."
            }
        else:
            raise Exception("Weather data unavailable.")

Part VII: Best Practice #4 – Security and Authorization

Exposing tools to AI agents is a security risk. An agent might be tricked by a malicious user into calling a tool with harmful parameters.

1. Authentication and Authorization

Every MCP server should require authentication. Use OAuth 2.0 or API keys. Ensure that the agent acts on behalf of the authenticated user.

In your tool execution logic, always check the user’s permissions.

def delete_user(user_id: str, current_user: User):
    if not current_user.is_admin:
        raise PermissionError("Only admins can delete users.")
    # Proceed with deletion

2. Input Sanitization

Never trust input from the AI. Sanitize all strings to prevent SQL injection, XSS, or command injection. Use parameterized queries for databases. Escape special characters for shell commands.

3. Rate Limiting

Implement rate limiting per user and per tool. Prevent an agent from calling a costly API thousands of times in a loop.

from functools import wraps
import time

def rate_limit(max_calls: int, period: int):
    def decorator(func):
        calls = []
        @wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            calls.append(now)
            # Remove calls older than the period
            while calls and calls[0] < now - period:
                calls.pop(0)
            if len(calls) > max_calls:
                raise Exception("Rate limit exceeded.")
            return func(*args, **kwargs)
        return wrapper
    return decorator

4. Audit Logging

Log every tool call, including the user ID, tool name, parameters, and result. This is crucial for debugging and security audits.

Part VIII: Best Practice #5 – Performance and Latency

AI agents are sensitive to latency. If a tool takes 10 seconds to respond, the user experience suffers.

1. Asynchronous Execution

Use async/await for I/O-bound operations. This allows the server to handle multiple requests concurrently.

import asyncio
import aiohttp

async def fetch_data(url: str):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()

2. Caching

Cache frequent read operations. Use Redis or in-memory caching. Set appropriate TTLs (Time-To-Live).

3. Pagination

For tools that return lists, implement pagination. Do not return 10,000 items at once. Return 10-50 items per page. Provide next_page_token for fetching more.

def list_items(page_token: Optional[str] = None, limit: int = 20):
    # Fetch items from DB with limit and offset
    items, next_token = db.query(limit=limit, cursor=page_token)
    return {
        "items": items,
        "next_page_token": next_token
    }

4. Timeout Configuration

Set strict timeouts for external API calls. If an API doesn’t respond in 5 seconds, fail fast.

Part IX: Advanced Pattern – Stateful Tools

Most tools are stateless. But some tasks require state. For example, a multi-step form or a long-running process.

Using MCP Resources for State

MCP Resources are designed for persistent data. You can use Resources to store the state of a long-running task.

  1. Start Task: The tool creates a new Resource with a unique ID and initial state.

  2. Check Status: Another tool reads the Resource to check progress.

  3. Update State: The background process updates the Resource.

  4. Complete: The final tool reads the completed Resource.

This decouples the long-running process from the immediate tool call.

Part X: Advanced Pattern – Dynamic Tool Discovery

In complex systems, you may have hundreds of tools. Loading them all at startup is inefficient. Use dynamic discovery.

Lazy Loading

Register tools on demand. When the agent asks for a list of tools, return only the relevant ones based on the current context.

Tool Groups

Group tools by domain (e.g., "CRM Tools", "Billing Tools"). Allow the agent to enable/disable groups. This reduces cognitive load on the LLM.

Part XI: Testing and Debugging MCP Tools

Testing AI tools is different from testing regular code. You need to test both the logic and the interaction with the LLM.

1. Unit Testing

Test the execution logic with mocked inputs. Ensure validation works.

def test_create_project_validation():
    with pytest.raises(ValidationError):
        CreateProjectArgs(name="", description="test", members=[], start_date="2026-01-01")

2. Integration Testing

Test the full MCP flow. Use an MCP client to call your server. Verify the JSON-RPC messages.

3. LLM Simulation

Simulate LLM calls. Feed your tool schema to a local LLM (like Llama 3) and ask it to generate arguments. Verify that the generated arguments are valid according to your schema.

4. Observability

Use distributed tracing (OpenTelemetry) to track requests from the Agent to the Tool. Monitor latency, error rates, and token usage.

Part XII: Real-World Case Study – Building a Financial Analysis Tool

Let’s apply these best practices to a real-world scenario. We will build an MCP server that provides financial analysis tools.

Requirements

  1. Get Stock Price: Fetch real-time price for a ticker.

  2. Get Historical Data: Fetch historical prices for a date range.

  3. Calculate Moving Average: Calculate SMA for a given period.

Step 1: Define Schemas (Pydantic)

from pydantic import BaseModel, Field
from typing import List, Optional
import datetime

class GetStockPriceArgs(BaseModel):
    ticker: str = Field(..., pattern=r'^[A-Z]{1,5}
    

    
    
  


, description="Stock ticker symbol (e.g., AAPL).")

class GetHistoricalDataArgs(BaseModel):
    ticker: str = Field(..., pattern=r'^[A-Z]{1,5}
    

    
    
  


, description="Stock ticker symbol.")
    start_date: str = Field(..., pattern=r'^\d{4}-\d{2}-\d{2}
    

    
    
  


, description="Start date YYYY-MM-DD.")
    end_date: str = Field(..., pattern=r'^\d{4}-\d{2}-\d{2}
    

    
    
  


, description="End date YYYY-MM-DD.")

class CalculateSMAArgs(BaseModel):
    prices: List[float] = Field(..., min_items=2, description="List of closing prices.")
    period: int = Field(..., ge=2, le=200, description="Moving average period.")

Step 2: Implement Tools

import yfinance as yf
import pandas as pd

def get_stock_price(ticker: str):
    stock = yf.Ticker(ticker)
    data = stock.history(period="1d")
    if data.empty:
        raise Exception(f"No data found for ticker {ticker}")
    return {"ticker": ticker, "price": float(data['Close'].iloc[-1])}

def get_historical_data(ticker: str, start_date: str, end_date: str):
    stock = yf.Ticker(ticker)
    data = stock.history(start=start_date, end=end_date)
    if data.empty:
        raise Exception(f"No data found for ticker {ticker} in range")
    return {
        "ticker": ticker,
        "data": [{"date": str(index.date()), "close": float(row['Close'])} for index, row in data.iterrows()]
    }

def calculate_sma(prices: List[float], period: int):
    if len(prices) < period:
        raise Exception("Not enough data points for the given period")
    df = pd.Series(prices)
    sma = df.rolling(window=period).mean().iloc[-1]
    return {"sma": float(sma)}

Step 3: Register with MCP Server

from mcp.server import Server
from mcp.types import Tool

app = Server("financial-analyzer")

@app.list_tools()
async def list_tools():
    return [
        Tool(
            name="get_stock_price",
            description="Fetches the current real-time price of a stock.",
            inputSchema=GetStockPriceArgs.model_json_schema()
        ),
        Tool(
            name="get_historical_data",
            description="Fetches historical daily closing prices for a stock.",
            inputSchema=GetHistoricalDataArgs.model_json_schema()
        ),
        Tool(
            name="calculate_sma",
            description="Calculates the Simple Moving Average for a list of prices.",
            inputSchema=CalculateSMAArgs.model_json_schema()
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "get_stock_price":
        args = GetStockPriceArgs(**arguments)
        return get_stock_price(args.ticker)
    elif name == "get_historical_data":
        args = GetHistoricalDataArgs(**arguments)
        return get_historical_data(args.ticker, args.start_date, args.end_date)
    elif name == "calculate_sma":
        args = CalculateSMAArgs(**arguments)
        return calculate_sma(args.prices, args.period)
    else:
        raise Exception(f"Unknown tool: {name}")

Step 4: Security and Error Handling

Add authentication middleware. Add try-except blocks in each tool function to return structured errors. Add rate limiting for the yfinance API.

Part XIII: Common Pitfalls and How to Avoid Them

1. Overloading Tools

Pitfall: Creating one giant tool that does everything. Solution: Break it down. Small, focused tools are easier to maintain and easier for the LLM to understand.

2. Ignoring Idempotency

Pitfall: Calling a "create" tool twice creates duplicate records. Solution: Make tools idempotent where possible. Use unique IDs. Check for existence before creating.

3. Poor Naming Conventions

Pitfall: Naming tools func1, action2. Solution: Use descriptive, verb-noun pairs. create_invoice, update_user_profile.

4. Neglecting Documentation

Pitfall: Leaving descriptions blank. Solution: Treat descriptions as critical code. Review them regularly.

Part XIV: Future Trends in MCP Tool Registration

As we look beyond 2026, several trends are emerging:

1. AI-Generated Tools

Tools that self-register. An agent analyzes an API documentation and automatically generates the MCP tool schema and stub code.

2. Semantic Tool Discovery

Instead of keyword matching, agents use vector embeddings to find tools based on semantic similarity. "I need to send a message" matches send_slack_message even if the word "slack" isn't in the query.

3. Verified Tools

A marketplace of verified, security-audited tools. Developers can plug in trusted tools with confidence.

4. Multi-Modal Tools

Tools that accept and return images, audio, and video. MCP is evolving to support binary data streams efficiently.

Conclusion

MCP Tool Registration is not just a technical detail; it is the art of translating human intent into machine action. By following these best practices—precision in schema, clarity in description, resilience in error handling, rigor in security, and efficiency in performance—you can build tools that empower AI agents to truly assist, automate, and augment human work.

The future of software is composable. The future of AI is collaborative. And the bridge between them is the well-registered tool. Go forth and build.


Appendix: Complete Code Reference

Python MCP Server Template

from mcp.server import Server
from mcp.types import Tool
from pydantic import BaseModel, Field
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Server("my-mcp-server")

# Define Args
class MyToolArgs(BaseModel):
    param1: str = Field(..., description="Description of param1")

# List Tools
@app.list_tools()
async def list_tools():
    return [
        Tool(
            name="my_tool",
            description="Description of what my_tool does.",
            inputSchema=MyToolArgs.model_json_schema()
        )
    ]

# Call Tool
@app.call_tool()
async def call_tool(name: str, arguments: dict):
    try:
        if name == "my_tool":
            args = MyToolArgs(**arguments)
            # Execute logic
            result = {"status": "success", "data": "result"}
            return result
        else:
            raise ValueError(f"Unknown tool: {name}")
    except Exception as e:
        logger.error(f"Error in tool {name}: {str(e)}")
        return {"error": str(e)}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

TypeScript MCP Server Template

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';

const MyToolArgsSchema = z.object({
  param1: z.string().describe("Description of param1")
});

const server = new Server(
  {
    name: 'my-mcp-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'my_tool',
        description: 'Description of what my_tool does.',
        inputSchema: MyToolArgsSchema.shape, // Note: Convert Zod to JSON Schema properly in production
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  if (name === 'my_tool') {
    const parsed = MyToolArgsSchema.parse(args);
    // Execute logic
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({ status: 'success', data: 'result' }),
        },
      ],
    };
  } else {
    throw new Error(`Unknown tool: ${name}`);
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

This guide provides the foundation. The rest is up to your creativity and rigor. Happy coding!