{"slug": "what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel", "title": "What I learned shipping a read-only MCP server on Next.js and Vercel", "summary": "A developer built Padel Expert Lab, a free French-language padel racket comparator, and shipped a read-only remote MCP server alongside it on Next.js and Vercel so users can query racket recommendations and specs directly through Claude or ChatGPT. The server exposes three tools with read-only annotations and runs sessionless streamable HTTP via mcp-handler, avoiding Redis; the developer documented fixes for trailing-slash 308 redirects that broke some MCP clients and registry authentication using an Ed25519 key at /.well-known/mcp-registry-auth. The server was accepted into the official MCP registry and picked up by Glama and mcptry within minutes, and it logs only anonymous per-day counters with no IP addresses or arguments.", "body_md": "I play padel as a beginner, and choosing my racket was hard: hundreds of models, and the specs are spread over the brands' websites. So I built [Padel Expert Lab](https://padelexpertlab.com/), a free padel racket comparator (in French), and then a small **remote MCP server** so that people can ask Claude or ChatGPT directly instead of reading forty product pages.\n\nThe server is read-only, has three tools and no authentication. It lives at `https://padelexpertlab.com/api/mcp/`, inside the same Next.js app as the site. Here is what was worth knowing.\n\n`recommander_raquettes`: up to 3 rackets for a player profile (level, style, weight, how often you play, budget). It runs the same engine as the site's quiz.`chercher_raquettes`: search by name or by criteria (brand, level, shape, price range, weight), 10 results per page.`voir_raquettes`: the specs of 1 to 4 rackets, side by side.\nEach tool has a `title` and `readOnlyHint: true`, `destructiveHint: false`, `openWorldHint: false`. Claude's connector directory checks these annotations, and read-only tools can run without asking the user every time.\n\nEvery answer ends with one link to a page of the site (\"open the racket page to see the photos\"). No shop link and no affiliate link: the assistant gets data, and the visitor gets a reason to come and look.\n\nI used [`mcp-handler`](https://github.com/vercel/mcp-handler) on a Next.js route handler. Streamable HTTP without sessions means no Redis and no long-lived connection: each request is an ordinary POST, which suits Vercel functions. GET answers 405, since there is nothing to stream.\n\nThe site uses `trailingSlash: true`, so Next.js answers a POST to `/api/mcp` with a **308** to `/api/mcp/`. Some MCP clients do not follow it. Then Claude's connector directory stored my address *without* the slash, and its probe reported the 308.\n\nThe fix: turn off Next's own redirect (`skipTrailingSlashRedirect: true`) and write the same two rules in `redirects()`, leaving `/api/mcp` out. One trap: a custom rule also matches its source *with* a final slash, so without a `(?!.*/$)` lookahead every address that already ended with a slash redirected to itself. I caught it on a local production build by checking 23 paths before shipping, and a test now checks the rules in both matching modes.\n\nThe [official registry](https://registry.modelcontextprotocol.io/) accepts a reverse-domain name (`com.padelexpertlab/raquettes`) if you prove you own the domain. The HTTP proof is a static file, `/.well-known/mcp-registry-auth`, holding an Ed25519 public key; `mcp-publisher login http` then signs with the private key, which stays off the repo. Two details: macOS's LibreSSL cannot generate Ed25519 keys (use Homebrew's OpenSSL 3), and with `trailingSlash: true` you want to check that `.well-known` files are served as they are (Next leaves `.well-known` alone).\n\nGlama and mcptry picked the server up from the registry on their own within minutes, and PulseMCP says it will do the same when it reopens: publish there first.\n\nThe server keeps one anonymous counter per day, tool and client family, with no IP address, no arguments and no text. The rate limiter keys on an HMAC of the address, in memory only. The privacy page says exactly that, and tests fail if the code starts logging arguments.\n\nThe specs come from the brands' official pages. The prices are the last ones collected by hand on the brands' official stores, so every answer that shows a price says it may have changed and links to how prices are updated. An assistant rephrases what you give it: the clearer your caveats, the fewer wrong answers it will repeat.\n\nIf you want to try it: add `https://padelexpertlab.com/api/mcp/` as a custom connector in Claude (or `claude mcp add --transport http padel-expert-lab https://padelexpertlab.com/api/mcp/` in Claude Code). The answers are in French. Documentation: [padelexpertlab.com/connecteur-ia/](https://padelexpertlab.com/connecteur-ia/) · Repository: [github.com/FluxCoreGroup/padelexpertlab-mcp](https://github.com/FluxCoreGroup/padelexpertlab-mcp)", "url": "https://wpnews.pro/news/what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel", "canonical_source": "https://dev.to/padel_expertlab_ccb31265/what-i-learned-shipping-a-read-only-mcp-server-on-nextjs-and-vercel-265g", "published_at": "2026-11-09 16:20:15+00:00", "updated_at": "2026-11-09 16:26:04.633438+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["Padel Expert Lab", "Next.js", "Vercel", "mcp-handler", "Claude", "ChatGPT", "Glama", "MCP registry"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel", "markdown": "https://wpnews.pro/news/what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel.md", "text": "https://wpnews.pro/news/what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel.txt", "jsonld": "https://wpnews.pro/news/what-i-learned-shipping-a-read-only-mcp-server-on-next-js-and-vercel.jsonld"}}