@artstorefronts/mcp
v0.2.0
Published
MCP server wrapping the Art Storefronts GraphQL surface for end-customer agents (Claude Desktop, Claude in Chrome, Cursor).
Readme
@asf/mcp
An MCP server that exposes the Art Storefronts GraphQL surface to MCP-capable agents — Claude Desktop, Claude in Chrome, Cursor, and anything else that speaks stdio MCP.
End-user shape: "On filip-cloudinary-regression-testing, add a product called 'Sunset' for $50, enable Lyve Canvas + Vibrance Gloss, remove the old print called 'Old Cat'" — and the agent walks through the right sequence of mutations under the user's authorization (website_ownage_policy enforcement happens server-side; this MCP server never bypasses it).
Status
v0.1 — initial release. Nine grouped tools wrapping the icono_pregame PR #4912 GraphQL surface. stdio transport. Env-var auth. Sibling to @asf/cli, which mirrors the same GraphQL client file-for-file (a future @asf/api package will collapse the duplication).
Why grouped tools and not one tool per mutation?
Empirically, admin agents start to degrade when an MCP server exposes more than ~20 tools — they reach for the wrong tool or stall. This server ships 9 tools with a verb discriminator + a verb-specific payload, well under that threshold. The tool descriptions name the underlying GraphQL field for every verb so the agent can cross-reference the runbook when in doubt.
Tool surface
| Tool | Verbs | Underlying GraphQL fields |
|---|---|---|
| discover | whoami, websites, website_detail | /api/v1/users/me, websites { stores pages ... } |
| manage_website | update, update_subscribe_bar, update_announcement_bar | updateWebsite, updateSubscribeBar, updateAnnouncementBar |
| manage_store | update_markups, update_default_sizes, update_medium_availabilities | updateMediumMarkups, updateDefaultActivatedSizes, updateMediumAvailabilities |
| manage_product | create, bulk_create, update, add_photo, remove_photo, delete, bulk_delete | createProduct(s), updateProduct, addProductPhoto, removeProductPhoto, deleteProduct(s) |
| manage_gallery | create, add_products, remove_products, set_cover, reorder | createProductGalleryPage, addProductsToGallery, removeProductsFromGallery, setGalleryCover, reorderGalleryProducts |
| manage_page | update, delete | updatePage, deletePage (cascade-safe — store/blog need cascadeConfirmation: true) |
| manage_contacts | create, bulk_create, delete, bulk_delete | createContact(s), deleteContact(s) |
| manage_shipping | update_datum, create_method, update_method, delete_method | updateShippingDatum, createShippingMethod, updateShippingMethod, deleteShippingMethod |
| upload_asset | (single verb) | POST /api/v1/uploads/s3/fetch_signed_url + S3 PUT |
Install — three steps
git clone [email protected]:Art-Storefronts/artstorefronts-mcp.git
cd artstorefronts-mcp
./scripts/install.sh --print-configThat runs nvm use (Node 20.x via .nvmrc), npm ci, npm run build, and then prints a ready-to-paste mcp.json snippet with the absolute path to dist/index.js already filled in.
For local development with hot reload: npm run dev (runs src/index.ts through tsx; no build step).
Auth
v0.2 reads credentials from the same OS keychain entry that asf auth login populates — no env vars, no ASF_TOKEN. Sign in once via the CLI on the workstation, point the MCP at the same host, and it picks up the access + refresh tokens automatically.
# One-time, on the workstation:
npm install -g @asf/cli # or `npm link` from a local checkout
asf auth login --host artstorefronts.com # browser opens, you click Allow
# Now the MCP can read the keychain entry; restart the host (Claude Desktop, ...).Token refresh is automatic: on a 401 the MCP server transparently exchanges the cached refresh_token for a new pair and retries the original request. If the refresh fails, the next tool call returns Not signed in to <host>. Run \asf auth login --host ` first.` — guiding the user through recovery.
Wire it up to your agent
The MCP server takes its target host via the --host flag in the args array. No env block needed.
{
"mcpServers": {
"asf": {
"command": "node",
"args": [
"/absolute/path/to/artstorefronts-mcp/dist/index.js",
"--host", "artstorefronts.com"
]
}
}
}For staging or local dev, change just the --host arg:
"args": ["/abs/path/dist/index.js", "--host", "staging.artstorefronts.com"]
"args": ["/abs/path/dist/index.js", "--host", "app.lvh.me:3000"]--host is optional — omit it and the server defaults to artstorefronts.com. Local-dev shorthand applies HTTP automatically to any host whose authority includes lvh.me, localhost, or 127.0.0.1; everything else gets HTTPS. Pass a full URL (https://...) to override the scheme inference.
| Agent | Config file location |
|---|---|
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Claude Desktop (Windows) | %APPDATA%\Claude\claude_desktop_config.json |
| Cursor | ~/.cursor/mcp.json |
| Claude in Chrome | Paste into the extension's MCP picker; it persists locally. |
| Claude Code (CLI) | .mcp.json at the repo root, or ~/.claude/mcp.json for a global server. |
Restart the agent after editing the config. The 9 tools should appear in the MCP picker. Try:
"List the websites I own, then create an ART_PRINT product named 'MCP Smoke' on the first one with image https://res.cloudinary.com/decosites/image/upload/v1426724962/image_quality_auditor_test_sml_el2jaa.jpg, then make a gallery called 'MCP Smoke Gallery' and add the product to it."
Smoke test against the local dev stack
# 1. Sign in once via the CLI (one-time per host):
asf auth login --host app.lvh.me:3000
# 2. Verify the MCP can read the keychain + boot:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| node dist/index.js --host app.lvh.me:3000
# 3. Configure Claude Desktop / Cursor / Claude Code to launch the MCP
# with `--host app.lvh.me:3000`, restart, and try the demo prompt.For a full end-to-end check, drive it through Claude Desktop with the prompt above and verify in the Rails console that the product + gallery were created and the product is linked to the gallery.
Smoke evidence per release lives under artifacts/asf-mcp/<release>/smoke/ in the artstore-agent workspace.
Architecture
artstorefronts-mcp/
src/
index.ts # stdio entry; wires Server + tool registry + GraphQLClient
graphql/
client.ts # fetch wrapper with JWT + Mozilla UA (Rack::Attack workaround)
operations.ts # one helper per GraphQL field, grouped by domain
tools/
types.ts # ToolDefinition / ToolResult shapes
registry.ts # exports the 9 grouped tools
discover.ts
manage_website.ts
manage_store.ts
manage_product.ts
manage_gallery.ts
manage_page.ts
manage_contacts.ts
manage_shipping.ts
upload_asset.ts
.github/workflows/ci.ymlsrc/graphql/ deliberately mirrors @asf/cli's src/utils/api.ts + per-domain command shapes so a future @asf/api extraction is a plain git mv. If you find yourself diverging the two clients, that is a signal to extract.
Authorization model
The MCP server adds no new authorization layer. Every request carries the JWT verbatim; the icono_pregame backend enforces the existing website_ownage_policy (site owners see only their owned websites; global admins see all). When the user says "my site filip-cloudinary-regression-testing", the agent picks the right website from discover.websites, and the server-side policy refuses anything outside the JWT's scope.
Roadmap
- 0.1 (this release): nine grouped tools, stdio transport, env-var auth, smoke against local dev stack.
- 0.2: optional
authenticatetool (if env-var-only auth proves too friction-heavy for end users). - 0.3: SSE / Streamable-HTTP transport for hosted multi-tenant deployments.
- Later: anything that requires a new GraphQL operation belongs in a follow-up
icono_pregamePR first — file it there, then add the MCP verb.
References
- GraphQL runbook (canonical curl reference)
- icono_pregame PR #4912 — the GraphQL surface this server wraps
- Sibling CLI:
@asf/cli - Jira: ART-8462 (initial MCP release), parent ART-8460
