@doswiftly/storefront-mcp
v4.0.0
Published
MCP server for the DoSwiftly Storefront API — validates GraphQL operations against the schema your project has installed, and searches the operation catalog, so AI coding assistants stop guessing.
Maintainers
Readme
@doswiftly/storefront-mcp
MCP server for the DoSwiftly Storefront API. It lets an AI coding assistant check a GraphQL operation against your schema before writing code around it, and look up operations, types and error codes without loading a whole reference into its context.
The problem it solves is narrow and expensive: an assistant that writes
cartLinesAdd instead of cartAddLines produces code that compiles, passes
review, and fails against the live API. Documentation asks the assistant to be
careful. This asks the schema.
Install
pnpm add -D @doswiftly/storefront-mcp @doswiftly/storefront-operationsBoth are dev-only. @doswiftly/storefront-operations carries the schema — this
server reads it from your install rather than shipping its own copy, so it can
never validate against a different schema than the one your project targets.
Configure
Most tools read a project-level .mcp.json:
{
"mcpServers": {
"doswiftly-storefront": {
"command": "npx",
"args": ["-y", "@doswiftly/storefront-mcp"]
}
}
}Start it from the project directory — the working directory decides which installed schema is used.
Any MCP client works; the server speaks stdio. If your client has its own config
format, point it at the doswiftly-storefront-mcp binary (or npx -y
@doswiftly/storefront-mcp) with no arguments.
Tools
| Tool | What it answers |
|---|---|
| validate_operation | Is this document valid against my schema — and what document id will the API key it by? |
| next_step | What is the one thing to do next, and where do I read about it? |
| get_project_state | How far along is this project, stage by stage? |
| report_friction | Tell the platform what I could not find or could not trust. |
| contribute_recipe | Send back how I built something there was no recipe for (opt-in). |
| search_operations | Which ready-made operation does what I want ("add lines to cart")? |
| get_operation | Give me that operation's body and typed variables, verbatim. |
| get_type | What fields / enum values does this type have? |
| list_error_codes | Which error code enums does the schema expose? |
| get_operation_error_codes | Which codes does this operation document? |
Use validate_operation on everything you write
It reports validation errors with locations, and for a valid document returns a
documentId in the API's sha256:<hex> form — the same value the codegen recipe
computes, so an id from here is the id the API will accept.
validate_operation({ source: "query P($h: String) { product(handle: $h) { id title } }" })
→ { valid: true, errors: [], documentId: "sha256:…" }
validate_operation({ source: "query P { product(slug: \"x\") { id } }" })
→ { valid: false, errors: [{ message: "Unknown argument \"slug\" on field …" }] }Pass the whole document, including fragments it spreads — an unresolved spread is reported rather than passed over.
Building a storefront: ask where you are, then what is next
Starting from an empty directory, the hard question is not what the schema contains — it is what to do first, and whether the thing you are about to build already exists.
get_project_state answers the first half by reading the project: each stage of
the build (scaffold, dependencies, identity, codegen, client, catalog,
session, cart, checkout, account, polish) comes back as done, todo
or unknown, each with the reason it was judged that way.
get_project_state()
→ { current: "cart",
stages: [ { name: "codegen", status: "done",
reason: "codegen.ts is configured and lib/gql/gql.ts has been generated" },
{ name: "cart", status: "todo",
reason: "the project has 20 GraphQL documents, none on this stage's root fields" }, … ] }unknown means "look before you act". It is used whenever a signal is
ambiguous — a config file that builds its values in a way a plain read cannot
follow, a cart surface that is wired but never filled. Treating it as "not done"
is how an assistant overwrites an afternoon of someone else's work.
next_step answers the second half, and answers it once: why this step comes now,
what finishes it, and either a command to run or the guide section to read.
next_step()
→ { stage: "cart",
why: "cart lines refer to what the catalog lists, so the catalog comes first…",
doneWhen: "the project has cart mutations",
support: { kind: "guide", package: "@doswiftly/storefront-operations",
section: "Cart mutation names", file: "…/AGENTS.md", sectionFound: true } }It never returns code to paste. Mechanical steps get a command; design decisions
get a pointer into the AGENTS.md that is already installed in your project —
and the pointer is checked against the installed file, so a section that has moved
says so instead of sending you to a heading that no longer exists.
When there is no confirmed recipe
Some steps — checkout, catalog filtering, variant selection — have no confirmed
recipe yet. The answer degrades rather than emptying: you still get the operations
the schema ships for that stage and the guide sections that constrain them, plus an
explicit warning that nothing here is proven and every operation should be checked
with validate_operation before you build on it.
Tell us what was missing
report_friction records a gap: what you were trying to build and what you could
not find or could not trust. Categories are missing_tool, incomplete_answer,
misleading_result, schema_gap and docs_contradict_behaviour.
Send only the description. The shop, the package versions, the kind of install and the stage you were on are read from the project, so a report cannot be filed against the wrong shop or an imagined version — and your code stays in your project. The response tells you exactly where the report went, and says so plainly when it went nowhere.
Reports travel to the API address your project already configures. A locally linked build writes a journal next to the checkout instead, so notes taken while developing the platform never crowd out the signal from real projects.
Sending a solution back — only if you say so
Checkout, catalog filtering and variant selection have no confirmed recipe yet,
which means the first person to build one does it without a road. contribute_recipe
sends back how it was done: which operations, in what order, what bites, and the
smallest fragment of glue that makes the sequence legible. Never a file.
It refuses unless your config declares it, and the refusal says what is missing:
// doswiftly.config.ts
const config = {
contributeRecipes: true,
shop: { slug: 'my-shop' },
api: { url: 'https://api.doswiftly.pl' },
};The literal true is what counts — this file is read, never executed, so a
computed value reads as "not declared". Failing that way round is deliberate:
the default costs a contribution, never a surprise.
What you send is a description you wrote, and it becomes raw material for a public
recipe rather than a published one. report_friction needs no declaration —
saying what was missing gives nothing away.
A caveat worth knowing about error codes
UserError.code is typed String, and the schema's own description of it points
at per-domain enums (CartErrorCode, CustomerErrorCode, …) that are not part
of the published schema. So:
list_error_codesreturns only the enums that really are exposed, and returns an explicit "not exposed" answer for the others — never an empty list, which would read as "this mutation has no error codes".get_operation_error_codesreturns codes named in an operation's description. That covers most cart and customer codes, but it is not exhaustive — the response says so. Always handle an unrecognised code as a generic failure.
Related
@doswiftly/storefront-operations— schema, ready-made operations, and anAGENTS.mdcovering the GraphQL layer.@doswiftly/storefront-sdk— the runtime library, with its ownAGENTS.mdcovering transport, session and cart conventions.
Requirements
Node.js 20 or newer. Works with any MCP-capable client.
