Merge pull request #2193 from acamq/fix/load-mcp-hint
fix(skill_mcp): improve hint for builtin MCP names
This commit is contained in:
commit
7df2a57efb
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_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 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 { 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 { SkillMcpArgs } from "./types"
|
||||||
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
|
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
|
||||||
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
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)"
|
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> {
|
function parseArguments(argsJson: string | Record<string, unknown> | undefined): Record<string, unknown> {
|
||||||
if (!argsJson) return {}
|
if (!argsJson) return {}
|
||||||
if (typeof argsJson === "object" && argsJson !== null) {
|
if (typeof argsJson === "object" && argsJson !== null) {
|
||||||
@ -132,6 +142,11 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
|
|||||||
const found = findMcpServer(args.mcp_name, skills)
|
const found = findMcpServer(args.mcp_name, skills)
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
const builtinHint = formatBuiltinMcpHint(args.mcp_name)
|
||||||
|
if (builtinHint) {
|
||||||
|
throw new Error(builtinHint)
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`MCP server "${args.mcp_name}" not found.\n\n` +
|
`MCP server "${args.mcp_name}" not found.\n\n` +
|
||||||
`Available MCP servers in loaded skills:\n` +
|
`Available MCP servers in loaded skills:\n` +
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user