Hmbown/CodeWhale · error

skill " " is unknown — skills/list names the catalog

Error message

skill "${params?.uri ?? ""}" is unknown — skills/list names the catalog

What it means

The computer-use MCP server implements skills/get by finding the skillPack entry matching `params.uri`, defaulting to SKILL_ROOT_URI when the uri is omitted. If no entry matches, it throws a paramError stating the skill is unknown and that skills/list names the catalog. Only URIs present in the bundled skill pack are valid skill handles.

Solutions

  1. Call skills/list and use one of the returned skill URIs verbatim in skills/get.
  2. If you want the top-level skill, omit params.uri — the handler defaults to SKILL_ROOT_URI.
  3. Refresh any cached catalog in the client after plugin updates and re-fetch skills/list.
  4. Verify you are not passing a resources/* URI into skills/get; the resource and skill lookups share skillPack but are separate endpoints with different contracts.

Example fix

// before
await mcp.request({ method: "skills/get", params: { uri: "skill://computer-use/automation" } }); // invented
// after
const { skills } = await mcp.request({ method: "skills/list" });
await mcp.request({ method: "skills/get", params: { uri: skills[0].uri } });
// or simply omit the uri for the root skill:
await mcp.request({ method: "skills/get" });
Defensive patterns

Strategy: validation

Validate before calling

// Before calling skills/get:
const { skills } = await client.request({ method: "skills/list" });
const known = new Set(skills.map((s) => s.uri));
if (params.uri !== undefined && !known.has(params.uri)) {
  throw new Error(`Unknown skill URI; catalog: ${[...known].join(", ")}`);
}

Type guard

function isKnownSkillUri(uri, skills) {
  return uri === undefined || (typeof uri === 'string' && skills.some((s) => s.uri === uri));
}

Try / catch

try {
  const { skill } = await client.request({ method: "skills/get", params });
} catch (err) {
  if (String(err.message).includes('is unknown')) {
    // omit uri to get the root skill, or re-list the catalog
    const { skill } = await client.request({ method: "skills/get" });
  } else throw err;
}

Prevention

When it happens

Trigger: A client calls skills/get with a uri not present in skillPack: a hallucinated or constructed skill URI, a stale URI from a previous plugin build, a typo'd or differently-cased string, or per-file URIs assumed to be skill-level URIs when only SKILL_ROOT_URI is the named skill (individual pack files are found only if registered as entries).

Common situations: An LLM client invents a skill name and requests its uri directly instead of listing first; tooling caches the skill catalog across plugin updates so old URIs 404; a developer passes a resource URI (from resources/list) to skills/get, mixing the two catalogs.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/3553a3d5a44992d1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:1286

  "resources/list"() {
    return { resources: skillPack.map(({ uri, rel, mime, size }) => ({ uri, name: rel, mimeType: mime, size })) };
  },
  "resources/read"(params) {
    const file = skillPack.find((f) => f.uri === params?.uri);
    if (!file) throw paramError(`resource "${params?.uri ?? ""}" is not part of the bundled skill pack — resources/list names the readable URIs`);
    return { contents: [{ uri: file.uri, mimeType: file.mime, text: file.text }] };
  },
  "skills/list"() {
    return {
      skills: [{
        uri: SKILL_ROOT_URI, name: SKILL_NAME, description: SKILL_DESCRIPTION,
        files: skillPack.map(({ uri, sha256, size }) => ({ uri, sha256, bytes: size })),
      }],
    };
  },
  "skills/get"(params) {
    const entry = skillPack.find((f) => f.uri === (params?.uri ?? SKILL_ROOT_URI));
    if (!entry) throw paramError(`skill "${params?.uri ?? ""}" is unknown — skills/list names the catalog`);
    return {
      skill: { uri: entry.uri, name: SKILL_NAME, description: SKILL_DESCRIPTION, frontmatter: entry.frontmatter, content: entry.text },
      manifest: skillPack.map(({ uri, sha256, size }) => ({ uri, sha256, bytes: size })),
    };
  },
  async "tools/call"(params) {
    if (params?.name === "stop_computer_control") return callTool(params);
    const previous = dispatch;
    let release;
    dispatch = new Promise((resolve) => { release = resolve; });
    try {
      await previous;
      throwIfAborted();
      return await callToolRecorded(params ?? {});
    } catch (err) {
      if (err?.code !== "cancelled") throw err;
      return { content: [{ type: "text", text: JSON.stringify(fail(null, controlStopped ? "control_stopped" : "cancelled", err.message)) }], isError: true };
    } finally { release(); }

View on GitHub (pinned to 73e0f67d83)