Connect an AI assistant

Reassign has a Model Context Protocol (MCP) server. An AI assistant that supports MCP can read and change your day plan through this server.

Connect an AI assistant

MCP server URL
https://reassign.app/api/mcp
Claude Code
claude mcp add --transport http reassign https://reassign.app/api/mcp

Cursor: Add to Cursor

Before you start

You need these items:

  • A Reassign account.
  • Reassign Pro. The 7-day free trial includes it.
  • An AI app that can add a remote MCP server.

The trial needs no card. Without Pro or the trial, the sign-in page shows an "Upgrade to Pro" button.

Server address

Add this address as a remote MCP server in your AI app:

MCP server address

https://reassign.app/api/mcp

You can also copy the address in the Reassign web app. Go to Settings › Integrations › MCP and find the Server address field.

ItemValue
TransportStreamable HTTP, in stateless JSON mode
RequestOne POST for each call. The result is one JSON body.
Server-sent events (SSE)Not supported. A GET request gets HTTP 405.
SessionThe server keeps no session.
Server namereassign (title "Reassign")
Server version2026.10.3.1, plus + and a 12-character build commit on a deployed server. A new version does not clear a cached tools/list.

Connect

Do these steps in an app that supports MCP:

  1. Add https://reassign.app/api/mcp as a remote MCP server.
  2. The app opens the Reassign sign-in page. Sign in with an email code or a passkey.
  3. Read the access that the app asks for. Then click Allow.
  4. In Reassign, go to Settings › Integrations › Authorized apps. Make sure that the app shows in the list.

The assistant can then read your events and create, edit, and delete them.

If you connected a calendar, the changes of the assistant also sync to that calendar. This is the same as a change that you make on the dial. The assistant cannot change events from a read-only calendar. Refer to Calendars.

claude.ai and Claude Desktop

claude.ai and Claude Desktop use the same connectors for your account. A connector that you add on claude.ai is also in Claude Desktop.

  1. Go to Settings › Connectors.
  2. Click Add custom connector.
  3. Paste the URL https://reassign.app/api/mcp.
  4. Sign in to Reassign and click Allow.

Claude Code

  1. Run this command in your terminal:

    Terminal

    claude mcp add --transport http reassign https://reassign.app/api/mcp
    
  2. Start Claude Code, run /mcp, and select reassign.

  3. Sign in to Reassign in the browser, and click Allow.

ChatGPT

  1. Add https://reassign.app/api/mcp as a remote MCP server (a custom connector) in ChatGPT.
  2. Sign in to Reassign and click Allow.

Sign-in and permissions

The server uses OAuth 2.0 with PKCE (S256). Your AI app does the OAuth steps itself:

  1. The app sends a request without a token. The server replies with HTTP 401 and a WWW-Authenticate header.
  2. The header points to the metadata at /.well-known/oauth-protected-resource/api/mcp.
  3. The app registers itself at /api/oauth/register (dynamic client registration).
  4. The app opens the consent page. You sign in and click Allow or Deny.
  5. The app gets an access token and sends it as Authorization: Bearer <token>.

The consent page shows the name of the app and the label "Unverified app" for an app that registered itself.

Scopes

ScopeConsent titleAccess
events:readView your scheduleRead your events, plans, and reflections.
events:writeRead and edit your scheduleRead, create, change and delete events and other plan data. It includes events:read.
  • For events:write, the consent text is "Read your schedule, and add, change, or remove events."
  • If the app asks for no scope, the server gives both scopes.
  • The consent page shows only the scopes that the app asks for. It has no control to select read-only access.
  • A token refresh cannot add a scope.
  • Each token refresh returns a new refresh token and spends the old one. The client must store the new refresh token each time. Refer to Authentication.

Token lifetimes

ItemLifetime
Authorization code60 seconds
Access token1 hour
Refresh token30 days

Personal access tokens

The MCP server accepts only OAuth access tokens. It does not accept personal access tokens (prefix rsk_live_). It also refuses an OAuth token for the REST API. Use personal access tokens with the REST API. Refer to Authentication.

Revoke access

  1. In Reassign, go to Settings › Integrations › Authorized apps.
  2. Find the app in the list. The list shows the scope, the date of approval, and the date of last use.
  3. Click Revoke.

The server stops all tokens of that app immediately. The app gets HTTP 401 with the detail authorization revoked.

Tools

Reassign has 10 tools. A read tool needs events:read or events:write. A write tool needs events:write.

ToolWhat it doesAccess
get_scheduleReads the events, free time and load for a date range (from to to). For one day, send the same date two times. The load is for each area, activity type, and project. It can also include weather and energy reports.Read
find_eventFinds scheduled events and Inbox items by name.Read
show_dayShows the day as the interactive 24-hour dial in the conversation.Read
write_eventsCreates, updates, shifts, or reflects events, or changes their checklists, one or many in one call. An update with a new start or end moves an event. Optional dryRun: true checks the ops without applying them.Write
delete_eventsDeletes events or clears whole days.Write
manage_categoriesCreates, updates, or deletes areas, activity types, and projects. It can also split or relink a project. A delete of a category that items use needs reassignTo.Write
manage_inboxWrites to the Inbox, the tray of time blocks that have no time yet.Write
undo_changesReverses a recent change with its undo token.Write
review_dayConfirms the review of a past day, or discards the review of any day.Write
send_feedbackSends 1 to 10 product feedback reports together to the Reassign team. It does not change your schedule.No scope

send_feedback needs no scope, so a read-only token can report a problem. It writes no user data. Refer to Rate limits for its limits.

tools/list can also show an eleventh tool, get_more_tools:

  • It is not a Reassign tool. The PostHog MCP SDK adds it when the product analytics of the server are on.
  • An assistant calls it when no tool fits the request. It sends a context text that tells what it needs.
  • The server records the first 500 characters of context as a missing-tool report. The tool changes no data.
  • It has no REST twin.

The server does not capture the assistant's model. Tool schemas have no llm_model parameter, and results contain no conversation_id block. Until 2026-12-01, calls from an older cached schema may still send llm_model or conversation_id; the server strips these arguments before validation. Refresh tools/list to get the current schemas.

  • All dates are ISO YYYY-MM-DD.
  • A time is a local datetime YYYY-MM-DDTHH:MM in the time zone of the user, with no offset. A span is start and end. An event past midnight ends on the next date.
  • A result with a span gives timezone one time, at the top level.
  • Each tool refuses an argument that it does not know.
  • get_schedule gives integrations.sources[].account, the account name at the calendar provider, to every caller.
  • get_schedule gives copyStyle on each writable event calendar in integrations.sources[].calendars[], and busyCopyTitle with busy. It can also give integrations.aiRuleWarnings. Refer to Scheduling.
  • The clock times in the prose of a tool, for example in show_day and in the get_schedule weather line, are 24-hour (15:00). The time format setting of the app does not change them.
  • Shared fields use the same names as the REST API. Refer to Core concepts and the REST reference, for example Events and Scheduling. The MCP read fields below describe the differences.

Read a schedule

Call get_schedule with from and to. It returns now, timezone, the events, free slots, and load for each day. The dates are inclusive. For one day, send the same date in both fields.

  • minDuration is an optional whole number from 1 to 1440, in minutes. It keeps only freeSlots at least that long. It returns each full gap and leaves the events unchanged.
  • includeLookups: false leaves out areas, activityTypes, projects, and integrations. Use it when the assistant has them from an earlier call.
  • includeSeries: true adds the recurring series in series.
  • includeBacklog: true adds up to 50 Inbox items in backlog, top of tray first. backlogPlannedOn filters by planned day. backlogProjectId filters by project; send "none" for the items with no project. backlogMinPriority (0 to 4) keeps the items with that priority or a higher priority. Pass nextBacklogOffset as backlogOffset with the same filters for the next page; null means the last page. These arguments need includeBacklog: true.
  • projects lists the projects: { id, name, areaId }. An archived project also has archived: true. An archived project refuses new links. A manage_categories project update with archived: false restores it.
  • Each day has loadByProject, the blocking minutes for each project. projectId: null is the time with no project. A read of more than one day also has a top-level loadByProject with the total. Refer to Projects and priority.
  • To find an Inbox item by name, use find_event. get_schedule refuses backlogQuery. The REST GET /schedule endpoint still accepts it.

Weather and energy

For one day, get_schedule can include two text reports:

ArgumentResult
includeWeather: trueReplaces the short weather headline with the full forecast for the day. Use it for outdoor plans or weather questions.
weatherLocationA city or place name to forecast, for example London. Requires includeWeather: true. Omit it to use the saved city, or a city guessed from the account's time zone.
includeEnergy: trueAdds energy: the forecast energy curve, with peak and dip windows from logged sleep. With no sleep logged, the report asks the user to log sleep.

includeWeather: true and includeEnergy: true each require from to equal to. A range, or weatherLocation without includeWeather: true, fails input validation.

get_schedule arguments

{
  "from": "2026-10-03",
  "to": "2026-10-03",
  "includeWeather": true,
  "weatherLocation": "London",
  "includeEnergy": true
}

The weather and energy values are prose. Their text can change; do not parse it. With weatherLocation, the forecast date and times are local to that place, and the report names its IANA time zone. The top-level timezone still applies to events and free slots.

For REST, use GET /weather and GET /energy. REST GET /schedule does not accept these three arguments.

Find events and Inbox items

Call find_event with a query. It searches names, with a case- and accent-insensitive match. Events match fuzzily; an Inbox name must contain the query.

find_event arguments

{
  "query": "dentist"
}

The result is { events, inbox, inboxMatchedCount, nextInboxOffset, ambiguous, timezone }.

  • events is best match first. A series appears once, as the occurrence that matches best; a tie gives the one nearest to today. ambiguous is true when different events match equally well. Inbox matches do not affect it.
  • Each event has a score from 0 to 1 for its name match: 1 is exact, 0.9 is a prefix, and 0.75 is a substring. Matches below 0.34 are omitted. Inbox items have no score.
  • inbox has up to 50 matches, top of tray first, in the same shape as the get_schedule backlog items. Use their ids with manage_inbox.
  • areaId and activityTypeId filter both lists. from, to, and timeOfDay filter events only. The date range includes an event that overlaps a requested day, also an overnight event that started earlier. timeOfDay uses the start time. See Search events for the event search window and filters.
  • inboxMatchedCount is the total after filtering. To read the next page, send nextInboxOffset as inboxOffset with the same query and filters. A null offset means the last page.
  • inboxOffset is a nonnegative integer and defaults to 0. It pages Inbox items only; it does not page events.

REST GET /events/search returns { events, ambiguous, timezone }. To search the Inbox through REST, use GET /schedule with includeBacklog=true and backlogQuery.

Undo tokens

  • Each write tool call that changes data and records an undo receipt returns one undoToken and its expiresAt, at the top level. This is true for write_events, delete_events, manage_categories, manage_inbox, and review_day.
  • One token undoes all the changes of that call.
  • A call that changed nothing returns no undoToken. A rejected batch also returns no undoToken. If the server cannot record the undo, the call also returns no undoToken, and the change stays. A review_day discard of a day with nothing recorded returns noop: true, empty updated and deletedIds, and no undoToken.
  • Send the token to undo_changes before expiresAt (30 minutes). After that, the change stays.
  • You can use each token one time only.
  • One undo_changes call takes 1 to 20 tokens. undo_changes returns no new token.
  • A stale row can list the rows that block the undo in error.conflictingRows. Refer to Stale tokens.

Batches

  • write_events, delete_events, and manage_inbox take 1 to 50 operations in one call.
  • A batch is atomic by default. If one operation fails, the server applies none of them.
  • With partial: true, the server applies the valid operations and reports the others as errors.
  • A schedule operation of manage_inbox obeys the rules of an event write. The span is 5 minutes to 168 hours. A blocking block that overlaps a blocking event fails with conflict and nearestSlots.
  • get_schedule gives userPreferences.conflictPolicy. Its value is always "propose": a write that overlaps a blocking event fails with conflict and gives nearestSlots.
  • The result has one row for each operation: { index, status, result?, error?, reason?, warnings? }. status is "ok", "error", or "skipped".
  • For manage_categories, the rows are in the arrays areas and activityTypes. The result has a projects array only when the call sent project ops.
  • write_events and delete_events read the result rows after the full batch commits. A later operation can change the row of an earlier operation.
  • A manage_categories delete of a category that events, Inbox items, trackers, or calendar defaults use needs reassignTo. Send another id to move the links, or null to clear them. Without it, the row fails with validation, and its error has usage and reassignTargets. For an area, usage also has projects, the count of its projects that are not archived. Ask the user, then send the delete again. Refer to Delete a category that items use.
  • A manage_categories delete of a shared default hides it, and your copy of it, for you only.
  • A manage_categories create or rename to the name of an area or an activity type that exists fails with conflict. Use the id of that category.
  • A project name must be unique among your live projects, in all areas. A duplicate fails with conflict. A delete of a project that a synced task list or Linear project feeds also fails with conflict. Link the list to a different project first, or archive the project.
  • write_events and manage_inbox take projectId and priority (0 none to 4 urgent). When a write sets a new project and no areaId, the item gets the area of the project, if the project has an area. On update, projectId: null removes the project and keeps the area.

An ok row from write_events can have warnings: ["classification_pending"]. The change is saved, but queued AI classification can fill eligible fields after the reply. A create keeps its kind, also the default blocking. Refer to Rows. Read the event again if you need its latest classification. The warning appears only when classification is queued and the account and plan allow it.

Check an event write

Add top-level dryRun: true to write_events to check its operations without applying them. It still requires events:write.

write_events dry run arguments

{
  "dryRun": true,
  "ops": [
    {
      "op": "create",
      "name": "Deep work",
      "start": "2026-10-03T14:00",
      "end": "2026-10-03T15:30"
    }
  ]
}
  • A valid op returns status: "skipped" and reason: "Dry run: this op is valid and was not applied.".
  • An invalid op returns its usual error, including conflicts and nearestSlots when available.
  • A dry run with only valid ops succeeds. If any op fails, the call has isError: true, including with partial: true; valid ops still appear as skipped rows.
  • The call writes nothing and returns no undoToken. It does not reserve a time.
  • To apply the ops, send them again without dryRun. The server checks conflicts again against the schedule at that time.

This flag belongs to MCP write_events. REST POST /events and MCP delete_events do not accept it.

Schedule new work

  1. Call get_schedule for the date or range. Read the local now, timezone, and each day's freeSlots. Set minDuration to the requested number of minutes to omit shorter gaps.
  2. Choose a span that fits the requested duration inside a free slot. For a new event, call write_events with an op: "create", a name, and the concrete local start and end.
  3. Keep the returned undoToken so undo_changes can reverse the booking.

write_events arguments

{
  "ops": [
    {
      "op": "create",
      "name": "Deep work",
      "start": "2026-10-03T14:00",
      "end": "2026-10-03T15:30"
    }
  ]
}
  • The example assumes that the span fits in a free slot from the preceding read. Times use the account's timezone.
  • On today, the returned free slots begin on a 5-minute mark more than 5 minutes after now.
  • write_events checks for conflicts when it writes. If the time became busy, read the schedule again or choose a returned nearestSlots span, then send the revised create.
  • For an existing Inbox item, use manage_inbox with op: "schedule", its id, and the chosen start and end. This places the same item on the dial.
  • calendarId and mirrorCalendarIds on a create choose the calendars. mirrorStyles sets each copy to full, private, or busy. A series with copies needs a home calendar. A series from a connected calendar is an exception. Refer to Rules of the fields.
  • A client-minted create id must be a UUID. Omit it to let the server make one.

REST clients can use POST /schedule/plan to search a window and book its best slot in one call.

Send product feedback

Collect the session's product feedback in one send_feedback call, with one item per issue in reports. Send 1 to 10 reports. Each needs a kind (bug, idea, or other) and a nonempty message of up to 4000 characters. Describe the problem, what you expected, and steps to reproduce. Leave out personal details and private schedule contents; the account is attached automatically.

send_feedback arguments

{
  "reports": [
    {
      "kind": "bug",
      "message": "The dial did not refresh after a successful event move. Reopening the day showed the new time."
    },
    {
      "kind": "idea",
      "message": "Let the dial highlight the free slots returned for a requested duration."
    }
  ]
}

The tool returns {} once accepted. One call sends one email to the team. The user receives at most one acknowledgment email per UTC day. REST POST /feedback keeps its single-report body { kind, message, submissionId? } and returns HTTP 204.

Retries

  • A write_events create has no requestId. If a response is lost, read the schedule to check whether the event was created before you send it again. A create can send its own UUID id. After a lost response, read that id to check the create.
  • send_feedback takes an optional top-level submissionId (UUID). Use the same value with identical reports for retries within 24 hours; use a new value for new content.

Resources

The server has one resource and no prompts.

URINameContent
ui://reassign/dialReassign dialThe interactive 24-hour dial as an HTML page, for the conversation.

The show_day tool uses this resource. An app that supports MCP Apps can show the dial in the conversation.

Errors

Request errors

The server sends these errors before a tool runs. The body is a JSON-RPC error.

HTTPJSON-RPC codeMessageCause
401-32001UnauthorizedNo token, a token that is not valid or expired, or a revoked app.
403-32600Reassign needs an active subscription.The account has no Reassign Pro plan and no trial. error.data is { "code": "permission", "feature": "mcp" }.
403-32600Origin not allowed.A browser request from an origin that is not allowed.
405-32000Method not allowed — this MCP endpoint is POST-only (no SSE).A GET request.
500-32603Internal server error.A server error. Do not retry.
503-32000Temporary backend error — please retry.A temporary server error. Retry the request.

Tool errors

A tool that refuses a call returns isError: true and one text block. The text is the JSON error envelope { "error": { code, message } }, also for a tool that returns prose. The error can also have the optional detail fields of the REST API: issues, feature, conflictingRows, conflictingRowsTruncated, usage, and reassignTargets. Refer to Error format. The code is also in _meta["reassign/error"].code. _meta has only the code.

For a rejected batch, the text is { error, timezone?, results }. For manage_categories, it is { error, areas, activityTypes, projects? }. The detail fields are only in the rows. _meta has a code only when all failed operations have the same code. _meta never has the codes unauthorized or batch_rejected.

Arguments that do not match the input schema of a tool do not get to the tool. The MCP SDK refuses them with isError: true and a prose text Input validation error: …, with no _meta. The REST API answers the same input with 422 validation.

CodeMeaning
conflictThe requested time, name, or id is already taken, or a short busy state blocks the write. A retry can work only for the busy state.
not_foundThe event, area, activity type, project, or day does not exist. An archived project also gives not_found for a new link.
validationThe arguments are wrong: out of range, contradictory, or not complete. A bad operation row has issues.
permissionThe target item does not allow the action, for example a park of a recurring event.
scopeThe token does not have the scope that the tool needs.
staleAn undo_changes row cannot apply, because a row that the write changed has a newer change. The undo changes nothing. The row can have conflictingRows.
read_onlyYou cannot change the event or the field from Reassign. For example, a task app owns the planned day of an Inbox item, or a project change cannot move the linked task in its app.
rate_limitedThe caller sent too many requests. Wait, then try again.
internalThe server failed. A retry can work.
  • In an atomic batch of write_events, delete_events, manage_inbox, or manage_categories, the valid operations show status: "skipped" when another operation fails. Only the failed operations show status: "error".
  • A write_events dry run uses skipped for every valid operation even when all of them are valid. Refer to Check an event write.
  • In write_events, manage_inbox, and manage_categories, a bad operation becomes an error row with the code validation. It does not fail the full call.
  • A write_events or manage_inbox call whose writes changed data is never a refusal. If the server cannot read a changed item again, its row has the code internal. The call keeps any recorded undoToken.
  • An undo_changes row fails with conflict while a calendar sync sends the same change. The undo changes nothing. Try it again after a short time.

Rate limits

The server has no general rate limit for tool calls, and no limit for each token. The limit of 600 requests per minute for each OAuth grant or personal access token applies to the REST API only. Refer to Errors.

Only send_feedback has limits:

LimitValue
Each user10 in 1 minute
Each user30 in 1 hour
All users120 in 1 minute

These limits count submissions, including retries and delivery failures, across the app, REST, and MCP. A grouped submission counts once. Above a limit, send_feedback returns the code rate_limited. Each server instance counts these limits separately.

The OAuth token endpoint has its own limit for each IP address. Refer to Authentication.

Example prompts

Type requests such as these to your assistant:

  • "What is on my schedule today?"
  • "Find 90 minutes of free time tomorrow afternoon for deep work."
  • "Move my gym session to 18:00."
  • "Clear my plan for Friday." Then: "Undo that."
  • "Mark my events from yesterday as kept or skipped, then confirm the day."