Problem
The base entity schema declares its $id as:
https://upjack.dev/schemas/v1/upjack-entity.schema.json
and every app entity schema references it via allOf: [{$ref: "https://upjack.dev/..."}].
That URL is a name, not a location — there's no guarantee the schema is actually served there. But because the $id looks like an HTTP URL, every JSON-Schema-aware consumer (FastMCP, validators, third-party tools) is justified in trying to dereference it over the network. This caused the exact bug fixed in 0.5.0: tools/list was blocking for ~4s per activity-enabled server while FastMCP's serializer tried to fetch the URL.
The 0.5.0 fix inlines the $ref at load_schema time so library-internal flows never re-resolve. That's a workaround. The root cause is that we're using a URL as an identifier.
Proposal
Switch the base-entity $id and the canonical $ref target to a URN:
{
"$id": "urn:upjack:schemas:v1:entity"
}
URNs are, by spec, non-dereferenceable — consumers can't accidentally try to HTTP-fetch them. Any tool that wants to resolve the schema has to bring its own local copy, which is exactly the contract Upjack ships anyway (bundled upjack-entity.schema.json).
Consequences
- Breaking change to the published schema contract — existing app schemas reference the HTTPS URL.
- Target 0.6.0.
- Migration: app-schema authors update one string. The library continues to bundle the schema under the new
\$id and auto-inline it at load time.
- The
load_schema / `_inline_base_entity_ref` machinery stays — same mechanism, different URI.
- Follow-up: consider whether
resolve_entity_schema helper (or a codemod in the app-builder skill) should help authors migrate.
Out of scope
- Hosting the schema somewhere real (e.g., on
schemas.nimblebrain.ai) — the whole point of this change is that the schema doesn't need to be hosted.
- Generalizing to arbitrary
$id URLs in user-authored schemas — those are the author's responsibility.
Related
Problem
The base entity schema declares its
$idas:and every app entity schema references it via
allOf: [{$ref: "https://upjack.dev/..."}].That URL is a name, not a location — there's no guarantee the schema is actually served there. But because the
$idlooks like an HTTP URL, every JSON-Schema-aware consumer (FastMCP, validators, third-party tools) is justified in trying to dereference it over the network. This caused the exact bug fixed in 0.5.0:tools/listwas blocking for ~4s per activity-enabled server while FastMCP's serializer tried to fetch the URL.The 0.5.0 fix inlines the
$refatload_schematime so library-internal flows never re-resolve. That's a workaround. The root cause is that we're using a URL as an identifier.Proposal
Switch the base-entity
$idand the canonical$reftarget to a URN:{ "$id": "urn:upjack:schemas:v1:entity" }URNs are, by spec, non-dereferenceable — consumers can't accidentally try to HTTP-fetch them. Any tool that wants to resolve the schema has to bring its own local copy, which is exactly the contract Upjack ships anyway (bundled
upjack-entity.schema.json).Consequences
\$idand auto-inline it at load time.load_schema/ `_inline_base_entity_ref` machinery stays — same mechanism, different URI.resolve_entity_schemahelper (or a codemod in the app-builder skill) should help authors migrate.Out of scope
schemas.nimblebrain.ai) — the whole point of this change is that the schema doesn't need to be hosted.$idURLs in user-authored schemas — those are the author's responsibility.Related