> ## Documentation Index
> Fetch the complete documentation index at: https://genai.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating a Custom MCP Server

> Build a custom Python FastMCP server and integrate its tools into a LangChain agentic client application

While prebuilt servers (like GitHub or PostgreSQL) are highly useful, production systems often require exposing internal company databases or custom computation engines. In this page, we build a custom **FastMCP Math Server** in Python and consume its tools in a separate agentic client application.

## 1. Defining the Custom Server using FastMCP

To build MCP servers easily, the SDK provides a high-level framework called **FastMCP**. FastMCP handles the JSON-RPC message serialization and transport setup automatically under the hood.

### Step 1: Install FastMCP Dependency

Run in your terminal:

```bash theme={null}
uv add "mcp[cli]"
```

### Step 2: Write the Server Script (`mcp_server.py`)

We initialize a `FastMCP` instance and register tools using the `@mcp.tool()` decorator:

```python theme={null}
# mcp_server.py
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP Server
mcp = FastMCP("MathServer")

@mcp.tool()
def add_numbers(a: float, b: float) -> float:
    """Adds two numbers together and returns the floating-point result."""
    return a + b

@mcp.tool()
def multiply_numbers(a: float, b: float) -> float:
    """Multiplies two numbers together and returns the floating-point result."""
    return a * b

if __name__ == "__main__":
    # Start stdio server loop
    mcp.run()
```

***

## 2. Consuming Custom Server Tools in an Agentic Client

Now, we build a client application that:

1. Spawns our `mcp_server.py` script as a subprocess stdio transport connection.
2. Queries the server's available tools.
3. Binds those tools to a Chat Model so the LLM can invoke them.

### Step 1: Initialize the Stdio Subprocess Param

We configure parameters to launch our server script:

```python theme={null}
from mcp import StdioServerParameters

server_params = StdioServerParameters(
    command="python3",
    args=["mcp_server.py"]
)
```

### Step 2: Bind MCP Tools to ChatModel

We fetch the tool definitions from the server, format them for LangChain, and bind them:

```python theme={null}
# Retrieve tools from the active MCP session
tools_response = await session.list_tools()

# Format MCP tools for the LangChain bind_tools schema
langchain_tools = []
for tool in tools_response.tools:
    # Convert tool schemas dynamically
    langchain_tools.append({
        "name": tool.name,
        "description": tool.description,
        "parameters": tool.inputSchema
    })

# Bind tools to the LLM
llm_with_tools = llm.bind_tools(langchain_tools)
```

***

## 3. Client Implementation Script (`mcp_client.py`)

Here is the complete, self-contained client script that connects to our server subprocess, binds the exposed math tools to Gemini, and executes a calculation query:

```python theme={null}
# mcp_client.py
import asyncio
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# Load API keys for LLM client
load_dotenv()

async def run_agentic_mcp():
    # Configure parameters to launch our local custom server file
    server_params = StdioServerParameters(
        command="python3",
        args=["mcp_server.py"]
    )

    print("Connecting to local custom Math MCP Server...")
    async with stdio_client(server_params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            # Step A: Handshake
            await session.initialize()
            print("Handshake Complete!\n")

            # Step B: List Tools from custom server
            response = await session.list_tools()
            print("--- Custom Server Tools Registered ---")
            for t in response.tools:
                print(f"Tool: {t.name} -> {t.description}")

            # Step C: Bind Server tools dynamically to LLM
            llm = init_chat_model("gemini-2.5-flash", model_provider="google_genai")
            
            # Format tool specifications
            tools_spec = []
            for t in response.tools:
                tools_spec.append({
                    "name": t.name,
                    "description": t.description,
                    "parameters": t.inputSchema
                })
            
            llm_with_tools = llm.bind_tools(tools_spec)

            # Step D: Invoke Agent with a math query
            query = "What is 453.25 + 98.75? Please use your calculator tools."
            print(f"\nUser Query: '{query}'")
            
            response_msg = llm_with_tools.invoke(query)
            
            # Step E: Handle Tool call
            if response_msg.tool_calls:
                call = response_msg.tool_calls[0]
                print(f"\nAgent requested tool execution: '{call['name']}' with arguments {call['args']}")
                
                # Execute tool execution request on custom MCP Server
                result = await session.call_tool(name=call['name'], arguments=call['args'])
                print(f"\nMCP Server Tool Return: {result.content[0].text}")
                
                # Feed back output to model to get final conversational response
                final_response = llm.invoke([
                    ("user", query),
                    response_msg,
                    ("user", f"Tool result was: {result.content[0].text}. Summarize the answer.")
                ])
                print(f"\nFinal Agent Response: {final_response.content}")
            else:
                print(f"\nFinal Agent Response: {response_msg.content}")

if __name__ == "__main__":
    asyncio.run(run_agentic_mcp())
```

***

## 4. Practice Exercises

### Practice 1: Adding a Division Tool

Modify the custom server script `mcp_server.py` to add a new division tool called `divide_numbers(a: float, b: float) -> float`. Define its docstring indicating that it divides `a` by `b`, and returns the result.

<Accordion title="Solution">
  Add the following decorator and function to the server script `mcp_server.py`:

  ```python theme={null}
  @mcp.tool()
  def divide_numbers(a: float, b: float) -> float:
      """Divides a by b and returns the floating-point result. Raises error if b is zero."""
      if b == 0:
          raise ValueError("Cannot divide by zero.")
      return a / b
  ```
</Accordion>
