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
No scenario required.
| Tool | Purpose |
|---|---|
get_organization_context | Org overview — call this first |
search_employees | Search by name, email, geography or skill |
get_employee_details | Full details including allocations and salary |
rank_employees | By salary or bonus — financial permission required |
search_teams | Team search by name |
get_team_details | Members, vacancies, contractors, projects |
search_projects | By name, lifecycle stage, owner |
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 |
query_analytics | Slice-and-dice across teams, projects, employees, cost centres, work types |
get_ai_usage_summary | AI spend by system |
get_ai_spend_by_team | AI spend by team with adoption rate |
get_ai_spend_by_person | Per-user AI spend |
list_scenarios | Existing scenario plans |
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 |
Write tools
Every 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_team_to_project | Allocate a team |
update_allocation | Change FTE, dates, or end an allocation |
add_ai_agent, update_ai_agent, delete_ai_agent | AI agent (seat-cost) CRUD |
update_budget | Set a budget amount on an effective date |
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, revoke the OAuth client at Settings → API Keys.
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