Documentation Index
Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
Use this file to discover all available pages before exploring further.
MCP Provider
An MCP Provider registers extra
Model Context Protocol tools with the
merchant’s MCP server. Once a merchant installs your app, every tool your
manifest declares becomes available to any AI assistant the merchant
already pointed at LaunchMyStore — Claude Desktop, Claude Code, Cursor,
Nova AI, and any other MCP-compatible client.
Where App Proxy lets a buyer’s browser call
your server, an MCP Provider lets a merchant’s AI assistant call your
server. Same idea — signed forwarding — different audience.
LaunchMyStore already ships 143 built-in MCP tools (get_products,
add_coupon, refund_order, …). An MCP Provider extension adds your
app’s tools to that registry. They show up in the same tools/list
response and are callable with the same client config — no extra MCP
server to add.
When to use an MCP Provider
| Use case | Tools to expose |
|---|
| Reviews app | reviews_list, reviews_moderate, reviews_reply |
| Loyalty app | loyalty_get_points, loyalty_grant_points, loyalty_redeem |
| Shipping app | shipping_quote, shipping_create_label, shipping_track |
| Email marketing app | campaign_create, campaign_send_test, audience_segment |
| Analytics app | analytics_run_query, report_generate |
Anything the merchant might reasonably ask an AI to do on your app’s
behalf is a candidate. Read-only tools are usually safe to expose
liberally; write tools should be guarded by your own scope checks.
If your tool needs to be callable by buyers (not merchants), use
App Proxy instead — MCP is merchant/operator only.
How it works
AI client LaunchMyStore MCP Your app server
───────── ───────────────── ───────────────
tools/list ───────────► 1. Built-in tools (143)
+ 2. Installed-app tools
from every app's
extensions.mcpProvider
◄─── combined list
tools/call 3. Match tool by namespace:
reviews_moderate ────► 4. Forward to your endpoint
with signed payload ──► 5. Verify signature
6. Run tool
7. Respond
◄──── response ◄──
◄──────────────── pass-through
- The merchant adds the LaunchMyStore MCP server to their AI client
(one-time, per the MCP Overview).
- When the client lists tools, the platform returns the built-in
registry plus every tool declared by every app the merchant has
installed.
- When the client calls a tool that belongs to your app, the platform
forwards the call to your
mcpProvider.endpoint with an HMAC-signed
body.
- Your server verifies the signature, runs the tool, returns the result
(JSON), and the platform streams it back to the AI client.
Manifest
Declare your tools in app.json under extensions.mcpProvider:
{
"handle": "foundry-reviews",
"name": "Foundry Reviews",
"version": "1.0.0",
"extensions": {
"mcpProvider": {
"endpoint": "https://reviews.your-domain.app/mcp",
"namespace": "reviews",
"timeoutMs": 10000,
"tools": [
{
"name": "reviews_list",
"description": "List recent reviews for a product, optionally filtered by rating or moderation status.",
"inputSchema": {
"type": "object",
"properties": {
"productId": { "type": "string", "description": "Product UUID" },
"minRating": { "type": "number", "minimum": 1, "maximum": 5 },
"status": { "type": "string", "enum": ["pending", "published", "rejected"] },
"limit": { "type": "number", "default": 20, "maximum": 100 }
},
"required": ["productId"]
},
"scopes": ["read_products"]
},
{
"name": "reviews_moderate",
"description": "Approve or reject a review by ID.",
"inputSchema": {
"type": "object",
"properties": {
"reviewId": { "type": "string" },
"action": { "type": "string", "enum": ["approve", "reject"] },
"reason": { "type": "string" }
},
"required": ["reviewId", "action"]
},
"scopes": ["write_products"],
"destructive": true
}
]
}
}
}
Top-level fields
| Field | Required | Description |
|---|
endpoint | yes | HTTPS URL of your MCP handler. The platform POSTs every tools/call for a tool in this provider to this URL. |
namespace | yes | Lowercase prefix prepended to every tool name when the platform exposes them. Tools below ship to the AI as reviews_list, reviews_moderate (manifest names are kept verbatim — pick names already prefixed with your namespace). |
timeoutMs | no | Per-call timeout (default 10000, max 30000). Calls exceeding this return an MCP error to the client. |
tools | yes | Array of tool definitions. At least one. |
| Field | Required | Description |
|---|
name | yes | Tool name. Must start with {namespace}_ (validated at install). Lowercase + underscores. |
description | yes | One- or two-sentence summary. This is the prompt the AI reads to decide when to call the tool — be precise about what it does and what it requires. |
inputSchema | yes | JSON Schema (draft-07 subset) describing parameters. The platform validates inputs before forwarding. |
scopes | no | Permission scopes required to call the tool. The platform rejects calls if the merchant’s MCP token doesn’t carry them. |
destructive | no | Set true for tools that mutate / delete data. AI clients show a confirmation prompt before invoking. Defaults to false. |
outputSchema | no | JSON Schema describing the response shape. Used for client-side rendering hints; not enforced. |
Wire protocol (LaunchMyStore → your app)
When a merchant’s AI client invokes one of your tools, the platform POSTs
to your endpoint:
POST https://reviews.your-domain.app/mcp
Content-Type: application/json
X-LMS-Signature: <hex(HMAC-SHA256(rawBody, app.clientSecret))>
X-LMS-AppId: <your app UUID>
X-LMS-RequestId: <uuid — also in body.request_id>
X-LMS-Shop: <merchant domainSlug>
{
"tool": "reviews_moderate",
"arguments": { "reviewId": "8f1d…-…", "action": "approve" },
"shop": {
"domainSlug": "raja337276",
"storeId": "9c1ee…-…",
"name": "Raja's Store"
},
"actor": {
"type": "merchant",
"userId": "5f02b…-…",
"scopes": ["read_products", "write_products"]
},
"request_id": "8f1d…-…"
}
| Field | Description |
|---|
tool | The full tool name (e.g. reviews_moderate). |
arguments | Validated against your inputSchema before forwarding. |
shop | The store the AI is operating on. |
actor.type | merchant, staff, or nova (the platform’s built-in AI). |
actor.userId | Stable id of the human or AI principal. |
actor.scopes | Scopes from the merchant’s MCP token. |
request_id | Echo this in your response logs for cross-correlation. |
Verifying in your app
The signature is the hex HMAC-SHA256 of the raw request body using
your app’s clientSecret. Identical to the
Functions network-access signing
and App Proxy patterns.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
// Capture the RAW body so the HMAC over it matches.
app.use('/mcp', express.raw({ type: 'application/json', limit: '1mb' }));
app.post('/mcp', (req, res) => {
const raw = req.body; // Buffer
const given = req.get('X-LMS-Signature') || '';
const expected = crypto
.createHmac('sha256', process.env.CLIENT_SECRET)
.update(raw)
.digest('hex');
if (
given.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(given, 'hex'), Buffer.from(expected, 'hex'))
) {
return res.status(401).json({ error: 'invalid signature' });
}
const body = JSON.parse(raw.toString('utf8'));
return handle(body, res);
});
Response shape
Return one of the MCP tools/call content types in a plain { content }
object — the platform wraps it into the full MCP envelope before sending
to the client:
{
"content": [
{ "type": "text", "text": "Approved review 8f1d…-… on product Cotton Shirt." }
]
}
To return structured data the AI can reason over, use the standard
text block with a JSON string, or pass structuredContent:
{
"content": [
{ "type": "text", "text": "Found 3 reviews matching status=pending." }
],
"structuredContent": {
"reviews": [
{ "id": "8f1d…", "rating": 5, "body": "Loved it" },
{ "id": "9c1ee…", "rating": 4, "body": "Solid quality" },
{ "id": "ab12c…", "rating": 2, "body": "Color was off" }
]
}
}
To signal a soft failure (e.g. “review not found”), respond with
isError: true:
{
"isError": true,
"content": [
{ "type": "text", "text": "Review 8f1d…-… does not exist." }
]
}
For hard server errors, return a non-2xx HTTP status; the platform maps
it to an MCP protocol error and surfaces it to the client.
Permissions & scopes
The merchant’s MCP token carries the scopes they granted to your app at
install. The platform enforces three layers before reaching your server:
- Tool scope check — if a tool declares
scopes: ["write_products"]
and the token lacks it, the platform rejects without calling you.
- Schema validation —
arguments must satisfy inputSchema. Type
mismatches return an MCP error without reaching you.
- Destructive confirmation — clients are encouraged to prompt for
destructive: true tools, but it’s a UI hint, not a hard guarantee.
Treat every write call as if it could fire.
Your handler should still re-check that the actor is allowed to
perform the tool — defense in depth.
Namespacing & collisions
All installed providers share one tool namespace. To prevent
collisions:
- Every tool name must start with
{namespace}_ (validated on install).
- Pick a unique, app-specific namespace —
reviews, loyalty,
shipping_aero, etc. The install endpoint rejects manifests that
collide with a built-in tool name or a tool already installed on the
merchant.
For multi-word app names use a short prefix: Foundry Reviews →
namespace: "reviews" (already short and clear) or namespace: "foundry"
if you ship more than one tool family later.
Caching, batching, and rate limits
| Concern | Behaviour |
|---|
| Concurrency | The platform calls your endpoint sequentially per merchant per MCP session. Concurrent merchants are independent. |
| Timeout | Default 10 s per call, max 30 s. Override via timeoutMs. |
| Retries | None. MCP tools/call is one-shot — surface partial-result info inside content. |
| Rate limit | Subject to the merchant’s MCP server quota (see MCP Overview). Your endpoint doesn’t have its own quota. |
If a tool needs heavy work, return early with a job id and expose a
companion *_status tool the AI can poll.
Discovery
Once your app is installed, the merchant can confirm the tools loaded by
running tools/list from their AI client. The standard
MCP Tools Reference page lists built-in tools — your app’s
tools appear in tools/list output but not in the public docs (since
they’re per-app).
Versioning
Your tool definitions are pinned to the app version that was installed.
When you publish a new app version with a changed mcpProvider, the
platform:
- Adds new tools immediately for fresh installs.
- Holds existing installs at the version they installed at — the merchant
upgrades through the standard app-update flow (App
Versioning).
So removing a tool in a new version doesn’t yank it out from under
existing merchants. Bump the major version when removing a tool to make
the change visible.
Security checklist
- ✅ Verify
X-LMS-Signature on every call. Reject unsigned calls
with 401.
- ✅ Re-authorize on the server side. Don’t trust
actor.scopes
blindly — re-check that the actor owns the resource they’re touching
(e.g. the review really belongs to this merchant).
- ✅ Be conservative with
destructive: false. If in doubt, mark
destructive so AI clients prompt for confirmation.
- ✅ Bound response size. Return at most ~50 KB of
text per call;
the AI has a context budget.
- ❌ Don’t return raw error stacks in
content — AI assistants will
paste them at the merchant. Log internally; respond with a clean
message.
Development & testing
- Run your MCP handler locally (
http://localhost:PORT/mcp).
- Tunnel it (ngrok / cloudflared) so the platform can reach it.
- Set
extensions.mcpProvider.endpoint to the tunnel URL in your dev
app’s app.json.
- Install the dev app on a test store.
- Add the LaunchMyStore MCP server to your AI client (per
MCP Overview) and ask the AI to call your tool by
description — e.g. “list pending reviews for product X”.
To unit-test your handler in isolation, mimic the platform’s POST:
import crypto from 'node:crypto';
const body = JSON.stringify({
tool: 'reviews_list',
arguments: { productId: 'p-1' },
shop: { domainSlug: 'dev-store', storeId: 's-1', name: 'Dev Store' },
actor: { type: 'merchant', userId: 'u-1', scopes: ['read_products'] },
request_id: 'r-1',
});
const sig = crypto.createHmac('sha256', process.env.CLIENT_SECRET).update(body).digest('hex');
const res = await fetch('http://localhost:4101/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-LMS-Signature': sig },
body,
});
console.log(await res.json());
See also