biz-mcp
by sai4243
README.md
# biz-mcp
A custom MCP (Model Context Protocol) server with **7 tools** that connect to an LLM/MCP client. It combines:
- **2 external API wrappers** - live FX rates (ECB/Frankfurter) and crypto prices (CoinGecko), both free and key-less.
- **2 database operations** - a key-value store backed by DynamoDB on AWS (falls back to a local JSON file for development).
- **3 business-logic tools** - invoice calculation, shipping estimation, and inventory-status assessment.
It runs on **AWS Lambda** behind a **Lambda Function URL** using the modern **streamable HTTP** MCP transport. The same code runs locally over **stdio** (for Claude Desktop) or **streamable HTTP**.
## Tools
| Name | Category | Description |
| --- | --- | --- |
| `get_exchange_rate` | External API | ECB reference-rate FX conversion (frankfurter.app, no key) |
| `get_crypto_price` | External API | Current crypto price (CoinGecko public API, no key) |
| `save_record` | Database | Put an arbitrary JSON object under `(pk, sk)` |
| `get_record` | Database | Read a JSON object under `(pk, sk)` |
| `calculate_invoice_total` | Business logic | Subtotal / discount / tax / total from line items |
| `estimate_shipping` | Business logic | Shipping cost from weight, distance and zone |
| `check_inventory_status` | Business logic | Reads a record and classifies stock as in_stock / low_stock / out_of_stock |
## Architecture
```
MCP client (Claude Desktop, Cursor, mcp inspector)
│ streamable HTTP (POST JSON-RPC / SSE)
▼
Lambda Function URL ──► lambda_function.py (protocol adapter)
│
▼
mcp_server.py (FastMCP, 7 tools)
├── apis.py → frankfurter.app, CoinGecko
├── db.py → DynamoDB (or local JSON)
└── business.py → invoice, shipping, inventory
```
## Project layout
```
apis.py External API wrappers
business.py Pure business logic
db.py Key-value store (DynamoDB or local JSON)
mcp_server.py FastMCP server defining the 7 tools
lambda_function.py Streamable-HTTP JSON-RPC adapter for Lambda
template.yaml SAM template (Lambda + Function URL + DynamoDB)
deploy.ps1 One-command deployment for Windows
tests/ pytest suite (unit + lambda handler + stdio E2E)
```
## Local setup
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements-dev.txt
```
### Check the tests
```powershell
pytest
```
### Run locally
- **stdio** (default, for Claude Desktop and the inspector):
```powershell
python mcp_server.py
```
- **streamable HTTP** (for browsers / HTTP clients):
```powershell
python mcp_server.py --transport http
# serves at http://127.0.0.1:8000/mcp
```
### Try it with the MCP Inspector
```powershell
npx @modelcontextprotocol/inspector python mcp_server.py
```
## Deploy to AWS Lambda
Prerequisites: [AWS CLI](https://aws.amazon.com/cli/) (`aws configure`) and [SAM CLI](https://aws.amazon.com/serverless/sam/).
```powershell
.\deploy.ps1
```
The script builds dependencies (`build_deps.ps1`), packages the template, and creates/updates the stack. `sam deploy`/`sam package` creates:
- a Lambda function (`biz-mcp`) with the Python 3.12 runtime,
- a **Lambda Function URL** (public, HTTPS, `AuthType: NONE`) with permissive CORS,
- a **DynamoDB** table (`biz-mcp-store`, `pk`/`sk` composite key, on-demand capacity) plus the IAM policy so the Lambda can access it,
- Lambda `url:Invoke` and `Function:Invoke` permissions (both are required since Oct 2025 for Function URLs with `AuthType: NONE`).
Notes on the deploy path:
- `FunctionUrl.Cors.AllowMethods` does **not** accept `OPTIONS` (supported enum: `GET | PUT | HEAD | POST | PATCH | DELETE | *`). Omit it; Lambda handles preflight automatically.
- Deployment uses `sam package` + `aws cloudformation create-stack`/`update-stack` directly rather than a changeset, because the CloudFormation `AWS::EarlyValidation::PropertyValidation` hook (Nov 2025) can spuriously fail changeset creation. `sam build` is not run on Windows; dependencies are built into `build/mcp.zip` by `build_deps.ps1` via `uv`.
Manual equivalents:
```powershell
.\build_deps.ps1 # build build/mcp.zip
sam package --template-file template.yaml --resolve-s3 --output-template-file packaged.yaml
aws cloudformation create-stack --stack-name biz-mcp --template-body file://packaged.yaml `
--capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND
```
### Point an MCP client at it
Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"biz-mcp": {
"type": "http",
"url": "https://YOUR_ENDPOINT/"
}
}
}
```
The MCP Inspector also accepts an HTTP server URL directly.
## Run the bundled agent (free Groq brain)
`agent/agent.py` is a ready-made agent that connects to the MCP endpoint and uses a
**free-tier Groq** model to drive the 7 tools with native function calling.
```powershell
# use the project venv so openai/mcp deps don't clash with other Python installs
& "C:\Users\lenovo\AppData\Local\Programs\Python\Python312\python.exe" -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r agent/requirements.txt
# get a free key once at https://console.groq.com/keys
setx GROQ_API_KEY "gsk_..."
python agent/agent.py --url https://YOUR_ENDPOINT/
# or a one-shot question:
python agent/agent.py --url https://YOUR_ENDPOINT/ `
--question "Convert 250 USD to EUR, then save it as pk=1201 sk=usd-eur"
```
The agent lists the MCP tools, then chains tool calls until it can answer (it can
save/read records in DynamoDB, check inventory, price orders and shipping, and pull
live FX/crypto rates). Check the current free model id with `GET /openai/v1/models`
and pass it via `--model` if the default is unavailable.
## Smoke test without a client
```powershell
curl -s -X POST https://YOUR_ENDPOINT/ `
-H "Content-Type: application/json" `
-H "Accept: application/json, text/event-stream" `
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'
```
`tools/list` will return the 7 tools:
```powershell
curl -s -X POST https://YOUR_ENDPOINT/ `
-H "Content-Type: application/json" `
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```
## How the transport works
`lambda_function.py` implements the MCP **streamable HTTP** protocol directly on Lambda:
- `POST` with a JSON-RPC body; notifications return `202`, requests return `200`.
- If the client sends `Accept: application/json` it receives a single JSON-RPC response.
- If the client sends `Accept: text/event-stream` it receives the same response wrapped in one SSE `message` event, so streaming-capable clients work without requiring API Gateway's response-streaming features.
- `OPTIONS` preflight plus plain API-Gateway v1/v2 proxy and Lambda-Function-URL events are handled by the same normalizer.
No long-lived connections are needed, which is exactly what makes this Lambda-friendly.
## Security notes (read before going public)
- The Function URL is `AuthType: NONE`, meaning **anyone with the URL can invoke the Lambda**.
- Each public call invokes billing and lets anyone read/write the DynamoDB table.
- For production: set `AuthType: AWS_IAM` and use a SigV4-signed MCP client, add request throttling, or front the URL with WAF. The tools also accept arbitrary `pk`/`sk`, so add an authorization layer (e.g. per-tenant key prefix) if the data is sensitive.
- CoinGecko and Frankfurter free tiers have rate limits; failures surface as tool errors.
## Customization
- Add tools: define a function in `mcp_server.py` with the `@mcp.tool()` decorator and a docstring; the JSON schema is generated automatically. Rebuild/`sam deploy` to publish.
- Note: `mcp` is pinned to `>=1.30,<2` because mcp 2.x hard-depends on `pywin32` on Windows, which breaks `sam build` on Windows hosts. The API used here is identical in v2 (just import `MCPServer` instead of `FastMCP`).
- Swap the store: keep `db.py`'s interface (`save_item`, `get_item`, `list_items`) and implement a new backend (e.g. RDS, S3).
- Switch transport: the built-in `mcp.run(transport="streamable-http")` path runs the same tools as a plain web server if you ever move to EC2 instead.This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues