From 318af51c21c07a98428c3bfbb24c0b1610753bf1 Mon Sep 17 00:00:00 2001 From: frouaix Date: Sat, 21 Feb 2026 17:34:02 -0800 Subject: [PATCH 01/10] Add E2E tests for all 10 apps and fix stale integration tests - Add 45 E2E tests in e2e-apps.test.ts covering: - System tools (ping, get_mode) - Readonly operations for all 10 apps (list_containers, list, search) - Create/action with dryRun=true for 6 apps - Error paths (invalid app, create in readonly mode) - Fix existing server.test.ts to use current generic app.* tool names - Fix TypeScript strict errors in safari.ts (parts[0] nullability) - No destructive operations tested (update, delete, run_script) Closes #1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/mcp-server/src/adapters/safari.ts | 4 +- .../test/integration/e2e-apps.test.ts | 267 ++++++++++++++++++ .../test/integration/server.test.ts | 19 +- 3 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 packages/mcp-server/test/integration/e2e-apps.test.ts diff --git a/packages/mcp-server/src/adapters/safari.ts b/packages/mcp-server/src/adapters/safari.ts index d9ba3b8..0cdbb7c 100644 --- a/packages/mcp-server/src/adapters/safari.ts +++ b/packages/mcp-server/src/adapters/safari.ts @@ -42,7 +42,7 @@ export class SafariAdapter implements ResourceAdapter { return { templateId: "safari.get_tab", parameters: { - tabIndex: parseInt(parts[0], 10) || 1, + tabIndex: parseInt(parts[0] ?? "1", 10) || 1, windowId: parts[1] ? parseInt(parts[1], 10) : 0, }, }; @@ -77,7 +77,7 @@ export class SafariAdapter implements ResourceAdapter { return { templateId: "safari.close_tab", parameters: { - tabIndex: parseInt(parts[0], 10) || 1, + tabIndex: parseInt(parts[0] ?? "1", 10) || 1, windowId: parts[1] ? parseInt(parts[1], 10) : 0, }, }; diff --git a/packages/mcp-server/test/integration/e2e-apps.test.ts b/packages/mcp-server/test/integration/e2e-apps.test.ts new file mode 100644 index 0000000..2397a76 --- /dev/null +++ b/packages/mcp-server/test/integration/e2e-apps.test.ts @@ -0,0 +1,267 @@ +/** + * End-to-end tests for all 10 Apple apps via the MCP server. + * + * These tests spawn the real MCP server and communicate via JSON-RPC over + * stdio, exercising the full pipeline: server → adapter → Swift executor → macOS app. + * + * Requirements: + * - macOS with automation permissions granted for the test runner + * - Apps may need at least some data (containers) to return results + * + * Scope: + * - Readonly operations (list_containers, list, search) with real execution + * - Create/action operations with dryRun=true (pipeline validation only) + * - Error paths (invalid app, wrong mode) + * - NO destructive operations (update, delete, run_script) + */ +import { describe, it, before, after } from "node:test"; +import { strict as assert } from "node:assert"; +import { spawn, ChildProcess } from "node:child_process"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const SERVER_PATH = resolve(__dirname, "../../src/index.ts"); + +let child: ChildProcess; +let nextId = 100; + +function sendMessage(proc: ChildProcess, message: object): void { + proc.stdin!.write(JSON.stringify(message) + "\n"); +} + +function readResponseById(proc: ChildProcess, id: number, timeoutMs = 15000): Promise> { + return new Promise((resolve, reject) => { + let buffer = ""; + const handler = (chunk: Buffer) => { + buffer += chunk.toString(); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (!line.trim()) continue; + try { + const msg = JSON.parse(line) as Record; + if (msg["id"] === id) { + proc.stdout!.off("data", handler); + resolve(msg); + return; + } + } catch { + // skip + } + } + }; + proc.stdout!.on("data", handler); + setTimeout(() => reject(new Error(`Timeout waiting for response id=${id}`)), timeoutMs); + }); +} + +/** Call a tool and return the parsed first text content. */ +async function callTool(name: string, args: Record = {}): Promise<{ + raw: Record; + text: string; + parsed: unknown; + isError: boolean; +}> { + const id = nextId++; + const promise = readResponseById(child, id); + sendMessage(child, { + jsonrpc: "2.0", + id, + method: "tools/call", + params: { name, arguments: args }, + }); + const response = await promise; + const result = response["result"] as Record; + const content = result["content"] as Array>; + const text = (content[0]?.["text"] as string) ?? ""; + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + parsed = text; + } + return { raw: response, text, parsed, isError: !!result["isError"] }; +} + +/** Initialize the server and complete the handshake. */ +async function initServer(): Promise { + const id = nextId++; + const promise = readResponseById(child, id); + sendMessage(child, { + jsonrpc: "2.0", + id, + method: "initialize", + params: { + protocolVersion: "2024-11-05", + capabilities: {}, + clientInfo: { name: "e2e-test", version: "1.0" }, + }, + }); + await promise; + sendMessage(child, { jsonrpc: "2.0", method: "notifications/initialized" }); + await new Promise((r) => setTimeout(r, 300)); +} + +const ALL_APPS = [ + "notes", "calendar", "reminders", "mail", "contacts", + "messages", "photos", "music", "finder", "safari", +]; + +// ─── Test suite ─────────────────────────────────────────────────────────────── + +describe("E2E app tests", () => { + before(async () => { + const env = { ...process.env }; + delete env["APPLESCRIPT_MCP_CONFIG"]; + child = spawn("npx", ["tsx", SERVER_PATH], { + stdio: ["pipe", "pipe", "pipe"], + env, + }); + await initServer(); + }); + + after(() => { + child.kill("SIGTERM"); + }); + + // ── System tools ────────────────────────────────────────────────────────── + + describe("system tools", () => { + it("ping returns version and app list", async () => { + const { parsed } = await callTool("applescript.ping"); + const p = parsed as Record; + assert.equal(p.ok, true); + assert.ok(typeof p.version === "string"); + assert.ok(Array.isArray(p.apps)); + assert.equal((p.apps as string[]).length, 10); + }); + + it("get_mode returns readonly by default", async () => { + const { parsed } = await callTool("applescript.get_mode"); + const p = parsed as Record; + assert.equal(p.mode, "readonly"); + }); + }); + + // ── Readonly per-app ────────────────────────────────────────────────────── + + describe("readonly operations", () => { + for (const app of ALL_APPS) { + describe(app, () => { + it(`${app}: list_containers`, async () => { + const { parsed, isError } = await callTool("app.list_containers", { app }); + // Some apps may error if not running or no permission — that's useful signal too + if (!isError) { + assert.ok(Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`); + } + }); + + it(`${app}: list (limit 3)`, async () => { + const { parsed, isError } = await callTool("app.list", { app, limit: 3 }); + if (!isError) { + assert.ok(Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`); + } + }); + + it(`${app}: search`, async () => { + const { parsed, isError } = await callTool("app.search", { app, query: "test", limit: 3 }); + if (!isError) { + assert.ok(Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`); + } + }); + }); + } + }); + + // ── Create mode with dryRun ─────────────────────────────────────────────── + + describe("create mode (dryRun)", () => { + it("switch to create mode", async () => { + const { parsed } = await callTool("applescript.set_mode", { mode: "create" }); + const p = parsed as Record; + assert.equal(p.newMode, "create"); + }); + + const createTests: Array<{ app: string; properties: Record }> = [ + { app: "notes", properties: { title: "E2E Test Note", body: "test body" } }, + { app: "calendar", properties: { title: "E2E Test Event", startDate: "2026-03-01T10:00:00", endDate: "2026-03-01T11:00:00" } }, + { app: "reminders", properties: { name: "E2E Test Reminder" } }, + { app: "mail", properties: { to: "test@example.com", subject: "E2E test", body: "test" } }, + { app: "contacts", properties: { firstName: "E2E", lastName: "Test" } }, + { app: "safari", properties: { url: "https://example.com" } }, + ]; + + for (const { app, properties } of createTests) { + it(`${app}: create (dryRun)`, async () => { + const { text, isError } = await callTool("app.create", { app, properties, dryRun: true }); + // dryRun should return the generated script, not an error + assert.ok(text.length > 0, "Expected non-empty response"); + // If the adapter supports create and dryRun worked, text should contain script + if (!isError) { + assert.ok(typeof text === "string"); + } + }); + } + + const actionTests: Array<{ app: string; action: string; parameters?: Record }> = [ + { app: "notes", action: "show" }, + { app: "calendar", action: "show" }, + { app: "reminders", action: "show" }, + { app: "music", action: "now_playing" }, + ]; + + for (const { app, action, parameters } of actionTests) { + it(`${app}: action "${action}" (dryRun)`, async () => { + const { text } = await callTool("app.action", { app, action, parameters: parameters ?? {}, dryRun: true }); + assert.ok(text.length > 0, "Expected non-empty response"); + }); + } + }); + + // ── Error paths ─────────────────────────────────────────────────────────── + + describe("error paths", () => { + it("rejects invalid app name", async () => { + const id = nextId++; + const promise = readResponseById(child, id); + sendMessage(child, { + jsonrpc: "2.0", + id, + method: "tools/call", + params: { name: "app.list_containers", arguments: { app: "nonexistent" } }, + }); + const response = await promise; + // Should return an error (either in result.isError or as JSON-RPC error) + const error = response["error"] as Record | undefined; + const result = response["result"] as Record | undefined; + assert.ok(error || result?.["isError"], + "Expected error for invalid app name"); + }); + + it("app.create rejected in readonly mode", async () => { + // Switch back to readonly + await callTool("applescript.set_mode", { mode: "readonly" }); + await new Promise((r) => setTimeout(r, 300)); + + // Try to call app.create — should fail (tool not found / disabled) + const id = nextId++; + const promise = readResponseById(child, id); + sendMessage(child, { + jsonrpc: "2.0", + id, + method: "tools/call", + params: { name: "app.create", arguments: { app: "notes", properties: { title: "should fail" } } }, + }); + const response = await promise; + const error = response["error"] as Record | undefined; + const result = response["result"] as Record | undefined; + // Disabled tools may return a JSON-RPC error or a result with isError + assert.ok(error || result?.["isError"], + "Expected error when calling create in readonly mode"); + }); + }); +}); diff --git a/packages/mcp-server/test/integration/server.test.ts b/packages/mcp-server/test/integration/server.test.ts index cfd717f..80a7f03 100644 --- a/packages/mcp-server/test/integration/server.test.ts +++ b/packages/mcp-server/test/integration/server.test.ts @@ -121,13 +121,16 @@ describe("MCP server integration", () => { const tools = result["tools"] as Array>; const toolNames = tools.map((t) => t["name"]); - // Default mode is readonly — only 4 tools visible - assert.equal(tools.length, 4, `Expected 4 tools in readonly mode, got ${tools.length}: ${toolNames.join(", ")}`); + // Default mode is readonly — only 7 tools visible + assert.equal(tools.length, 7, `Expected 7 tools in readonly mode, got ${tools.length}: ${toolNames.join(", ")}`); assert.ok(toolNames.includes("applescript.ping")); - assert.ok(toolNames.includes("applescript.list_apps")); assert.ok(toolNames.includes("applescript.get_mode")); assert.ok(toolNames.includes("applescript.set_mode")); - assert.ok(!toolNames.includes("notes.create_note"), "notes.create_note should not be visible in readonly"); + assert.ok(toolNames.includes("app.list_containers")); + assert.ok(toolNames.includes("app.list")); + assert.ok(toolNames.includes("app.get")); + assert.ok(toolNames.includes("app.search")); + assert.ok(!toolNames.includes("app.create"), "app.create should not be visible in readonly"); }); it("should handle ping tool call", async () => { @@ -149,7 +152,7 @@ describe("MCP server integration", () => { const text = content[0]!["text"] as string; const parsed = JSON.parse(text); assert.equal(parsed.ok, true); - assert.equal(parsed.version, "0.1.0"); + assert.equal(parsed.version, "0.2.0"); }); it("should show all tools after switching to create mode", async () => { @@ -188,9 +191,9 @@ describe("MCP server integration", () => { const toolNames = tools.map((t) => t["name"]); // Should now include create-level tools - assert.ok(toolNames.includes("notes.create_note"), "notes.create_note should be visible in create mode"); - assert.ok(toolNames.includes("calendar.create_event"), "calendar.create_event should be visible in create mode"); - assert.ok(toolNames.includes("mail.compose_draft"), "mail.compose_draft should be visible in create mode"); + assert.ok(toolNames.includes("app.create"), "app.create should be visible in create mode"); + assert.ok(toolNames.includes("app.action"), "app.action should be visible in create mode"); + assert.ok(toolNames.includes("applescript.run_template"), "applescript.run_template should be visible in create mode"); // But not full-mode tools assert.ok(!toolNames.includes("applescript.run_script"), "run_script should not be visible in create mode"); }); From 8b5c75365c93baf8dc2023e4e37ece58ec2ab09e Mon Sep 17 00:00:00 2001 From: frouaix Date: Sat, 21 Feb 2026 17:39:02 -0800 Subject: [PATCH 02/10] Fix CI: update pnpm to v10 to match lockfile v9 The lockfile was generated with pnpm 10 (lockfileVersion 9.0) but CI was pinned to pnpm 8, causing 'not compatible lockfile' errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f7cca..3a89ec5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,9 @@ jobs: node-version: '20' - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: - version: 8 + version: 10 - name: Get pnpm store directory id: pnpm-cache From 0b21fccbf000a5ec5af521f6fe6f27508c8e1a8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:40:31 +0000 Subject: [PATCH 03/10] Initial plan From c2ed66ec8b14e9622915b9058ee3038a4b264377 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:40:50 +0000 Subject: [PATCH 04/10] Initial plan From 570c0db13d6eedc3930a0a4ed57ccb37beb8caf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:41:08 +0000 Subject: [PATCH 05/10] Initial plan From e592de29844dcca92f0ec1cba9fa1adf23b8bae5 Mon Sep 17 00:00:00 2001 From: Francois Rouaix <876178+frouaix@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:41:42 -0800 Subject: [PATCH 06/10] Update packages/mcp-server/test/integration/e2e-apps.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../test/integration/e2e-apps.test.ts | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/mcp-server/test/integration/e2e-apps.test.ts b/packages/mcp-server/test/integration/e2e-apps.test.ts index 2397a76..1dcc3f1 100644 --- a/packages/mcp-server/test/integration/e2e-apps.test.ts +++ b/packages/mcp-server/test/integration/e2e-apps.test.ts @@ -149,28 +149,63 @@ describe("E2E app tests", () => { describe("readonly operations", () => { for (const app of ALL_APPS) { describe(app, () => { + let hadSuccess = false; + + after(() => { + // Ensure that at least one readonly operation succeeded for this app. + assert.ok( + hadSuccess, + `Expected at least one successful readonly operation for app ${app}`, + ); + }); + it(`${app}: list_containers`, async () => { const { parsed, isError } = await callTool("app.list_containers", { app }); // Some apps may error if not running or no permission — that's useful signal too if (!isError) { - assert.ok(Array.isArray(parsed) || typeof parsed === "object", - `Expected array or object, got ${typeof parsed}`); + assert.ok( + Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`, + ); + hadSuccess = true; + } else { + // Validate error shape so error paths are also covered. + assert.ok( + parsed !== null && typeof parsed === "object", + `Expected structured error object, got ${typeof parsed}`, + ); } }); it(`${app}: list (limit 3)`, async () => { const { parsed, isError } = await callTool("app.list", { app, limit: 3 }); if (!isError) { - assert.ok(Array.isArray(parsed) || typeof parsed === "object", - `Expected array or object, got ${typeof parsed}`); + assert.ok( + Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`, + ); + hadSuccess = true; + } else { + assert.ok( + parsed !== null && typeof parsed === "object", + `Expected structured error object, got ${typeof parsed}`, + ); } }); it(`${app}: search`, async () => { const { parsed, isError } = await callTool("app.search", { app, query: "test", limit: 3 }); if (!isError) { - assert.ok(Array.isArray(parsed) || typeof parsed === "object", - `Expected array or object, got ${typeof parsed}`); + assert.ok( + Array.isArray(parsed) || typeof parsed === "object", + `Expected array or object, got ${typeof parsed}`, + ); + hadSuccess = true; + } else { + assert.ok( + parsed !== null && typeof parsed === "object", + `Expected structured error object, got ${typeof parsed}`, + ); } }); }); From cb28ba6c13fe8bf50394ed6680f7161ce4c79a35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:42:45 +0000 Subject: [PATCH 07/10] Fix readResponseById: clear timeout on success, remove handler on timeout, unref timer Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- packages/mcp-server/test/integration/e2e-apps.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/mcp-server/test/integration/e2e-apps.test.ts b/packages/mcp-server/test/integration/e2e-apps.test.ts index 2397a76..b185da1 100644 --- a/packages/mcp-server/test/integration/e2e-apps.test.ts +++ b/packages/mcp-server/test/integration/e2e-apps.test.ts @@ -43,6 +43,7 @@ function readResponseById(proc: ChildProcess, id: number, timeoutMs = 15000): Pr const msg = JSON.parse(line) as Record; if (msg["id"] === id) { proc.stdout!.off("data", handler); + clearTimeout(timer); resolve(msg); return; } @@ -52,7 +53,11 @@ function readResponseById(proc: ChildProcess, id: number, timeoutMs = 15000): Pr } }; proc.stdout!.on("data", handler); - setTimeout(() => reject(new Error(`Timeout waiting for response id=${id}`)), timeoutMs); + const timer = setTimeout(() => { + proc.stdout!.off("data", handler); + reject(new Error(`Timeout waiting for response id=${id}`)); + }, timeoutMs); + timer.unref(); }); } From 7d1d302d86ff78fdbe12108a249774171e0f700b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:43:19 +0000 Subject: [PATCH 08/10] Handle JSON-RPC error responses in callTool() helper Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- packages/mcp-server/test/integration/e2e-apps.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/mcp-server/test/integration/e2e-apps.test.ts b/packages/mcp-server/test/integration/e2e-apps.test.ts index 2397a76..75f910a 100644 --- a/packages/mcp-server/test/integration/e2e-apps.test.ts +++ b/packages/mcp-server/test/integration/e2e-apps.test.ts @@ -72,6 +72,12 @@ async function callTool(name: string, args: Record = {}): Promi params: { name, arguments: args }, }); const response = await promise; + if (response["error"]) { + const err = response["error"] as Record; + const code = err["code"] != null ? ` (code: ${err["code"]})` : ""; + const text = String(err["message"] ?? JSON.stringify(err)) + code; + return { raw: response, text, parsed: err, isError: true }; + } const result = response["result"] as Record; const content = result["content"] as Array>; const text = (content[0]?.["text"] as string) ?? ""; From b0dbaa0d3753d7cf814126c928ddf55f2a18478c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:43:46 +0000 Subject: [PATCH 09/10] Gate E2E tests behind APPLESCRIPT_E2E flag and skip in CI Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- packages/mcp-server/test/integration/e2e-apps.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/mcp-server/test/integration/e2e-apps.test.ts b/packages/mcp-server/test/integration/e2e-apps.test.ts index 2397a76..7f67436 100644 --- a/packages/mcp-server/test/integration/e2e-apps.test.ts +++ b/packages/mcp-server/test/integration/e2e-apps.test.ts @@ -109,8 +109,16 @@ const ALL_APPS = [ ]; // ─── Test suite ─────────────────────────────────────────────────────────────── +// These tests require macOS with Automation permissions and real apps running. +// Gate behind APPLESCRIPT_E2E=1 and skip automatically in CI. -describe("E2E app tests", () => { +const skipReason: string | undefined = process.env.CI + ? "Skipped in CI environment (set APPLESCRIPT_E2E=1 and unset CI to run)" + : !process.env.APPLESCRIPT_E2E + ? "Set APPLESCRIPT_E2E=1 to run E2E tests locally" + : undefined; + +describe("E2E app tests", { skip: skipReason }, () => { before(async () => { const env = { ...process.env }; delete env["APPLESCRIPT_MCP_CONFIG"]; From bae1e9c3decbfbd3560197868eb8ff843b839a0d Mon Sep 17 00:00:00 2001 From: frouaix Date: Sat, 21 Feb 2026 18:27:30 -0800 Subject: [PATCH 10/10] Fix CI lint: add missing ESLint deps and configure Node globals - Add @eslint/js and globals to devDependencies - Add Node.js globals to eslint config (process, Buffer, setTimeout, etc.) - Ignore dist/ directory in eslint - Remove unused project option from parser (test files not in tsconfig) - Disable base no-unused-vars (handled by @typescript-eslint version) - Fix unused beforeEach import in server.test.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/mcp-server/eslint.config.js | 7 ++++++- packages/mcp-server/package.json | 2 ++ packages/mcp-server/test/unit/server.test.ts | 2 +- pnpm-lock.yaml | 12 ++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/eslint.config.js b/packages/mcp-server/eslint.config.js index ac36618..ed5044c 100644 --- a/packages/mcp-server/eslint.config.js +++ b/packages/mcp-server/eslint.config.js @@ -1,17 +1,21 @@ import eslint from "@eslint/js"; import tseslint from "@typescript-eslint/eslint-plugin"; import tsparser from "@typescript-eslint/parser"; +import globals from "globals"; export default [ + { ignores: ["dist/"] }, eslint.configs.recommended, { files: ["src/**/*.ts", "test/**/*.ts"], languageOptions: { parser: tsparser, + globals: { + ...globals.node, + }, parserOptions: { ecmaVersion: "latest", sourceType: "module", - project: "./tsconfig.json", }, }, plugins: { @@ -27,6 +31,7 @@ export default [ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-non-null-assertion": "warn", "no-console": "off", + "no-unused-vars": "off", }, }, ]; diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 1aa574a..ba664b3 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -25,11 +25,13 @@ "zod": "^3.24.1" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@types/node": "^20.17.10", "@typescript-eslint/eslint-plugin": "^8.19.1", "@typescript-eslint/parser": "^8.19.1", "esbuild": "^0.27.3", "eslint": "^9.17.0", + "globals": "^17.3.0", "prettier": "^3.4.2", "tsx": "^4.19.2", "typescript": "^5.7.2" diff --git a/packages/mcp-server/test/unit/server.test.ts b/packages/mcp-server/test/unit/server.test.ts index 196f489..3ff6890 100644 --- a/packages/mcp-server/test/unit/server.test.ts +++ b/packages/mcp-server/test/unit/server.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach } from "node:test"; +import { describe, it } from "node:test"; import { strict as assert } from "node:assert"; import { createServer, ServerDeps } from "../../src/server.js"; import { ConfigSchema } from "../../src/config/schema.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f01e21..1de71c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: specifier: ^3.24.1 version: 3.25.76 devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@types/node': specifier: ^20.17.10 version: 20.19.33 @@ -32,6 +35,9 @@ importers: eslint: specifier: ^9.17.0 version: 9.39.2 + globals: + specifier: ^17.3.0 + version: 17.3.0 prettier: specifier: ^3.4.2 version: 3.8.1 @@ -634,6 +640,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@17.3.0: + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1616,6 +1626,8 @@ snapshots: globals@14.0.0: {} + globals@17.3.0: {} + gopd@1.2.0: {} has-flag@4.0.0: {}