fix(kiro): parse .mcp.json wrapper key and support remote MCP servers (#259)
* fix(kiro): parse .mcp.json wrapper key and support remote MCP servers * refactor: extract unwrapMcpServers helper to deduplicate parser logic Address review feedback by extracting the mcpServers unwrap logic into a shared helper used by both loadMcpServers and loadMcpPaths.
This commit is contained in:
@@ -53,7 +53,7 @@ export function convertClaudeToKiro(
|
|||||||
convertCommandToSkill(command, usedSkillNames, agentNames),
|
convertCommandToSkill(command, usedSkillNames, agentNames),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert MCP servers (stdio only)
|
// Convert MCP servers (stdio and remote)
|
||||||
const mcpServers = convertMcpServers(plugin.mcpServers)
|
const mcpServers = convertMcpServers(plugin.mcpServers)
|
||||||
|
|
||||||
// Build steering files from CLAUDE.md
|
// Build steering files from CLAUDE.md
|
||||||
@@ -177,19 +177,20 @@ function convertMcpServers(
|
|||||||
|
|
||||||
const result: Record<string, KiroMcpServer> = {}
|
const result: Record<string, KiroMcpServer> = {}
|
||||||
for (const [name, server] of Object.entries(servers)) {
|
for (const [name, server] of Object.entries(servers)) {
|
||||||
if (!server.command) {
|
if (server.command) {
|
||||||
console.warn(
|
|
||||||
`Warning: MCP server "${name}" has no command (HTTP/SSE transport). Kiro only supports stdio. Skipping.`,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry: KiroMcpServer = { command: server.command }
|
const entry: KiroMcpServer = { command: server.command }
|
||||||
if (server.args && server.args.length > 0) entry.args = server.args
|
if (server.args && server.args.length > 0) entry.args = server.args
|
||||||
if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
|
if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
|
||||||
|
|
||||||
console.log(`MCP server "${name}" will execute: ${server.command}${server.args ? " " + server.args.join(" ") : ""}`)
|
|
||||||
result[name] = entry
|
result[name] = entry
|
||||||
|
} else if (server.url) {
|
||||||
|
const entry: KiroMcpServer = { url: server.url }
|
||||||
|
if (server.headers && Object.keys(server.headers).length > 0) entry.headers = server.headers
|
||||||
|
result[name] = entry
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`Warning: MCP server "${name}" has no command or url. Skipping.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,8 @@ async function loadMcpServers(
|
|||||||
|
|
||||||
const mcpPath = path.join(root, ".mcp.json")
|
const mcpPath = path.join(root, ".mcp.json")
|
||||||
if (await pathExists(mcpPath)) {
|
if (await pathExists(mcpPath)) {
|
||||||
return readJson<Record<string, ClaudeMcpServer>>(mcpPath)
|
const raw = await readJson<Record<string, unknown>>(mcpPath)
|
||||||
|
return unwrapMcpServers(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
@@ -232,12 +233,20 @@ async function loadMcpPaths(
|
|||||||
for (const entry of toPathList(value)) {
|
for (const entry of toPathList(value)) {
|
||||||
const resolved = resolveWithinRoot(root, entry, "mcpServers path")
|
const resolved = resolveWithinRoot(root, entry, "mcpServers path")
|
||||||
if (await pathExists(resolved)) {
|
if (await pathExists(resolved)) {
|
||||||
configs.push(await readJson<Record<string, ClaudeMcpServer>>(resolved))
|
const raw = await readJson<Record<string, unknown>>(resolved)
|
||||||
|
configs.push(unwrapMcpServers(raw))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return configs
|
return configs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unwrapMcpServers(raw: Record<string, unknown>): Record<string, ClaudeMcpServer> {
|
||||||
|
if (raw.mcpServers && typeof raw.mcpServers === "object") {
|
||||||
|
return raw.mcpServers as Record<string, ClaudeMcpServer>
|
||||||
|
}
|
||||||
|
return raw as Record<string, ClaudeMcpServer>
|
||||||
|
}
|
||||||
|
|
||||||
function mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {
|
function mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {
|
||||||
return configs.reduce((acc, config) => ({ ...acc, ...config }), {})
|
return configs.reduce((acc, config) => ({ ...acc, ...config }), {})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,11 +174,7 @@ describe("convertClaudeToKiro", () => {
|
|||||||
expect(bundle.mcpServers.local.args).toEqual(["hello"])
|
expect(bundle.mcpServers.local.args).toEqual(["hello"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("MCP HTTP servers skipped with warning", () => {
|
test("MCP HTTP servers converted with url", () => {
|
||||||
const warnings: string[] = []
|
|
||||||
const originalWarn = console.warn
|
|
||||||
console.warn = (msg: string) => warnings.push(msg)
|
|
||||||
|
|
||||||
const plugin: ClaudePlugin = {
|
const plugin: ClaudePlugin = {
|
||||||
...fixturePlugin,
|
...fixturePlugin,
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
@@ -189,11 +185,32 @@ describe("convertClaudeToKiro", () => {
|
|||||||
skills: [],
|
skills: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bundle = convertClaudeToKiro(plugin, defaultOptions)
|
||||||
|
|
||||||
|
expect(Object.keys(bundle.mcpServers)).toHaveLength(1)
|
||||||
|
expect(bundle.mcpServers.httpServer).toEqual({ url: "https://example.com/mcp" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MCP servers with no command or url skipped with warning", () => {
|
||||||
|
const warnings: string[] = []
|
||||||
|
const originalWarn = console.warn
|
||||||
|
console.warn = (msg: string) => warnings.push(msg)
|
||||||
|
|
||||||
|
const plugin: ClaudePlugin = {
|
||||||
|
...fixturePlugin,
|
||||||
|
mcpServers: {
|
||||||
|
broken: {} as any,
|
||||||
|
},
|
||||||
|
agents: [],
|
||||||
|
commands: [],
|
||||||
|
skills: [],
|
||||||
|
}
|
||||||
|
|
||||||
const bundle = convertClaudeToKiro(plugin, defaultOptions)
|
const bundle = convertClaudeToKiro(plugin, defaultOptions)
|
||||||
console.warn = originalWarn
|
console.warn = originalWarn
|
||||||
|
|
||||||
expect(Object.keys(bundle.mcpServers)).toHaveLength(0)
|
expect(Object.keys(bundle.mcpServers)).toHaveLength(0)
|
||||||
expect(warnings.some((w) => w.includes("no command") || w.includes("HTTP"))).toBe(true)
|
expect(warnings.some((w) => w.includes("no command or url"))).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("plugin with zero agents produces empty agents array", () => {
|
test("plugin with zero agents produces empty agents array", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user