modelcontextprotocol/servers · error · Error

Unknown resource: ${uri.toString()}

Error message

Unknown resource: ${uri.toString()}

What it means

Thrown by `parseResourceId` in the resource-template resolver when the URI neither starts with the text base nor the blob base. The first guard is a defensive check: a URI reaching this resolver is expected to match one of the two registered template prefixes (`demo://resource/dynamic/text` or `.../blob`). Reaching it with any other URI is treated as an unknown resource.

Source

Thrown at src/everything/resources/templates.ts:145

  new URL(`${blobUriBase}/${resourceId}`);

/**
 * Parses the resource identifier from the provided URI and validates it
 * against the given variables. Throws an error if the URI corresponds
 * to an unknown resource or if the resource identifier is invalid.
 *
 * @param {URL} uri - The URI of the resource to be parsed.
 * @param {Record<string, unknown>} variables - A record containing context-specific variables that include the resourceId.
 * @returns {number} The parsed and validated resource identifier as an integer.
 * @throws {Error} Throws an error if the URI matches unsupported base URIs or if the resourceId is invalid.
 */
const parseResourceId = (uri: URL, variables: Record<string, unknown>) => {
  const uriError = `Unknown resource: ${uri.toString()}`;
  if (
    uri.toString().startsWith(textUriBase) &&
    uri.toString().startsWith(blobUriBase)
  ) {
    throw new Error(uriError);
  } else {
    const idxStr = String((variables as any).resourceId ?? "");
    const idx = Number(idxStr);
    if (Number.isFinite(idx) && Number.isInteger(idx) && idx > 0) {
      return idx;
    } else {
      throw new Error(uriError);
    }
  }
};

/**
 * Register resource templates with the MCP server.
 * - Text and blob resources, dynamically generated from the URI {resourceId} variable
 * - Any finite positive integer is acceptable for the resourceId variable
 * - List resources method will not return these resources
 * - These are only accessible via template URIs
 * - Both blob and text resources:

View on GitHub (pinned to 76d64c822f)

Solutions

  1. Ensure the requested URI matches one of the registered templates: `demo://resource/dynamic/text/{id}` or `demo://resource/dynamic/blob/{id}`.
  2. Use the resources/templates/list response to obtain valid URI templates rather than constructing them by hand.
  3. Route static (non-templated) resource URIs to the static resource handler, not the dynamic template resolver.

Example fix

// before
readResource('demo://resource/static/text/1')
// after
readResource('demo://resource/dynamic/text/1')
Defensive patterns

Strategy: validation

Validate before calling

const TEXT_BASE = 'demo://resource/dynamic/text';
const BLOB_BASE = 'demo://resource/dynamic/blob';
function isKnownDynamicUri(u: string): boolean {
  return u.startsWith(TEXT_BASE) || u.startsWith(BLOB_BASE);
}
// only route URIs that pass this check to the dynamic template handler

Type guard

function isDynamicResourceUri(uri: URL): boolean {
  const s = uri.toString();
  return s.startsWith('demo://resource/dynamic/text/') || s.startsWith('demo://resource/dynamic/blob/');
}

Prevention

When it happens

Trigger: A resources/read or template-completion request whose URI does not begin with `demo://resource/dynamic/text` or `demo://resource/dynamic/blob` — e.g. a client constructs `demo://resource/dynamic/other/1` or sends a static resource URI to the dynamic template handler.

Common situations: Misrouting requests to the wrong template handler, typos in hand-built URIs, or a client that strips/rewrites the scheme. Note the logic uses `&&` (both startsWith checks), so in practice this branch is hard to hit unless the URI matches neither prefix — which then falls into the else and validates the id; this specific throw is reached only when the URI is entirely foreign to both bases.

Related errors


AI-assisted analysis of modelcontextprotocol/servers@76d64c822f (2026-08-12). Data as JSON: /api/errors/b71605bbcaf2a97c. Report an issue: GitHub.