client-report-mcp
README.md
# client-report-mcp
A remote MCP server that lets Claude fetch client report data (starting with
Arbor Energy, designed to extend to other clients) from
arborreport.polarisconsulting.net, authenticated via Microsoft Entra ID.
## Status: scaffold, not yet production-ready
This was built without access to two things — fill these in before handing
off to end users:
1. **The real report API contract.** `src/report-client.ts` currently
*guesses* a REST shape (`GET {REPORT_API_BASE_URL}/reports/{clientName}`).
Confirm the actual endpoint, auth requirements, and response shape with
whoever owns arborreport.polarisconsulting.net, then update that file.
2. **Real Entra ID app registration values** (tenant ID, client ID, client
secret) — see `.env.example`. Also confirm whether the report API expects
the Entra access token issued for basic sign-in scopes, or a specific API
scope (`api://<report-app-id>/...`) — this is a common gap and will show
up as 401/403 errors from the report API if wrong.
Everything else — the OAuth 2.1/DCR proxy in front of Entra ID, and the MCP
tool wiring — is implemented and has been type-checked and smoke-tested
(server boots, serves `/healthz` and OAuth metadata correctly).
## How it fits together
```
Claude <-- OAuth 2.1 / Dynamic Client Registration --> this server <-- OAuth2 --> Entra ID
|
v
arborreport.polarisconsulting.net
```
See the comment block at the top of `src/oauth.ts` for the full step-by-step
of the authorization flow.
## Local setup
```bash
npm install
cp .env.example .env # fill in real ENTRA_* and REPORT_API_BASE_URL values
npm run dev
```
For local testing with Claude (which needs a public HTTPS URL), run this
behind a tunnel (e.g. `ngrok http 3000`) and set `PUBLIC_URL` to the tunnel's
HTTPS URL before starting the server.
## Deploying
This is a plain Express app — deploy it anywhere that can run Node and is
reachable over the public internet (or reachable via an allowlisted outbound
connection if going through a corporate firewall). Cloudflare Workers/another
managed platform can also handle hosting + autoscaling if preferred; you'd
port the Express routes to that platform's request model.
Set the real `PUBLIC_URL` to wherever it ends up living, since the OAuth
callback URL (`{PUBLIC_URL}/oauth/callback`) has to match what's registered
in Entra ID for this app.
**Important:** the token stores in `src/oauth.ts` (`pendingAuthorizations`,
`pendingCodes`, `issuedTokens`) are in-memory `Map`s. They will not survive a
restart and won't work if you run more than one instance behind a load
balancer. Replace with Redis or a database before this goes beyond a
single-instance pilot.
## Handing this off to a Polaris Owner
Once deployed and confirmed working end-to-end:
1. Give the Owner (e.g. Andrew Shea or Nathan Loy) the deployed **MCP server
URL** (`{PUBLIC_URL}/mcp`).
2. They add it under **Organization settings > Connectors > Add > Custom**
in Claude — no OAuth Client ID/Secret needs to be entered there, since
this server implements its own Dynamic Client Registration and handles
the Entra exchange internally.
3. Each tech then connects individually from **Customize > Connectors** and
signs in with their own Microsoft account — access mirrors whatever that
person can already see in the report portal.
## Testing the OAuth flow before handoff
The official MCP inspector can walk through the full authorization flow
against a locally-tunneled instance of this server before you hand it to
anyone — see the "Test your connector" step in Anthropic's custom connector
docs.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues