{"record":{"id":"c2ff1bbfa539e4b7","repo":"Hmbown/CodeWhale","slug":"resource-params-uri-is-not-part-of-the-bundled-skill-pack","errorCode":null,"errorMessage":"resource \"${params?.uri ?? \"\"}\" is not part of the bundled skill pack — resources/list names the readable URIs","messagePattern":"resource \"(.+?)\" is not part of the bundled skill pack — resources/list names the readable URIs","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/mcp/server.mjs","lineNumber":1273,"sourceCode":"        experimental: { \"io.modelcontextprotocol/skills\": {} },\n      },\n      serverInfo: { name: SERVER_NAME, version: APP_VERSION, platforms: [\"darwin\", \"win32\", \"linux\", \"harmonyos\"], transports: [\"local\", \"ssh\", \"hdc\"] },\n    };\n  },\n  \"tools/list\"() {\n    // The advertised surface is what every session pays for; merged-away wire\n    // names stay callable as aliases but are never listed. A capability grant\n    // narrows the listing further, never widens it.\n    const advertised = TOOLS.filter((t) => t.hidden !== true);\n    if (!GRANT) return { tools: advertised };\n    return { tools: advertised.filter((t) => t.name === \"stop_computer_control\" || GRANT.has(t.name) || (MERGED_EXPANSION[t.name] ?? []).some((wire) => GRANT.has(wire))) };\n  },\n  \"resources/list\"() {\n    return { resources: skillPack.map(({ uri, rel, mime, size }) => ({ uri, name: rel, mimeType: mime, size })) };\n  },\n  \"resources/read\"(params) {\n    const file = skillPack.find((f) => f.uri === params?.uri);\n    if (!file) throw paramError(`resource \"${params?.uri ?? \"\"}\" is not part of the bundled skill pack — resources/list names the readable URIs`);\n    return { contents: [{ uri: file.uri, mimeType: file.mime, text: file.text }] };\n  },\n  \"skills/list\"() {\n    return {\n      skills: [{\n        uri: SKILL_ROOT_URI, name: SKILL_NAME, description: SKILL_DESCRIPTION,\n        files: skillPack.map(({ uri, sha256, size }) => ({ uri, sha256, bytes: size })),\n      }],\n    };\n  },\n  \"skills/get\"(params) {\n    const entry = skillPack.find((f) => f.uri === (params?.uri ?? SKILL_ROOT_URI));\n    if (!entry) throw paramError(`skill \"${params?.uri ?? \"\"}\" is unknown — skills/list names the catalog`);\n    return {\n      skill: { uri: entry.uri, name: SKILL_NAME, description: SKILL_DESCRIPTION, frontmatter: entry.frontmatter, content: entry.text },\n      manifest: skillPack.map(({ uri, sha256, size }) => ({ uri, sha256, bytes: size })),\n    };\n  },","sourceCodeStart":1255,"sourceCodeEnd":1291,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/mcp/server.mjs#L1255-L1291","documentation":"The computer-use MCP server implements resources/read by looking up the requested URI in the bundled `skillPack` array (the files exposed via resources/list). If no entry matches the given `params.uri`, it throws a paramError explaining that the resource is not part of the bundled skill pack and that resources/list names the readable URIs. It is a strict allow-list: only URIs exactly matching a bundled file can be read.","triggerScenarios":"An MCP client calls resources/read with a uri that is not in skillPack: a stale URI cached from an older server version, a guessed/constructed URI, a file path instead of the pack's URI scheme, an omitted uri (params undefined yields \"\" in the message), or a URI with different casing/whitespace than the exact registered one.","commonSituations":"A client hard-coded resource URIs from an older plugin version before the skill pack changed; a developer tests resources/read with a local file path like /docs/skill.md instead of the pack's canonical URI; an LLM-driven client hallucinates a plausible URI; the request omits params entirely so uri falls back to the empty string.","solutions":["Call resources/list first and copy a uri from its response verbatim into resources/read.","Check the client's cached URIs against the current server version — regenerate them from resources/list if the skill pack changed.","Ensure the params object actually includes a `uri` field with the exact string (no path-style substitution, no case/trailing-slash differences).","If the needed resource genuinely should exist, rebuild/update the plugin so skillPack contains it."],"exampleFix":"// before\nawait mcp.request({ method: \"resources/read\", params: { uri: \"docs/getting-started.md\" } });\n// after\nconst { resources } = await mcp.request({ method: \"resources/list\" });\nconst wanted = resources.find((r) => r.name === \"getting-started.md\");\nawait mcp.request({ method: \"resources/read\", params: { uri: wanted.uri } });","handlingStrategy":"validation","validationCode":"// Before calling resources/read:\nconst { resources } = await client.listResources();\nconst readable = new Set(resources.map((r) => r.uri));\nif (!readable.has(myUri)) {\n  throw new Error(`URI \"${myUri}\" not offered; use one of: ${[...readable].join(\", \")}`);\n}","typeGuard":"function isKnownResourceUri(uri, resources) {\n  return typeof uri === 'string' && resources.some((r) => r.uri === uri);\n}","tryCatchPattern":"try {\n  const res = await client.readResource({ uri });\n} catch (err) {\n  if (String(err.message).includes('not part of the bundled skill pack')) {\n    const { resources } = await client.listResources();\n    // re-read with resources[0].uri or surface the valid URIs to the user\n  } else throw err;\n}","preventionTips":["Always list resources before reading; never hard-code URIs.","Invalidate cached URIs after any plugin/server update.","Match URIs exactly — no case, slash, or scheme improvisation.","Pass an explicit uri string in params; a missing uri silently becomes \"\" and fails."],"tags":["mcp","resources","lookup-failed","validation"],"backgroundTag":"resource-not-found","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}