A demonstration project that exposes a SQL Server database to Claude via a FastAPI REST layer and the Model Context Protocol (MCP). Claude can call a tool named get_customer_details in natural language and receive live data from the database.
Claude (chat / Claude Code)
│ natural language
▼
Anthropic Messages API
│ tool_use → get_customer_details(customer_id)
▼
mcp_tool.py OR mcp_server.py (stdio MCP)
│ HTTP GET /customer/{id}
▼
FastAPI (main.py)
│ parameterised SQL query
▼
SQL Server (CustomerDemo / dbo.Customer)
There are two ways to wire Claude to the API:
| Mode | File | Use when |
|---|---|---|
| Direct Python script | mcp_tool.py |
Testing from the terminal; runs a full Claude conversation loop |
| MCP stdio server | mcp_server.py |
Connecting to Claude Desktop or Claude Code via .mcp.json |
| Layer | Technology |
|---|---|
| API framework | FastAPI + Uvicorn |
| Database driver | pyodbc + ODBC Driver 18 for SQL Server |
| Data validation | Pydantic v2 |
| HTTP client | httpx |
| AI SDK | Anthropic Python SDK |
| Config | python-dotenv |
| Protocol | Model Context Protocol — JSON-RPC 2.0 over stdio |
DemoMCPforAPI/
├── .env ← your local config (git-ignored)
├── .env.example ← template — copy to .env
├── .mcp.json ← MCP server config for Claude Code / Claude Desktop
├── main.py ← FastAPI app (routes, middleware)
├── models.py ← Pydantic request/response models
├── database.py ← SQL Server connection + queries
├── mcp_tool.py ← MCP tool schema + Claude API demo script
├── mcp_server.py ← Stdio MCP server for Claude Desktop / Claude Code
├── requirements.txt ← Python dependencies
├── setup_database.sql ← creates DB, table, and sample data
└── README.md
The script setup_database.sql creates the CustomerDemo database, the dbo.Customer table, and populates it with 10 sample rows. It is safe to run against an existing database — each step checks before acting.
CREATE TABLE [dbo].[Customer] (
[CustomerID] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Forename] NVARCHAR(50) NOT NULL,
[Surname] NVARCHAR(50) NOT NULL,
[UpdatedDateTime] DATETIME2(7) NOT NULL DEFAULT (GETDATE()),
[AddressLine1] NVARCHAR(100) NOT NULL,
[AddressLine2] NVARCHAR(100) NULL
);| CustomerID | Forename | Surname | AddressLine1 | AddressLine2 |
|---|---|---|---|---|
| 1 | Alice | Johnson | 1 Castle Lane | Winchester |
| 2 | Jane | Smith-London | 45 High Street | Top Floor Flat |
| 3 | Alex | Jones | 88 Baker Street | Marylebone |
| 4 | Sam | Taylor | 12 Rose Cottage | Oaklands |
| 5 | Robert | Tables | 10 Downing Street | SW1A 2AA |
| 6 | Emma | Wilson | 7 Elm Road | (null) |
| 7 | Liam | Brown | 22 Victoria Terrace | Edinburgh |
| 8 | Sophia | Davis | 99 Harbour View | Bristol |
| 9 | Noah | Evans | 3 Quarry Hill | Leeds |
| 10 | Olivia | Harris | 51 Kingsway | Cardiff |
Note: CustomerID 5 (Robert Tables) is a nod to Little Bobby Tables — it confirms that parameterised queries handle special surnames without any SQL injection risk.
sqlcmd -S "YOUR_SERVER\YOUR_INSTANCE" -E -i setup_database.sqlThe -E flag uses Windows Authentication (no username/password required). On success you will see the 10 inserted rows printed as a verification query at the end.
If sqlcmd is not on your PATH, find it at:
C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe
Or run the script directly from SQL Server Management Studio (SSMS) by opening setup_database.sql and pressing F5.
- Python 3.11–3.13 — Python 3.14+ is not yet supported by
pydantic-core(see Troubleshooting) - ODBC Driver 18 for SQL Server — download here
- SQL Server running and accessible (run setup_database.sql if the
CustomerDemodatabase has not yet been created) - Anthropic API key — get one here
cd C:\path\to\DemoMCPforAPI
py -3.13 -m venv .venvUse
py -3.13(or whichever 3.11–3.13 version you have installed) to avoid Python 3.14 compatibility issues.
.venv\Scripts\python.exe -m pip install -r requirements.txtIf you see an execution policy error when trying to run
.venv\Scripts\Activate.ps1, use the full path to the venv's Python/uvicorn executables directly instead — activation is not required.
Copy-Item .env.example .envThen edit .env and fill in your Anthropic API key:
DATABASE_URL=DRIVER={ODBC Driver 18 for SQL Server};SERVER=[YOUR_DB_SERVER];DATABASE=CustomerDemo;Trusted_Connection=yes;TrustServerCertificate=yes
APP_PORT=8000
APP_ENV=development
ANTHROPIC_API_KEY=sk-ant-....venv\Scripts\uvicorn.exe main:app --host 127.0.0.1 --port 8000 --reloadVerify it is running:
Invoke-RestMethod http://localhost:8000/healthExpected output:
{
"status": "ok",
"database": "CustomerDemo",
"version": "Microsoft SQL Server..."
}Invoke-RestMethod http://localhost:8000/customer/2Requires the FastAPI server to be running in a separate terminal first.
# Test the Python client directly (no Claude involved)
.venv\Scripts\python.exe mcp_tool.py 2
# Run a full Claude conversation with tool use
.venv\Scripts\python.exe mcp_tool.py "Show me details for customer 2"The second form sends your message to Claude via the Anthropic API. Claude decides to call get_customer_details, the script executes the call, returns the result to Claude, and Claude formats the final answer.
Once .mcp.json is present and Claude Code has been restarted, just ask in the chat:
"Show me details for customer 2"
Claude Code will call the tool natively.
Open http://localhost:8000/docs in a browser while the server is running. You can test all endpoints interactively from the Swagger UI.
Checks that the API is up and that SQL Server is reachable.
Response 200:
{
"status": "ok",
"database": "CustomerDemo",
"version": "Microsoft SQL Server 2025..."
}Response 503: Database is unreachable.
Returns the full customer record for the given integer ID.
Response 200:
{
"CustomerID": 2,
"Forename": "Jane",
"Surname": "Smith-London",
"UpdatedDateTime": "2026-03-31T14:16:34.683333",
"AddressLine1": "45 High Street",
"AddressLine2": "Top Floor Flat"
}Response 404: No customer exists with that ID.
Response 500: Unexpected database error (check server logs).
Defines two Pydantic models used for request/response validation:
Customer— mirrors thedbo.Customertable columns; all fields exceptCustomerIDare optional to handle NULLs gracefully.HealthResponse— returned by the/healthendpoint.
Handles all SQL Server interaction:
- Opens a new connection per request using the
DATABASE_URLenv var. Per-request connections are used (rather than a pool) because Windows Integrated Authentication tokens can expire in pooled connections. db_cursor()is a context manager that commits on success and rolls back on any exception, always closing the connection infinally.fetch_customer()executes a parameterised query using?placeholders — no string concatenation, so SQL injection is not possible.check_db_health()runsSELECT @@VERSIONas a lightweight connectivity probe.
The FastAPI application:
- CORS is set to allow all origins (
*) for local demo use — restrict this before any network exposure. /healthcallscheck_db_health()and returns 503 if it throws./customer/{customer_id}callsfetch_customer(), returns 404 if the result isNone, and wraps any other exception as a generic 500.- Swagger docs (
/docs) are disabled automatically whenAPP_ENV=production.
Two things in one file:
TOOL_SCHEMA— the JSON object Claude expects in thetoolsarray of a Messages API call. Paste this into a Claude Project's custom tools section or pass it to the API.get_customer_details(customer_id)— a Python function that callsGET /customer/{customer_id}using httpx and returns the parsed JSON.run_claude_demo(user_message)— a full agentic loop: sends the message → handlestool_usestop reason → executes the tool → sendstool_result→ repeats untilend_turn.
A minimal JSON-RPC 2.0 server over stdio implementing the MCP protocol (version 2024-11-05). Claude Desktop and Claude Code launch this as a subprocess and communicate with it line-by-line on stdin/stdout.
Handles three MCP methods:
initialize— returns server capabilitiestools/list— returns[TOOL_SCHEMA]tools/call— callsget_customer_details()and wraps the result in an MCP content block
Notifications (messages with no id) receive no response, as required by the spec.
Claude Desktop does not read .mcp.json, so full paths are required in its own config file. Edit %APPDATA%\Claude\claude_desktop_config.json, replacing C:\path\to with your actual project path:
{
"mcpServers": {
"customer-demo": {
"command": "C:\\path\\to\\DemoMCPforAPI\\.venv\\Scripts\\python.exe",
"args": ["C:\\path\\to\\DemoMCPforAPI\\mcp_server.py"],
"env": {
"API_BASE_URL": "http://localhost:8000"
}
}
}
}Note: If you prefer a relative path for the script (
"args": ["mcp_server.py"]), add a"cwd"field set to the project directory so Claude Desktop knows where to look — e.g."cwd": "C:\\path\\to\\DemoMCPforAPI". Without it, the relative path will be resolved from an unpredictable working directory and the server will fail to start.
Restart Claude Desktop. A hammer icon (🔨) in the chat bar confirms the tool is connected.
The .mcp.json file in this directory is already configured. Restart Claude Code (reload the VSCode window) with the FastAPI server running and the get_customer_details tool will be available in the chat.
pydantic-core uses a Rust extension that does not yet support Python 3.14. Install Python 3.13 from python.org, then recreate the venv:
Remove-Item -Recurse -Force .venv
py -3.13 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt.venv\Scripts\Activate.ps1 : The module '.venv' could not be loaded.
Either allow user-scoped scripts:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserOr skip activation entirely and call venv executables by full path:
.venv\Scripts\uvicorn.exe main:app --host 127.0.0.1 --port 8000 --reload
.venv\Scripts\python.exe mcp_tool.py "Show me details for customer 2"The FastAPI server is not running. Start it in a separate terminal:
.venv\Scripts\uvicorn.exe main:app --host 127.0.0.1 --port 8000 --reloadThe customer does not exist in the database. Find valid IDs:
sqlcmd -S "YOUR_SERVER\YOUR_INSTANCE" -d CustomerDemo -Q "SELECT TOP 10 CustomerID, Forename, Surname FROM dbo.Customer ORDER BY CustomerID"ANTHROPIC_API_KEY is not set. Add it to .env:
ANTHROPIC_API_KEY=sk-ant-...Then ensure .env is being loaded — mcp_tool.py calls load_dotenv() at the top, so the file must exist in the working directory.
SQL Server is unreachable. Check:
- The SQL Server service is running (
services.msc→ SQL Server (YOUR_INSTANCE)) - The
DATABASE_URLin.envhas the correct server name - Your Windows account has access to the
CustomerDemodatabase
- Confirm
.mcp.jsonexists in the project root. - Confirm the FastAPI server is running on port 8000.
- Reload the VSCode window (
Ctrl+Shift+P→ "Developer: Reload Window"). - Check the Claude Code output panel for MCP connection errors.
This project is configured for local development only. Before exposing it on a network:
| Concern | Current | Production recommendation |
|---|---|---|
| Authentication | None | Add API key middleware or OAuth 2.0 / JWT |
| CORS | Allow all origins | Restrict to your specific front-end domain |
| Transport | HTTP | Terminate TLS at a reverse proxy (nginx, Caddy) |
| Rate limiting | None | Add slowapi or gateway-level rate limiting |
| SQL injection | Parameterised queries ✓ | Already safe |
| Error details | Generic 500 messages ✓ | Already safe — stack traces go to stderr only |
| Credentials | .env file (git-ignored) |
Use a secrets manager in production |
| DB account | Windows Auth (your user) | Use a dedicated service account with SELECT-only access |