feat(skill-loader): support mcp.json file for AmpCode compatibility

- Added loadMcpJsonFromDir() to load MCP config from skill directory's mcp.json
- Supports AmpCode format (mcpServers wrapper) and direct format
- mcp.json takes priority over YAML frontmatter when both exist
- Added 3 tests covering mcpServers format, priority, and direct format

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-01-01 23:01:06 +09:00
parent 4039722160
commit ff760e5865
2 changed files with 146 additions and 3 deletions
+32 -2
View File
@@ -10,7 +10,7 @@ import type { CommandDefinition } from "../claude-code-command-loader/types"
import type { SkillScope, SkillMetadata, LoadedSkill } from "./types"
import type { SkillMcpConfig } from "../skill-mcp-manager/types"
function parseSkillMcpConfig(content: string): SkillMcpConfig | undefined {
function parseSkillMcpConfigFromFrontmatter(content: string): SkillMcpConfig | undefined {
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
if (!frontmatterMatch) return undefined
@@ -25,6 +25,34 @@ function parseSkillMcpConfig(content: string): SkillMcpConfig | undefined {
return undefined
}
function loadMcpJsonFromDir(skillDir: string): SkillMcpConfig | undefined {
const mcpJsonPath = join(skillDir, "mcp.json")
if (!existsSync(mcpJsonPath)) return undefined
try {
const content = readFileSync(mcpJsonPath, "utf-8")
const parsed = JSON.parse(content) as Record<string, unknown>
// AmpCode format: { "mcpServers": { "name": { ... } } }
if (parsed && typeof parsed === "object" && "mcpServers" in parsed && parsed.mcpServers) {
return parsed.mcpServers as SkillMcpConfig
}
// Also support direct format: { "name": { command: ..., args: ... } }
if (parsed && typeof parsed === "object" && !("mcpServers" in parsed)) {
const hasCommandField = Object.values(parsed).some(
(v) => v && typeof v === "object" && "command" in (v as Record<string, unknown>)
)
if (hasCommandField) {
return parsed as SkillMcpConfig
}
}
} catch {
return undefined
}
return undefined
}
function parseAllowedTools(allowedTools: string | undefined): string[] | undefined {
if (!allowedTools) return undefined
return allowedTools.split(/\s+/).filter(Boolean)
@@ -39,7 +67,9 @@ function loadSkillFromPath(
try {
const content = readFileSync(skillPath, "utf-8")
const { data, body } = parseFrontmatter<SkillMetadata>(content)
const mcpConfig = parseSkillMcpConfig(content)
const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content)
const mcpJsonMcp = loadMcpJsonFromDir(resolvedPath)
const mcpConfig = mcpJsonMcp || frontmatterMcp
const skillName = data.name || defaultName
const originalDescription = data.description || ""