mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-06 20:55:44 +08:00
Compare commits
4 Commits
function-c
...
feat/to-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
543fff6207 | ||
|
|
9de930dddc | ||
|
|
07737a8edd | ||
|
|
fdb7689992 |
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte-llm": {
|
||||
"type": "http",
|
||||
"url": "https://svelte-llm.stanislav.garden/mcp/mcp"
|
||||
"svelte": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
.vscode/mcp.json
vendored
2
.vscode/mcp.json
vendored
@@ -3,7 +3,7 @@
|
||||
"Svelte MCP": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["dist/lib/stdio.js"]
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.2",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.7",
|
||||
"@sveltejs/adapter-vercel": "^5.6.3",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
@@ -53,6 +52,7 @@
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"remult": "3.3.0-next.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-eslint-parser": "^1.3.2",
|
||||
@@ -62,6 +62,7 @@
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@sveltejs/mcp-schema": "workspace:^",
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
"@tmcp/transport-http": "^0.6.3",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { http_transport } from '$lib/mcp/index.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { db, entities } from '$lib/server/db/index.js';
|
||||
|
||||
export async function handle({ event, resolve }) {
|
||||
const mcp_response = await http_transport.respond(event.request, {
|
||||
db,
|
||||
entities,
|
||||
});
|
||||
// we are deploying on vercel the SSE connection will timeout after 5 minutes...for
|
||||
// the moment we are not sending back any notifications (logs, or list changed notifications)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { createClient } from '@libsql/client';
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import * as schema from './schema.js';
|
||||
import { remult, SqlDatabase } from 'remult';
|
||||
import { TursoDataProvider } from 'remult/remult-turso';
|
||||
// let's disable it for the moment...i can't figure out a way to make it wotk with eslint
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { DATABASE_TOKEN, DATABASE_URL } from '$env/static/private';
|
||||
import { Content } from '../../../shared/entities/Content.js';
|
||||
import { ContentDistilled } from '../../../shared/entities/ContentDistilled.js';
|
||||
import { Distillation } from '../../../shared/entities/Distillation.js';
|
||||
import { DistillationJob } from '../../../shared/entities/DistillationJob.js';
|
||||
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
||||
|
||||
@@ -13,3 +19,13 @@ const client = createClient({
|
||||
});
|
||||
|
||||
export const db = drizzle(client, { schema, logger: true });
|
||||
|
||||
remult.dataProvider = new SqlDatabase(new TursoDataProvider(client));
|
||||
SqlDatabase.LogToConsole = true;
|
||||
|
||||
export const entities = {
|
||||
Content,
|
||||
ContentDistilled,
|
||||
Distillation,
|
||||
DistillationJob,
|
||||
};
|
||||
|
||||
19
apps/mcp-remote/src/shared/FieldVector.ts
Normal file
19
apps/mcp-remote/src/shared/FieldVector.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Fields, type FieldOptions } from 'remult';
|
||||
|
||||
export function FieldVector<entityType = unknown>(
|
||||
...options: (FieldOptions<entityType, number[]> & { dimensions?: number })[]
|
||||
) {
|
||||
const dimensions = options[0].dimensions ?? 1024;
|
||||
|
||||
return Fields.object<entityType, number[]>(
|
||||
{
|
||||
valueConverter: {
|
||||
fieldTypeInDb: `F32_BLOB(${dimensions})`,
|
||||
toDb: (val) => JSON.stringify(val),
|
||||
toDbSql: (val) => `vector32(${val})`,
|
||||
fromDb: (val: Buffer) => Array.from(new Float32Array(val)),
|
||||
},
|
||||
},
|
||||
...options,
|
||||
);
|
||||
}
|
||||
35
apps/mcp-remote/src/shared/entities/Content.ts
Normal file
35
apps/mcp-remote/src/shared/entities/Content.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { FieldVector } from '../FieldVector';
|
||||
|
||||
@Entity<Content>('content', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'content',
|
||||
})
|
||||
export class Content {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
path!: string;
|
||||
|
||||
@Fields.string()
|
||||
filename!: string;
|
||||
|
||||
@Fields.string()
|
||||
content!: string;
|
||||
|
||||
@Fields.integer()
|
||||
size_bytes!: number;
|
||||
|
||||
@FieldVector({ allowNull: true })
|
||||
embeddings: number[] | null = null;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
}
|
||||
35
apps/mcp-remote/src/shared/entities/ContentDistilled.ts
Normal file
35
apps/mcp-remote/src/shared/entities/ContentDistilled.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { FieldVector } from '../FieldVector';
|
||||
|
||||
@Entity<ContentDistilled>('content_distilled', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'content_distilled',
|
||||
})
|
||||
export class ContentDistilled {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
path!: string;
|
||||
|
||||
@Fields.string()
|
||||
filename!: string;
|
||||
|
||||
@Fields.string()
|
||||
content!: string;
|
||||
|
||||
@Fields.integer()
|
||||
size_bytes!: number;
|
||||
|
||||
@FieldVector({ allowNull: true })
|
||||
embeddings: number[] | null = null;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
}
|
||||
36
apps/mcp-remote/src/shared/entities/Distillation.ts
Normal file
36
apps/mcp-remote/src/shared/entities/Distillation.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, Field, Fields } from "remult"
|
||||
import { Relations } from "remult"
|
||||
import { DistillationJob } from "./DistillationJob.js"
|
||||
|
||||
@Entity<Distillation>("distillations", {
|
||||
allowApiCrud: true,
|
||||
dbName: "distillations",
|
||||
})
|
||||
export class Distillation {
|
||||
@Fields.integer()
|
||||
id!: number
|
||||
|
||||
@Fields.string()
|
||||
preset_name!: string
|
||||
|
||||
@Fields.string()
|
||||
version!: string
|
||||
|
||||
@Fields.string()
|
||||
content!: string
|
||||
|
||||
@Fields.integer()
|
||||
size_kb!: number
|
||||
|
||||
@Fields.integer()
|
||||
document_count!: number
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
distillation_job_id?: number
|
||||
|
||||
@Relations.toOne(() => DistillationJob, { field: "distillation_job_id" })
|
||||
distillation_job?: DistillationJob
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number
|
||||
}
|
||||
64
apps/mcp-remote/src/shared/entities/DistillationJob.ts
Normal file
64
apps/mcp-remote/src/shared/entities/DistillationJob.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { Relations } from 'remult';
|
||||
import { Distillation } from './Distillation.js';
|
||||
|
||||
@Entity<DistillationJob>('distillation_jobs', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'distillation_jobs',
|
||||
})
|
||||
export class DistillationJob {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
preset_name!: string;
|
||||
|
||||
@Fields.string({ allowNull: true })
|
||||
batch_id?: string;
|
||||
|
||||
@Fields.string()
|
||||
status!: string;
|
||||
|
||||
@Fields.string()
|
||||
model_used!: string;
|
||||
|
||||
@Fields.integer()
|
||||
total_files!: number;
|
||||
|
||||
@Fields.integer()
|
||||
processed_files = 0;
|
||||
|
||||
@Fields.integer()
|
||||
successful_files = 0;
|
||||
|
||||
@Fields.boolean()
|
||||
minimize_applied = false;
|
||||
|
||||
@Fields.integer()
|
||||
total_input_tokens = 0;
|
||||
|
||||
@Fields.integer()
|
||||
total_output_tokens = 0;
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
started_at?: number;
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
completed_at?: number;
|
||||
|
||||
@Fields.string({ allowNull: true })
|
||||
error_message?: string;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
|
||||
// Relations toMany
|
||||
@Relations.toMany(() => Distillation)
|
||||
distillations?: Distillation[];
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@sveltejs/kit": "^2.42.2",
|
||||
"@types/eslint-scope": "^8.3.2",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@typescript-eslint/types": "^8.44.0"
|
||||
"@typescript-eslint/types": "^8.44.0",
|
||||
"remult": "3.3.0-next.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,36 +117,6 @@ describe('add_autofixers_issues', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a suggestion when calling a function inside an effect', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { fetch_data } from './data.js';
|
||||
$effect(() => {
|
||||
fetch_data();
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are calling the function \`fetch_data\` inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { fetch_data } from './data.js';
|
||||
$effect(() => {
|
||||
fetch_data.fetch();
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are calling a function inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
with_possible_inits('($init)', ({ init }) => {
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import type {
|
||||
AssignmentExpression,
|
||||
CallExpression,
|
||||
Identifier,
|
||||
Node,
|
||||
UpdateExpression,
|
||||
} from 'estree';
|
||||
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
|
||||
import type { Autofixer, AutofixerState } from './index.js';
|
||||
import { left_most_id } from '../ast/utils.js';
|
||||
import type { AST } from 'svelte-eslint-parser';
|
||||
@@ -33,9 +27,9 @@ function run_if_in_effect(
|
||||
}
|
||||
}
|
||||
|
||||
function assign_or_update_visitor(
|
||||
function visitor(
|
||||
node: UpdateExpression | AssignmentExpression,
|
||||
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
|
||||
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
|
||||
) {
|
||||
run_if_in_effect(path, state, () => {
|
||||
function check_if_stateful_id(id: Identifier) {
|
||||
@@ -64,25 +58,9 @@ function assign_or_update_visitor(
|
||||
}
|
||||
}
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
function call_expression_visitor(
|
||||
node: CallExpression,
|
||||
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
|
||||
) {
|
||||
run_if_in_effect(path, state, () => {
|
||||
const function_name =
|
||||
node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function';
|
||||
state.output.suggestions.push(
|
||||
`You are calling ${function_name} inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
export const assign_in_effect: Autofixer = {
|
||||
UpdateExpression: assign_or_update_visitor,
|
||||
AssignmentExpression: assign_or_update_visitor,
|
||||
CallExpression: call_expression_visitor,
|
||||
UpdateExpression: visitor,
|
||||
AssignmentExpression: visitor,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { McpServer } from 'tmcp';
|
||||
import { setup_prompts, setup_resources, setup_tools } from './handlers/index.js';
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
|
||||
import type { Schema } from '@sveltejs/mcp-schema';
|
||||
import type { ClassType } from 'remult';
|
||||
|
||||
export const server = new McpServer(
|
||||
{
|
||||
@@ -21,7 +22,7 @@ export const server = new McpServer(
|
||||
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.',
|
||||
},
|
||||
).withContext<{ db: LibSQLDatabase<Schema> }>();
|
||||
).withContext<{ db: LibSQLDatabase<Schema>; entities: Record<string, ClassType<unknown>> }>();
|
||||
|
||||
export type SvelteMcp = typeof server;
|
||||
|
||||
|
||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -25,7 +25,7 @@ importers:
|
||||
version: 10.1.8(eslint@9.36.0(jiti@2.6.0))
|
||||
eslint-plugin-import:
|
||||
specifier: ^2.32.0
|
||||
version: 2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))
|
||||
version: 2.32.0(eslint@9.36.0(jiti@2.6.0))
|
||||
eslint-plugin-svelte:
|
||||
specifier: ^3.12.3
|
||||
version: 3.12.4(eslint@9.36.0(jiti@2.6.0))(svelte@5.39.6)(ts-node@10.9.2(@types/node@24.5.2)(typescript@5.9.2))
|
||||
@@ -50,6 +50,9 @@ importers:
|
||||
|
||||
apps/mcp-remote:
|
||||
dependencies:
|
||||
'@libsql/client':
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0
|
||||
'@sveltejs/mcp-schema':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/mcp-schema
|
||||
@@ -69,9 +72,6 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.36.0
|
||||
version: 9.36.0
|
||||
'@libsql/client':
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0
|
||||
'@modelcontextprotocol/inspector':
|
||||
specifier: ^0.16.7
|
||||
version: 0.16.8(@types/node@24.5.2)(typescript@5.9.2)
|
||||
@@ -111,6 +111,9 @@ importers:
|
||||
prettier-plugin-svelte:
|
||||
specifier: ^3.3.3
|
||||
version: 3.4.0(prettier@3.6.2)(svelte@5.39.6)
|
||||
remult:
|
||||
specifier: 3.3.0-next.1
|
||||
version: 3.3.0-next.1
|
||||
svelte:
|
||||
specifier: ^5.0.0
|
||||
version: 5.39.6
|
||||
@@ -193,6 +196,9 @@ importers:
|
||||
'@typescript-eslint/types':
|
||||
specifier: ^8.44.0
|
||||
version: 8.44.1
|
||||
remult:
|
||||
specifier: 3.3.0-next.1
|
||||
version: 3.3.0-next.1
|
||||
|
||||
packages/mcp-stdio:
|
||||
dependencies:
|
||||
@@ -3219,6 +3225,9 @@ packages:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
remult@3.3.0-next.1:
|
||||
resolution: {integrity: sha512-41psdKqvkch17x/lOT4fJ9iE5Ee1MchZ1yrlWGCV8oSsXhdp9H8/eIay2LPp62c3YBx9CgcZgpwwXVmRnzoWdw==}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5775,17 +5784,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)):
|
||||
eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
|
||||
eslint: 9.36.0(jiti@2.6.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)):
|
||||
eslint-plugin-import@2.32.0(eslint@9.36.0(jiti@2.6.0)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -5796,7 +5804,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.36.0(jiti@2.6.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0))
|
||||
eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.36.0(jiti@2.6.0))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -5807,8 +5815,6 @@ snapshots:
|
||||
semver: 6.3.1
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
@@ -6730,6 +6736,10 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
remult@3.3.0-next.1:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
Reference in New Issue
Block a user