MCP Server
Doorstep is an MCP server that gives your AI agent the ability to do real things in the physical world. Connect it to any MCP-compatible client — Claude, GPT, or your own agent — and your agent gets tools to create tasks, approve quotes, and track progress. Currently serving San Francisco only.
Protocol
MCP (Streamable HTTP)Auth
OAuth or API keyBilling
Card on file, charged per taskSetup
Add Doorstep to any MCP-compatible client. All clients connect via OAuth — your account is created automatically the first time you log in.
MCP Server URL
https://trydoorstep.app/mcp
Claude
Paste this into your agent:
Claude Desktop
Add this to your Claude Desktop MCP settings:
{
"mcpServers": {
"doorstep": {
"url": "https://trydoorstep.app/mcp"
}
}
}Cursor
Add this to .cursor/mcp.json in your project or ~/.cursor/mcp.json globally:
{
"mcpServers": {
"doorstep": {
"url": "https://trydoorstep.app/mcp"
}
}
}Restart Cursor after saving. OAuth triggers on first use.
Windsurf
Add this to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"doorstep": {
"url": "https://trydoorstep.app/mcp"
}
}
}VS Code / Copilot
Add this to your VS Code MCP settings (Command Palette → “MCP: Edit Raw Configuration”):
{
"servers": {
"doorstep": {
"url": "https://trydoorstep.app/mcp"
}
}
}Codex CLI
Paste this into your agent:
Gemini CLI
Paste this into your agent:
OpenClaw
First, generate an API key from your dashboard. Then add the Doorstep MCP server to your openclaw.json:
{
"mcpServers": {
"doorstep": {
"command": "npx",
"args": ["-y", "doorstep"],
"env": {
"DOORSTEP_API_KEY": "${DOORSTEP_API_KEY}"
}
}
}
}OpenClaw uses a local stdio bridge and requires an API key. You can also install directly from ClawHub.
Other MCP Clients
Any MCP client that supports HTTP transport can connect to Doorstep. Point it at the server URL and authenticate via OAuth or Bearer token:
https://trydoorstep.app/mcp
If your client uses a JSON config file, add {"mcpServers":{"doorstep":{"url":"https://trydoorstep.app/mcp"}}}. OAuth triggers automatically on first use.
API Key (alternative)
If your client does not support OAuth, you can authenticate with a Doorstep API key instead.Generate one from your dashboard and pass it as a Bearer token:
Authorization: Bearer doorstep_sk_...
First time? When you connect via OAuth, your account is created automatically. Before you can create tasks, you need to add a credit card. Use the get_accounttool to check your billing status — it will include a link to add your card if needed.
Billing
Doorstep requires a credit card on file before you can create tasks. You are charged per task, and only when you explicitly approve a quote.
Card required upfront
Add a credit card before creating your first task. The do tool will give you a link if you haven't set one up yet.
No charge without approval
Every task goes through a quote step. Your card is only charged when you call approve_task. You always see the price before committing.
Flat task fees
Every task costs a flat $5, $10, or $20 based on complexity. Pass-through costs (purchases, supplies) are billed at cost on top. The quoted total includes everything.
Tools
doCreate a new task. Describe what you need in plain language and Doorstep will research it, build a plan, and present a quote for approval. A credit card must be on file.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
input | string | Required | Natural-language description of what you need done in the real world. |
context | object | Optional | Optional structured context — addresses, preferences, constraints, or any relevant data. |
max_budget | number | Optional | Maximum budget in USD. The quote must come in at or under this amount. |
scheduled_for | string (ISO 8601) | Optional | Schedule dispatch for a future time (e.g. "2026-04-01T09:00:00-07:00"). Must fall within service hours (Mon–Fri 8 AM – 5 PM PT). If omitted and submitted after hours, dispatch is auto-scheduled for the next window. |
callback_url | string (url) | Optional | Webhook URL to receive task updates. A signing secret is returned in the response. |
Example
use_mcp_tool doorstep do
{
"input": "Drop off a dozen donuts at the Figma office in SF tomorrow morning",
"context": {
"address": "760 Market St, San Francisco, CA 94102",
"preferred_shop": "Bob's Donuts"
},
"max_budget": 100
}Response
The tool waits up to 2 minutes for the quote. If the quote is ready, the response includes the plan and pricing. Otherwise, it returns a task ID to check later with get_task.
{
"id": "7f8e9d0c-...",
"status": "quoted",
"plan": {
"steps": [
"Pick up one dozen assorted donuts from Bob's Donuts",
"Drive to 760 Market St, San Francisco",
"Deliver to Figma office with handwritten note"
],
"estimated_minutes_min": 30,
"estimated_minutes_max": 60
},
"tier_cents": 2000,
"estimated_total_cents": 5600,
"message": "Quote ready. Review the plan and pricing, then approve, request changes, or decline."
}list_tasksList your tasks, newest first. Optionally filter by status. Returns a summary of each task. Use get_task for full details on a specific task.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | Optional | Filter by task status (received, researching, needs_info, quoted, payment_pending, approved, in_progress, completed, failed, cancelled). |
limit | number | Optional | Maximum number of tasks to return (default 20, max 50). |
Response
{
"tasks": [
{
"id": "7f8e9d0c-...",
"status": "completed",
"input": "Drop off a dozen donuts at the Figma office...",
"created_at": "2026-03-27T10:00:00Z",
"updated_at": "2026-03-27T12:30:00Z",
"estimated_total_cents": 5600
},
{
"id": "a1b2c3d4-...",
"status": "in_progress",
"input": "Pick up dry cleaning from Valencia St...",
"created_at": "2026-03-28T09:00:00Z",
"updated_at": "2026-03-28T09:45:00Z"
}
],
"count": 2
}get_taskCheck the status of a task, including any pending follow-up questions. When status is "quoted", the response includes the plan and price awaiting your approval.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID returned when you created the task. |
Response
{
"id": "7f8e9d0c-...",
"status": "completed",
"input": "Drop off a dozen donuts at the Figma office...",
"plan": { ... },
"resolution": "Delivered 12 assorted donuts from Bob's. Front desk signed.",
"estimated_total_cents": 5600
}approve_taskApprove a quoted task and authorize payment. Your card on file is charged the quoted amount. No charge is made until you explicitly call this tool.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to approve. |
Response
{
"id": "7f8e9d0c-...",
"status": "in_progress",
"estimated_total_cents": 5600,
"approved_at": "2026-03-27T12:30:00Z",
"message": "Task approved. $56.00 has been charged to your card on file."
}respond_to_taskAnswer a follow-up question from the Doorstep team. Tasks move to 'needs_info' when we need clarification before proceeding.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to respond to. |
response | string | Required | Your answer to the follow-up question. |
Example
use_mcp_tool doorstep respond_to_task
{
"task_id": "7f8e9d0c-...",
"response": "Yes, glazed donuts are fine. No nuts please."
}revise_quoteRequest changes to a quoted task. Provide feedback and the task will be re-researched with a new quote generated.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to revise. |
feedback | string | Required | What you want changed (e.g. "Keep the total under $70" or "Use a different shop"). |
cancel_taskCancel a task. Before payment, cancellation is free. After payment but before the doer starts, a full refund is issued. If the doer is actively working, cancellation requires force: true and no refund is issued.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to cancel. |
force | boolean | Optional | Set to true to confirm cancellation of an in-progress task with an active doer (no refund will be issued). |
Response
{
"id": "7f8e9d0c-...",
"status": "cancelled",
"refunded": true,
"message": "Task cancelled. A full refund has been issued to your card on file."
}rate_taskLeave a rating for a completed task. Only works on tasks with status "completed". One rating per task.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The UUID of the completed task. |
rating | integer (1-5) | Required | Star rating from 1 (poor) to 5 (excellent). |
comment | string | Optional | Optional text feedback about the task experience. |
Response
{
"review_id": "r1a2b3c4-...",
"task_id": "7f8e9d0c-...",
"rating": 5,
"comment": "Fast and careful delivery. Great communication.",
"message": "Review submitted. Thank you for your feedback!"
}get_receiptGet a cost breakdown for a task. Returns estimated costs (from the quote) and actual costs (from the doer’s final bill, if submitted). Useful after task completion to see what was charged.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to get the receipt for. |
Response
{
"task_id": "7f8e9d0c-...",
"status": "completed",
"quote": {
"task_fee_cents": 2000,
"pass_through_cents": 3600,
"total_cents": 5600
},
"final": {
"task_fee_cents": 2000,
"pass_through_cents": 3200,
"total_cents": 5200,
"amount_charged_cents": 5600,
"refund_cents": 400
},
"line_items": [
{ "description": "Assorted donuts from Bob's", "amount_cents": 3200 }
],
"message": "Receipt with quote and final costs."
}get_messagesRetrieve the message thread for a task. Messages include doer questions, your replies, and system notes. Use this to check if the doer has asked a question or posted a status update.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to retrieve messages for. |
Response
{
"task_id": "7f8e9d0c-...",
"status": "in_progress",
"messages": [
{
"id": "a1b2c3d4-...",
"sender_type": "doer",
"body": "I'm at the shop — they have red, white, and pink roses. Which do you prefer?",
"created_at": "2026-03-27T14:22:00Z"
},
{
"id": "e5f6g7h8-...",
"sender_type": "client",
"body": "Red roses please.",
"created_at": "2026-03-27T14:25:00Z"
}
],
"message_count": 2
}send_messageSend a message to the doer working on your task. Use this to answer doer questions, provide clarifications, or give additional instructions. The message is relayed to the doer via Telegram.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to send a message for. |
message | string | Required | The message to send to the doer. |
image_url | string (url) | Optional | Optional image URL to attach. The image will be persisted and delivered to the doer. |
Example
use_mcp_tool doorstep send_message
{
"task_id": "7f8e9d0c-...",
"message": "Red roses please. A dozen."
}Response
{
"task_id": "7f8e9d0c-...",
"status": "in_progress",
"message_count": 3,
"message": "Message sent."
}wait_for_updateBlock until something changes on a task — a status transition, a new doer message, or task completion. Returns the event that occurred. Call this in a loop after approving a task to stay informed without manual polling.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string (uuid) | Required | The task ID to watch for updates. |
timeout | number | Optional | How long to wait in seconds (default 120, max 300). Returns "no_change" if nothing happens before the timeout. |
Events
task.quotedQuote is ready — plan and pricing available for approval.task.status_changedTask status changed (e.g. approved, in_progress, assigned).task.messageThe doer sent a message. Review and respond via send_message.task.completedTask is complete. Resolution details included.no_changeNothing happened before the timeout. Call wait_for_update again.Response
{
"task_id": "7f8e9d0c-...",
"event": "task.message",
"status": "in_progress",
"message_from": "doer",
"message_body": "I'm at the shop — they have red, white, and pink roses. Which do you prefer?",
"message": "New message from doer. Review and respond if needed via send_message."
}Account
registerCreate a new Doorstep account and get an API key. No prior authentication required. The user gets a full account they can also log into at trydoorstep.app.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Required | Email address for the account. |
password | string | Required | Password for the account (min 8 characters). |
name | string | Optional | Display name for the account. |
Response
{
"api_key": "doorstep_sk_...",
"user_id": "u1a2b3c4-...",
"message": "Account created. Use this API key as a Bearer token...",
"billing_url": "https://trydoorstep.app/billing"
}get_accountCheck your account status, including whether billing is set up. Returns a billing URL if you need to add a credit card.
Parameters
No parameters required.
Response
{
"name": "Sarah Chen",
"email": "sarah@example.com",
"has_payment_method": false,
"billing_url": "https://trydoorstep.app/billing",
"message": "No credit card on file. Add one at the billing URL before creating tasks."
}get_settingsGet the current account settings, including emergency contact info and spending limits.
Parameters
No parameters required.
Response
{
"monthly_spending_limit": 500,
"phone": "***1234",
"fallback_confirmed": true
}update_settingsUpdate account settings. Only provided fields are changed; omitted fields keep their current values.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
monthly_spending_limit | number | null | Optional | Maximum total spend per calendar month in USD. Set to null to remove the limit. |
phone | string | Optional | Emergency contact phone number. Doers will use this to reach you if your agent is offline during a task. |
Example
use_mcp_tool doorstep update_settings
{
"monthly_spending_limit": 200,
"phone": "+14155551234"
}Task Lifecycle
Every task follows this flow. Most tasks move from received to quoted within minutes. Real-world execution (in_progress to completed) depends on the errand.
Received
Task created. Doorstep is researching your request and building a plan.
Researching
Actively gathering info — checking availability, pricing, logistics.
Needs Info
We have a follow-up question. Use respond_to_task to answer.
Quoted
Plan and price quote are ready. Use approve_task to greenlight and authorize payment.
Payment Pending
You approved the quote but payment has not been collected yet. If your card was declined, update your payment method and call approve_task again to retry.
Approved
You approved the quote and payment was charged. A tasker is being assigned.
Scheduled
Task approved and paid. Dispatch is scheduled for a future service window. Use wait_for_update to receive a notification when dispatch occurs.
In Progress
A tasker is actively working on your request.
Completed
Task finished successfully. Resolution details available via get_task.
Failed
Something went wrong. We'll reach out to resolve it.
Cancelled
Task was cancelled before completion.
Getting Results Back
Real-world tasks take time — minutes to hours. Here’s how results flow back to your agent.
Within the session (quoting phase)
When you call do, Doorstep waits up to 2 minutes for the quote to be ready. Most quotes come back in under a minute, so you get the plan and pricing in the same tool response. If it takes longer, you get a task ID to check later.
Across sessions (execution phase)
Once a task is approved and a doer is working on it, execution can take hours. Your MCP session may end in the meantime. To check results later, just call get_taskwith the task ID in a new session. Ask your agent “What happened with my cake delivery?” and it will call get_task to get the latest status and resolution.
Doer communication (messaging)
While a task is in progress, the doer may have questions — “They have three options, which do you want?” Use get_messages to check for doer messages and send_messageto reply. This works in any MCP client — including Cursor, Claude Code, and other tools that can’t receive webhooks.
Webhooks (for agent platforms)
If your agent platform has its own server endpoint, pass a callback_urlwhen creating a task. Doorstep will POST status updates to that URL as they happen — no polling needed. See below.
Webhooks
When you provide a callback_urlon task creation, Doorstep sends POST requests to that URL whenever something happens — a quote is ready, a doer asks a question, or the status changes.
Events
| Parameter | Type | Required | Description |
|---|---|---|---|
task.quoted | event | Optional | Quote is ready — plan and pricing are available for approval. |
task.message | event | Optional | The doer sent a message or asked a question about the task. |
task.status_changed | event | Optional | Task status changed (approved, in_progress, assigned, etc.). |
task.completed | event | Optional | Task finished — resolution details included. |
Payload
{
"event": "task.completed",
"task_id": "7f8e9d0c-...",
"timestamp": "2026-03-27T16:30:00Z",
"task": {
"id": "7f8e9d0c-...",
"status": "completed",
"input": "Drop off a dozen donuts at the Figma office...",
"resolution": "Delivered 12 assorted donuts from Bob's. Front desk signed.",
"estimated_total_cents": 5600
}
}Verifying signatures
Every webhook includes an X-Doorstep-Signature header. Verify it by computing an HMAC-SHA256 of the raw request body using the callback_secret returned at task creation.
import { createHmac } from 'crypto';
function verify(body, secret, signature) {
const expected = 'sha256=' +
createHmac('sha256', secret).update(body).digest('hex');
return expected === signature;
}Doorstep retries failed deliveries up to 3 times with exponential backoff. Your endpoint should return a 2xx status to acknowledge receipt. If all retries fail, the event is dropped — you can always fall back to polling with get_task.