Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/quick-comics-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'sv': patch
---

feat(mcp): include an `AGENTS.md` or similar when using the `mcp` addon
3 changes: 2 additions & 1 deletion documentation/docs/30-add-ons/17-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ npx sv add mcp

## What you get

- A good mcp configuration for your project depending on your IDE
- A MCP configuration for [local](https://svelte.dev/docs/mcp/local-setup) or [remote](https://svelte.dev/docs/mcp/remote-setup) setup
- A [README for agents](https://agents.md/) to help you use the MCP server effectively

## Options

Expand Down
65 changes: 55 additions & 10 deletions packages/addons/mcp/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineAddon, defineAddonOptions } from '@sveltejs/cli-core';
import { defineAddon, defineAddonOptions, log } from '@sveltejs/cli-core';
import { parseJson } from '@sveltejs/cli-core/parsers';
import { getSharedFiles } from '../../create/utils.ts';
import { getHighlighter } from '../../cli/commands/add/utils.ts';

const options = defineAddonOptions()
.add('ide', {
Expand Down Expand Up @@ -60,7 +62,8 @@ export default defineAddon({
| {
schema?: string;
mcpServersKey?: string;
filePath: string;
agentPath: string;
mcpPath: string;
typeLocal?: 'stdio' | 'local';
typeRemote?: 'http' | 'remote';
env?: boolean;
Expand All @@ -70,48 +73,82 @@ export default defineAddon({
| { other: true }
> = {
'claude-code': {
filePath: '.mcp.json',
agentPath: 'CLAUDE.md',
mcpPath: '.mcp.json',
typeLocal: 'stdio',
typeRemote: 'http',
env: true
},
cursor: {
filePath: '.cursor/mcp.json'
agentPath: 'AGENTS.md',
mcpPath: '.cursor/mcp.json'
},
gemini: {
agentPath: 'GEMINI.md',
schema:
'https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json',
filePath: '.gemini/settings.json'
mcpPath: '.gemini/settings.json'
},
opencode: {
agentPath: 'AGENTS.md',
schema: 'https://opencode.ai/config.json',
mcpServersKey: 'mcp',
filePath: 'opencode.json',
mcpPath: 'opencode.json',
typeLocal: 'local',
typeRemote: 'remote',
command: ['npx', '-y', '@sveltejs/mcp'],
args: null
},
vscode: {
agentPath: 'AGENTS.md',
mcpServersKey: 'servers',
filePath: '.vscode/mcp.json'
mcpPath: '.vscode/mcp.json'
},
other: {
other: true
}
};

const filesAdded: string[] = [];
const filesExistingAlready: string[] = [];

const sharedFiles = getSharedFiles().filter((file) => file.include.includes('mcp'));
const agentFile = sharedFiles.find((file) => file.name === 'AGENTS.md');

for (const ide of options.ide) {
const value = configurator[ide];
if ('other' in value) continue;

const { mcpServersKey, filePath, typeLocal, typeRemote, env, schema, command, args } = value;
sv.file(filePath, (content) => {
const {
mcpServersKey,
agentPath,
mcpPath,
typeLocal,
typeRemote,
env,
schema,
command,
args
} = value;

// We only add the agent file if it's not already added
if (!filesAdded.includes(agentPath)) {
sv.file(agentPath, (content) => {
if (content) {
filesExistingAlready.push(agentPath);
return content;
}
filesAdded.push(agentPath);
return agentFile?.contents ?? '';
});
}

sv.file(mcpPath, (content) => {
const { data, generateCode } = parseJson(content);
if (schema) {
data['$schema'] = schema;
}
const key = mcpServersKey || 'mcpServers';
const key = mcpServersKey ?? 'mcpServers';
data[key] ??= {};
data[key].svelte =
options.setup === 'local'
Expand All @@ -120,6 +157,14 @@ export default defineAddon({
return generateCode();
});
}

if (filesExistingAlready.length > 0) {
const highlighter = getHighlighter();
log.warn(
`${filesExistingAlready.map((path) => highlighter.path(path)).join(', ')} already exists, we didn't touch ${filesExistingAlready.length > 1 ? 'them' : 'it'}. ` +
`See ${highlighter.website('https://svelte.dev/docs/mcp/overview#Usage')} for manual setup.`
);
}
},
nextSteps({ highlighter, options }) {
const steps = [];
Expand Down
7 changes: 3 additions & 4 deletions packages/create/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { mkdirp, copy, dist } from './utils.ts';
import { mkdirp, copy, dist, getSharedFiles } from './utils.ts';

export type TemplateType = (typeof templateTypes)[number];
export type LanguageType = (typeof languageTypes)[number];
Expand All @@ -19,7 +19,7 @@ export type File = {
contents: string;
};

export type Condition = TemplateType | LanguageType | 'playground';
export type Condition = TemplateType | LanguageType | 'playground' | 'mcp';

export type Common = {
files: Array<{
Expand Down Expand Up @@ -66,8 +66,7 @@ function write_template_files(template: string, types: LanguageType, name: strin
}

function write_common_files(cwd: string, options: Options, name: string) {
const shared = dist('shared.json');
const { files } = JSON.parse(fs.readFileSync(shared, 'utf-8')) as Common;
const files = getSharedFiles();

const pkg_file = path.join(cwd, 'package.json');
const pkg = /** @type {any} */ JSON.parse(fs.readFileSync(pkg_file, 'utf-8'));
Expand Down
7 changes: 2 additions & 5 deletions packages/create/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import path from 'node:path';
import * as js from '@sveltejs/cli-core/js';
import { parseJson, parseScript, parseSvelte } from '@sveltejs/cli-core/parsers';
import { isVersionUnsupportedBelow } from '@sveltejs/cli-core';
import { dist } from './utils.ts';
import type { Common } from './index.ts';
import { getSharedFiles } from './utils.ts';

export function validatePlaygroundUrl(link: string): boolean {
try {
Expand Down Expand Up @@ -182,9 +181,7 @@ export function setupPlaygroundProject(

// add playground shared files
{
const shared = dist('shared.json');
const { files } = JSON.parse(fs.readFileSync(shared, 'utf-8')) as Common;
const playgroundFiles = files.filter((file) => file.include.includes('playground'));
const playgroundFiles = getSharedFiles().filter((file) => file.include.includes('playground'));

for (const file of playgroundFiles) {
let contentToWrite = file.contents;
Expand Down
23 changes: 23 additions & 0 deletions packages/create/shared/+mcp/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:

## Available MCP Tools:

### 1. list-sections

Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.

### 2. get-documentation

Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.

### 3. svelte-autofixer

Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.

### 4. playground-link

Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
7 changes: 7 additions & 0 deletions packages/create/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Common } from './index.ts';

export function mkdirp(dir: string): void {
try {
Expand Down Expand Up @@ -44,3 +45,9 @@ export function dist(path: string): string {
new URL(`./${!insideDistFolder ? 'dist/' : ''}${path}`, import.meta.url).href
);
}

export function getSharedFiles(): Common['files'] {
const shared = dist('shared.json');
const { files } = JSON.parse(fs.readFileSync(shared, 'utf-8')) as Common;
return files;
}
Loading