diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 0797996..bafe370 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -12,10 +12,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "context7", - "grep_app" - ] + "minLength": 1 } }, "disabled_agents": { diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts new file mode 100644 index 0000000..10f62cb --- /dev/null +++ b/src/config/schema.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, test } from "bun:test" +import { OhMyOpenCodeConfigSchema } from "./schema" + +describe("disabled_mcps schema", () => { + test("should accept built-in MCP names", () => { + //#given + const config = { + disabled_mcps: ["context7", "grep_app"], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toEqual(["context7", "grep_app"]) + } + }) + + test("should accept custom MCP names", () => { + //#given + const config = { + disabled_mcps: ["playwright", "sqlite", "custom-mcp"], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toEqual(["playwright", "sqlite", "custom-mcp"]) + } + }) + + test("should accept mixed built-in and custom names", () => { + //#given + const config = { + disabled_mcps: ["context7", "playwright", "custom-server"], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toEqual(["context7", "playwright", "custom-server"]) + } + }) + + test("should accept empty array", () => { + //#given + const config = { + disabled_mcps: [], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toEqual([]) + } + }) + + test("should reject non-string values", () => { + //#given + const config = { + disabled_mcps: [123, true, null], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(false) + }) + + test("should accept undefined (optional field)", () => { + //#given + const config = {} + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toBeUndefined() + } + }) + + test("should reject empty strings", () => { + //#given + const config = { + disabled_mcps: [""], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(false) + }) + + test("should accept MCP names with various naming patterns", () => { + //#given + const config = { + disabled_mcps: [ + "my-custom-mcp", + "my_custom_mcp", + "myCustomMcp", + "my.custom.mcp", + "my-custom-mcp-123", + ], + } + + //#when + const result = OhMyOpenCodeConfigSchema.safeParse(config) + + //#then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.disabled_mcps).toEqual([ + "my-custom-mcp", + "my_custom_mcp", + "myCustomMcp", + "my.custom.mcp", + "my-custom-mcp-123", + ]) + } + }) +}) diff --git a/src/config/schema.ts b/src/config/schema.ts index e1e3908..6f2097c 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { McpNameSchema } from "../mcp/types" +import { AnyMcpNameSchema, McpNameSchema } from "../mcp/types" const PermissionValue = z.enum(["ask", "allow", "deny"]) @@ -234,7 +234,7 @@ export const RalphLoopConfigSchema = z.object({ export const OhMyOpenCodeConfigSchema = z.object({ $schema: z.string().optional(), - disabled_mcps: z.array(McpNameSchema).optional(), + disabled_mcps: z.array(AnyMcpNameSchema).optional(), disabled_agents: z.array(BuiltinAgentNameSchema).optional(), disabled_skills: z.array(BuiltinSkillNameSchema).optional(), disabled_hooks: z.array(HookNameSchema).optional(), @@ -265,4 +265,4 @@ export type SkillsConfig = z.infer export type SkillDefinition = z.infer export type RalphLoopConfig = z.infer -export { McpNameSchema, type McpName } from "../mcp/types" +export { AnyMcpNameSchema, type AnyMcpName, McpNameSchema, type McpName } from "../mcp/types" diff --git a/src/mcp/index.test.ts b/src/mcp/index.test.ts new file mode 100644 index 0000000..287ee0a --- /dev/null +++ b/src/mcp/index.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "bun:test" +import { createBuiltinMcps } from "./index" + +describe("createBuiltinMcps", () => { + test("should return all MCPs when disabled_mcps is empty", () => { + //#given + const disabledMcps: string[] = [] + + //#when + const result = createBuiltinMcps(disabledMcps) + + //#then + expect(result).toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(2) + }) + + test("should filter out disabled built-in MCPs", () => { + //#given + const disabledMcps = ["context7"] + + //#when + const result = createBuiltinMcps(disabledMcps) + + //#then + expect(result).not.toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(1) + }) + + test("should filter out both built-in MCPs when both disabled", () => { + //#given + const disabledMcps = ["context7", "grep_app"] + + //#when + const result = createBuiltinMcps(disabledMcps) + + //#then + expect(result).not.toHaveProperty("context7") + expect(result).not.toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(0) + }) + + test("should ignore custom MCP names in disabled_mcps", () => { + //#given + const disabledMcps = ["context7", "playwright", "custom"] + + //#when + const result = createBuiltinMcps(disabledMcps) + + //#then + expect(result).not.toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(1) + }) + + test("should handle empty disabled_mcps by default", () => { + //#given + //#when + const result = createBuiltinMcps() + + //#then + expect(result).toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(2) + }) + + test("should only filter built-in MCPs, ignoring unknown names", () => { + //#given + const disabledMcps = ["playwright", "sqlite", "unknown-mcp"] + + //#when + const result = createBuiltinMcps(disabledMcps) + + //#then + expect(result).toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(Object.keys(result)).toHaveLength(2) + }) +}) diff --git a/src/mcp/index.ts b/src/mcp/index.ts index c0fefd6..2449e18 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -9,11 +9,11 @@ const allBuiltinMcps: Record = {} for (const [name, config] of Object.entries(allBuiltinMcps)) { - if (!disabledMcps.includes(name as McpName)) { + if (!disabledMcps.includes(name)) { mcps[name] = config } } diff --git a/src/mcp/types.ts b/src/mcp/types.ts index cbfc448..61d9672 100644 --- a/src/mcp/types.ts +++ b/src/mcp/types.ts @@ -3,3 +3,7 @@ import { z } from "zod" export const McpNameSchema = z.enum(["context7", "grep_app"]) export type McpName = z.infer + +export const AnyMcpNameSchema = z.string().min(1) + +export type AnyMcpName = z.infer