On this page
- MCP
- OAuth 2.1
- Streamable HTTP
Estimay MCP server
Estimay runs a remote Model Context Protocol server, so an AI assistant can create planning poker rooms, add stories, read a room and record the agreed estimates, as the person who connected it. This page lists how to connect it, how it signs in, and every tool. For what it is good for, see planning poker from your AI assistant.
REST API. There is no public REST API yet: the MCP server is how other software works with Estimay today. A public API, with personal access tokens and an OpenAPI description, is on our roadmap.
Server
- URL:
https://estimay.io/api/mcp - Transport: Streamable HTTP, stateless: every request stands alone, so there is no session to resume and
GETstreams are not offered. Both the current protocol and the 2025 revisions are served. - Auth: OAuth 2.1 bearer tokens (below). A request without one is answered
401with aWWW-Authenticateheader pointing to the protected resource metadata. - Price: free today, with no account, like the rest of Estimay.
Connecting a client
Add the server the way your client takes it; the Connect panel has each one to copy.
- URL
- Claude (claude.ai and the desktop app): Settings → Connectors → Add custom connector, then paste it. ChatGPT, VS Code and most other clients take a URL in their "Add server" dialog too.
- CLI
- Claude Code. Then type /mcp in a session and choose estimay to sign in.
- JSON config
- For clients configured with a file, such as Cursor (~/.cursor/mcp.json, or .cursor/mcp.json in a project).
The first time a tool is called, the client opens Estimay in your browser. You allow it there, as the guest that browser already is: rooms it creates are hosted by you in that browser.
Authentication
Estimay has no accounts, so the authorization server binds each connection to a browser's guest identity. Clients find everything themselves:
- Discovery.
https://estimay.io/.well-known/oauth-protected-resource/api/mcp(RFC 9728) names the authorization server, whose metadata is athttps://estimay.io/.well-known/oauth-authorization-server(RFC 8414). - Registration.
POST /api/oauth/register(RFC 7591) withredirect_uris: https, http tolocalhostor127.0.0.1, or an app's own scheme. Public clients (none) andclient_secret_post/client_secret_basicare supported. - Authorization.
GET /api/oauth/authorizewith PKCE (S256only). The person sees the consent page, chooses Read only or Read & write, and the browser returns to the client with a code (andiss, RFC 9207). - Tokens.
POST /api/oauth/tokentrades the code for an access token (one hour) and a refresh token. Each refresh returns a new refresh token and retires the old one.
Scopes
| Scope | Allows |
|---|---|
rooms:read | Reading the rooms the person is in, with the |
rooms:write | Every tool. Asked for by default; the person can still choose Read only. |
A connection unused for 30 days is deleted. The person sees and disconnects their connections under Connected assistants; a disconnected connection's next request gets 401.
Tools
Every tool that takes a room accepts its 5-character code or its invitation link. Each answers with the room as the person's own view shows it: its cards (deck), the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results. Votes are never shown before the reveal.
Tools are called with JSON-RPC tools/call at the server's URL; an answer's content holds one text item.
create_room
Create a room
Creates a planning poker room with you as its host, optionally with its first stories. Returns the room and its invitation link to share with the team.
Scope: rooms:write
Arguments
titlestringoptionalThe room name everyone sees, like "Sprint 42 planning".
modelive | asyncoptionallive (the default): the team votes together on the story on the table. async: every story is open for voting as it is added, and people vote in their own time.
deckfibonacci | modifiedFibonacci | tshirt | powersOfTwo | aiTokens | risk | clarity | agentReadyoptionalThe cards to vote with. Fibonacci when left out.
custom_cardsstring[]optionalThe room's own cards instead of a deck, 2 to 20 of up to 5 characters.
anonymousbooleanoptionalShow revealed votes only as totals, never who picked which card.
auto_revealbooleanoptionalLive rooms: turn the cards over once everyone present has voted.
passwordstringoptionalA password newcomers must type. Left out, anyone with the link can join.
stories{ title, ticket? }[]optionalUp to 200 stories, in the order to estimate them.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_room",
"arguments": {
"title": "Sprint 42 planning",
"stories": [
{
"title": "Refund to the original card"
}
]
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
join_room
Join a room
Joins a room someone else created, so you can read it and act in it. Not needed for rooms you created.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
passwordstringoptionalThe room's password, when it has one.
spectatorbooleanoptionalJoin to watch, without voting.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "join_room",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
get_room
Read a room
Reads a room you are in: its cards, who is there, the story on the table, and every story with its status and agreed estimate. Votes show only once revealed.
Scope: rooms:read
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_room",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
add_stories
Add stories
Adds stories to a room's list, on top, in the order given. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
stories{ title, ticket? }[]requiredUp to 200 stories, in the order to estimate them.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "add_stories",
"arguments": {
"room": "AB2CD",
"stories": [
{
"title": "Refund to the original card"
}
]
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
start_voting
Start voting
Puts a story on the table for the team to vote on. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
story_idstring (story id)optionalThe story to put on the table. Left out, the next one not estimated yet.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "start_voting",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
vote
Vote
Votes on the story on the table as you. Only when the person asks you to vote for them, with the card they chose: never offer the team a number before the reveal.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
cardstringrequiredOne of the room's cards, as get_room lists them in deck.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "vote",
"arguments": {
"room": "AB2CD",
"card": "5"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
reveal
Reveal the cards
Turns the cards over on the story on the table; live rooms count down for a few seconds first. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "reveal",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
set_estimate
Set the estimate
Saves a story's agreed estimate once its cards are revealed, or on any story not being voted on. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
estimatestring or nullrequiredThe agreed estimate, one of the room's cards; null clears it.
story_idstring (story id)optionalThe story to estimate. Left out, the revealed story on the table.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "set_estimate",
"arguments": {
"room": "AB2CD",
"estimate": "5"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
edit_story
Edit a story
Changes a story's title or tracker key. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
story_idstring (story id)requiredA story's id, as get_room lists it.
titlestringrequiredThe new title.
ticketstringoptionalThe new tracker key. Left out, it stays as it is.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "edit_story",
"arguments": {
"room": "AB2CD",
"story_id": "3f8c2a6e-5b1d-4e7a-9c0f-2d4b6a8e1c3f",
"title": "Refund to the original card, in cents"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
delete_story
Delete a story
Removes a story and its votes from the room. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
story_idstring (story id)requiredA story's id, as get_room lists it.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_story",
"arguments": {
"room": "AB2CD",
"story_id": "3f8c2a6e-5b1d-4e7a-9c0f-2d4b6a8e1c3f"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
revote
Vote again
Clears the cards on the story on the table and opens it for another round, when the team wants to vote again after talking. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "revote",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
start_timer
Start the discussion timer
Starts a discussion timer everyone in a live room sees, or stops it with null. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
secondsinteger or nullrequiredThe discussion's length in seconds, 1 to 3599; null stops the timer.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "start_timer",
"arguments": {
"room": "AB2CD",
"seconds": 120
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
change_deck
Change the cards
Changes the room's cards to a built-in deck or its own; votes still open are cleared. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
deckfibonacci | modifiedFibonacci | tshirt | powersOfTwo | aiTokens | risk | clarity | agentReadyoptionalA built-in deck. Give this or custom_cards.
custom_cardsstring[]optionalThe room's own cards, 2 to 20 of up to 5 characters. Give this or deck.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "change_deck",
"arguments": {
"room": "AB2CD",
"deck": "fibonacci"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
set_facilitator
Make someone a facilitator
Lets someone in the room run the game with the host (stories, voting, reveal, settings), or takes it back. Host only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
personstringrequiredThe person's name, as get_room lists it.
facilitatorbooleanrequiredtrue to let them run the game (stories, voting, reveal, settings), false to take it back.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "set_facilitator",
"arguments": {
"room": "AB2CD",
"person": "Ada",
"facilitator": true
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
export_results
Export the results
The room's stories with their agreed estimates and extra answers, and the total, as a Markdown table or CSV, ready to paste into notes, a ticket or a spreadsheet.
Scope: rooms:read
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
formatmarkdown | csvoptionalmarkdown (the default): a table and the total. csv: comma-separated text.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "export_results",
"arguments": {
"room": "AB2CD",
"format": "markdown"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}A Markdown table of the stories with their agreed estimates and the total, or comma-separated CSV.
write_estimates
Write estimates to the tracker
Writes every agreed estimate back to the Jira, Linear, Trello or Asana item it was imported from, through the room's own tracker connection. Host or facilitator only.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_estimates",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}A line per tracker: how many estimates were written to it, and how many of its stories have none yet or are deleted there. When the room isn't connected to a tracker, what to do instead.
end_room
End the room
Ends the room and deletes it for everyone, stories and votes included. Host only. Export the results first if they are needed.
Scope: rooms:write
Arguments
roomstringrequiredThe room: its 5-character code (like K7PQM) or its invitation link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "end_room",
"arguments": {
"room": "AB2CD"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "…"
}
]
}
}The room as JSON, as the person's own view shows it: its cards, the people in it, the story on the table, and every story with its status, estimate and, once revealed, its results.
Prompts
Ready-made requests your assistant can offer as commands (in Claude Code, as /mcp__estimay__<name>). Every argument is optional text; the message comes back filled in, with a fallback for each one left out.
plan_sprint
Set up a planning poker room
Gathers the stories to estimate and creates an Estimay room with them, ready to share.
Arguments
sourcestringoptionalWhere the stories come from, like "the top of our Jira backlog" or "milestone 3 on GitHub".
titlestringoptionalThe room name, like "Sprint 42 planning".
{
"jsonrpc": "2.0",
"id": 1,
"method": "prompts/get",
"params": {
"name": "plan_sprint",
"arguments": {
"source": "the top of our Jira backlog",
"title": "Sprint 42 planning"
}
}
}Set up an Estimay planning poker room called "Sprint 42 planning" for the top of our Jira backlog.
1. Gather the stories (use my tracker's own tools if it is connected). Keep each title to one line and keep the tracker key as the ticket.
2. Show me the list and ask whether anything should be added, dropped or reordered.
3. Call create_room once with all the stories, in that order.
4. Give me the room link to share with the team.
Do not suggest estimates: the team votes on its own, in the room.refine_stories
Refine stories before the vote
Critiques stories the team wrote (edge cases, assumptions, gaps in acceptance criteria) without estimating them.
Arguments
storiesstringoptionalThe stories to refine, or where to find them.
{
"jsonrpc": "2.0",
"id": 1,
"method": "prompts/get",
"params": {
"name": "refine_stories",
"arguments": {
"stories": "PAY-142 and PAY-151"
}
}
}Help us refine PAY-142 and PAY-151 before we estimate them.
For each story list: the edge cases, the assumptions it makes, the acceptance criteria it is missing, and risks or dependencies on other teams.
Where a story looks too big, propose a split by workflow step or by data variation, keeping the happy path first.
Keep it short, as a list per story the team can tick or cross out. Do not give sizes or estimates.
If we agree on changes, update the stories in the room with edit_story or add_stories.record_outcome
Record the estimates
Reads a finished room and records its agreed estimates: in the tracker, and as a summary for the team's notes.
Arguments
roomstringoptionalThe room's code or link.
{
"jsonrpc": "2.0",
"id": 1,
"method": "prompts/get",
"params": {
"name": "record_outcome",
"arguments": {
"room": "AB2CD"
}
}
}Record the outcome of AB2CD.
1. Call export_results and show me the table and the total.
2. Point out the stories whose votes were far apart (get_room has the results) as questions for the team, not as judgements.
3. If the room's stories came from Jira, Linear, Trello or Asana, call write_estimates. Otherwise offer to update the issues with my tracker's own tools.Limits and errors
- Rate: 120 requests a minute per connection; more are answered
429. - Stories: up to 200 in one
create_roomoradd_stories; titles up to 120 characters, tracker keys up to 40. - Who may do what: the room's rules apply as in the browser. Adding stories, starting a vote, revealing and setting estimates need the host or a facilitator; anyone in the room may vote.
- Errors come back as tool results with
isError, in plain words with what to do next ("Join the session first. Call join_room first."), so the assistant can recover by itself.
Questions or a client that doesn't connect? Tell us, with the client's name and what it said.