Poof
Poof · Developer reference

Poof MCP Connector

A read-only Model Context Protocol server that lets an AI agent platform answer questions from one Poof organization's books — financial summaries, P&L, job profitability, receivables, payables, transactions, and month-end close status.

Server poof v0.1.0 · Last updated 2026-10-20

Nothing writes. Every tool on this surface reads. The connector cannot create, edit, categorize, approve, or send anything. Changes in Poof always require a signed-in person acting in the Poof app with their own permissions.

Endpoint

MCP endpoint
https://mcp.poofai.com/mcp
Transport
Streamable HTTP, stateless. POST only; GET and DELETE return 405. Responses are JSON (no SSE stream). No session IDs — every request is independent, so it works behind load balancers without affinity.
Content types
Send Content-Type: application/json and Accept: application/json, text/event-stream.
Implementation
@modelcontextprotocol/sdk 1.30 server, TypeScript.

Authentication

Every request to /mcp carries a bearer token. A request without one gets 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp", which is where an MCP client starts.

1. Sign in with Poof (OAuth 2.1): Claude, ChatGPT and other MCP clients

The person connecting signs in to Poof the normal way (password or Google, then their two-step code if they use one), sees which company and what the AI will read, and chooses Allow or Deny. The connection acts as that person in that company, and their role decides which tools appear (an office manager's AI sees receivables and invoices but no P&L). Removing the person from the company, or Disconnect in Settings → Chat apps → AI assistants, ends access on the next call.

Resource metadata
/.well-known/oauth-protected-resource/mcp (RFC 9728)
Server metadata
/.well-known/oauth-authorization-server (RFC 8414)
Registration
POST /mcp/oauth/register, dynamic client registration (RFC 7591). Public clients only (token_endpoint_auth_method: none). Redirect URIs must be https://, or http://localhost / 127.0.0.1 (any port).
Authorization
GET /mcp/oauth/authorize, authorization code with PKCE S256 (required). The resource parameter, if sent, must be this server's /mcp URL.
Token
POST /mcp/oauth/token (form or JSON): authorization_code and refresh_token grants.
Revocation
POST /mcp/oauth/revoke (RFC 7009) ends the connection.
Scope
mcp:read
Lifetimes
Access token 1 hour. Refresh token 60 days, rotated on every use; presenting an already-used refresh token ends the connection. Codes are one-time and expire in 5 minutes.
Fixed client ID
An app that asks for a client ID instead of registering itself (Grok) can use any ID, such as poof, with no secret, when its return address is that app's own site (claude.ai, chatgpt.com, grok.com).
Who can connect
Anyone whose role in the company can use Ask Poof. Viewers can't.

2. Agent key (pilots and server-to-server)

Poof can issue an agent key per organization, per bot, on request. Keys look like agk_<accountId>_<secret>, are shown once, are bcrypt-hashed at rest, carry a scope list (below) and are revoked instantly. Send it as Authorization: Bearer agk_….

Tools

Nineteen read-only tools. Every tool is annotated readOnlyHint: true, destructiveHint: false, openWorldHint: false and carries a title. Each declares the scope an agent key must hold (a key with the blanket mcp:read sees all of them); for a signed-in person, their role decides. Dates are YYYY-MM-DD; periods resolve against today.

ToolScopeWhat it returns
get_financial_summarymcp:financials.readOne-call overview for a period: revenue, COGS, gross profit, operating expenses, net income, plus open/overdue A/R and unpaid A/P. The right first call for "how is the business doing".
get_profit_and_lossmcp:financials.readFull income statement between two dates, every revenue/COGS/expense account, accrual or cash basis.
get_job_profitabilitymcp:financials.readPer-job P&L for trade contractors: revenue, COGS split into labor / subcontractors / materials, gross profit and margin, plus an Unassigned bucket.
search_transactionsmcp:financials.readBank/book transactions by text, category, review status, date range and amount. status=FOR_REVIEW lists what is waiting for a person.
get_ar_agingmcp:ar.readReceivables aging as of a date, bucketed current / 1–30 / 31–60 / 61–90 / 90+ by customer, with underlying invoices.
get_open_invoicesmcp:ar.readItemized unpaid invoices: number, customer, total, amount due, due date, days overdue, status, with totals.
get_ap_agingmcp:ap.readPayables aging by vendor in the same buckets.
get_open_billsmcp:ap.readItemized unpaid bills with due dates and amounts.
get_ap_cash_planmcp:ap.readThe most recent 13-week cash plan: weekly beginning cash, inflows, required and discretionary payments, ending cash and reserve breaches, plus a suggested action per bill (pay now / this week / scheduled / defer).
get_close_statusmcp:close.readMonth-end close tie-out results for a period: each check with pass / warn / fail / skipped, severity (blocker vs warning) and any dollar delta — GL balance, A/R and A/P subledger ties, bank reconciliation, accrual roll-forward.
get_accrual_schedulesmcp:accruals.readAccrual subledger roll-forward for a period: each schedule (accrued liability, customer deposit, deferred revenue, reserve) with opening, accrued, released and closing balances, the matching GL balance, and whether they tie.
get_reportmcp:financials.readBusiness reports with the numbers Poof's screens show: KPIs (close rate, average ticket, top techs, callbacks, members), revenue by service and more.
get_purchase_ordersmcp:ap.readPurchase orders to suppliers and what is on order, for a job, one PO, or all open POs.
get_fixed_assetsmcp:assets.readEvery van, truck, tool and piece of equipment: class, in-service date, cost, depreciation so far and book value.
get_depreciationmcp:assets.readA month's depreciation by asset, whether it is posted, and the year so far.
get_loansmcp:assets.readEach loan: lender, balance owed, rate, payment, payments left, next due and interest paid this year.
list_proposalsmcp:proposals.readProposals waiting for a person in the Approvals inbox: id, tool, summary, status, created. Read only.
get_proposalmcp:proposals.readOne proposal with its items, evidence and decision so far. Read only.
request_approval_linkmcp:proposals.readA link a person can open to decide a proposal in Poof. The link decides nothing by itself; the person does, signed in, with their own permission.

Tool results are JSON in a single text content block. Each tool's full input schema is served by tools/list.

Scopes

Scopes are set when the key is issued. One key per bot per organization is the intended shape.

Data protection

Everything returned through this connector passes Poof's egress policy for third-party agent platforms, which cannot be configured below enforce:

Limits and access

Tenancy
A connection or key is bound to exactly one Poof organization. Every tool is scoped to that organization; there is no cross-organization read.
Rate limit
120 requests per minute per client IP. Standard RateLimit-* headers are returned.
Regions
No regional restriction. Poof is US-hosted; books are USD.
Plan
Any active Poof organization. Signing in is self-serve; agent keys are issued by Poof on request from an organization admin.
Evaluation
A demo organization with synthetic data is available for connector review and testing. Ask at the support address below.

Example

curl -X POST https://mcp.poofai.com/mcp \
  -H "Authorization: Bearer poof_at_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"get_financial_summary","arguments":{"period":"last_month"}}}'

Errors

HTTPMeaning
401Missing, expired, disconnected or unknown token. The WWW-Authenticate header points to the sign-in metadata.
403Key holds no mcp:* scope.
405Non-POST request to /mcp.
429Rate limit exceeded.
200 + isError: trueTool-level failure (a report could not be produced, identity protection unavailable). The message is in the content block; invalid arguments and unknown tools come back as JSON-RPC errors instead.