mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 03:19:38 +08:00
Compare commits
13 Commits
add-devtoo
...
add-docker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dab61bdb3c | ||
|
|
0719e06f6a | ||
|
|
7346b0387a | ||
|
|
3af6c887e9 | ||
|
|
d91a4f2b51 | ||
|
|
ec2e12f7f2 | ||
|
|
7805fb8a24 | ||
|
|
5280f37a05 | ||
|
|
621c6cf3c7 | ||
|
|
5772110214 | ||
|
|
b171f6a19f | ||
|
|
3882733a66 | ||
|
|
e178374683 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,4 +24,3 @@ vite.config.ts.timestamp-*
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
dist
|
||||
10
.vscode/mcp.json
vendored
10
.vscode/mcp.json
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"servers": {
|
||||
"Svelte MCP": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["dist/lib/stdio.js"]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
20
README.md
20
README.md
@@ -12,23 +12,11 @@ pnpm dev
|
||||
|
||||
1. Set the VOYAGE_API_KEY for embeddings support
|
||||
|
||||
### Local dev tools
|
||||
|
||||
#### MCP inspector
|
||||
#### Optional tools
|
||||
|
||||
```
|
||||
pnpm run inspect
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Then visit http://localhost:6274/
|
||||
|
||||
- Transport type: `Streamable HTTP`
|
||||
- http://localhost:5173/mcp
|
||||
|
||||
#### Database inspector
|
||||
|
||||
```
|
||||
pnpm run db:studio
|
||||
```
|
||||
|
||||
https://local.drizzle.studio/
|
||||
- MCP Inspector: http://localhost:6274/ (Connect with `http://host.docker.internal:5173/mcp` + Streamable HTTP)
|
||||
- http://localhost:8081/ - Adminer SQLite frontend
|
||||
|
||||
31
bypass.php
Normal file
31
bypass.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
function adminer_object() {
|
||||
include_once "plugins/login-sqlite.php";
|
||||
|
||||
class NoAuthSqlite extends Adminer {
|
||||
function login($login, $password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function credentials() {
|
||||
return array('/tmp/test.db', '', '');
|
||||
}
|
||||
|
||||
function database() {
|
||||
return '/tmp/test.db';
|
||||
}
|
||||
|
||||
function loginForm() {
|
||||
echo '<input type="hidden" name="auth[driver]" value="sqlite">';
|
||||
echo '<input type="hidden" name="auth[server]" value="/tmp/test.db">';
|
||||
echo '<input type="hidden" name="auth[username]" value="">';
|
||||
echo '<input type="hidden" name="auth[password]" value="">';
|
||||
echo '<input type="hidden" name="auth[db]" value="/tmp/test.db">';
|
||||
echo '<p><input type="submit" value="Connect to SQLite Database"></p>';
|
||||
}
|
||||
}
|
||||
|
||||
return new NoAuthSqlite;
|
||||
}
|
||||
|
||||
include "adminer.php";
|
||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
mcp-inspector:
|
||||
image: ghcr.io/modelcontextprotocol/inspector:latest
|
||||
ports:
|
||||
- '6274:6274'
|
||||
- '6277:6277'
|
||||
environment:
|
||||
- DANGEROUSLY_OMIT_AUTH=true
|
||||
- HOST=0.0.0.0
|
||||
restart: unless-stopped
|
||||
container_name: mcp-inspector-sveltejs-mcp
|
||||
|
||||
adminer:
|
||||
image: adminer:4.6.2
|
||||
ports:
|
||||
- '8081:8080'
|
||||
volumes:
|
||||
- ./test.db:/tmp/test.db
|
||||
- ./bypass.php:/var/www/html/bypass.php
|
||||
environment:
|
||||
- ADMINER_DEFAULT_SERVER=sqlite:/tmp/test.db
|
||||
- ADMINER_DESIGN=hydra
|
||||
restart: unless-stopped
|
||||
container_name: adminer-sveltejs-mcp
|
||||
command: ["php", "-S", "[::]:8080", "-t", "/var/www/html", "/var/www/html/bypass.php"]
|
||||
11
package.json
11
package.json
@@ -4,15 +4,10 @@
|
||||
"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",
|
||||
"dev": "vite dev --host",
|
||||
"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",
|
||||
@@ -26,8 +21,7 @@
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"inspect": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector"
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
@@ -40,7 +34,6 @@
|
||||
"@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,15 +1,10 @@
|
||||
import type { Node } from 'estree';
|
||||
import type { AST } from 'svelte-eslint-parser';
|
||||
import type { Visitors } from 'zimmerframe';
|
||||
import type { ParseResult } from '../server/analyze/parse.js';
|
||||
|
||||
export type Autofixer = Visitors<
|
||||
Node | AST.SvelteNode,
|
||||
{
|
||||
output: { issues: string[]; suggestions: string[] };
|
||||
parsed: ParseResult;
|
||||
desired_svelte_version: number;
|
||||
}
|
||||
Node,
|
||||
{ output: { issues: string[]; suggestions: string[] }; parsed: ParseResult }
|
||||
>;
|
||||
|
||||
export const assign_in_effect: Autofixer = {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
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,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
}
|
||||
@@ -7,8 +7,6 @@ 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(
|
||||
{
|
||||
@@ -25,7 +23,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. After you correct the component call this tool again to confirm all the issues are fixed.',
|
||||
'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',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -37,18 +35,11 @@ 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.array(v.string()),
|
||||
suggestions: v.array(v.string()),
|
||||
require_another_tool_call_after_fixing: v.boolean(),
|
||||
issues: v.optional(v.array(v.string())),
|
||||
suggestions: v.optional(v.array(v.string())),
|
||||
}),
|
||||
annotations: {
|
||||
title: 'Svelte Autofixer',
|
||||
@@ -57,61 +48,14 @@ server.tool(
|
||||
openWorldHint: false,
|
||||
},
|
||||
},
|
||||
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
|
||||
async ({ code, filename }) => {
|
||||
const content: { issues: string[]; suggestions: string[] } = { issues: [], suggestions: [] };
|
||||
|
||||
const compilation_result = compile(code, {
|
||||
filename: filename || 'Component.svelte',
|
||||
generate: false,
|
||||
runes: desired_svelte_version >= 5,
|
||||
});
|
||||
const parsed = parse(code, filename ?? 'Component.svelte');
|
||||
|
||||
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;
|
||||
// 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 }, autofixer);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { stdio_transport } from './mcp/index.js';
|
||||
|
||||
stdio_transport.listen();
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -18,4 +18,7 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
server: {
|
||||
allowedHosts: ['host.docker.internal'],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user