Appearance
MCP server
Flowstate is an MCP server. Install it once into Claude.ai or ChatGPT, and your engineers can ask questions about workforce, AI spend, projects, scenarios — answered against your live Flowstate data — from the chat window. Same server, two install paths.
For the install side, see Flowstate in Claude and Flowstate in ChatGPT. This page is the protocol reference.
Endpoints
The server is mounted at /api/mcp on every tenant subdomain.
| Method | Path | Purpose |
|---|---|---|
| GET | /.well-known/oauth-authorization-server | OAuth discovery (RFC 8414) |
| POST | /api/mcp/oauth/register | Dynamic client registration (RFC 7591) |
| GET / POST | /api/mcp/oauth/authorize | OAuth consent and authorization-code issuance |
| POST | /api/mcp/oauth/token | Token exchange and refresh |
| POST | /api/mcp/protocol | MCP JSON-RPC requests (Streamable HTTP) |
| GET | /api/mcp/protocol | SSE for server-initiated messages |
| DELETE | /api/mcp/protocol | Optional session cleanup |
Base URL example:
https://acme.flowstate.inc/api/mcpFeature toggle
The entire /api/mcp surface is gated behind the per-tenant mcp_external_access feature toggle. With the toggle off, every endpoint returns:
json
{
"error": "mcp_disabled",
"error_description": "MCP external access is not enabled for this organization"
}with HTTP status 403. Contact Flowstate support to flip the toggle on for your tenant.
OAuth flow
1. Discovery
The MCP client fetches the discovery document:
GET https://{tenant}.flowstate.inc/.well-known/oauth-authorization-serverResponse:
json
{
"issuer": "https://{tenant}.flowstate.inc",
"authorization_endpoint": "https://{tenant}.flowstate.inc/api/mcp/oauth/authorize",
"token_endpoint": "https://{tenant}.flowstate.inc/api/mcp/oauth/token",
"registration_endpoint": "https://{tenant}.flowstate.inc/api/mcp/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"scopes_supported": ["openid", "profile"]
}2. Dynamic client registration
The MCP client registers itself:
POST /api/mcp/oauth/register
Content-Type: application/json
{
"client_name": "Claude.ai",
"redirect_uris": ["https://claude.ai/api/mcp/callback"]
}Response (201 Created):
json
{
"client_id": "mcp_<32 hex>",
"client_secret": "<64 hex>",
"client_name": "Claude.ai",
"redirect_uris": ["https://claude.ai/api/mcp/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_post"
}3. Authorization
Standard authorization-code with PKCE (S256 only). The user is redirected to the consent page on /api/mcp/oauth/authorize and approves the requested scopes. Auth codes have a 5-minute TTL.
4. Token exchange
POST /api/mcp/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=<auth code>&
client_id=<client id>&
client_secret=<client secret>&
redirect_uri=<registered redirect>&
code_verifier=<PKCE verifier>Response:
json
{
"access_token": "<JWT, 1 hour>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<opaque, 30 days>",
"scope": "openid profile"
}Refresh tokens are rotated on every use of the refresh_token grant.
Calling tools
Tool calls go to the protocol endpoint:
POST /api/mcp/protocol
Authorization: Bearer <access token>
Content-Type: application/json
{ "jsonrpc": "2.0", "method": "tools/call", "id": 1, "params": { ... } }The transport is Streamable HTTP (per the MCP spec). A fresh server context is created per request, scoped to the calling user.
Rate limiting
100 requests per minute per user. Implemented as a sliding-window counter. If the limiter's backing store is unreachable the limiter fails open (the request is allowed).
Every response carries:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: <0–100>Exceeding the limit returns:
json
{
"jsonrpc": "2.0",
"error": { "code": -32000, "message": "Rate limit exceeded. Try again shortly." },
"id": null
}with HTTP status 429.
Tool catalogue
Read tools — workforce and analytics
No scenario required.
| Tool | Purpose |
|---|---|
get_organization_context | Org overview — call this first |
search_employees | Search by name, email, geography, skill, or custom attributes |
get_employee_details | Full details including allocations and salary |
rank_employees | By salary or bonus — financial permission required |
search_teams | Team search by name or custom attributes |
get_team_details | Members, vacancies, contractors, projects |
search_projects | By name, delivery status, owner, or custom attributes |
get_project_details | Allocations and financial data |
find_projects_with_issues | Completed-with-allocations or no-allocations |
search_contractors | By name or team |
search_vacancies | By role, team, status |
get_geographies | Locations and IDs |
list_job_roles | Standardised role titles |
list_custom_attributes | The org's custom attribute definitions, for use with the search_* filters |
query_analytics | Slice-and-dice workforce metrics (FTE, cost, headcount), AI spend and usage (AI_COST, AI_TOKENS, AI_REQUESTS, AI_SESSIONS), code-delivery throughput (PR_COUNT, COST_PER_PR, LINES_CHANGED) and effort submissions across teams, projects, employees, providers, models, repos, cost centres, work types |
list_scenarios | Existing scenario plans |
Read tools — AI usage and security
AI spend/usage breakdowns route through query_analytics (see above) — the former dedicated spend tools were removed.
| Tool | Purpose |
|---|---|
get_ai_security_metrics | Shadow AI usage, DLP signal counts, and customer-data exfiltration event counts |
Read tools — effort
| Tool | Purpose |
|---|---|
get_project_effort | Actual effort breakdown for a project |
get_team_effort | Actual effort breakdown for a team |
get_unattributed_effort | Effort not linked to any Flowstate project |
get_effort_gaps | People active in the period whose effort never reaches the capitalisation split |
get_uncosted_effort | Workers with real effort but no resolvable salary or rate |
get_submission_leaderboard | On-time vs late effort-report submissions per submitter |
Read tools — code delivery
Require the effort-view permission; person cost requires financial permissions.
| Tool | Purpose |
|---|---|
get_code_delivery_summary | Period-scoped PR throughput with attributed cost and effort, filterable by team, repo, or initiative |
get_code_delivery_trends | Monthly code delivery trend plus per-initiative spend series |
list_pull_requests | Paginated, searchable pull requests with attributed cost, effort, and project and initiative links |
Read tools — portfolio and initiatives
| Tool | Purpose |
|---|---|
search_initiatives | Search initiatives by name; filter by status, financeMode, or portfolio lens |
get_initiative_details | An Initiative's finance fields + rollups (effort actuals, forecast, variance), child projects, and the derived capitalisable / isInert signal |
list_portfolio | The Objective → Initiative → Project tree with a variance figure per Initiative |
list_objectives | Objectives (the strategy layer) with Key-Result progress and linked-initiative counts |
Read tools — budgets and forecast
Financial permissions required.
| Tool | Purpose |
|---|---|
list_budget_requests | Budget requests, filterable by fiscal year and status |
get_budget_request | A budget request with all its proposals and statuses |
get_budget_proposal | A budget proposal with status, assignee, and approver |
get_budget_envelope | A team's budget envelope for a fiscal year, with divergence data |
list_forecast_budget_snapshots | The locked baselines that variance and drift compare the live forecast against |
get_forecast_budget_drift_summary | How far the live forecast has drifted from a locked budget snapshot |
Read tools — CapEx and R&D
Financial or R&D-tax view permissions required.
| Tool | Purpose |
|---|---|
get_capex_project_breakdown | Per-project CapEx cost breakdown by resource type for the period |
get_capex_initiative_breakdown | Capitalisation cost rolled up by Initiative |
get_capex_declared_vs_claimed | Declared vs claimed effort cost, surfacing unclaimed capitalisable spend |
list_capex_claims | CapEx claims with initiative, project count, and status |
list_rd_claims | R&D tax claims with workflow stage, project, fiscal year, and amounts |
Write tools — scenarios
Every scenario write tool mutates a specific scenario, never live data. The user must call create_scenario first; subsequent write calls take a planId argument.
| Tool | Purpose |
|---|---|
create_scenario | New scenario; required before any write |
add_team, update_team, delete_team | Team CRUD |
add_employee, update_employee, move_employee, terminate_employees | Employee CRUD + lifecycle |
add_vacancy, update_vacancy, delete_vacancy | Vacancy CRUD |
add_contractor, update_contractor, delete_contractor | Contractor CRUD |
add_project, update_project, delete_project | Project CRUD |
allocate_employee_to_project | Allocate one engineer |
allocate_contractor_to_project | Allocate a contractor |
allocate_vacancy_to_project | Allocate a vacancy (planned hire) |
allocate_team_to_project | Allocate a team |
update_allocation | Change FTE, dates, or end an allocation |
remove_allocation | Remove an allocation from the scenario |
add_ai_agent, update_ai_agent, delete_ai_agent | Resourced AI agent CRUD; an agent runs a catalogue model and must have a human owner |
update_budget | Set a budget amount on an effective date |
Budget workflow tools
These operate on the live budget request and proposal workflow, not a scenario. They drive the same workflow as Finance → Budgets in the app.
| Tool | Purpose |
|---|---|
create_budget_request | Create a budget request for the organization |
cancel_budget_request | Cancel (close) a budget request, freeing the fiscal-year slot for a fresh cycle |
assign_budget_proposal | Assign a proposal to a user for an unmanaged team |
submit_budget_proposal | Submit a proposal for review |
approve_budget_proposal | Approve a submitted proposal; a child proposal rolls up into its parent team's proposal |
reject_budget_proposal | Reject a submitted proposal and return it to the assignee |
merge_budget_proposal | Refuses and explains the correct flow: budgets are finalised by locking the budget request, never merged |
add_budget_proposal_comment | Append a comment to a proposal's review thread |
Initiative finance tools
These manage initiatives. Unlike the scenario write tools above, they write LIVE data directly — an Initiative is a live governance object (like a cost centre or a budget), not a scenario edit, so there is no planId and no merge step. Money fields require financial permissions.
| Tool | Purpose |
|---|---|
add_initiative | Create a human-authored Initiative; optionally set financeMode and link an objective / portfolio lens |
update_initiative | Patch finance fields (financeMode, cost centres, lens, objective, budgetedCost/budgetCurrency, marginPct, committedFte, AI budget) — partial update |
delete_initiative | Delete an Initiative (hard delete) |
adopt_project_to_initiative | Adopt a project into an Initiative as its finance parent (≤1 parent; rejects with PROJECT_ALREADY_ADOPTED) |
detach_project_from_initiative | Detach a project, restoring its own cost centre |
create_capex_claim | Create a CapEx/R&D claim — Initiative-anchored (auto-pulls child projects; the Initiative must be CapEx-eligible) or ad-hoc over projectIds; the one-claim-per-project-per-period guard returns PROJECT_ALREADY_CLAIMED |
delete_capitalisation | Delete a per-project capitalisation workflow record (not the CapEx claim) |
There is no MCP tool for planning a commitment (committed FTE + AI budget) onto an Initiative. That is a GraphQL-only operation. The GraphQL contract the Initiative tools map onto is documented at API → Initiatives.
Objective tools
Objectives are the strategy layer above Initiatives. These write live data.
| Tool | Purpose |
|---|---|
add_objective, update_objective, delete_objective | Objective CRUD: name, description, status, owner, start/target dates |
add_key_result, update_key_result, delete_key_result | Key Result CRUD under an Objective: measurable targets with an increase/decrease direction |
AI attribution
| Tool | Purpose |
|---|---|
mark_my_ai_sessions_as_project | Attribute your own AI sessions to a project so their spend rolls up to it; sessions you don't own are ignored. Writes live data. |
Live writes, not scenarios
The budget workflow, Initiative finance, Objective, and AI attribution tools mutate live data and inherit the caller's RBAC: add_initiative needs ROADMAP_INITIATIVES_CREATE, update_initiative / adopt / detach need ROADMAP_INITIATIVES_UPDATE, and create_capex_claim needs FINANCIALS_VIEW_SUMMARY.
Permissions
Tool calls inherit the OAuth user's Flowstate permissions. If the user can't read salaries in the UI, rank_employees returns a permission error from MCP. There's no privilege escalation through the connector.
Audit and SIEM
Every protocol request emits a SIEM event with:
- The RPC method
- The OAuth client name
- The user, org and tenant identifiers
- Request ID, IP, user agent
Rate-limit hits and request errors emit higher-severity events. If your org has SIEM integration wired up, MCP traffic flows there alongside the rest of the platform.
Scenarios via MCP
Write operations require an explicit planId. The model is:
- Call
create_scenario({ title, type })→ returns a scenarioid. - Pass that
idasplanIdon every subsequent write call. - Once happy with the scenario, the user submits it for approval through the Workforce → Scenarios → Inbox UI. There's no MCP tool for approval — that's deliberately a human gate.
Read tools work against live data by default. To read from a specific scenario, pass planId to query_analytics (it accepts an optional scenario ID for what-if reads).
Disabling
To disable MCP for the whole org, ask Flowstate support to flip mcp_external_access off. The connector immediately starts returning 403 mcp_disabled. Existing tokens are not revoked but become unusable.
To revoke a single user's grant, remove the Flowstate connector from that user's chat client (Claude.ai or ChatGPT). Access tokens expire after one hour and refresh tokens are rotated on every use, so a removed connector loses access quickly. MCP OAuth grants do not appear on the Settings → Users & Access → API Keys page; that page manages REST API keys only.
See also
- Flowstate in Claude — Claude.ai install path
- Flowstate in ChatGPT — ChatGPT Connectors install path
- API → AI usage — what AI data the read tools surface
- API → Scenarios — scenarios the write tools mutate