Merge pull request #2193 from acamq/fix/load-mcp-hint
fix(skill_mcp): improve hint for builtin MCP names
This commit is contained in:
45
src/tools/skill-mcp/builtin-mcp-hint.test.ts
Normal file
45
src/tools/skill-mcp/builtin-mcp-hint.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
|
||||
import { SkillMcpManager } from "../../features/skill-mcp-manager"
|
||||
import { createSkillMcpTool } from "./tools"
|
||||
|
||||
const mockContext = {
|
||||
sessionID: "test-session",
|
||||
messageID: "msg-1",
|
||||
agent: "test-agent",
|
||||
directory: "/test",
|
||||
worktree: "/test",
|
||||
abort: new AbortController().signal,
|
||||
metadata: () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
|
||||
describe("skill_mcp builtin MCP hint", () => {
|
||||
it("returns builtin hint for context7", async () => {
|
||||
const tool = createSkillMcpTool({
|
||||
manager: new SkillMcpManager(),
|
||||
getLoadedSkills: () => [],
|
||||
getSessionID: () => "session",
|
||||
})
|
||||
|
||||
await expect(
|
||||
tool.execute({ mcp_name: "context7", tool_name: "resolve-library-id" }, mockContext),
|
||||
).rejects.toThrow(/builtin MCP/)
|
||||
|
||||
await expect(
|
||||
tool.execute({ mcp_name: "context7", tool_name: "resolve-library-id" }, mockContext),
|
||||
).rejects.toThrow(/context7_resolve-library-id/)
|
||||
})
|
||||
|
||||
it("keeps skill-loading hint for unknown MCP names", async () => {
|
||||
const tool = createSkillMcpTool({
|
||||
manager: new SkillMcpManager(),
|
||||
getLoadedSkills: () => [],
|
||||
getSessionID: () => "session",
|
||||
})
|
||||
|
||||
await expect(
|
||||
tool.execute({ mcp_name: "unknown-mcp", tool_name: "x" }, mockContext),
|
||||
).rejects.toThrow(/Load the skill first/)
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,9 @@
|
||||
export const SKILL_MCP_TOOL_NAME = "skill_mcp"
|
||||
|
||||
export const SKILL_MCP_DESCRIPTION = `Invoke MCP server operations from skill-embedded MCPs. Requires mcp_name plus exactly one of: tool_name, resource_name, or prompt_name.`
|
||||
|
||||
export const BUILTIN_MCP_TOOL_HINTS: Record<string, string[]> = {
|
||||
context7: ["context7_resolve-library-id", "context7_query-docs"],
|
||||
websearch: ["websearch_web_search_exa"],
|
||||
grep_app: ["grep_app_searchGitHub"],
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
|
||||
import { SKILL_MCP_DESCRIPTION } from "./constants"
|
||||
import { BUILTIN_MCP_TOOL_HINTS, SKILL_MCP_DESCRIPTION } from "./constants"
|
||||
import type { SkillMcpArgs } from "./types"
|
||||
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
|
||||
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
||||
@@ -71,6 +71,16 @@ function formatAvailableMcps(skills: LoadedSkill[]): string {
|
||||
return mcps.length > 0 ? mcps.join("\n") : " (none found)"
|
||||
}
|
||||
|
||||
function formatBuiltinMcpHint(mcpName: string): string | null {
|
||||
const nativeTools = BUILTIN_MCP_TOOL_HINTS[mcpName]
|
||||
if (!nativeTools) return null
|
||||
return (
|
||||
`"${mcpName}" is a builtin MCP, not a skill MCP.\n` +
|
||||
`Use the native tools directly:\n` +
|
||||
nativeTools.map((toolName) => ` - ${toolName}`).join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
function parseArguments(argsJson: string | Record<string, unknown> | undefined): Record<string, unknown> {
|
||||
if (!argsJson) return {}
|
||||
if (typeof argsJson === "object" && argsJson !== null) {
|
||||
@@ -132,6 +142,11 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
|
||||
const found = findMcpServer(args.mcp_name, skills)
|
||||
|
||||
if (!found) {
|
||||
const builtinHint = formatBuiltinMcpHint(args.mcp_name)
|
||||
if (builtinHint) {
|
||||
throw new Error(builtinHint)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`MCP server "${args.mcp_name}" not found.\n\n` +
|
||||
`Available MCP servers in loaded skills:\n` +
|
||||
|
||||
Reference in New Issue
Block a user