From d9b4a34bacf366c82e1cf414ccb98da5287e4f1f Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Mon, 9 Mar 2026 02:31:18 +0100 Subject: [PATCH] fix: resolve type errors in notes-service, MCP server, and test helpers - Add missing userId arg to syncNoteTags() calls in notes-service - Add seedUser option to createTestDb() for test setup - Add explicit type annotations to MCP server.tool() callbacks (zod v4) - Fix ambiguous getByText selector in settings e2e test Co-Authored-By: Claude Opus 4.6 --- src/lib/server/db/test-helpers.ts | 13 ++++++++++++- src/lib/server/mcp/server.ts | 29 +++++++++++++++-------------- src/lib/server/notes-service.ts | 4 ++-- tests/e2e/settings.spec.ts | 2 +- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/lib/server/db/test-helpers.ts b/src/lib/server/db/test-helpers.ts index 11de6af..f045213 100644 --- a/src/lib/server/db/test-helpers.ts +++ b/src/lib/server/db/test-helpers.ts @@ -6,7 +6,7 @@ import * as schema from './schema.js'; * Create an in-memory SQLite database for testing. * Each test gets a fresh database. */ -export function createTestDb() { +export function createTestDb(options?: { seedUser?: boolean }) { const sqlite = new Database(':memory:'); sqlite.pragma('journal_mode = WAL'); sqlite.pragma('foreign_keys = OFF'); @@ -110,5 +110,16 @@ export function createTestDb() { `); const db = drizzle(sqlite, { schema }); + + if (options?.seedUser) { + db.insert(schema.users).values({ + email: 'test@test.com', + displayName: 'Test User', + role: 'admin', + authProvider: 'password', + createdAt: new Date() + }).run(); + } + return { db, sqlite }; } diff --git a/src/lib/server/mcp/server.ts b/src/lib/server/mcp/server.ts index 058e2a4..0550c7a 100644 --- a/src/lib/server/mcp/server.ts +++ b/src/lib/server/mcp/server.ts @@ -1,5 +1,6 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; +import type { NoteFilter } from '$lib/types/index.js'; import { listNotes, getNote, @@ -43,8 +44,8 @@ export function createMcpServer(userId: number): McpServer { .describe('Filter notes by status'), tag: z.string().optional().describe('Filter by tag name') }, - async ({ filter = 'all', tag }) => { - let result = listNotes(userId, filter); + async ({ filter = 'all', tag }: { filter?: string; tag?: string }) => { + let result = listNotes(userId, filter as NoteFilter); if (tag) { result = result.filter((n) => @@ -62,7 +63,7 @@ export function createMcpServer(userId: number): McpServer { 'get_note', 'Get a single note by ID with its tags and attachments', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const note = getNote(userId, id); if (!note) { return { @@ -86,7 +87,7 @@ export function createMcpServer(userId: number): McpServer { color: z.enum(NOTE_COLORS).optional().describe('Note color'), checklistMode: z.boolean().optional().describe('Enable checklist mode') }, - async ({ title, content, color, checklistMode }) => { + async ({ title, content, color, checklistMode }: { title?: string; content?: string; color?: string; checklistMode?: boolean }) => { const note = createNote(userId, { title, content, color, checklistMode }); return { content: [{ type: 'text' as const, text: JSON.stringify(note, null, 2) }] @@ -105,7 +106,7 @@ export function createMcpServer(userId: number): McpServer { pinned: z.boolean().optional().describe('Pin/unpin'), checklistMode: z.boolean().optional().describe('Enable/disable checklist mode') }, - async ({ id, ...updates }) => { + async ({ id, ...updates }: { id: string; title?: string; content?: string; color?: string; pinned?: boolean; checklistMode?: boolean }) => { const result = updateNote(userId, id, updates); if (!result) { return { @@ -124,7 +125,7 @@ export function createMcpServer(userId: number): McpServer { 'trash_note', 'Move a note to trash', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const result = updateNote(userId, id, { trashed: true }); if (!result) { return { @@ -142,7 +143,7 @@ export function createMcpServer(userId: number): McpServer { 'restore_note', 'Restore a note from trash', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const result = updateNote(userId, id, { trashed: false }); if (!result) { return { @@ -160,7 +161,7 @@ export function createMcpServer(userId: number): McpServer { 'archive_note', 'Archive a note', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const result = updateNote(userId, id, { archived: true }); if (!result) { return { @@ -178,7 +179,7 @@ export function createMcpServer(userId: number): McpServer { 'unarchive_note', 'Unarchive a note', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const result = updateNote(userId, id, { archived: false }); if (!result) { return { @@ -196,7 +197,7 @@ export function createMcpServer(userId: number): McpServer { 'delete_note', 'Permanently delete a note', { id: z.string().describe('Note ID') }, - async ({ id }) => { + async ({ id }: { id: string }) => { const existing = getNote(userId, id); if (!existing) { return { @@ -221,7 +222,7 @@ export function createMcpServer(userId: number): McpServer { 'search_notes', 'Search notes by title, content, or tag name', { query: z.string().describe('Search query') }, - async ({ query }) => { + async ({ query }: { query: string }) => { const results = searchNotes(userId, query); return { content: [{ type: 'text' as const, text: JSON.stringify(results, null, 2) }] @@ -243,7 +244,7 @@ export function createMcpServer(userId: number): McpServer { id: z.string().describe('Note ID'), pinned: z.boolean().describe('Whether to pin (true) or unpin (false)') }, - async ({ id, pinned }) => { + async ({ id, pinned }: { id: string; pinned: boolean }) => { const result = updateNote(userId, id, { pinned }); if (!result) { return { @@ -275,7 +276,7 @@ export function createMcpServer(userId: number): McpServer { ) .describe('Array of note IDs with their new sort orders') }, - async ({ orders }) => { + async ({ orders }: { orders: { id: string; sortOrder: number }[] }) => { reorderNotes(userId, orders); return { content: [{ type: 'text' as const, text: `Reordered ${orders.length} notes` }] @@ -290,7 +291,7 @@ export function createMcpServer(userId: number): McpServer { noteId: z.string().describe('Note ID to attach the image to'), imageUrl: z.string().url().describe('URL of the image to fetch') }, - async ({ noteId, imageUrl }) => { + async ({ noteId, imageUrl }: { noteId: string; imageUrl: string }) => { const existing = getNote(userId, noteId); if (!existing) { return { diff --git a/src/lib/server/notes-service.ts b/src/lib/server/notes-service.ts index bbb53ca..c63f79b 100644 --- a/src/lib/server/notes-service.ts +++ b/src/lib/server/notes-service.ts @@ -93,7 +93,7 @@ export function createNote(userId: number, input: CreateNoteInput) { db.transaction((tx) => { tx.insert(notes).values(newNote).run(); }); - syncNoteTags(id, extractedTags); + syncNoteTags(id, extractedTags, userId); return { ...newNote, tags: extractedTags }; } @@ -149,7 +149,7 @@ export function updateNote(userId: number, id: string, input: UpdateNoteInput) { if (input.title !== undefined || input.content !== undefined) { const content = `${updated!.title} ${updated!.content}`; const extractedTags = extractTags(content); - syncNoteTags(id, extractedTags); + syncNoteTags(id, extractedTags, userId); } const tagMap = fetchTagsForNotes([id]); diff --git a/tests/e2e/settings.spec.ts b/tests/e2e/settings.spec.ts index 0e2abcf..1b4f25a 100644 --- a/tests/e2e/settings.spec.ts +++ b/tests/e2e/settings.spec.ts @@ -62,7 +62,7 @@ test.describe('Settings — API Key Management', () => { // Then the MCP settings page is shown await expect(page).toHaveURL('/settings/mcp'); - await expect(page.getByText('API Keys')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible(); // When the user clicks back to Profile await page.getByRole('link', { name: 'Profile' }).click();