mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 19:45:32 +08:00
Compare commits
12 Commits
ci
...
add-devtoo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
684d7effef | ||
|
|
a0f8abb632 | ||
|
|
f85e4612ac | ||
|
|
11aceaf047 | ||
|
|
e515b9eaf9 | ||
|
|
ffafb8a492 | ||
|
|
cba6ab8f4b | ||
|
|
052331a80e | ||
|
|
887137e794 | ||
|
|
037f830557 | ||
|
|
7c3eb030ce | ||
|
|
2ded5711fb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ vite.config.ts.timestamp-*
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
dist
|
||||
10
.vscode/mcp.json
vendored
Normal file
10
.vscode/mcp.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"servers": {
|
||||
"Svelte MCP": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["dist/lib/stdio.js"]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
21
README.md
21
README.md
@@ -11,3 +11,24 @@ pnpm dev
|
||||
```
|
||||
|
||||
1. Set the VOYAGE_API_KEY for embeddings support
|
||||
|
||||
### Local dev tools
|
||||
|
||||
#### MCP inspector
|
||||
|
||||
```
|
||||
pnpm run inspect
|
||||
```
|
||||
|
||||
Then visit http://localhost:6274/
|
||||
|
||||
- Transport type: `Streamable HTTP`
|
||||
- http://localhost:5173/mcp
|
||||
|
||||
#### Database inspector
|
||||
|
||||
```
|
||||
pnpm run db:studio
|
||||
```
|
||||
|
||||
https://local.drizzle.studio/
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
"description": "The official Svelte MCP server implementation",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
"svelte-mcp": "./dist/lib/stdio.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"build:mcp": "tsc --project tsconfig.build.json",
|
||||
"prepublishOnly": "pnpm build:mcp",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
@@ -21,7 +26,8 @@
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"inspect": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
@@ -34,6 +40,7 @@
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.7",
|
||||
"@sveltejs/adapter-vercel": "^5.6.3",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
|
||||
1983
pnpm-lock.yaml
generated
1983
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { walk as _walk } from 'zimmerframe';
|
||||
import type { Autofixer } from './mcp/autofixers';
|
||||
|
||||
export type WalkParams<
|
||||
T extends {
|
||||
@@ -31,25 +30,3 @@ export function walk<
|
||||
...visitors,
|
||||
});
|
||||
}
|
||||
|
||||
export function mix_visitors(autofixers: Record<string, Autofixer>): Autofixer {
|
||||
const visitors: Autofixer = {};
|
||||
for (const key in autofixers) {
|
||||
const new_visitors = autofixers[key];
|
||||
for (const node_type in new_visitors) {
|
||||
if (node_type in visitors) {
|
||||
const existing_visitor = visitors[node_type as keyof typeof visitors]!;
|
||||
const new_visitor = new_visitors[node_type as keyof typeof visitors]!;
|
||||
visitors[node_type as keyof typeof visitors] = (node, ctx) => {
|
||||
(existing_visitor as any)(node, ctx);
|
||||
(new_visitor as any)(node, ctx);
|
||||
};
|
||||
} else {
|
||||
visitors[node_type as keyof typeof visitors] = new_visitors[
|
||||
node_type as keyof typeof visitors
|
||||
] as never;
|
||||
}
|
||||
}
|
||||
}
|
||||
return visitors;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { Node } from 'estree';
|
||||
import type { AST } from 'svelte-eslint-parser';
|
||||
import type { Visitors } from 'zimmerframe';
|
||||
import type { ParseResult } from '$lib/server/analyze/parse.js';
|
||||
import type { ParseResult } from '../server/analyze/parse.js';
|
||||
|
||||
export type Autofixer = Visitors<
|
||||
Node,
|
||||
{ output: { issues: string[]; suggestions: string[] }; parsed: ParseResult }
|
||||
Node | AST.SvelteNode,
|
||||
{
|
||||
output: { issues: string[]; suggestions: string[] };
|
||||
parsed: ParseResult;
|
||||
desired_svelte_version: number;
|
||||
}
|
||||
>;
|
||||
|
||||
export const assign_in_effect: Autofixer = {
|
||||
|
||||
70
src/lib/mcp/eslint.ts
Normal file
70
src/lib/mcp/eslint.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ESLint } from 'eslint';
|
||||
import svelte_parser from 'svelte-eslint-parser';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import type { Config } from '@sveltejs/kit';
|
||||
|
||||
let svelte_5_linter: ESLint | undefined;
|
||||
|
||||
let svelte_4_linter: ESLint | undefined;
|
||||
|
||||
function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
||||
return [
|
||||
...svelte.configs.recommended,
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
rules: {
|
||||
'no-self-assign': 'warn',
|
||||
'svelte/infinite-reactive-loop': 'warn',
|
||||
'svelte/no-dupe-else-if-blocks': 'warn',
|
||||
'svelte/no-dupe-on-directives': 'warn',
|
||||
'svelte/no-dupe-style-properties': 'warn',
|
||||
'svelte/no-dupe-use-directives': 'warn',
|
||||
'svelte/no-object-in-text-mustaches': 'warn',
|
||||
'svelte/no-raw-special-elements': 'warn',
|
||||
'svelte/no-reactive-functions': 'warn',
|
||||
'svelte/no-reactive-literals': 'warn',
|
||||
'svelte/no-store-async': 'warn',
|
||||
'svelte/no-svelte-internal': 'warn',
|
||||
'svelte/no-unnecessary-state-wrap': 'warn',
|
||||
'svelte/no-unused-props': 'warn',
|
||||
'svelte/no-unused-svelte-ignore': 'warn',
|
||||
'svelte/no-useless-children-snippet': 'warn',
|
||||
'svelte/no-useless-mustaches': 'warn',
|
||||
'svelte/prefer-svelte-reactivity': 'warn',
|
||||
'svelte/prefer-writable-derived': 'warn',
|
||||
'svelte/require-event-dispatcher-types': 'warn',
|
||||
'svelte/require-store-reactive-access': 'warn',
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
parser: svelte_parser,
|
||||
parserOptions: {
|
||||
svelteConfig: svelte_config,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function get_linter(version: number) {
|
||||
if (version < 5) {
|
||||
return (svelte_4_linter ??= new ESLint({
|
||||
overrideConfigFile: true,
|
||||
baseConfig: base_config({
|
||||
compilerOptions: {
|
||||
runes: false,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
}
|
||||
return (svelte_5_linter ??= new ESLint({
|
||||
overrideConfigFile: true,
|
||||
baseConfig: base_config({
|
||||
compilerOptions: {
|
||||
runes: true,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mix_visitors, walk } from '$lib';
|
||||
import { walk } from '../index.js';
|
||||
import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot';
|
||||
import { HttpTransport } from '@tmcp/transport-http';
|
||||
import { StdioTransport } from '@tmcp/transport-stdio';
|
||||
@@ -7,6 +7,8 @@ import { McpServer } from 'tmcp';
|
||||
import * as v from 'valibot';
|
||||
import { parse } from '../server/analyze/parse.js';
|
||||
import * as autofixers from './autofixers.js';
|
||||
import { get_linter } from './eslint.js';
|
||||
import { compile } from 'svelte/compiler';
|
||||
|
||||
const server = new McpServer(
|
||||
{
|
||||
@@ -23,7 +25,7 @@ const server = new McpServer(
|
||||
completions: {},
|
||||
},
|
||||
instructions:
|
||||
'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code',
|
||||
'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code. After you correct the component call this tool again to confirm all the issues are fixed.',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -35,11 +37,18 @@ server.tool(
|
||||
'Given a svelte component or module returns a list of suggestions to fix any issues it has. This tool MUST be used whenever the user is asking to write svelte code before sending the code back to the user',
|
||||
schema: v.object({
|
||||
code: v.string(),
|
||||
desired_svelte_version: v.pipe(
|
||||
v.union([v.literal(4), v.literal(5)]),
|
||||
v.description(
|
||||
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
|
||||
),
|
||||
),
|
||||
filename: v.optional(v.string()),
|
||||
}),
|
||||
outputSchema: v.object({
|
||||
issues: v.optional(v.array(v.string())),
|
||||
suggestions: v.optional(v.array(v.string())),
|
||||
issues: v.array(v.string()),
|
||||
suggestions: v.array(v.string()),
|
||||
require_another_tool_call_after_fixing: v.boolean(),
|
||||
}),
|
||||
annotations: {
|
||||
title: 'Svelte Autofixer',
|
||||
@@ -48,12 +57,62 @@ server.tool(
|
||||
openWorldHint: false,
|
||||
},
|
||||
},
|
||||
async ({ code, filename }) => {
|
||||
const content: { issues: string[]; suggestions: string[] } = { issues: [], suggestions: [] };
|
||||
async ({ code, filename, desired_svelte_version }) => {
|
||||
const content: {
|
||||
issues: string[];
|
||||
suggestions: string[];
|
||||
require_another_tool_call_after_fixing: boolean;
|
||||
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
|
||||
try {
|
||||
// compile without generating to get warnings and errors
|
||||
|
||||
const parsed = parse(code, filename ?? 'Component.svelte');
|
||||
const compilation_result = compile(code, {
|
||||
filename: filename || 'Component.svelte',
|
||||
generate: false,
|
||||
runes: desired_svelte_version >= 5,
|
||||
});
|
||||
|
||||
walk(parsed.ast as unknown as Node, { output: content, parsed }, mix_visitors(autofixers));
|
||||
for (const warning of compilation_result.warnings) {
|
||||
content.issues.push(
|
||||
`${warning.message} at line ${warning.start?.line}, column ${warning.start?.column}`,
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = parse(code, filename ?? 'Component.svelte');
|
||||
|
||||
// Run each autofixer separately to avoid interrupting logic flow
|
||||
for (const autofixer of Object.values(autofixers)) {
|
||||
walk(
|
||||
parsed.ast as unknown as Node,
|
||||
{ output: content, parsed, desired_svelte_version },
|
||||
autofixer,
|
||||
);
|
||||
}
|
||||
|
||||
const eslint = get_linter(desired_svelte_version);
|
||||
const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' });
|
||||
|
||||
for (const message of results[0].messages) {
|
||||
if (message.severity === 2) {
|
||||
content.issues.push(
|
||||
`${message.message} at line ${message.line}, column ${message.column}`,
|
||||
);
|
||||
} else if (message.severity === 1) {
|
||||
content.suggestions.push(
|
||||
`${message.message} at line ${message.line}, column ${message.column}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error & { start?: { line: number; column: number } };
|
||||
content.issues.push(
|
||||
`${error.message} at line ${error.start?.line}, column ${error.start?.column}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (content.issues.length > 0 || content.suggestions.length > 0) {
|
||||
content.require_another_tool_call_after_fixing = true;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
|
||||
4
src/lib/stdio.ts
Normal file
4
src/lib/stdio.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
import { stdio_transport } from './mcp/index.js';
|
||||
|
||||
stdio_transport.listen();
|
||||
21
tsconfig.build.json
Normal file
21
tsconfig.build.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/lib/stdio.ts", "src/lib/mcp/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src/routes/**/*", "src/app.html", "src/hooks.server.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user