Docs
Build with Onhand.
Connect any MCP client. Setup, sign-in, tools, errors and limits, all on one page.
Connect your assistant
Use Streamable HTTP and paste the URL for your client. Search works without sign-in. Actions ask you to sign in.
- Claude. Open connector settings and add a custom connector named Onhand.
https://mcp.askonhand.com/mcp/claude - ChatGPT. Enable Developer mode and add a custom MCP connector named Onhand.
https://mcp.askonhand.com/mcp/chatgpt - Grok. Open Connectors and add Onhand as a custom MCP connector.
https://mcp.askonhand.com/mcp/grok - Cursor. Open Settings → MCP and add a server named onhand with this URL.
https://mcp.askonhand.com/mcp/cursor - Muse. Ask Muse to create a custom connector named Onhand using Streamable HTTP and this URL.
https://mcp.askonhand.com/mcp/muse
Cursor also accepts this in .cursor/mcp.json:
{ "mcpServers": { "onhand": { "url": "https://mcp.askonhand.com/mcp/cursor" } } }One-click links are on the setup page. MCP uses POST and OPTIONS; GET returns 405. The surface comes from the URL, never a tool argument.
Example prompts
- Find a barber in Oak Cliff and help me book a haircut Saturday morning.
- Ask whether this coffee shop has a patio where I can bring my dog.
- Help me get a quote for repairing a leaking faucet in Dallas.
- Find plant nurseries near Lakewood in Dallas that are open now.
- Show me this florist's address, phone number, and website.
- What hours does this garden center post for Sunday?
What works depends on what each shop has shared or connected. Onhand asks before it confirms anything. A handoff is not a finished booking or order.
Authentication
Protected tools use OAuth 2.1 authorization code flow with PKCE S256 through WorkOS AuthKit. Send the access token as Authorization: Bearer <token>. Onhand validates the issuer, audience, signature, expiry, and active-token status. Protected writes fail closed when the issuer cannot be reached.
A 401 includes a WWW-Authenticate challenge with resource_metadata. Follow it to the protected-resource document, then its authorization_servers entry to AuthKit discovery. The MCP origin also serves /.well-known/oauth-protected-resource and a compatibility proxy at /.well-known/oauth-authorization-server. A surface-specific resource document is served only when that resource is configured.
Client ID Metadata Documents and Dynamic Client Registration (DCR fallback) are handled by AuthKit. Use its discovered metadata support and registration endpoint; Onhand does not register clients. Request only openid profile email offline_access. Tool permissions below are identity-derived capabilities, not OAuth scopes to request from a dynamic client.
Merchant permissions depend on organization membership and role. With several organizations, call list_subjects then select_subject. Follow returned setup, identity-link, or verification guidance before retrying merchant tools.
Results and confirmation
Fresh connected systems may report availability bands, never raw counts. Approved catalogs make no stock claim. Website facts are unconfirmed with source and date. Directory results say “call to confirm”; missing or stale availability is withheld. Ranking is never for sale.
Prepare calls write an intent, not an upstream change. Show the returned consequence and get the person’s approval before calling the matching confirm tool. Intents expire after ten minutes and bind the actor, target, and tool. Confirm uses the frozen arguments and rechecks the target. Changed targets require a new prepare and approval.
Prepared intent: intent, expires_at, consequence[]. Prepared action adds action_id, provider, destination_url, handoff_kind. Confirmed action returns action_id, booking_id, status, provider, destination_url, handoff_kind, consequence[], already_confirmed; replayed confirmations may omit consequence. A handoff is not a completed booking or purchase.
Prepare tools advertise readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint false. Confirmed profile/hours changes advertise destructiveHint true. Booking, checkout, cancellation, and review writes advertise openWorldHint true and destructiveHint true; cancellation and review submission advertise idempotentHint true. Quote confirmation advertises all three false. Account selection/setup and outcome reporting advertise idempotentHint true. Other reads advertise readOnlyHint true and openWorldHint false.
Onhand never processes payment, holds funds, or issues refunds. People complete payment with the business or its provider.
Public tool reference
Generated MCP tool reference with schemas and permissions
Inputs are JSON objects. “?” means optional; ids are UUIDs except opaque intents. Empty input is {}. All tools share the MCP rate limit below. Outputs are JSON encoded in MCP content[].text; inspect that JSON for refusals and guidance.
Optional city on the three location searches accepts a city name, slug or neighborhood, such as Dallas or Oak Cliff. An unrecognized city asks for clarification. Nearby alternatives have an unconfirmed city match. On ChatGPT, coordinates can come from location metadata; without a usable location, the tools ask for one. You can use find_business by name without coordinates. Coverage describes evidenced facts and freshness, not whether a city has shops. A listing with an unknown observation date can still have contact details while reporting missing freshness evidence.
Coordinates: lat −90…90, lng −180…180. radius_m: positive integer, default 8000, maximum 50000. q: 1-200 characters; category: 3-80; name: 3-120; near: 2-120; question: 3-300. attributes: up to 12 string pairs (keys 1-40, values 1-120 characters). idempotency_key: 8-200 characters; intent: 1-200. See each tool’s tools/list inputSchema for field bounds.
Public discovery
No sign-in required.
| Tool | Inputs | Outputs | Mode / side effects |
|---|---|---|---|
discover_local | lat, lng, q, category, radius_m?, attributes?, city? | offerings, website_items, directory (businesses, needs, gaps) | Read. Search catalogs and directory businesses together. |
search_local_offerings | lat, lng, q, radius_m?, attributes?, city? | array of approved offerings followed by unconfirmed website items | Read. Ranked offerings, prices, source, age, and eligible availability. |
find_business | name, near? | businesses[] | Read. Up to 10 name matches with business_id, address, categories, disambiguator. |
find_nearby_businesses | lat, lng, category, q?, radius_m?, city? | businesses[], label, ordering, needs, gaps | Read. Up to 25 businesses with known and missing details. Verified callers may record an anonymous demand count. |
request_area | category, lat?, lng? | status (accepted|queued|limited|unavailable), message | Write. Ask the shopper first. Submit an anonymous area request without email; may be unavailable while collection is off. No ingest or completion date is promised. |
get_business | id | profile; or business_not_found | Read. Hours, contact details, handoffs, FAQs, coverage, catalog summary, source evidence. |
ask_business | business_id, question | business_id, question, answered, answers, facts, note; or business_not_found | Read. Read approved answers; does not send a message. |
get_open_slots | business_id, offering_id?, limit? | businessId, slots[], source_type; or business_not_found | Read. Current windows, soonest first; no holds. limit defaults to 10, maximum 20. |
check_availability | offering_id | id, title, nullable priceCents, price_type, price_display, data_age_seconds, source_type, availability, withheld; or offering_not_found | Read. Read synced data; withhold availability when ineligible or stale. |
list_verified_reviews | business_id | array of review_id, rating, body, verified; or business_not_found | Read. Up to 20 receipt-backed reviews; no shopper identity. |
Shopper actions
Sign-in required. Permissions: prepare and status use actions:prepare; confirm and outcome reports use actions:confirm; reviews use reviews:write.
| Tool | Inputs | Outputs | Mode / side effects |
|---|---|---|---|
prepare_booking | business_id, handoff_kind (reservation|appointment), offering_id?, slot_id?, idempotency_key | prepared action | Prepare. Stage a booking or provider handoff; no reservation yet. |
confirm_booking | intent | confirmed action | Write. Create an eligible Square appointment or record a provider handoff. |
prepare_booking_cancellation | booking_id, idempotency_key | ok, state: prepared, intent, booking_id, expires_at, cutoff, consequence | Prepare. Check policy for an Onhand-created Square appointment; no upstream change. |
confirm_booking_cancellation | intent | ok, state: cancelled, booking_id; or refusal | Write. Cancel the prepared appointment. Repeats check status without another cancellation. |
prepare_checkout | business_id, offering_id, idempotency_key | prepared action | Prepare. Stage checkout for one available item. |
confirm_checkout_handoff | intent | confirmed action | Write. Create eligible Square/Shopify hosted checkout or record a provider handoff. |
prepare_quote_request | business_id, idempotency_key | prepared action | Prepare. Stage a quote-provider handoff. |
confirm_quote_handoff | intent | confirmed action | Write. Record the handoff; does not submit a quote or send contact details. |
get_action_status | action_id | action_id, booking_id, status, provider, cancellation_supported; or null | Read. Only the original shopper can read the action. |
submit_verified_review | booking_id, rating (integer 1-5), body (1-500 characters) | ok, review_id, already_submitted; or refusal | Write. Publish once for the original shopper after verified completion. Ask for approval first. |
report_action_outcome | action_id, outcome (completed|failed|abandoned) | recorded, outcome, recheck_queued; or recorded: false, reason | Write. Record one report within 72 hours of confirmation; failure may queue a link recheck. Not proof of completion. |
Account and organization
Any verified sign-in; no merchant membership required.
| Tool | Inputs | Outputs | Mode / side effects |
|---|---|---|---|
list_subjects | {} | subjects[] (organization_id, name, role, selected), setup_tool? | Read. List this identity’s organizations. |
select_subject | organization_id | ok, organization_id; or ok: false | Write. Save an organization choice after checking membership; repeatable. |
setup_account | kind (shopper|claim), display_name? (1-80 characters) | account, display_name, next, claim_tool? | Write. Save an account and optional shopper name; repeatable. Grants no ownership. |
start_business_claim | business_id | business_id, display_name, claim_url, next; or account_required, setup_tool, kind | Write. Return the claim page after setup_account with kind claim. Ownership requires verification there. |
Merchant tools
Sign-in and an active organization membership required. company:read for company/locations; connections:read for connections; billing:read for subscription. Profile/hours pairs need company:write; links need connections:write or billing:write.
| Tool | Inputs | Outputs | Mode / side effects |
|---|---|---|---|
get_company | {} | company or company: null | Read. Own company profile; identity selects the company. |
list_locations | {} | locations[] | Read. Own location_id, address, timezone, pickup_available. |
list_connections | {} | connections[] | Read. Own connector_id, provider, status, capabilities, sync health; no credentials. |
get_subscription | {} | subscription or subscription: null | Read. Own subscription status; no Stripe identifiers. |
update_company_profile | display_name?, website?, phone?, categories?, idempotency_key | prepared intent | Prepare. Stage at least one listing field; website/phone accept null to clear. |
confirm_update_company_profile | intent | applied, consequence | Write. Apply the frozen listing changes. |
update_hours | location_id, hours[] ({dow, open, close}), idempotency_key | prepared intent | Prepare. Stage a full week; omitted days close. dow 0-6 (Sunday-Saturday); times HH:mm, no overlaps. Split overnight windows. |
confirm_update_hours | intent | applied, consequence | Write. Replace the location’s hours with the frozen week. |
get_connection_link | provider (square|shopify_pos|calendly), action (connect|reconnect|map_locations|disconnect) | url, provider, action, done_here: false, what_happens_there | Read. Return dashboard link and audit the request; connection changes happen there. |
get_billing_link | {} | url, done_here: false, what_happens_there | Read. Return dashboard link and audit the request; billing changes happen there. |
Errors and recovery
Authorization and route refusals use HTTP status plus {"error":"code"}. Tool refusals usually use {"error":"code","message":"…"} inside MCP text; cancellation and review refusals include ok: false. Do not treat HTTP 200 as action success. SDK tool validation failures return isError: true with plain text. Protocol errors use JSON-RPC error.code and error.message: −32700 (parse), −32600 (invalid request), −32601 (unknown method), −32602 (invalid params), −32603 (internal).
- 401 invalid_token: missing, invalid, expired, or inactive token. Follow the challenge and sign in again.
- 403 insufficient_scope: identity lacks permission. 403 selection_required: choose a returned organization with select_subject.
- 503 oauth_not_configured, issuer_unreachable, link_unavailable, authorization_server_unavailable: authorization or discovery unavailable; retry later.
- 404 unknown_surface, unknown_tool, resource_not_configured: check URL/tool. 405 method_not_allowed: use POST. 413: body too large. 429: wait for Retry-After.
- Setup guidance (HTTP 200): status setup_required, link_required, or verification_pending, with message and URL when applicable. Follow the guidance; repeated sign-in does not grant merchant access.
- Account tools: invalid_display_name, unavailable; account_required means call setup_account with kind claim. select_subject may return ok: false for a refused choice.
- Prepare/confirm: already_prepared, unknown_intent, expired, wrong_principal, wrong_tool, already_used, target_unavailable, target_changed, offering_unavailable, booking_limit, identity_required, provider_unavailable, provider_refused. Recheck status before retrying a provider action; a reused prepare key does not create another intent.
- Checkout: reconciling or not_found may also be returned. Pending or reconciling means the provider outcome is unknown; use get_action_status before preparing another action.
- Cancellation: not_found, unsupported, expired, already_used, already_prepared, target_changed, cutoff_passed, policy_unavailable, already_terminal, provider_unavailable, provider_refused, pending, reconciliation_required, lifecycle_conflict. Pending or ambiguous outcomes require reconciliation; repeating the same intent does not resend cancellation.
- Merchant writes/links: forbidden, no_business, unknown_location; profile validation: empty_change, name_empty, name_too_long, website_too_long, website_not_https, phone_too_long, category_too_long, too_many_categories; hours: too_many_entries, bad_day, bad_time, close_before_open, overlapping. Correct input before preparing again.
- Reviews: invalid_input, not_eligible, unavailable. A review requires the original shopper’s verified completed booking or handoff; a self-reported outcome is insufficient.
- Outcome reports: recorded: false with reason unknown_action, not_confirmed, window_closed, or already_reported.
Unknown or unpublished MCP business lookups return business_not_found with business_id; check_availability returns offering_not_found with offering_id. Both set isError: true. Searches can return empty arrays; an empty result is not an availability claim.
Rate and request limits
MCP POST requests share a limit of 120 per minute per IP and mcp-session-id. Without that header, callers at the same IP share the anonymous bucket. This includes initialization, tool listing, and calls; there is no separate quota per tool. Routes without an override use the server default of 600 requests per minute per IP.
429 responses use statusCode, error, message and include Retry-After and rate-limit headers. Wait for the indicated interval. Request bodies are limited to 32,768 bytes. One busy session should not spend another session’s allowance. Upcoming Square bookings are capped at two per business and five total per shopper.
Support and policies
See privacy, terms, or support. Contact help@askonhand.com.