Skip to main content
Glama
tusharkotlapure

learn_mcp

Simple MCP Server + LLM Agent

A small learning project that demonstrates how to build an MCP (Model Context Protocol) server from scratch using Python, connect to it with an MCP client, and let an LLM discover and call MCP tools.

The project uses:

Python 3.10+

uv for Python project/dependency management

MCP Python SDK 2.x

OpenRouter for the LLM

OpenAI Python SDK as an OpenRouter-compatible API client

STDIO transport between the MCP client and server

Architecture

User | v LLM (OpenRouter) | | decides which MCP tool to call v MCP Client | | STDIO / JSON-RPC v MCP Server | | executes tool v Tool Result | v LLM | v Final Answer

The important idea is that the LLM does not directly access the application's Python data. It receives MCP tool definitions, decides which tool to use, and the MCP client executes the selected tool on the server.

Project Structure

test_mcp/ ├── server.py ├── client.py ├── llm_test.py ├── pyproject.toml ├── uv.lock └── README.md

server.py

Contains the MCP server and exposes tools such as:

add

subtract

multiply

get_customer

get_customer_balance

find_customer

It also demonstrates MCP resources and prompts.

client.py

Connects to the MCP server over STDIO, discovers its tools, converts MCP tool definitions into an LLM-compatible format, sends them to the LLM, executes requested MCP tools, and feeds tool results back to the LLM.

llm_test.py

A small standalone test to verify that the OpenRouter API connection works before integrating the LLM with MCP.

Prerequisites

Install:

Python 3.10 or newer

uv

An OpenRouter API key

Check Python:

python3 --version

Check uv:

uv --version

Setup

Clone or create the project and enter the directory:

cd ~/test_mcp

If dependencies are not already installed:

uv sync

The project should contain the MCP SDK and OpenAI Python SDK.

If needed, they can be added with:

uv add "mcp[cli]" uv add openai

Configure OpenRouter

Set your API key as an environment variable.

macOS/Linux:

export OPENROUTER_API_KEY="your-api-key"

Verify it exists:

echo $OPENROUTER_API_KEY

Do not commit the API key to Git.

For a persistent shell configuration, you can add the export to your shell profile, but be careful not to expose the key in source code or public repositories.

Run the MCP Server

The server can be started directly:

uv run server.py

The server uses STDIO, so it will normally appear to wait silently for requests. That is expected.

The server also contains:

if name == "main": mcp.run()

which starts the MCP server using the default STDIO transport.

Inspect the MCP Server

The MCP CLI/Inspector can be used to inspect the server:

uv run mcp dev server.py

The Inspector allows you to see:

Available tools

Tool descriptions

Input schemas

Resources

Prompts

Tool execution results

This is useful for debugging the MCP server before involving an LLM.

Test the LLM Separately

Before connecting the LLM to MCP, test OpenRouter:

uv run python llm_test.py

The test sends a simple question to OpenRouter and prints the model response.

The project currently uses an OpenRouter free model. Free model availability and limits can change over time.

Run the MCP + LLM Client

Run:

uv run python client.py

The client performs the following steps.

  1. Connect to the MCP server

server = StdioServerParameters( command="uv", args=["run", "server.py"], )

The MCP client starts the server as a subprocess.

  1. Discover tools

mcp_result = await client.list_tools()

The client asks the MCP server which tools it provides.

  1. Convert MCP tools for the LLM

MCP tool definitions are converted into the OpenAI-compatible function/tool format:

{ "type": "function", "function": { "name": tool.name, "description": tool.description, "parameters": tool.input_schema, }, }

This gives the LLM enough information to decide which tool it should use.

  1. Send the user question and tools to the LLM

The client sends:

User question + Available MCP tools

For example:

What is John's balance?

  1. LLM requests a tool

The LLM may decide:

find_customer(name="John")

The client parses the requested tool and arguments.

  1. MCP client executes the tool

The client calls:

await client.call_tool( tool_name, arguments, )

The MCP server executes the corresponding Python function.

For example:

find_customer("John")

returns:

{ "id": 1, "name": "John", "balance": 5000, "status": "active" }

  1. Tool result is sent back to the LLM

The MCP result is added to the conversation as a tool message:

messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result_text, })

The LLM now knows what the tool returned.

  1. Agent loop

The client repeats the LLM/tool process:

LLM | | tool call? +---- No ----> Final answer | Yes | v MCP tool | v Tool result | v LLM again

The loop ends when the LLM returns a normal response without requesting another tool.

Example

Input:

What is John's balance?

Possible flow:

LLM requested tool: Tool: find_customer Arguments: {'name': 'John'}

MCP result: { "id": 1, "name": "John", "balance": 5000, "status": "active" }

Final LLM response: John's balance is 5000.

MCP Concepts Demonstrated

Tools

Tools are executable functions exposed by the MCP server.

Example:

@mcp.tool() def get_customer_balance(customer_id: int) -> int: ...

The LLM can decide when to call them.

Resources

Resources represent data that can be exposed through MCP resource URIs.

Example:

customer://1

Resources are conceptually different from tools: a tool represents an action/function, while a resource represents information/data.

Prompts

Prompts are reusable prompt templates exposed by the MCP server.

Example:

@mcp.prompt() def analyze_customer(customer_id: int) -> str: ...

MCP Client

The client connects to the MCP server and provides operations such as:

await client.list_tools()

and:

await client.call_tool(...)

STDIO Transport

In this project, the client launches the MCP server locally and communicates with it over standard input/output.

client.py | | stdin/stdout | server.py

This is convenient for local development and learning.

Why the LLM Does Not Know the Python Dictionary

The server contains customer data:

customers = { 1: {"name": "John", "balance": 5000, "status": "active"}, ... }

The LLM does not directly see this Python variable.

Instead, it sees tool definitions such as:

find_customer(name) get_customer_balance(customer_id)

The LLM asks the MCP client to execute a tool, and the tool result is returned to the LLM.

This separation is an important part of the architecture.

Learning Progression

This project was built incrementally:

Learn basic Python/MCP concepts

Create a basic MCP server

Add simple tools

Add customer-related tools

Add MCP resources

Add MCP prompts

Inspect the server using MCP Inspector

Build an MCP client

Connect the client to an LLM

Convert MCP tools into LLM-compatible tool definitions

Let the LLM choose a tool

Execute the tool through MCP

Send the tool result back to the LLM

Implement the basic agent/tool-calling loop

Important Mental Model

Keep these four components separate:

MCP Server | | provides capabilities v MCP Tools | | are executed by v MCP Client | | orchestrated by decisions from v LLM

More precisely:

LLM = decides what to do

MCP Client = connects to the MCP server and invokes tools

MCP Server = exposes tools/resources/prompts

Tool = performs the actual operation

The LLM provides the intelligence for deciding which capability to use, while MCP provides a standardized way to expose and invoke those capabilities.

Next Steps

Potential extensions to this project:

Add a proper maximum tool-call limit

Handle MCP tool errors

Validate LLM-generated tool arguments

Support multiple tool calls

Use structured MCP tool outputs

Separate the agent loop into reusable functions/classes

Add real backend/API calls instead of in-memory customer data

Explore MCP resources more deeply

Explore MCP prompts

Run the MCP server over Streamable HTTP instead of STDIO

Add authentication and authorization

Connect the MCP server to a real application

Build a small chat interface on top of the agent

Security Notes

This project is intentionally simple and is for learning.

For production:

Never hardcode API keys

Validate LLM-generated tool arguments

Restrict which tools the model can invoke

Validate authorization before executing sensitive operations

Handle tool failures and timeouts

Add logging/observability

Add limits to the number of tool calls

Avoid exposing sensitive data unnecessarily

Treat LLM-generated tool arguments as untrusted input

License

This project is intended as a learning/demo project.

Available Tools

6 tools
addA

Add two numbers together.

ParametersJSON Schema
NameRequiredDescriptionDefault
aYes
bYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

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

The behavior is transparent for a pure arithmetic operation: it takes two numbers and returns their sum. It does not describe edge cases or return details, but the operation is simple and output schema is available.

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, concise sentence with no unnecessary words. It is well-structured and immediately understandable.

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?

For a simple arithmetic tool, the description is complete enough for correct use. It lacks explicit edge-case or return-type details, but the operation's simplicity and available output schema reduce the need for further explanation.

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 description says 'two numbers,' which collectively defines both parameters as addends. It does not individually describe a or b, but for addition the individual roles are clear and order is irrelevant.

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?

The description clearly states the tool's exact purpose: adding two numbers together. It is a specific verb and resource, and it naturally distinguishes from sibling tools like subtract and multiply.

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

Usage Guidelines4/5

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

The description makes it clear that this tool is for addition, and sibling names imply when to use other operations. However, it does not explicitly state when not to use this tool or provide alternative selection guidance.

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

find_customerB

Find a customer using their name.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes

TDQS

B3.3/5.0
Behavior2/5

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

There are no annotations and the description does not mention whether the operation is read-only, what it returns, or how it handles missing or ambiguous names. Since 'find' suggests a lookup, the lack of explicit side-effect or result information leaves uncertainty.

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 concise sentence that directly states the tool's purpose and key parameter. No unnecessary words or redundant details are present.

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?

For a simple lookup tool, the description gives the essential action and input, but it omits output shape and error behavior. Without an output schema, more detail about what is returned would improve completeness.

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

Parameters3/5

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

The only parameter, 'name', is mentioned in the description as the lookup key, so its basic purpose is clear. However, the description does not clarify exact matching, case sensitivity, or whether multiple matches are possible.

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 finds a customer using their name, which identifies both the action and the resource. It is distinguishable from sibling tools like get_customer and get_customer_balance, though 'find' is slightly less precise than 'get'.

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 phrase 'using their name' implies the intended use when a customer name is available. However, it does not explicitly contrast with get_customer or explain when to prefer this tool over alternatives.

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

get_customerB

Get customer details using the customer ID.

ParametersJSON Schema
NameRequiredDescriptionDefault
customer_idYes

TDQS

B3.2/5.0
Behavior2/5

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

There are no annotations and the description does not mention side effects, read-only behavior, permissions, or potential errors, so the agent is not informed about behavioral expectations.

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 clear sentence with no unnecessary words or repetition, making it easy to parse and act on.

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?

The description is adequate for a simple lookup but does not specify what 'customer details' includes or what the response shape will be, especially since no output schema is provided.

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

Parameters2/5

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

The customer_id parameter is only described as 'customer ID', adding little beyond the schema title. With 0% schema description coverage, the description should provide more detail about the parameter's format or meaning.

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?

The description clearly states the action ('Get'), the resource ('customer details'), and the required input ('customer ID'), distinguishing it from arithmetic tools and the balance-specific sibling.

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 guidance is provided on when to use this tool versus get_customer_balance or find_customer, leaving the agent to infer the appropriate choice from the tool names alone.

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

get_customer_balanceA

Get the current balance of a customer.

ParametersJSON Schema
NameRequiredDescriptionDefault
customer_idYes

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?

The word 'get' implies a read-only operation, but the description does not explicitly disclose side effects, error behavior (e.g., customer not found), or output format. With no annotations, the description carries the full burden and only partially covers transparency.

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 concise sentence with no redundancy or unnecessary details. It efficiently conveys the core functionality without fluff.

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?

For a simple getter, the description provides enough context to understand the primary purpose. However, it does not mention potential error cases, return value specifics, or interaction with sibling tools, leaving minor gaps in completeness.

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

Parameters2/5

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

The only parameter, customer_id, is self-explanatory by name, but the schema description coverage is 0%, and the tool description adds no clarifying details about its meaning, constraints, or typical usage. Since coverage is low, the description should have compensated but did not.

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?

The description clearly states the tool's purpose: retrieving a customer's balance. It distinguishes from sibling tools like get_customer and find_customer by focusing specifically on balance retrieval, making the action unambiguous.

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 does not explicitly explain when to use this tool versus the sibling tools. While the purpose is clear, it lacks guidance on scenarios where this tool is preferred over alternatives, such as when only balance is needed rather than full customer details.

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

multiplyA

multiply two numbers together.

ParametersJSON Schema
NameRequiredDescriptionDefault
aYes
bYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.6/5.0
Behavior2/5

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

There are no annotations to supplement the description, so the full burden falls on the text. It does not disclose return type, error behavior, or any side effects, leaving the agent to assume the operation is pure and returns a numeric result.

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, focused sentence with no extraneous information. It is highly concise and directly conveys the operation.

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 simplicity of the tool and the presence of an output schema, the description is adequate for an agent to understand the operation. It could mention the result type, but the schema likely covers that, so nothing critical is missing.

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

Parameters3/5

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

The description mentions 'two numbers' which maps directly to the parameters a and b, but it does not elaborate beyond that. The schema defines them as integers, and the description adds minimal semantic value.

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?

The description clearly states the action ('multiply') and the resource ('two numbers'), leaving no ambiguity about the tool's purpose. It distinguishes itself from the sibling tools add and subtract by specifying the multiplication operation.

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 directly implies the use case (performing multiplication), but it does not explicitly state when to choose this tool over alternatives like add or subtract. The usage is inferred from the verb, but no conditional guidance is provided.

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

subtractA

Subtract one number from another.

ParametersJSON Schema
NameRequiredDescriptionDefault
aYes
bYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior4/5

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

With no annotations, the description carries the full burden. It accurately describes the core behavior without mentioning side effects, which is acceptable for a pure arithmetic function. However, it does not explicitly confirm the absence of side effects or describe return behavior.

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, concise sentence with no extraneous details. It effectively communicates the core operation in minimal words.

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?

For a simple arithmetic tool, the description is mostly adequate, but it lacks critical parameter details (order of operands) and any mention of return format or errors. The minimal schema and absence of annotations increase the need for a more thorough description.

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

Parameters2/5

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

The description does not clarify the order of subtraction. The phrase 'subtract one number from another' is ambiguous regarding whether a - b or b - a is intended. Since subtraction is non-commutative, this is a significant gap for the two integer parameters.

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?

The description clearly states a specific verb 'subtract' and the object 'one number from another', making it distinct from sibling tools like add and multiply.

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 is self-explanatory for a simple arithmetic operation, but it does not explicitly state when to use this tool versus the sibling add or multiply tools. The scoping is implied by the operation rather than stated.

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. 6 tool updatesv0.1.0
    • First observedadd
    • First observedfind_customer
    • First observedget_customer
    • First observedget_customer_balance
    • First observedmultiply
    • First observedsubtract

TDQS

A3.7/5.0
Disambiguation5/5

Each tool performs a distinct action: arithmetic operations are clearly separate, and customer tools differentiate by ID (get_customer), balance (get_customer_balance), and name search (find_customer). No functional overlap exists.

Naming Consistency4/5

All tool names use lowercase with underscores, but there is a slight inconsistency between the 'get_' prefix for two customer tools and the 'find_' prefix for another. Arithmetic tools follow a simple verb pattern, but overall the naming is fairly consistent.

Tool Count5/5

Six tools is a reasonable size for a server covering two small domains (arithmetic and customer lookup). It is neither too sparse nor overwhelming.

Completeness3/5

The arithmetic set lacks division, and the customer tools cover only read/lookup operations, missing create, update, or delete. While the scope may be intentional, it leaves common operations absent for a full-featured server.

Maintenance

ActivityMaintained
ResponsivenessNo issues

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

  • F
    license
    Not graded
    quality
    D
    maintenance
    A tutorial MCP server for learning the Model Context Protocol by building file and system tools. Provides hands-on experience creating custom tools that enable AI models to interact with files and execute system commands.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    An educational MCP server example built with FastMCP that demonstrates how to expose tools, resources, and prompts to AI clients. Provides a learning foundation for building MCP servers with Python and integrating them with AI applications like IDEs and chatbots.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    An educational example demonstrating how to build MCP servers in Python using FastMCP, showing how to expose tools, resources, and prompts to AI clients.
    -

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/tusharkotlapure/learn_mcp'

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