MCP Server
Connect Claude, Cursor, and other MCP clients to your sprintrr projects, tasks, and milestones — or call the HTTP API directly.
The sprintrr Model Context Protocol (MCP) server lets AI assistants and your own tooling read and modify your projects, tasks, and milestones with a scoped API key. There are two ways to use it:
- The
@sprintrr/mcp-serverpackage — a stdio MCP server you wire into Claude Desktop, Claude Code, or Cursor. This is what most people want. - The hosted HTTP API — a plain REST-ish interface under
https://www.sprintrr.ai/api/mcpfor custom integrations.
Both authenticate with the same sk_live_… API key and enforce the same
permission scopes.
Everything the MCP server can touch is scoped to your account. A key never sees another user's data, and every change it makes is attributed to that key in your Activity feed.
Create an API key
- Sign in to sprintrr and open Settings → Integrations → MCP API Keys.
- Click Create New Key, give it a recognizable name (e.g. "Claude Desktop – laptop"), and optionally set an expiry.
- Copy the key immediately — it is shown once and only the prefix
(
sk_live_xxxx…) is stored afterward.
You can hold up to 10 keys per account. Each row shows the key name, prefix, last-used time, request count, and expiry. Revoke a key any time with the trash icon; revocation is immediate.
Keys are generated as sk_live_ followed by 24 random bytes, stored only as
a SHA‑256 hash, and (optionally) expire after the number of days you choose
(1–365).
Quickstart by client
Pick your MCP client below — each section has the exact config-file path for macOS and Windows, the JSON snippet that works for that client, and how to verify the integration is live. After any change, restart the client (Antigravity auto-reloads).
The in-app key dialog has a Copy config button that produces a ready-to-paste JSON snippet with your key already filled in.
Claude Desktop
Claude Desktop only supports stdio MCP servers, so the sprintrr
config uses the @sprintrr/mcp-server npm package.
1. Open the config. In Claude Desktop, click the Claude menu in the top bar (macOS) or the system tray (Windows) → Settings… → Developer tab → Edit Config. The file opens in your default editor and gets created if it doesn't exist.
Direct paths if you'd rather edit by hand:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json(resolves toC:\Users\<USERNAME>\AppData\Roaming\Claude\claude_desktop_config.json)
2. Paste this JSON (merge with any existing mcpServers you have):
{
"mcpServers": {
"sprintrr": {
"command": "npx",
"args": ["-y", "@sprintrr/mcp-server"],
"env": {
"SPRINTRR_API_KEY": "sk_live_your_key_here"
}
}
}
}3. Restart Claude Desktop fully (quit + reopen). An MCP indicator appears at the bottom-right of the conversation input — click it to see the sprintrr tools listed.
4. Verify. Ask Claude: "List my sprintrr projects." You should
see it call list_projects and return a list.
Claude Code uses the same stdio shape and the same @sprintrr/mcp-server
package. From the Claude Code CLI, run claude mcp add sprintrr -- npx -y @sprintrr/mcp-server
and set SPRINTRR_API_KEY in your shell environment. The CLI manages the
config file for you.
Cursor
Cursor supports remote HTTP MCP servers natively — recommended for sprintrr because it skips the npm install. The stdio shape from Claude Desktop also works if you'd rather mirror your other client setups.
1. Open the config. In Cursor, **Settings → Features → MCP →
- Add New MCP Server**. The form writes to
mcp.jsonautomatically. Or edit by hand:
- Global (applies everywhere)
- macOS:
~/.cursor/mcp.json - Windows:
%USERPROFILE%\.cursor\mcp.json(resolves toC:\Users\<USERNAME>\.cursor\mcp.json)
- macOS:
- Project (in the repo root):
<repo>/.cursor/mcp.json. Wins over global when both define the same server.
2. Paste this JSON (recommended — direct HTTP, no npm install):
{
"mcpServers": {
"sprintrr": {
"url": "https://www.sprintrr.ai/api/mcp",
"headers": {
"Authorization": "Bearer sk_live_your_key_here"
}
}
}
}Or the stdio variant if you prefer:
{
"mcpServers": {
"sprintrr": {
"command": "npx",
"args": ["-y", "@sprintrr/mcp-server"],
"env": {
"SPRINTRR_API_KEY": "sk_live_your_key_here"
}
}
}
}3. Restart Cursor. In Settings → Tools & MCP, a green dot next
to sprintrr means the server is connected. Red dot = check the key
and the URL.
4. Verify. Open the chat sidebar and ask: "List my sprintrr
projects." The tools panel should show the list_projects call.
Antigravity (Google)
Antigravity's MCP transport is similar to Cursor's but with one
critical difference: HTTP servers use serverUrl, not url.
Antigravity requires serverUrl, not url. Copying a Cursor or
generic MCP snippet directly will fail silently — the server just
won't appear in the agent panel. The stdio (command/args/env)
shape works without modification if you'd rather use the npm package.
1. Open the config. In Antigravity, click the "…" (Additional
Options) menu at the top of the Agent panel → MCP Servers →
Manage MCP Servers → View raw config. This opens
mcp_config.json directly in the editor; Antigravity auto-reloads on
save.
Direct paths if you'd rather edit by hand:
- macOS:
~/.gemini/antigravity/mcp_config.json - Windows:
C:\Users\<USERNAME>\.gemini\antigravity\mcp_config.json
2. Paste this JSON (note serverUrl):
{
"mcpServers": {
"sprintrr": {
"serverUrl": "https://www.sprintrr.ai/api/mcp",
"headers": {
"Authorization": "Bearer sk_live_your_key_here"
}
}
}
}3. Save the file. Antigravity reloads MCP servers automatically — no restart needed.
4. Verify. Open the agent panel, ask: "List my sprintrr projects." The sprintrr tools should appear in the agent's available-tools list.
For deeper Antigravity-specific details, see the Antigravity MCP docs.
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
SPRINTRR_API_KEY | Yes | — | Your sk_live_… MCP key |
SPRINTRR_API_URL | No | https://www.sprintrr.ai | API base URL (override for self-hosted/staging) |
The package is published as @sprintrr/mcp-server (binary
sprintrr-mcp, Node ≥ 18). Install it globally with
npm install -g @sprintrr/mcp-server or just let npx fetch it as shown
above.
Tools
The MCP server registers 30 tools. * marks required arguments.
Projects
| Tool | Arguments | Returns |
|---|---|---|
list_projects | teamId? | Active project list (excludes archived) |
get_project | projectId* | Project + analytics (task counts, completion %, hours) |
create_project | name*, description?, startDate?, targetLaunchDate?, totalSprints?, teamId? | Created project |
update_project | projectId*, name?, description?, startDate?, targetLaunchDate?, totalSprints? | Updated project |
delete_project | projectId* | { success: true } — also deletes its tasks & milestones |
list_archived_projects | teamId? | Archived projects (hidden from list_projects) |
archive_project | projectId* | Updated project with archivedAt set. Reversible — tasks/milestones/folder are preserved |
unarchive_project | projectId* | Updated project, restored to its original folder if it still exists |
move_project_to_folder | projectId*, folderId* (string or null) | Updated project. null moves to top level |
Tasks
| Tool | Arguments | Returns |
|---|---|---|
list_tasks | projectId*, status?, priority?, assignedTo? | Task list |
get_task | taskId* | Task (incl. time-tracking fields) |
create_task | projectId*, title*, description?, category?, priority?, status?, estimatedHours?, dueDate?, sprintWeek?, milestoneId?, assignedTo? | Created task |
update_task | taskId*, plus any of the create fields + actualHours? | Updated task |
delete_task | taskId* | { success: true } |
Milestones
| Tool | Arguments | Returns |
|---|---|---|
list_milestones | projectId* | Milestone list (with progress %) |
get_milestone | milestoneId* | Milestone + its tasks |
create_milestone | projectId*, title*, description?, startDate?, targetDate? | Created milestone |
update_milestone | milestoneId*, title?, description?, startDate?, targetDate?, status? | Updated milestone |
assign_task_to_milestone | taskId*, milestoneId? (omit/empty to unassign) | Updated task |
Comments
| Tool | Arguments | Returns |
|---|---|---|
list_comments | taskId* | Comment list, threaded one level deep (top-level comments carry their replies), enriched with author identity |
create_comment | taskId*, body*, parentId? | Created comment (201) |
update_comment | commentId*, body* | Updated comment |
delete_comment | commentId* | { success: true } — deleting a top-level comment also removes its replies |
Comments behave exactly like the in-app feature. Replies are one level
deep — parentId must point at a top-level comment. Mention a teammate by
putting an @[Display Name](userId) token in body; they receive a
task_mention notification. Creating a comment also emits the same
notifications as the web app (comment / reply / mention) and an
MCP-attributed activity-feed event scoped to the parent task (the key name is
recorded). Editing does not re-send mention notifications.
Folders
| Tool | Arguments | Returns |
|---|---|---|
list_folders | teamId? | Folder list (folders are team-scoped in a team workspace, personal otherwise) |
create_folder | name*, parentId?, teamId? | Created folder (201). Pass parentId to create a subfolder under a root folder |
update_folder | folderId*, name* | Renamed folder |
delete_folder | folderId* | { success: true } — subfolders cascade; projects inside fall back to the top level |
Folders are 2 levels deep at most (folder → subfolder → projects); a
third level is rejected by both the API and the database. Folder scope must
match the project scope when moving (team folder for team projects, personal
otherwise). Deleting a folder never deletes projects — they always fall
back to the top level via ON DELETE SET NULL.
User & utility
| Tool | Arguments | Returns |
|---|---|---|
get_user_profile | — | { id, email, fullName, avatarUrl } |
get_credits | — | { availableCredits, usedCredits, totalCredits, planType, resetDate } |
search_tasks | query*, projectId? | Matching tasks (title/description, max 50) |
Enums. status: Not Started, In Progress, Completed, Blocked.
priority: Low, Medium, High. category: Development, Design,
Testing, Research, Planning, Marketing, Strategy,
Communication, Administration, Finance, Legal, Operations,
Misc.
When you omit optional fields on create, the server fills sensible
defaults: project totalSprints = 4, start = today, target = +28 days;
task category = Development, priority = Medium, status = Not Started,
due = +7 days; milestone target = +14 days.
Date cascades — direct HTTP only
Atomic date shifts for projects and milestones are reachable via direct
HTTP (POST with Authorization: Bearer sk_live_...), not yet through
the npm wrapper's named tools. Two endpoints, both gated by the
write:projects scope that's in the default permissions for every key
created in Settings → API Keys.
Shift a project start (cascades to every non-completed milestone + task, optionally the launch date too):
curl -X POST https://www.sprintrr.ai/api/mcp/projects/<PROJECT_ID>/shift-dates \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"deltaDays": 7, "shiftLaunch": true}'Move a milestone, optionally cascading the rest (shiftScope
defaults to "cascade"; pass "only" to move just the pivot):
curl -X POST https://www.sprintrr.ai/api/mcp/milestones/<MILESTONE_ID>/shift-cascade \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"deltaDays": -3, "shiftScope": "cascade"}'Both return:
{
"milestones_shifted": 4,
"tasks_shifted": 17,
"completed_skipped": 2,
"min_task_date_after_shift": "2026-06-12",
"project_start_after_shift": "2026-06-05",
"dry_run": false
}Preview mode. Add "dryRun": true to either body. The endpoint
returns the same payload — counts, post-shift dates — but the database
is untouched and no activity-feed event is recorded. Lets an agent
describe consequences before the user OKs the real apply. Re-POST
without the flag (or with "dryRun": false) to commit.
# What would happen if I shifted the project by +14 days?
curl -X POST https://www.sprintrr.ai/api/mcp/projects/<PROJECT_ID>/shift-dates \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"deltaDays": 14, "dryRun": true}'Completed milestones and tasks are always skipped (their dates are historical). For project-scope shifts, tasks without a milestone shift along with the project; for milestone cascades, only tasks bound to shifted milestones move.
Blog (admin-only)
The MCP route also exposes endpoints under /blog/* for the
Sprintrr-team CMS — create, update, publish, and delete posts and
categories. These use the same Bearer auth and the same scope
machinery, with the scopes read:blog / write:blog.
Admin-only. Every blog handler additionally re-checks
isAdminUser() server-side before mutating. Keys belonging to
non-admin users get 403 Forbidden — blog requires admin even if
their permissions array claims write:blog. Public MCP keys
never include blog scopes by default — these endpoints exist for
the Sprintrr team's internal authoring workflow via the
sprintrr-blog Claude Code skill, not for the public MCP surface.
If you're an admin and want to drive your own automation against the blog (rather than the bundled skill), the endpoints are:
| Method | Path | Scope |
|---|---|---|
GET | /api/mcp/blog/posts | read:blog |
GET | /api/mcp/blog/posts/{slugOrId} | read:blog |
POST | /api/mcp/blog/posts | write:blog |
PUT | /api/mcp/blog/posts/{id} | write:blog |
POST | /api/mcp/blog/posts/{id}/publish | write:blog |
POST | /api/mcp/blog/posts/{id}/unpublish | write:blog |
DELETE | /api/mcp/blog/posts/{id} | write:blog |
GET | /api/mcp/blog/categories | read:blog |
POST | /api/mcp/blog/categories | write:blog |
PUT | /api/mcp/blog/categories/{id} | write:blog |
DELETE | /api/mcp/blog/categories/{id} | write:blog |
Resources
The server also exposes read-only MCP resources:
sprintrr://projects
sprintrr://projects/archived
sprintrr://projects/{id}
sprintrr://projects/{id}/tasks
sprintrr://projects/{id}/milestones
sprintrr://tasks/{id}
sprintrr://tasks/{id}/comments
sprintrr://milestones/{id}
sprintrr://folders
sprintrr://user/profile
sprintrr://user/creditsHTTP API reference
For custom integrations, call the API directly. Base URL:
https://www.sprintrr.ai/api/mcpEvery request must send:
Authorization: Bearer sk_live_your_key_hereResponses are JSON. Errors are { "error": "message" } with the status
codes listed under Errors.
GET
| Path | Query | Scope | Returns |
|---|---|---|---|
/projects | teamId? | read:projects | Project[] — excludes archived |
/projects/archived | teamId? | read:projects | Archived Project[] |
/projects/:id | — | read:projects | Project + analytics |
/projects/:id/tasks | status?, priority? | read:tasks | Task[] |
/tasks/:id | — | read:tasks | Task |
/tasks/:id/comments | — | read:comments | Comment[] (threaded one level deep) |
/projects/:id/milestones | — | read:milestones | Milestone[] |
/milestones/:id | — | read:milestones | Milestone + tasks |
/folders | teamId? | read:folders | Folder[] |
/user/profile | — | read:user | { id, email, fullName, avatarUrl } |
/user/credits | — | read:user | { availableCredits, usedCredits, totalCredits, planType, resetDate } |
/search/tasks | q*, projectId? | read:tasks | Task[] (max 50) |
analytics is { totalTasks, completedTasks, inProgressTasks, blockedTasks, completionPercentage, totalEstimatedHours, totalActualHours }.
POST
| Path | Body | Scope | Status |
|---|---|---|---|
/projects | { name*, description?, startDate?, targetLaunchDate?, totalSprints?, teamId? } | write:projects | 201 |
/projects/:id/tasks | { title*, description?, category?, priority?, status?, estimatedHours?, dueDate?, sprintWeek?, milestoneId?, assignedTo? } | write:tasks | 201 |
/projects/:id/milestones | { title*, description?, startDate?, targetDate?, status? } | write:milestones | 201 |
/projects/:id/archive | empty | write:projects | 200 |
/projects/:id/unarchive | empty | write:projects | 200 |
/tasks/:id/comments | { body*, parentId? } | write:comments | 201 |
/folders | { name*, parentId?, teamId? } | write:folders | 201 |
PUT
| Path | Body | Scope |
|---|---|---|
/projects/:id | { name?, description?, startDate?, targetLaunchDate?, totalSprints?, techStack?, folderId? } | write:projects |
/tasks/:id | { title?, description?, category?, priority?, status?, estimatedHours?, actualHours?, dueDate?, sprintWeek?, milestoneId?, assignedTo? } | write:tasks |
/milestones/:id | { title?, description?, startDate?, targetDate?, status? } | write:milestones |
/comments/:id | { body* } | write:comments |
/folders/:id | { name* } | write:folders |
Pass folderId: null on /projects/:id to move a project to the top
level, or a folder ID to move it into that folder. The scope-consistency
trigger rejects cross-scope moves (e.g. personal project into a team
folder).
DELETE
| Path | Scope | Returns |
|---|---|---|
/projects/:id | write:projects | { success: true } |
/tasks/:id | write:tasks | { success: true } |
/milestones/:id | write:milestones | { success: true } |
/comments/:id | write:comments | { success: true } |
/folders/:id | write:folders | { success: true } — projects inside move to top level |
Example
# List projects
curl https://www.sprintrr.ai/api/mcp/projects \
-H "Authorization: Bearer sk_live_your_key_here"
# Create a task
curl -X POST https://www.sprintrr.ai/api/mcp/projects/PROJECT_ID/tasks \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "title": "Fix login bug", "priority": "High" }'{
"id": "…",
"title": "Fix login bug",
"status": "Not Started",
"priority": "High",
"category": "Development"
}Permissions & scopes
Permissions are strings of the form action:resource. The valid set is:
read:projects write:projects
read:tasks write:tasks
read:milestones write:milestones
read:comments write:comments
read:folders write:folders
read:user
read:blog write:blog (admin-only — see "Blog (admin-only)" above)Wildcards are supported when matching: * (everything),
*:projects (any action on projects), read:* (any read). A new key is
created with all eleven non-blog permissions by default; pass a
permissions array to POST /api/user/mcp-keys to issue a narrower key
(for example, a read-only key for reporting), or to grant read:blog /
write:blog on a key that belongs to an admin user. Non-admin keys
never see the blog endpoints regardless of the scopes they claim, thanks
to the isAdminUser() server-side re-check. Archive and move-to-folder
operations on projects fall under write:projects (no separate scope).
Managing keys programmatically
These endpoints use your app session (not an MCP key) and live under
/api/user/mcp-keys:
| Method | Body | Result |
|---|---|---|
GET | — | { keys: [{ id, name, key_prefix, permissions, last_used_at, use_count, expires_at, created_at }] } |
POST | { name (1–100)*, permissions?: string[], expiresInDays?: 1–365 } | { key: { …, apiKey }, message } — apiKey is the full key, shown once |
DELETE | { keyId: <uuid> } | { success: true } |
A maximum of 10 keys per account is enforced on create.
Activity attribution
Every create/update/delete made through the MCP server is recorded in your
Activity feed with source: mcp and the
name of the key that made the change, so MCP-driven edits are always
distinguishable from manual ones. Status transitions map to specific
actions: moving a task/milestone to Completed/Done records
completed; another status change records status_changed; changing
assignees records assigned; otherwise updated.
Errors
| Status | Meaning |
|---|---|
400 | Invalid JSON body, or a required query param (e.g. q) is missing |
401 | Missing, malformed, invalid, or expired API key |
403 | The key lacks the required permission scope |
404 | The project/task/milestone/user was not found (or not yours) |
500 | Unexpected server error |
Troubleshooting
401 Invalid or expired API key— the header must be exactlyAuthorization: Bearer sk_live_…. Confirm the key hasn't expired or been revoked; if in doubt, create a new one.403 Permission denied— the key was issued without the scope this call needs. Recreate it with the default (full) permissions or the specific scope.- "I lost my key" — keys are unrecoverable by design (only a hash is stored). Revoke the old one and create a new key.
- Pointing at staging/self-hosted — set
SPRINTRR_API_URLin the MCP clientenvblock.