Skip to content

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.

MethodPathPurpose
GET/.well-known/oauth-authorization-serverOAuth discovery (RFC 8414)
POST/api/mcp/oauth/registerDynamic client registration (RFC 7591)
GET / POST/api/mcp/oauth/authorizeOAuth consent and authorization-code issuance
POST/api/mcp/oauth/tokenToken exchange and refresh
POST/api/mcp/protocolMCP JSON-RPC requests (Streamable HTTP)
GET/api/mcp/protocolSSE for server-initiated messages
DELETE/api/mcp/protocolOptional session cleanup

Base URL example:

https://acme.flowstate.inc/api/mcp

Feature 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-server

Response:

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.

ToolPurpose
get_organization_contextOrg overview — call this first
search_employeesSearch by name, email, geography or skill
get_employee_detailsFull details including allocations and salary
rank_employeesBy salary or bonus — financial permission required
search_teamsTeam search by name
get_team_detailsMembers, vacancies, contractors, projects
search_projectsBy name, lifecycle stage, owner
get_project_detailsAllocations and financial data
find_projects_with_issuesCompleted-with-allocations or no-allocations
search_contractorsBy name or team
search_vacanciesBy role, team, status
get_geographiesLocations and IDs
list_job_rolesStandardised role titles
query_analyticsSlice-and-dice across teams, projects, employees, cost centres, work types
get_ai_usage_summaryAI spend by system
get_ai_spend_by_teamAI spend by team with adoption rate
get_ai_spend_by_personPer-user AI spend
list_scenariosExisting scenario plans
get_project_effortActual effort breakdown for a project
get_team_effortActual effort breakdown for a team
get_unattributed_effortEffort 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.

ToolPurpose
create_scenarioNew scenario; required before any write
add_team, update_team, delete_teamTeam CRUD
add_employee, update_employee, move_employee, terminate_employeesEmployee CRUD + lifecycle
add_vacancy, update_vacancy, delete_vacancyVacancy CRUD
add_contractor, update_contractor, delete_contractorContractor CRUD
add_project, update_project, delete_projectProject CRUD
allocate_employee_to_projectAllocate one engineer
allocate_team_to_projectAllocate a team
update_allocationChange FTE, dates, or end an allocation
add_ai_agent, update_ai_agent, delete_ai_agentAI agent (seat-cost) CRUD
update_budgetSet 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:

  1. Call create_scenario({ title, type }) → returns a scenario id.
  2. Pass that id as planId on every subsequent write call.
  3. 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 Documentation