mcp-research
README.md
# MCP Research
A TypeScript monorepo containing a read-only financial API and stateless remote MCP server on Cloudflare. Both transports share the same user-scoped application services backed by Cloudflare D1 and Drizzle ORM. The MCP endpoint supports development bearer tokens and an OAuth 2.1 authorization-code flow with PKCE.
## Requirements and setup
- Node.js 24 LTS or newer
- pnpm 10.33.0 (pinned through `packageManager`)
- A Cloudflare account only when deploying
```sh
corepack enable
pnpm install
cp apps/finance-api/.dev.vars.example apps/finance-api/.dev.vars
pnpm --filter @mcp-research/finance-api db:migrate:local
pnpm --filter @mcp-research/finance-api db:seed:local
pnpm dev
```
The local Worker is then available at the URL printed by Wrangler. `GET /` is a public health check. Financial endpoints require one of the development-only bearer tokens configured in `.dev.vars`:
```sh
curl -H 'Authorization: Bearer dev-alice' \
'http://localhost:8787/v1/transactions?from=2026-06-01&to=2026-06-30'
```
The development authentication mechanism refuses all requests unless `ENVIRONMENT=development`. It must be replaced with the OAuth authorization context before production deployment.
## MCP
The Streamable HTTP MCP endpoint is available at:
```text
POST /mcp
```
It exposes four read-only tools:
```text
get_financial_overview
list_transactions
get_transaction
summarize_cashflow
```
For a quick local connection, start the Worker and run MCP Inspector:
```sh
pnpm dev
npx @modelcontextprotocol/inspector@latest
```
Connect the Inspector to `http://localhost:8787/mcp`. The OAuth flow signs in through GitHub, verifies the immutable numeric GitHub user ID against an owner allowlist, and then displays the MCP consent screen. Direct development protocol requests can instead use `Authorization: Bearer dev-alice` or `Authorization: Bearer dev-bob`.
The MCP server is stateless: every request creates a new MCP server and transport. Tool handlers call the same application services as the Hono routes and never make loopback HTTP requests.
### OAuth
The Worker provides the MCP OAuth endpoints and discovery documents:
```text
GET|POST /authorize
GET /callback
POST /token
POST /register
GET /.well-known/oauth-protected-resource
GET /.well-known/oauth-authorization-server
```
Only the `finance:read` scope is supported. OAuth grants carry an internal `userId`; MCP tools derive ownership from that grant and never accept a user ID as input. The verified GitHub identity is mapped to the pre-existing financial owner through the `identity_links` table.
Create a GitHub OAuth App with `http://localhost:8787/callback` as its local callback URL, then configure these ignored `.dev.vars` values:
```text
FINANCE_OWNER_USER_ID=usr_alice
GITHUB_ALLOWED_USER_ID=<immutable numeric GitHub user ID>
GITHUB_CLIENT_ID=<OAuth App client ID>
GITHUB_CLIENT_SECRET=<OAuth App client secret>
```
The authenticated GitHub ID must exactly match `GITHUB_ALLOWED_USER_ID`. On the first successful owner login, the server may create that one identity link to `FINANCE_OWNER_USER_ID`; it never creates financial users. All other valid GitHub accounts receive `403`. Use a separate GitHub OAuth App with the deployed HTTPS callback URL for production, and store its client secret with `wrangler secret put` rather than in source control.
For a remote deployment, create a KV namespace for OAuth token and grant storage and replace the placeholder `OAUTH_KV` ID in `apps/finance-api/wrangler.jsonc`:
```sh
pnpm --filter @mcp-research/finance-api exec wrangler kv namespace create OAUTH_KV
```
If a browser-based MCP client uses a different origin, add it to the comma-separated `MCP_ALLOWED_ORIGINS` binding. Requests without an `Origin` header are supported; untrusted supplied origins receive `403`.
## API
All financial routes derive the user ID from the authenticated principal. No route accepts a caller-supplied user ID.
```text
GET /v1/me
GET /v1/accounts
GET /v1/accounts/:accountId
GET /v1/balances
GET /v1/transactions?account_id=&from=&to=&cursor=&limit=
GET /v1/transactions/:transactionId
GET /v1/analytics/cashflow?from=&to=
```
Amounts are integers in minor currency units. Transaction listings use an opaque cursor and default to 50 results, with a maximum of 100.
## Database
The Drizzle schema lives in `apps/finance-api/src/db/schema.ts`. After changing it, generate a migration with:
```sh
pnpm --filter @mcp-research/finance-api db:generate
```
The committed D1 identifier is a local placeholder. Before remote deployment, create the database and replace `database_id` in `apps/finance-api/wrangler.jsonc` with the returned ID:
```sh
pnpm --filter @mcp-research/finance-api exec wrangler d1 create finance-api-db
pnpm --filter @mcp-research/finance-api exec wrangler d1 migrations apply finance-api-db --remote
pnpm --filter @mcp-research/finance-api exec wrangler d1 execute finance-api-db --remote --file=./seed/production-bootstrap.sql
```
The deterministic development seed is for local development and integration tests only. The
production bootstrap inserts the configured owner without adding sample financial data; keep its
user ID aligned with `FINANCE_OWNER_USER_ID`.
## Quality checks
```sh
pnpm test
pnpm format:check
pnpm lint
pnpm typecheck
pnpm build
```
The Worker test suite runs inside Cloudflare's Workers runtime and applies the D1 migrations to isolated local storage.
`GET /` returns:
```json
{ "service": "mcp-research-finance-api", "status": "ok" }
```
Run `pnpm --filter @mcp-research/finance-api types` after changing `wrangler.jsonc` bindings; Wrangler generates `apps/finance-api/worker-configuration.d.ts`, which TypeScript consumes through the API tsconfig. Deploy with `pnpm --filter @mcp-research/finance-api run deploy` after configuring a real D1 database and production authentication.
## Structure
```text
apps/finance-api/src/ Hono routes, MCP tools, OAuth, services, repositories, and schema
apps/finance-api/migrations/ generated D1 migrations
apps/finance-api/seed/ deterministic development data
apps/finance-api/test/ Worker-runtime integration tests
tsconfig.base.json shared strict TypeScript settings
turbo.json workspace task graph
```
The architecture is deliberately one Worker. `/v1` and `/mcp` are transport adapters over shared finance services. Separate Workers or applications should be introduced only when operational or security boundaries justify them.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues