MCP Tool Server
by ziyakhan04
README.md
# MCP Tool Server — AI Agent Tool Integration Platform
A small, production-shaped example of how an AI agent can discover and call reusable backend tools through the **Model Context Protocol (MCP)**, with the same business logic also exposed as a conventional REST API — proving that MCP is an additional interface, not a parallel implementation.
## Problem Statement
AI agents need a standard way to call backend capabilities (databases, external APIs, calculators, utilities) without every application inventing its own bespoke tool-calling glue. This project demonstrates that pattern end-to-end: a real agent, backed by a real LLM, discovering and invoking real tools against a real database and a real external API — while proving the tools aren't just an AI-only side channel by exposing the identical logic over REST.
## What is MCP?
The Model Context Protocol is an open protocol for connecting AI applications to external tools and data sources through a standard client/server interface. An MCP **server** exposes typed, discoverable **tools** (and optionally resources/prompts); an MCP **client** connects to it, lists what's available, and invokes tools with structured arguments, getting structured results back.
The key difference from "just calling a REST API": tool discovery is dynamic and machine-readable. An agent doesn't need hardcoded knowledge of your API's shape — it asks the server what it can do, gets typed schemas back, and an LLM can reason about which tool fits a request. This project's agent (see `app/agent/`) never hardcodes tool names; it calls `list_tools()` at runtime.
## Why This Project Exists
To demonstrate, concretely and testably:
1. A clean separation between transport/adapter layers (MCP tools, REST endpoints) and business logic (services)
2. Zero duplication of logic between the MCP and REST interfaces
3. A real (not simulated) LLM-driven agent loop doing tool discovery and selection
4. Production-style error handling, logging, validation, and testing at every layer
## Architecture
```mermaid
flowchart TD
User --> Agent
Agent --> LLM[LLM Provider - Groq]
Agent --> MCPClient[MCP Client]
MCPClient --> MCPServer[FastMCP Server]
MCPServer --> CalcTool[Calculator Tools]
MCPServer --> TaskTool[Task Tools]
MCPServer --> GitHubTool[GitHub Tool]
MCPServer --> UtilTool[Utility Tool]
TaskTool --> TaskService[Task Service]
GitHubTool --> GitHubService[GitHub Service]
TaskService --> TaskRepo[Task Repository]
TaskRepo --> SQLite[(SQLite)]
GitHubService --> GitHubAPI[GitHub Public API]
RestAPI[FastAPI REST API] --> TaskService
RestAPI --> GitHubService
```
MCP tools and FastAPI endpoints both call directly into the same service layer (`app/services/`). Neither interface contains business logic of its own.
## Key Features
- **Calculator tools**: `add`, `subtract`, `multiply`, `divide` — typed, no `eval()`, clean division-by-zero handling
- **Task management**: full CRUD (`create_task`, `list_tasks`, `get_task`, `complete_task`, `delete_task`) backed by SQLite, with a repository/service split
- **GitHub tool**: `get_repository_info(owner, repo)` against the public GitHub API, with input validation, timeouts, and clean 404/error handling
- **Utility tool**: `text_statistics` — word/character/sentence counts and average word length
- **REST API**: FastAPI endpoints reusing the identical service layer, with OpenAPI docs at `/docs`
- **AI Agent**: connects via MCP, dynamically discovers tools, uses Groq for tool selection, executes via MCP, and never touches the database or GitHub directly
- **Tests**: 23+ unit and integration tests covering services, repositories, REST endpoints, and MCP tool invocation
## Technology Stack
| Layer | Technology |
|---|---|
| MCP server | FastMCP 2.14 |
| REST API | FastAPI |
| Validation | Pydantic v2 |
| Persistence | SQLite (stdlib `sqlite3`, no ORM) |
| Testing | pytest, pytest-asyncio |
| LLM (agent tool selection) | Groq (`openai/gpt-oss-120b`) |
| Config | pydantic-settings, `.env` |
FastMCP is a third-party framework, not part of this project. Credit: [PrefectHQ/fastmcp](https://github.com/PrefectHQ/fastmcp).
## Project Structure
MCP-Tool-Server/
├── app/
│ ├── main.py # FastAPI entrypoint
│ ├── config.py # env-based settings
│ ├── logging_config.py
│ ├── agent/ # LLM provider + agent loop + CLI
│ ├── mcp/
│ │ ├── server.py # FastMCP server, tool registration
│ │ └── tools/ # thin MCP adapters
│ ├── api/
│ │ └── routes.py # thin REST adapters
│ ├── services/ # business logic (shared by MCP + REST)
│ ├── repositories/ # SQL, isolated here only
│ ├── database/ # connection + schema
│ └── schemas/ # Pydantic models
├── tests/
│ ├── unit/
│ └── integration/
├── docs/
│ └── architecture.md
├── requirements.txt
├── requirements-dev.txt
└── .env.example
## Installation
```powershell
git clone <your-repo-url>
cd MCP-Tool-Server
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt -r requirements-dev.txt
```
## Environment Configuration
Copy `.env.example` to `.env` and fill in your Groq API key (get one free at https://console.groq.com):
DATABASE_URL=sqlite:///./data/app.db
GITHUB_API_URL=https://api.github.com
LOG_LEVEL=INFO
LLM_PROVIDER=groq
LLM_API_KEY=your_key_here
LLM_MODEL=openai/gpt-oss-120b
`.env` is gitignored and must never be committed.
## Running the MCP Server
```powershell
python -m app.mcp.server
```
Starts in stdio transport mode, waiting for an MCP client.
## Running the FastAPI REST API
```powershell
uvicorn app.main:app --reload
```
Docs at `http://127.0.0.1:8000/docs`.
## Running the AI Agent
```powershell
python -m app.agent.cli
```
Example prompts:
Calculate 125 / 5.
Create a high priority task called Finish Resume.
What are the stars and forks of PrefectHQ/fastmcp?
## Example Tool Calls (via MCP Client)
```python
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 25, "b": 18})
result = await client.call_tool("create_task", {"title": "Finish Resume", "priority": "high"})
result = await client.call_tool("get_repository_info", {"owner": "PrefectHQ", "repo": "fastmcp"})
```
## API Examples (REST)
```powershell
$body = @{ title = "Prepare for interview"; priority = "high" } | ConvertTo-Json
Invoke-RestMethod -Uri http://127.0.0.1:8000/tasks -Method Post -Body $body -ContentType "application/json"
Invoke-RestMethod -Uri http://127.0.0.1:8000/tasks -Method Get
Invoke-RestMethod -Uri "http://127.0.0.1:8000/github/PrefectHQ/fastmcp" -Method Get
```
## Testing
```powershell
pytest -v
```
23 tests covering calculator logic, task service CRUD + edge cases, GitHub service against faked HTTP responses (no real network calls in tests), REST endpoint integration, and MCP tool invocation via FastMCP's in-process `Client`.
## Design Decisions
- **No ORM for SQLite**: a single-table app doesn't justify SQLAlchemy; hand-written parameterized SQL, isolated entirely inside `repositories/`, is simpler to reason about and defend.
- **Service layer is the single source of business logic**: both MCP tools and REST routes are thin adapters that call the same functions — verified directly by tests that exercise both paths against the same behavior.
- **`lru_cache`d settings**: environment variables are read once per process; tests explicitly clear this cache when they need to override config (see `tests/conftest.py`).
- **Logging to stderr, not stdout**: the MCP stdio transport uses stdout for protocol messages; logging there would corrupt the stream.
- **Agent gives a direct, transparent answer rather than a second LLM "explain the result" call**: keeps the demo deterministic; a natural-language rephrasing pass is a documented future improvement, not a hidden non-determinism.
## Error Handling
Every service raises specific, named exceptions (`TaskNotFoundError`, `RepositoryNotFoundError`, `GitHubServiceError`, `ValueError` for bad input). FastAPI translates these into structured JSON responses with appropriate status codes (404, 400, 502, 500) via `app/main.py`'s exception handlers — no stack traces are ever returned to a client. On the MCP side, FastMCP converts the same exceptions into `ToolError`s that a calling agent must explicitly catch, exactly as demonstrated in `app/agent/agent.py`.
## Security Considerations
- No secrets in source code; all config via environment variables, `.env` gitignored
- No `eval()` anywhere — calculator tools are plain arithmetic
- GitHub `owner`/`repo` inputs validated against a strict character pattern before being used in a URL
- All SQL is parameterized; no string-interpolated queries
- External HTTP calls have explicit timeouts
- Errors never leak stack traces or internal details to callers
- LLM-generated tool arguments are validated by the same Pydantic schemas every other caller goes through — the agent does not get a trusted bypass
## Future Improvements
- Migrate SQLite → PostgreSQL for concurrent/multi-process use
- Containerize with Docker
- Add a second LLM provider (OpenAI/Gemini) behind the existing `LLMProvider` abstraction
- Add a rephrasing pass so the agent's final answer reads naturally rather than stating the raw tool result
- Add retry/backoff around the GitHub HTTP client
- Correlation IDs across log lines for request tracing
## FastMCP Attribution
Built on [FastMCP](https://github.com/PrefectHQ/fastmcp) by PrefectHQ. This project is an independent application using FastMCP as a dependency; it is not a fork of, or affiliated with, FastMCP itself.
## License
MITThis server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues