YourSite MCP API
/api/mcp is the agentic write path for the site. It exists so copy, settings,
products and prices change without a code edit and without a redeploy — the
values live in Postgres and pages read them at request time.
Reach for this before editing a marketing string in a .tsx file. If the text
is section content, it is almost certainly already DB-driven.
Protocol
JSON-RPC 2.0 over POST /api/mcp. Two methods: tools/list and tools/call.
curl -s -X POST "$SITE/api/mcp" \
-H "Authorization: Bearer $MCP_SECRET_KEY" \
-H "X-Caller-Label: claude-code" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"list_products","arguments":{}}}'
Authentication — two paths, both fail closed
Bearer MCP_SECRET_KEY + X-Caller-Label. For automation and agents.
The label is mandatory: a valid bearer token with no label is rejected.
MCP_SECRET_KEY is one shared secret, so it identifies that something is
authorised, not who — the label is what lands in the audit trail as
mcp-api-key:<label>. Pass a real, specific label.
- A Supabase session cookie with role
editor or admin. This is the
path the admin UI uses; the caller is recorded as their email.
Anything else gets 401.
The 27 tools
| Group | Tools |
|---|
| Section content | list_pages, get_section_schema, get_section_content, preview_section_update, update_section_content, get_section_history, rollback_section |
| Blog | list_blog_posts, get_blog_post, create_blog_post, update_blog_post, delete_blog_post, get_blog_categories |
| Careers | list_jobs, get_job, create_job, update_job, delete_job |
| Products & pricing | list_products, upsert_product, set_product_price |
| Site config | get_site_settings, set_app_config, set_feature_flag, set_social_link |
| Short links | list_short_links, create_short_link |
get_section_schema first, then preview_section_update, then
update_section_content. Preview shows the rendered result without writing, and
it is cheap — use it rather than writing and inspecting.
rollback_section exists because every section write is versioned. A bad copy
change is a rollback, not a redeploy.
Commercial scope — enforced, not advisory
upsert_product refuses any product whose title, description or category
reads as lead generation, a lead list, or anything priced per lead.
This is a Razorpay merchant-account constraint, not a style preference: checkout
may take payment for our SaaS and our own services, and nothing else. The guard
sits in the write path precisely because an automation caller has no human at
the keyboard to catch it. Do not try to word around it — if a product trips the
guard, the product is the problem.
Money
Prices are paise, integers. set_product_price takes paise. A price of 0
means "request a quote" and suppresses the Buy button — it does not mean free.
Never introduce a float. rupeesToPaise / paiseToRupees in
src/lib/form-schema.ts are the only conversions.
What changes live, and what needs a deploy
| Change | Effect |
|---|
| Section content, products, prices, settings, flags, links | Live immediately. Pages read the DB per request. |
| A new field or a new tool | Code change → commit → deploy. |
Anything NEXT_PUBLIC_* | Rebuild, not just restart — it is compiled into the browser bundle. |
If someone asks for "a price change without a deploy", that is set_product_price
and it is already possible. Say so rather than planning a release.
Related
.env.example names MCP_SECRET_KEY; docs/OPS.md §3 says which store holds
the value for which environment.
[[site-design]] covers the admin UI that calls these same paths.
[[brand]] governs any copy written through them.