npm.io
0.8.0 • Published yesterdayCLI

@artstorefronts/mcp

Licence
MIT
Version
0.8.0
Deps
2
Size
152 kB
Vulns
0
Weekly
0

@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 git@github.com:Art-Storefronts/artstorefronts-mcp.git
cd artstorefronts-mcp
./scripts/install.sh --print-config

That 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.yml

src/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 authenticate tool (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_pregame PR first — file it there, then add the MCP verb.

References