Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .cocoignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.claude
.github
.github
*.test.ts
108 changes: 74 additions & 34 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,112 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Commands
## Monorepo Structure

This is a pnpm monorepo containing Svelte MCP (Model Context Protocol) server implementations across multiple packages and applications:

- **apps/mcp-remote**: SvelteKit web application with MCP server functionality
- **packages/mcp-server**: Core MCP server implementation with code analysis tools
- **packages/mcp-stdio**: Standalone MCP server CLI with STDIO transport
- **packages/mcp-schema**: Shared schema definitions and database utilities

This is a Svelte MCP (Model Context Protocol) server implementation that includes both SvelteKit web interface and MCP server functionality.
## Development Commands

### Setup

```bash
pnpm i
cp .env.example .env
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
# Set the VOYAGE_API_KEY for embeddings support in .env
```

### Starting the mcp-remote app in dev mode

```bash
# Start the SvelteKit development server for mcp-remote (from root)
pnpm dev
```

### Common Commands
Or navigate to the app directory:

```bash
cd apps/mcp-remote
pnpm dev
```

### Common Commands (from root)

- `pnpm build` - Build all packages and applications
- `pnpm check` - Run type checking across all packages
- `pnpm lint` - Run prettier check and eslint across all packages
- `pnpm format` - Format code with prettier across all packages
- `pnpm test` - Run unit tests across all packages
- `pnpm test:watch` - Run tests in watch mode

### mcp-remote App Commands

Navigate to `apps/mcp-remote/` to run these commands:

- `pnpm dev` - Start SvelteKit development server
- `pnpm build` - Build the application for production
- `pnpm build` - Build the SvelteKit application for production
- `pnpm build:mcp` - Build the MCP server TypeScript files
- `pnpm start` - Run the MCP server (Node.js entry point)
- `pnpm check` - Run Svelte type checking
- `pnpm check:watch` - Run type checking in watch mode
- `pnpm lint` - Run prettier check and eslint
- `pnpm format` - Format code with prettier
- `pnpm test` - Run unit tests with vitest
- `pnpm test:watch` - Run tests in watch mode

### Database Commands (Drizzle ORM)

- `pnpm db:push` - Push schema changes to database
- `pnpm db:generate` - Generate migration files
- `pnpm db:migrate` - Run migrations
- `pnpm db:studio` - Open Drizzle Studio
- `pnpm inspect` - Start MCP inspector at http://localhost:6274/

### MCP Inspector Usage

After running `pnpm inspect`, visit http://localhost:6274/:
- Transport type: `Streamable HTTP`
- URL: http://localhost:5173/mcp (when dev server is running)

## Architecture

### MCP Server Implementation
### Monorepo Package Structure

- **@sveltejs/mcp-remote**: Full SvelteKit application with web interface and MCP server
- **@sveltejs/mcp-server**: Core MCP server logic and code analysis engine (private workspace package)
- **@sveltejs/mcp**: Standalone CLI MCP server with STDIO transport (publishable)
- **@sveltejs/mcp-schema**: Shared database schema and utilities (private workspace package)

The core MCP server is implemented in `src/lib/mcp/index.ts` using the `tmcp` library with:
### mcp-remote App (apps/mcp-remote)

- **Transport Layers**: Both HTTP (`HttpTransport`) and STDIO (`StdioTransport`) support
- **Schema Validation**: Uses Valibot with `ValibotJsonSchemaAdapter`
- **Main Tool**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes
The main SvelteKit application that provides both web interface and MCP server functionality:

### Code Analysis Engine
- **Entry Point**: `src/index.js` for Node.js MCP server
- **SvelteKit Integration**: `src/hooks.server.ts` integrates MCP HTTP transport with SvelteKit requests
- **MCP Server**: `src/lib/mcp/index.ts` - HTTP and STDIO transport support
- **Database**: SQLite with Drizzle ORM, vector storage for embeddings
- **Content Sync**: `src/lib/server/contentSync.ts` and `src/lib/server/contentDb.ts` for content management

Located in `src/lib/server/analyze/`:
### mcp-server Package (packages/mcp-server)

- **Parser** (`parse.ts`): Uses `svelte-eslint-parser` and TypeScript parser to analyze Svelte components
- **Scope Analysis**: Tracks variables, references, and scopes across the AST
- **Rune Detection**: Identifies Svelte 5 runes (`$state`, `$effect`, `$derived`, etc.)
Core MCP server implementation shared across applications:

### Autofixer System
- **Main Export**: `src/index.ts`
- **MCP Implementation**: `src/mcp/index.ts` using `tmcp` library with Valibot schema validation
- **Code Analysis**: Svelte component parsing with `svelte-eslint-parser` and TypeScript parser
- **Autofixers**: Visitor pattern implementations for code analysis and suggestions
- **Tools**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes

- **Autofixers** (`src/lib/mcp/autofixers.ts`): Visitor pattern implementations for code analysis
- **Walker Utility** (`src/lib/index.ts`): Enhanced AST walking with visitor mixing capabilities
- **Current Autofixer**: `assign_in_effect` - detects assignments to `$state` variables inside `$effect` blocks
### mcp-stdio Package (packages/mcp-stdio)

### Database Layer
Standalone publishable MCP server with STDIO transport:

- **ORM**: Drizzle with SQLite backend
- **Schema** (`src/lib/server/db/schema.ts`): Vector table for embeddings support
- **Utils** (`src/lib/server/db/utils.ts`): Custom float32 array type for vectors
- **CLI Binary**: `svelte-mcp` command
- **Entry Point**: `src/index.ts`
- **Transport**: Uses `@tmcp/transport-stdio` for command-line integration

### SvelteKit Integration
### Database Layer (mcp-remote)

- **Hooks** (`src/hooks.server.ts`): Integrates MCP HTTP transport with SvelteKit requests
- **Routes**: Basic web interface for the MCP server
- **ORM**: Drizzle with SQLite backend (`test.db`)
- **Schema**: Located in `src/lib/server/db/schema.ts` with vector table for embeddings
- **Configuration**: `drizzle.config.ts` in mcp-remote app

## Key Dependencies

Expand All @@ -81,7 +121,7 @@ Located in `src/lib/server/analyze/`:

## Environment Configuration

Required environment variables:
For the mcp-remote app (`apps/mcp-remote/.env`):

- `DATABASE_URL`: SQLite database path (default: `file:test.db`)
- `VOYAGE_API_KEY`: API key for embeddings support (optional)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Repo for the official Svelte MCP server.

```
pnpm i
cp .env.example .env
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
pnpm dev
```

Expand Down
6 changes: 4 additions & 2 deletions apps/mcp-remote/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { defineConfig } from 'drizzle-kit';

if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');

export default defineConfig({
schema: './src/lib/server/db/schema.ts',
dialect: 'turso',
dbCredentials: { url: process.env.DATABASE_URL, authToken: process.env.DATABASE_TOKEN },
dbCredentials: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_TOKEN || '',
},
verbose: true,
strict: true,
});
6 changes: 5 additions & 1 deletion apps/mcp-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@eslint/js": "^9.36.0",
"@libsql/client": "^0.14.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/adapter-vercel": "^5.6.3",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
Expand All @@ -64,6 +65,9 @@
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.6.2"
"@tmcp/transport-http": "^0.6.2",
"@types/tar-stream": "^3.1.4",
"minimatch": "^10.0.3",
"tar-stream": "^3.1.7"
}
}
138 changes: 138 additions & 0 deletions apps/mcp-remote/src/lib/cacheDb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { db } from '$lib/server/db';
import { cache } from '$lib/server/db/schema';
import { and, eq, sql } from 'drizzle-orm';

export interface CacheEntry {
id: number;
cache_key: string;
data: Buffer;
size_bytes: number;
expires_at: Date;
created_at: Date;
}

export class CacheDbService {
private defaultTTL: number;

constructor(defaultTTLMinutes: number = 60) {
this.defaultTTL = defaultTTLMinutes;
}

async get(key: string): Promise<Buffer | null> {
try {
const result = await db
.select({ data: cache.data })
.from(cache)
.where(and(eq(cache.cache_key, key), sql`${cache.expires_at} > ${new Date()}`))
.limit(1);

if (result.length === 0) {
return null;
}

return result[0].data;
} catch (error) {
console.error('Error getting cache entry:', error);
return null;
}
}

async set(key: string, data: Buffer, ttlMinutes?: number): Promise<void> {
const ttl = ttlMinutes || this.defaultTTL;
const expires_at = new Date(Date.now() + ttl * 60 * 1000);
const now = new Date();

try {
await db
.insert(cache)
.values({
cache_key: key,
data,
size_bytes: data.length,
expires_at,
created_at: now,
updated_at: now,
})
.onConflictDoUpdate({
target: cache.cache_key,
set: {
data,
size_bytes: data.length,
expires_at,
updated_at: now,
},
});
} catch (error) {
console.error('Error setting cache entry:', error);
throw error;
}
}

async delete(key: string): Promise<boolean> {
try {
const result = await db.delete(cache).where(eq(cache.cache_key, key));
return result.rowsAffected > 0;
} catch (error) {
console.error('Error deleting cache entry:', error);
return false;
}
}

async clear(): Promise<void> {
try {
await db.delete(cache);
} catch (error) {
console.error('Error clearing cache:', error);
throw error;
}
}

async deleteExpired(): Promise<number> {
try {
const result = await db.delete(cache).where(sql`${cache.expires_at} <= ${new Date()}`);
return result.rowsAffected;
} catch (error) {
console.error('Error deleting expired cache entries:', error);
return 0;
}
}

async getStatus(): Promise<{ count: number; keys: string[]; totalSizeBytes: number }> {
try {
const result = await db
.select({
cache_key: cache.cache_key,
size_bytes: cache.size_bytes,
})
.from(cache)
.where(sql`${cache.expires_at} > ${new Date()}`)
.orderBy(cache.created_at);

const keys = result.map((row) => row.cache_key);
const totalSizeBytes = result.reduce((sum, row) => sum + row.size_bytes, 0);

return {
count: result.length,
keys,
totalSizeBytes,
};
} catch (error) {
console.error('Error getting cache status:', error);
return { count: 0, keys: [], totalSizeBytes: 0 };
}
}

async has(key: string): Promise<boolean> {
try {
const result = await db
.select({ exists: sql`1` })
.from(cache)
.where(and(eq(cache.cache_key, key), sql`${cache.expires_at} > ${new Date()}`))
.limit(1);
return result.length > 0;
} catch (error) {
console.error('Error checking cache entry:', error);
return false;
}
}
}
Loading
Loading