MCP server guide

Last updated: 2026-10-31

The Garage Sale QR MCP server lets an MCP host such as Claude Code or Claude Desktop create and manage your listings from local photo files, using your personal access token.

Overview

The server never calls a language model itself. It only exposes tools. The host model looks at the previews, proposes the title, category, condition and price, and you approve or adjust them in the conversation.

Photos are converted to JPEG, resized and stripped of all EXIF and GPS data on your machine before any byte leaves it, both for previews and for uploads. Prefer a terminal workflow? See the CLI guide.

Install

From a checkout of the garage-qr repository, with Node 20 or newer:

pnpm install && pnpm --filter @garage-qr/mcp build

This produces apps/mcp/dist/index.js, a self-contained executable. Native image dependencies are loaded from node_modules, so keep the checkout in place.

Create a token

  1. Sign in and open your account page.
  2. In the API tokens section create a token with the scopes listings:read, listings:write and photos:write.
  3. Copy the value that starts with gqr_pat_. It is shown once.

Configure

Environment variables
VariableRequiredDefaultPurpose
GQR_TOKENyesnoneYour personal access token. Never logged.
GQR_API_URLnohttps://api.garageqr.appAPI base URL. Use http://localhost:8787 for local development.
GQR_WEB_URLnohttps://garageqr.appUsed for listing URLs and the host encoded in QR codes.

The process exits with a message on stderr if the token is missing. There are no config files; stdout carries only the MCP protocol and logs go to stderr.

Claude Code

claude mcp add garage-qr -e GQR_TOKEN=gqr_pat_xxx -- node /abs/path/to/garage-qr/apps/mcp/dist/index.js

Claude Desktop

claude_desktop_config.json
{
  "mcpServers": {
    "garage-qr": {
      "command": "node",
      "args": ["/abs/path/to/garage-qr/apps/mcp/dist/index.js"],
      "env": { "GQR_TOKEN": "gqr_pat_xxx" }
    }
  }
}

Example session

Illustrative, not a recording.

You: I have photos of my bike in ./bike. Create a listing for it.

Model: (calls preview_photos with the three files) This looks like a red 21-speed road bike in good condition. Proposed: title "Red 21-speed road bike", category sports_outdoors, condition good, price $120. Shall I create it?

You: Make it $90 and mention it comes with a lock.

Model: (calls create_listing, then upload_photos, then get_qr with a PDF path) Done. All 3 photos uploaded with location data removed. The printable sticker is saved next to the photos.

Tools

Tools
ToolPurpose
preview_photosDownscaled, metadata-free previews of up to 8 local images as image blocks. Per-file errors do not fail the call.
create_listingCreate a listing from text fields. Returns id, slug, opaque id and URL.
upload_photosUpload local photos to a listing one by one, with a per-file result.
list_listingsList your listings, optionally by status.
get_listingOne listing in full, with its photos.
update_listingPatch listing fields.
mark_sold / mark_reserved / mark_activeChange listing status.
delete_listingPermanent delete. Refuses unless confirm is true; the model must ask you first.
get_qrQR sticker as SVG text or a PDF resource, with an optional path to save it.

There are deliberately no account, token or auth tools.

Troubleshooting

  • unauthorized (401): the token is wrong, revoked or expired. Create a new one on your account page and update the host config. Repeated bad tokens from one address are rate limited.
  • insufficient_scope (403): the error names the required scope. Create a token that has it.
  • rate_limited (429): wait the number of seconds given. The SDK already retried short waits automatically.
  • unsupported_media_type or exif_rejected_local: that file could not be converted safely. It is skipped and reported; other files still proceed.
  • Server not listed in the host: check that the path to dist/index.js is absolute and that the build step was run.