diff --git a/.changeset/quick-comics-tell.md b/.changeset/quick-comics-tell.md new file mode 100644 index 000000000..b02705c61 --- /dev/null +++ b/.changeset/quick-comics-tell.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat(mcp): include an `AGENTS.md` or similar when using the `mcp` addon diff --git a/documentation/docs/30-add-ons/17-mcp.md b/documentation/docs/30-add-ons/17-mcp.md index 7382f45dd..991c1333e 100644 --- a/documentation/docs/30-add-ons/17-mcp.md +++ b/documentation/docs/30-add-ons/17-mcp.md @@ -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 diff --git a/packages/addons/mcp/index.ts b/packages/addons/mcp/index.ts index 05c5f8a03..41b7080f4 100644 --- a/packages/addons/mcp/index.ts +++ b/packages/addons/mcp/index.ts @@ -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', { @@ -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; @@ -70,43 +73,84 @@ 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: '.opencode/agent/svelte.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); + + const newContent: string[] = []; + + const prefixFile = sharedFiles.find((file) => file.name === `${ide}-prefix-AGENTS.md`); + if (prefixFile) newContent.push(prefixFile.contents); + if (agentFile) newContent.push(agentFile.contents); + + return newContent.join('\n'); + }); + } + + sv.file(mcpPath, (content) => { const { data, generateCode } = parseJson(content); if (schema) { data['$schema'] = schema; @@ -120,6 +164,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 = []; diff --git a/packages/create/index.ts b/packages/create/index.ts index b415953ae..59b1ba8fb 100644 --- a/packages/create/index.ts +++ b/packages/create/index.ts @@ -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]; @@ -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<{ @@ -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')); diff --git a/packages/create/playground.ts b/packages/create/playground.ts index 9ab4e0ab5..0741e30d0 100644 --- a/packages/create/playground.ts +++ b/packages/create/playground.ts @@ -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 { @@ -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; diff --git a/packages/create/shared/+mcp/AGENTS.md b/packages/create/shared/+mcp/AGENTS.md new file mode 100644 index 000000000..a6e66ff15 --- /dev/null +++ b/packages/create/shared/+mcp/AGENTS.md @@ -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. diff --git a/packages/create/shared/+mcp/opencode-prefix-AGENTS.md b/packages/create/shared/+mcp/opencode-prefix-AGENTS.md new file mode 100644 index 000000000..d59301257 --- /dev/null +++ b/packages/create/shared/+mcp/opencode-prefix-AGENTS.md @@ -0,0 +1,17 @@ +--- +description: Svelte MCP development assistant with access to documentation and tools +mode: primary +temperature: 0.1 +tools: + svelte_mcp_list-sections: true + svelte_mcp_get-documentation: true + svelte_mcp_svelte-autofixer: true + svelte_mcp_playground-link: true + write: false + edit: false +permission: + svelte_mcp_*: allow + write: deny + edit: deny + bash: deny +--- diff --git a/packages/create/utils.ts b/packages/create/utils.ts index bd5531a3a..08dd6891e 100644 --- a/packages/create/utils.ts +++ b/packages/create/utils.ts @@ -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 { @@ -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; +}