Skip to content

feat(cli): expose skill_lookup MCP tool from happyServer so non-Claude/Codex agents (OpenCode, Gemini, Cursor) can use $skill #752

@swear01

Description

@swear01

What problem are you trying to solve?

HAPI already discovers skills across agent flavors (cli/src/modules/common/skills.ts) and the web composer offers $skillname autocomplete in every session, but the literal \$skill text only does anything for the two agents that have native skill machinery:

  • Claude (SDK): the Skill tool looks up SKILL.md on demand.
  • Codex (CLI): ~/.codex/skills/ is folded into the prompt header.

For OpenCode, Gemini, and Cursor the autocomplete still shows skills, but pressing send delivers \$skill to the model as plain text — the backend has no idea what it means, so the skill is silently ignored. That's a quiet UX trap (autocomplete promises something that doesn't fire).

Proposed change

Add a skill_lookup(name) tool to the existing HAPI MCP server (cli/src/claude/utils/startHappyServer.ts:21, the same one that already exposes change_title / display_image). The tool:

  • accepts the skill name,
  • runs the same listSkills / SKILL.md read path the web autocomplete already uses,
  • returns the SKILL.md body (and a small metadata header) so the model can use it inline.

Pair it with a one-line system-prompt addition (e.g. injected via buildHapiMcpBridge's opener for flavors that don't have native skills): "User messages starting with $name refer to a HAPI skill. Call the skill_lookup MCP tool with that name to fetch its body before acting."

Why this approach

Architecturally HAPI already has the MCP bridge — every Codex/OpenCode/Gemini session connects to it and the model already calls change_title etc. through that channel. Skills become a tool call instead of a string substitution, which means:

  • True lazy loading (only the SKILL.md the model decides to use enters context — unlike inlining everything at send time).
  • One implementation that works for OpenCode, Gemini, Cursor, and any future ACP-based flavor — no per-flavor expansion path needed.
  • Doesn't conflict with native handling — Claude (SDK Skill tool) and Codex (skills folder) can keep using their existing paths; this tool is a fallback for flavors that don't have one.
  • Discoverable — once registered, every flavor with MCP wiring picks it up for free.

Alternative considered

Per-flavor text expansion in the runner (e.g. runOpencode.ts reads SKILL.md when it sees \$skill, inlines into the prompt before backend.prompt). Smaller diff, but no lazy load, and we have to repeat the integration for every non-skill-native flavor.

Out of scope (for now)

  • Adapting arguments after the skill name. The MCP tool result should include the remaining user text so the model knows what to do; no parsing on the HAPI side.
  • Skill caching inside the model context across turns — that's a model-side optimisation.

Related

  • OpenCode: slash commands not working #671 — OpenCode slash command support. While testing that, OpenCode autocomplete showed \$ skills but the model didn't react. This issue is the follow-up for the skills half of the same UX gap.
  • 远程模式无法读取/斜杠命令和$skills #225 (closed) — earlier report that remote mode couldn't see skills; the discovery path is fixed but the invocation path is still flavor-specific.
  • cli/src/modules/common/skills.ts — existing skill discovery, ready to be reused.
  • cli/src/claude/utils/startHappyServer.ts:21 — where the new MCP tool would live alongside change_title and display_image.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions