feat(skill): display MCP server capabilities when skill with MCP is loaded
🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -5,6 +5,8 @@ import { TOOL_DESCRIPTION_NO_SKILLS, TOOL_DESCRIPTION_PREFIX } from "./constants
|
|||||||
import type { SkillArgs, SkillInfo, SkillLoadOptions } from "./types"
|
import type { SkillArgs, SkillInfo, SkillLoadOptions } from "./types"
|
||||||
import { discoverSkills, type LoadedSkill } from "../../features/opencode-skill-loader"
|
import { discoverSkills, type LoadedSkill } from "../../features/opencode-skill-loader"
|
||||||
import { parseFrontmatter } from "../../shared/frontmatter"
|
import { parseFrontmatter } from "../../shared/frontmatter"
|
||||||
|
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
|
||||||
|
import type { Tool, Resource, Prompt } from "@modelcontextprotocol/sdk/types.js"
|
||||||
|
|
||||||
function loadedSkillToInfo(skill: LoadedSkill): SkillInfo {
|
function loadedSkillToInfo(skill: LoadedSkill): SkillInfo {
|
||||||
return {
|
return {
|
||||||
@@ -49,6 +51,64 @@ function extractSkillBody(skill: LoadedSkill): string {
|
|||||||
return templateMatch ? templateMatch[1].trim() : skill.definition.template || ""
|
return templateMatch ? templateMatch[1].trim() : skill.definition.template || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function formatMcpCapabilities(
|
||||||
|
skill: LoadedSkill,
|
||||||
|
manager: SkillMcpManager,
|
||||||
|
sessionID: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (!skill.mcpConfig || Object.keys(skill.mcpConfig).length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const sections: string[] = ["", "## Available MCP Servers", ""]
|
||||||
|
|
||||||
|
for (const [serverName, config] of Object.entries(skill.mcpConfig)) {
|
||||||
|
const info: SkillMcpClientInfo = {
|
||||||
|
serverName,
|
||||||
|
skillName: skill.name,
|
||||||
|
sessionID,
|
||||||
|
}
|
||||||
|
const context: SkillMcpServerContext = {
|
||||||
|
config,
|
||||||
|
skillName: skill.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.push(`### ${serverName}`)
|
||||||
|
sections.push("")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [tools, resources, prompts] = await Promise.all([
|
||||||
|
manager.listTools(info, context).catch(() => []),
|
||||||
|
manager.listResources(info, context).catch(() => []),
|
||||||
|
manager.listPrompts(info, context).catch(() => []),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (tools.length > 0) {
|
||||||
|
sections.push(`**Tools**: ${tools.map((t: Tool) => t.name).join(", ")}`)
|
||||||
|
}
|
||||||
|
if (resources.length > 0) {
|
||||||
|
sections.push(`**Resources**: ${resources.map((r: Resource) => r.uri).join(", ")}`)
|
||||||
|
}
|
||||||
|
if (prompts.length > 0) {
|
||||||
|
sections.push(`**Prompts**: ${prompts.map((p: Prompt) => p.name).join(", ")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tools.length === 0 && resources.length === 0 && prompts.length === 0) {
|
||||||
|
sections.push("*No capabilities discovered*")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
|
sections.push(`*Failed to connect: ${errorMessage.split("\n")[0]}*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.push("")
|
||||||
|
sections.push(`Use \`skill_mcp\` tool with \`mcp_name="${serverName}"\` to invoke.`)
|
||||||
|
sections.push("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition {
|
export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition {
|
||||||
const skills = options.skills ?? discoverSkills({ includeClaudeCodePaths: !options.opencodeOnly })
|
const skills = options.skills ?? discoverSkills({ includeClaudeCodePaths: !options.opencodeOnly })
|
||||||
const skillInfos = skills.map(loadedSkillToInfo)
|
const skillInfos = skills.map(loadedSkillToInfo)
|
||||||
@@ -75,13 +135,26 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
|
|||||||
const body = extractSkillBody(skill)
|
const body = extractSkillBody(skill)
|
||||||
const dir = skill.path ? dirname(skill.path) : skill.resolvedPath || process.cwd()
|
const dir = skill.path ? dirname(skill.path) : skill.resolvedPath || process.cwd()
|
||||||
|
|
||||||
return [
|
const output = [
|
||||||
`## Skill: ${skill.name}`,
|
`## Skill: ${skill.name}`,
|
||||||
"",
|
"",
|
||||||
`**Base directory**: ${dir}`,
|
`**Base directory**: ${dir}`,
|
||||||
"",
|
"",
|
||||||
body,
|
body,
|
||||||
].join("\n")
|
]
|
||||||
|
|
||||||
|
if (options.mcpManager && options.getSessionID && skill.mcpConfig) {
|
||||||
|
const mcpInfo = await formatMcpCapabilities(
|
||||||
|
skill,
|
||||||
|
options.mcpManager,
|
||||||
|
options.getSessionID()
|
||||||
|
)
|
||||||
|
if (mcpInfo) {
|
||||||
|
output.push(mcpInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.join("\n")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { SkillScope, LoadedSkill } from "../../features/opencode-skill-loader/types"
|
import type { SkillScope, LoadedSkill } from "../../features/opencode-skill-loader/types"
|
||||||
|
import type { SkillMcpManager } from "../../features/skill-mcp-manager"
|
||||||
|
|
||||||
export interface SkillArgs {
|
export interface SkillArgs {
|
||||||
name: string
|
name: string
|
||||||
@@ -20,4 +21,8 @@ export interface SkillLoadOptions {
|
|||||||
opencodeOnly?: boolean
|
opencodeOnly?: boolean
|
||||||
/** Pre-merged skills to use instead of discovering */
|
/** Pre-merged skills to use instead of discovering */
|
||||||
skills?: LoadedSkill[]
|
skills?: LoadedSkill[]
|
||||||
|
/** MCP manager for querying skill-embedded MCP servers */
|
||||||
|
mcpManager?: SkillMcpManager
|
||||||
|
/** Session ID getter for MCP client identification */
|
||||||
|
getSessionID?: () => string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user