Skip to main content
Glama
varunmulay-droid

Pandas Excel Analytics MCP

README.md
# Pandas Excel Analytics MCP

A deterministic Model Context Protocol (MCP) server exposing Pandas, NumPy,
Excel/openpyxl, and automated EDA operations over uploaded tabular datasets.

This server does **not** contain an LLM, embeddings, RAG, or Langflow/OpenRouter
credentials. It is purely the data-execution layer, meant to be called by an
MCP client (e.g. Langflow's MCP Tool node) driven by an LLM.

Built on the current stable MCP Python SDK (`mcp` v2.x), where the server
class is `mcp.server.mcpserver.MCPServer` — the successor to the
pre-2.0 `mcp.server.fastmcp.FastMCP` name used in older tutorials. The public
API (`.tool()`, `.streamable_http_app()`) is unchanged.

## Architecture

 ![Architecture Diagram](architecture.svg)

## Install

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
```

## Run locally

```bash
uvicorn server:app --reload --host 127.0.0.1 --port 8000
```

- MCP endpoint: `http://127.0.0.1:8000/mcp`
- Health check: `http://127.0.0.1:8000/health`

## Test with MCP Inspector

```bash
npx @modelcontextprotocol/inspector
```

Then connect to `http://127.0.0.1:8000/mcp` using the "Streamable HTTP"
transport in the Inspector UI, and browse/call the 27 registered tools.

## Run the test suite

```bash
pytest tests/ -v
```

## Environment variables

| Variable | Purpose | Required |
|---|---|---|
| `PORT` | Port to bind (set automatically by Render) | No (default 8000) |
| `MCP_API_TOKEN` | Bearer token for authenticated requests | Recommended in production |
| `MCP_REQUIRE_AUTH` | Force auth even without inspecting `MCP_API_TOKEN` | No |
| `MCP_ALLOWED_HOSTS` | Comma-separated hostnames this server is reachable at, e.g. `your-service.onrender.com` | **Yes, in production** |
| `MCP_MAX_FILE_SIZE_MB` | Upload size limit (default 50) | No |
| `MCP_MAX_ROWS` / `MCP_MAX_COLUMNS` | Dataset shape limits | No |
| `MCP_MAX_EXCEL_SHEETS` | Excel workbook sheet limit | No |
| `MCP_MAX_OUTPUT_ROWS` | Max rows returned per tool call | No |

`OPENROUTER_API_KEY` and any LLM credentials are intentionally **not**
consumed by this server — keep them in Langflow.

## Deploy to Render

1. Push this repository to GitHub.
2. In Render, "New +" → "Blueprint", point it at the repo (uses `render.yaml`).
3. Render will set `MCP_API_TOKEN` automatically (via `generateValue: true`);
   copy it from the Render dashboard's Environment tab for your MCP client.
4. Set `MCP_ALLOWED_HOSTS` to your actual `*.onrender.com` hostname (the
   Blueprint pre-fills a guess — update it once Render assigns the final
   service name).
5. Deploy. Render runs `pip install -r requirements.txt` then
   `uvicorn server:app --host 0.0.0.0 --port $PORT`.
6. Verify: `curl https://YOUR-SERVICE.onrender.com/health` → `{"status":"ok"}`.

Production MCP URL:
```
https://YOUR-SERVICE.onrender.com/mcp
```

## Security notes

- No `eval`/`exec`/shell execution/arbitrary imports anywhere in the codebase.
- Every dataset is addressed by an opaque `dataset_id` — callers never supply
  filesystem paths.
- Filenames are sanitised and path-joined under a fixed storage root
  (`utils/security.py::safe_join`) — path traversal is structurally impossible.
- `TransportSecuritySettings` host allowlist is always configured; the
  SDK's default localhost-only protection is never disabled.
- Bearer-token auth is opt-in via `MCP_API_TOKEN`; when unset the server
  runs unauthenticated (fine for local dev, **not** for a public Render URL).

## Next step: connecting Langflow

1. In Langflow, add an **MCP Tools** / **MCP Client** component.
2. Point it at `https://YOUR-SERVICE.onrender.com/mcp` using the Streamable
   HTTP transport, and add header `Authorization: Bearer <MCP_API_TOKEN>`.
3. Set the component to "Tool Mode" so all 27 tools populate as a Toolset.
4. Wire that Toolset into your Base LLM Agent (OpenRouter) node.
5. Test with a prompt like: *"Upload sales.csv and tell me which region has
   the highest profit."*

Langflow + OpenRouter + embeddings/RAG are deliberately out of scope for this
backend and should be added only after the above is verified working.