Skip to main content
Glama
theaidrill

Cost Explorer MCP

by theaidrill
README.md
# Cost Explorer MCP

An **MCP App** — a tool that returns a real interactive UI, rendered live inside the
conversation, instead of just text. This one estimates Claude API costs: sliders for request
volume and token size, a live comparison chart across five models, and a button that hands
the picked model straight back to the conversation.

- Hand-rolled JSON-RPC 2.0 over HTTP — no `@modelcontextprotocol/sdk` on the server side
- Zero runtime dependencies — pure arithmetic, no external API call, so `tools/call` costs
  nothing to run
- Deploys as a single AWS Lambda function behind a Function URL

## Files

| File | Purpose |
|---|---|
| `index.mjs` | The server — JSON-RPC dispatch, plus `_meta.ui.resourceUri` on the tool and `resources/read` serving the bundled UI |
| `pricing.mjs` | The pricing data and the `estimate()` function — the only file you'd touch when prices change |
| `ui/mcp-app.html`, `ui/mcp-app.js` | The app itself — sliders, model cards, an SVG bar chart |
| `vite.config.js` | Bundles `ui/` into one self-contained `dist/mcp-app.html` |
| `package.json` | Zero `dependencies` — only `devDependencies`, build-time only |

## Build

```
npm install
npm run build
```

Produces `dist/mcp-app.html` — a single file with CSS and JS inlined, required since the host
renders it in a sandboxed iframe under a strict CSP.

## Deploy

Zip exactly three things: `index.mjs`, `pricing.mjs`, and `dist/`. Upload to a new Lambda
function (Node.js 22+, no native dependencies), set the `MCP_API_KEY` environment variable,
and create a Function URL with `AuthType: NONE` plus a CORS block:

- Allow origin: `*`
- Allow methods: `GET`, `POST`
- Allow headers: `content-type`, `x-api-key`, `mcp-protocol-version`, `mcp-session-id`, `last-event-id`

The CORS block matters because a browser-based host preflights every call with an `OPTIONS`
request before the real one — without it, that preflight gets rejected and the app never
loads.

## Connect

Add it as a custom connector in Claude Desktop or Claude web, using the Function URL with the
key appended as a query param (`?key=...`) — the connector dialog has no header field, so the
handler accepts the key via query string as a fallback to the `x-api-key` header.

Maintenance

ActivityMaintained
ResponsivenessNo issues