From 51bbf8dd56af5bf0a84f34dc2b7d6d6433ece30a Mon Sep 17 00:00:00 2001 From: asemabdallah Date: Sun, 11 Oct 2026 02:28:19 +0200 Subject: [PATCH] fix(mcp): load skills on Windows --- apps/mcp/src/skills.ts | 7 +++++-- apps/mcp/tests/skills.test.ts | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/mcp/src/skills.ts b/apps/mcp/src/skills.ts index e578d8a..6be709d 100644 --- a/apps/mcp/src/skills.ts +++ b/apps/mcp/src/skills.ts @@ -14,8 +14,11 @@ export function listSkills(): string[] { } export function loadSkill(name: string): string { - const resolved = resolve(SKILLS_DIR, `${name}.txt`); - if (!resolved.startsWith(resolve(SKILLS_DIR) + "/")) { + const root = resolve(SKILLS_DIR); + const resolved = resolve(root, `${name}.txt`); + // Compare directories rather than a "/" prefix so the check also holds + // for the backslash paths that resolve() returns on Windows. + if (dirname(resolved) !== root) { throw new Error(`Invalid skill name: ${name}`); } return readFileSync(resolved, "utf-8"); diff --git a/apps/mcp/tests/skills.test.ts b/apps/mcp/tests/skills.test.ts index 57a35e1..af7343b 100644 --- a/apps/mcp/tests/skills.test.ts +++ b/apps/mcp/tests/skills.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { listSkills } from "../src/skills"; +import { listSkills, loadSkill } from "../src/skills"; describe("listSkills", () => { it("returns an array containing the bundled skills", () => { @@ -8,3 +8,16 @@ describe("listSkills", () => { expect(skills).toContain("master-agent-playbook"); }); }); + +describe("loadSkill", () => { + it("loads a bundled skill by name", () => { + expect(loadSkill("master-agent-playbook").length).toBeGreaterThan(0); + }); + + it.each(["../package", "nested/skill", "/etc/passwd"])( + "rejects names outside the skills directory: %s", + (name) => { + expect(() => loadSkill(name)).toThrow("Invalid skill name"); + } + ); +});