SvelteKit moved fast enough that training data contains three incompatible generations of it: Sapper, SvelteKit 1.0, and SvelteKit 2 on Svelte 5. The single most useful thing an agent can know is which Svelte version the project runs and whether runes are enabled, because that decides the syntax of nearly every component it writes. It also needs the universal versus server-only load distinction, since that boundary is enforced by filename rather than by anything visible inside the file.
Scoped read and write access to the project directory.
SvelteKit routing is entirely filename driven, so the agent has to read the real src/routes tree before it can place a +page.server.js correctly.
Repository history, diffs, and blame.
History tells the agent whether the codebase has already migrated to runes, which is otherwise a guess.
Real browser automation for navigating and asserting on the rendered app.
Form actions and use:enhance only prove correct when a real submit round trip runs, including the no-JavaScript fallback.
Schema inspection and read queries against your database.
Server load functions query directly, so real schema access stops the agent from inventing table and column names.
These are the failures that repeat across sessions, so each one belongs in .mcp.json.
Agents still reach for a session store from $app/stores, a load function that receives page and fetch as a legacy shape, or Sapper's preload. None of that exists any more, and session in particular was removed in favour of event.locals plus data returned from load. Put the version in your rules file and add: 'Load functions live in +page.js or +page.server.js, take a single event argument, and per request state comes from event.locals set in hooks.server.js.'
In a Svelte 5 runes project an agent will still write export let for props and a $: block for derived values, which either fails to compile or silently opts the component out of runes mode. The mix is hard to spot because both syntaxes look correct in isolation. State it plainly: 'Svelte 5 runes mode. Props use $props(), state uses $state(), derived values use $derived(), side effects use $effect(). Never use export let or $: labels.'
A +page.js load runs on the server for the first render and then again in the browser on client navigation, so an agent that queries the database or imports from $env/static/private there ships a build error at best and a leaked secret at worst. Agents pick the wrong file because both are named load. Write the rule as: 'Anything touching the database, private env, or a server-only SDK goes in +page.server.js or a .server.js module. +page.js is browser code too.'
Asked to add a form, agents default to a POST endpoint under routes/api plus a client fetch handler, which throws away progressive enhancement, the built-in validation return shape, and automatic invalidation of load data. It works, so nobody catches it in review. Add: 'Mutations from a form use a named action in +page.server.js with use:enhance on the client. Only create a route under api/ for external consumers or webhooks.'
For internal navigation and post-mutation refreshes, agents write window.location.href or location.reload(), which drops the router, refetches every load, and loses client state. Both are common in generic JavaScript training data and neither is ever correct inside a SvelteKit app. Specify: 'Use goto from $app/navigation for internal navigation and invalidate or invalidateAll to refresh load data. window.location is only for external URLs.'
Agents call items.push(x) or obj.key = v and move on. Under Svelte 5 $state that works because of the proxy, but in a legacy component or on a plain non-reactive value it silently renders nothing, and agents cannot tell which mode a file is in without looking. Make it explicit: state your Svelte version and add 'in legacy components reassign rather than mutate, and never assume a plain module level object is reactive.'