demo-tools
A minimal MCP server exposing four trivial tools: arithmetic calculation, current time lookup, and in-memory note storage. It serves as a teaching example to demonstrate the complete MCP request/response cycle with a Gemini agent.
README
MCP Demo: A Gemini Agent That Calls MCP Tools
The Model Context Protocol (MCP) is an open standard
for connecting LLM applications to external tools and data. Instead of every app
inventing its own plugin format, an MCP server declares what it can do — each
tool's name, description, and a JSON Schema for its arguments — and any MCP
client can discover and invoke those tools over a standard transport. This repo
is a deliberately small, readable reference implementation of both halves: a server
(server.py) exposing four trivial tools, and an agent (agent.py) that connects to
it, hands the tool list to Google Gemini, lets the model decide what to call, executes
the calls against the server, and feeds the results back until the model produces a
final answer. Two files, no framework, no database — the point is that you can read
the whole request/response cycle end to end in one sitting.
Architecture
sequenceDiagram
actor User
participant Agent as agent.py<br/>(MCP client)
participant Gemini as Gemini<br/>(OpenAI-compatible API)
participant Server as server.py<br/>(MCP server)
Agent->>Server: spawn subprocess, then tools/list
Server-->>Agent: tool names + JSON Schemas
User->>Agent: "What is (17 * 23) + 4?"
loop until the model stops asking for tools
Agent->>Gemini: messages + tool schemas
Gemini-->>Agent: tool_calls: calculate(expression)
Agent->>Server: tools/call over stdio
Server-->>Agent: "(17 * 23) + 4 = 395"
end
Gemini-->>Agent: final answer (no tool_calls)
Agent-->>User: "(17 * 23) + 4 = 395"
In plain terms:
User -> Agent -> Gemini (decides which tool) -> Agent -> MCP Server (executes it)
^ |
+--------- result fed back ----------------+
|
v
final answer -> User
The agent launches the server as a subprocess and talks to it over stdio (JSON-RPC on stdin/stdout). Nothing listens on a network port; the only outbound traffic is to the Gemini API.
The tools
All four live in server.py. Their JSON Schemas are generated from the Python type hints, and their descriptions come from the docstrings — that is what the model reads when deciding what to call.
| Tool | Input | Output |
|---|---|---|
calculate |
expression: str — arithmetic like "(17 * 23) + 4" |
"(17 * 23) + 4 = 395" |
get_current_time |
timezone: str = "UTC" — an IANA name like "Asia/Karachi" |
"2026-08-11 14:44:43 PKT (Asia/Karachi)" |
add_note |
text: str — the note to save |
"Saved note #1: buy milk" |
list_notes |
(none) | numbered list of notes, or "No notes saved yet." |
Notes:
calculatedoes not useeval(). The expression is parsed to an AST and only whitelisted numeric operations are evaluated. Tool arguments originate from an LLM reacting to untrusted user text, so treating them as untrusted input matters.- Notes are in-memory, held in a list inside the server process. They live as long
as one
agent.pysession and then vanish. No database is needed for the demo; swapping the list for SQLite would not change a single line of the agent. - Invalid input (bad expression, unknown timezone, empty note) raises, which MCP reports as a tool error. The agent forwards that text to the model, which usually corrects itself and retries. Both the success and the error path are handled.
Setup
Requires Python 3.10+ and a Google AI Studio API key.
git clone https://github.com/mallahim01/MCP-server
cd MCP-server
# 1. Create and activate a virtual environment
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 2. Install dependencies
pip install -r requirements.txt
# 3. Configure your key
copy .env.example .env # Windows (cp on macOS/Linux)
# then edit .env and set GEMINI_API_KEY
Get a free key at aistudio.google.com/apikey.
.env is gitignored so your key never leaves your machine.
.env variables:
| Variable | Required | Default | Notes |
|---|---|---|---|
GEMINI_API_KEY |
yes | — | Your Google AI Studio key |
GEMINI_MODEL |
no | gemini-flash-latest |
Any model your key can use that supports tool calling |
Running it
One command — the agent starts the MCP server for you:
python agent.py "what is 17 * 23 + 4?" # one-shot
python agent.py # interactive chat
You never run server.py yourself. In interactive mode the server subprocess and the
conversation history both stay alive across questions, which is what makes the notes
tool useful.
Example 1 — a question that needs a tool
$ python agent.py "What is (17 * 23) + 4?"
Connected to MCP server 'demo-tools'.
Model: gemini-flash-latest
Tools: calculate, get_current_time, add_note, list_notes
You: What is (17 * 23) + 4?
[tool] calculate({"expression": "(17 * 23) + 4"})
[tool] -> (17 * 23) + 4 = 395
Assistant: (17 * 23) + 4 = 395
The model recognised the question as arithmetic, emitted a calculate tool call, the
agent executed it against the MCP server, and the model turned the result into prose.
Example 2 — two tools in one turn
$ python agent.py "Save a note that says 'buy milk', then list all my notes."
[tool] add_note({"text": "buy milk"})
[tool] -> Saved note #1: buy milk
[tool] list_notes({})
[tool] -> 1. buy milk
Assistant: I've saved your note. Here are your notes:
1. buy milk
Two round trips: the model called add_note, read the result, then called
list_notes before answering. The loop keeps going until the model stops asking
for tools.
Example 3 — no tool needed
$ python agent.py "What is the capital of France?"
Assistant: The capital of France is Paris.
No [tool] lines: the model answered directly. Handling this case is a one-line
branch in the agent, but it is the case people forget.
How it works
The whole flow is in run_turn(). Two protocols meet here, and the
interesting part is how little glue they need.
Startup. agent.py spawns server.py with sys.executable and wraps its stdio
pipes in an MCP Client. Entering the client performs the MCP handshake. It then
calls list_tools() — the tool list is discovered, never hardcoded, so adding a
tool to the server is all it takes for the agent to start offering it.
The bridge. mcp_tool_to_openai_schema() is the entire translation layer between
the two standards. An MCP tool already carries name, description, and
input_schema (JSON Schema); OpenAI's tools parameter wants the same three things
nested under a "function" key. That is a five-line function, and it is why MCP tools
work with any tool-calling model.
The loop. For each user turn, up to MAX_TURNS times:
- Send the conversation plus every tool schema to Gemini.
- If the reply has no
tool_calls, that is the final answer — return it. - Otherwise, for each requested call: parse the JSON arguments, invoke
mcp_client.call_tool(name, args), and flatten the MCP content blocks to text. A tool that raises comes back withis_errorset; the error text is passed to the model rather than crashing, so it can fix its arguments and retry. - Append one
role: "tool"message per call — everytool_call_idmust be answered or the next request is rejected — and loop.
The MAX_TURNS cap is a safety net: a confused model could otherwise call tools
forever.
One Gemini quirk. Gemini's newer models attach an opaque thought_signature to
each tool call and require it to be echoed back in the follow-up request; dropping it
is a 400. It is not part of the OpenAI spec, so the SDK parks it in model_extra,
and _serialize_tool_call() copies it through. Against real OpenAI the field is
simply absent and the same code works unchanged. This is the kind of detail that only
shows up when you actually run the thing.
Project layout
server.py MCP server + the four tool definitions
agent.py MCP client + the Gemini tool-calling loop
requirements.txt mcp, openai, python-dotenv, tzdata
.env.example template for your API key
.env your real key (gitignored, never committed)
Notes and limitations
This is a teaching example, not a production system. It has no authentication, no persistence, no retries, and no observability, all deliberately. A few things worth knowing if you run it:
- The free Gemini tier is quota-limited both per minute and per day, per
model (at the time of writing, 5/min and 20/day for the default model), and a
single question costs one request per tool-calling round — so a two-tool question
costs three. The agent reports a
429as a plain message rather than a traceback. If you exhaust the daily quota, setGEMINI_MODELto a different model; the quotas are counted per model. tzdatais in the requirements because Windows ships no IANA timezone database, whichget_current_timeneeds. On Linux/macOS it is usually redundant but harmless.- This uses v2 of the MCP Python SDK (
MCPServer,Client). The v1 API (FastMCP,ClientSession+stdio_client) is different; if you are following an older tutorial, that is why the imports do not match.
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。