Skip to content

fix(skills): accept any URI scheme, and stop rewriting the one we parsed - #1369

Merged
panyam merged 1 commit into
mainfrom
fix/skills-scheme-agnostic
Sep 10, 2026
Merged

fix(skills): accept any URI scheme, and stop rewriting the one we parsed#1369
panyam merged 1 commit into
mainfrom
fix/skills-scheme-agnostic

Conversation

@panyam

@panyam panyam commented Sep 10, 2026

Copy link
Copy Markdown
Owner

ParseURI required the scheme be exactly skill, and it sits on the live client path
(ReadSkillManifest, ReadSkillFile, ReadDirectory). A server serving a conforming
catalog under a domain-native scheme was refused outright.

SEP-2640 says the opposite in as many words:

Servers SHOULD use the skill:// URI scheme … A server MAY serve skills under
another scheme native to its domain (for example,
github://owner/repo/skills/refunds/SKILL.md). No scheme is privileged.

and adds that a host MUST NOT conclude a resource is a skill merely because its URI
carries a particular scheme, which is the same rule read from the other side.

So we were enforcing a SHOULD as a MUST. Our own conformance check,
sep-2640-skill-uri-scheme, already grades this a WARNING, which means mcpkit's client
was stricter than mcpkit's own suite and no scenario could have caught it.

The half that would have been worse

Relaxing the parse alone introduces a subtler bug. String, SkillRootURI and
ManifestURI rebuilt from the Scheme constant rather than from what was parsed, so a
github:// URI came back out as skill://. That is a different resource, one the
server does not serve, and the failure would then surface as a digest mismatch or a
missing resource rather than as anything to do with schemes.

All three now rebuild from the parsed scheme, with a fallback to the constant for a
URIParts assembled by hand rather than parsed.

What did not change

Every structural rule still applies, to every scheme: traversal segments, empty
segments, SKILL.md placement, and the Agent Skills name grammar on the final
skill-path segment. TestParseURI_SchemeAgnostic_StillStructural pins that, so the
relaxation cannot quietly become a laxer parse.

What is newly rejected is a URI carrying no scheme at all. That is a relative reference,
and it belongs to ResolveRelative rather than here. ErrInvalidScheme keeps its name
so errors.Is callers are unaffected, but its message and doc now say what it actually
means.

Server-side construction is untouched and still emits skill://, which is the SHOULD.

Tests

  • TestParseURI_SchemeAgnosticskill, github, https and a custom scheme all
    parse, and String/SkillRootURI/ManifestURI round-trip without rewriting
  • TestParseURI_SchemeAgnostic_StillStructural — traversal, empty segment, manifest-as-
    directory and a bad name all still fail under github://
  • TestResolveRelative_PreservesScheme — a relative reference inside a github:// skill
    stays in that skill
  • Two existing cases asserted the old behaviour and encoded the bug; they are now a
    relative reference and a scheme-relative URI, which are the things that should fail

make test and the ext/skills suite are green.

Provenance

Noticed while reading the MCP Inspector's SEP-2640 writeup for Inspector 2.6.0, which
grades the scheme as deliberately unconstrained and reaches the same reading
independently.

`ParseURI` required the scheme be exactly `skill`, and it sits on the live
client path (`ReadSkillManifest`, `ReadSkillFile`, `ReadDirectory`). So a
server serving a conforming catalog under a domain-native scheme was refused
outright. SEP-2640 says the opposite in as many words:

  Servers SHOULD use the `skill://` URI scheme ... A server MAY serve skills
  under another scheme native to its domain (for example,
  `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged.

and adds that a host MUST NOT conclude a resource is a skill merely because
its URI carries a particular scheme, which is the same rule read from the
other side. We were enforcing a SHOULD as a MUST, and our own conformance
check already grades it a WARNING, so mcpkit's client was stricter than
mcpkit's own suite.

Relaxing the parse alone would have introduced a worse bug. `String`,
`SkillRootURI` and `ManifestURI` rebuilt from the `Scheme` constant, so a
parsed `github://` URI came back out as `skill://` — a different resource the
server does not serve, and one that would then fail the digest check for the
wrong reason. All three now rebuild from the parsed scheme, falling back to
the constant for a URIParts assembled by hand.

Every structural rule is unchanged and still applies to every scheme:
traversal, empty segments, SKILL.md placement, and the name grammar. What is
now rejected is a URI with no scheme at all, which is a relative reference and
belongs to ResolveRelative.

Server-side construction still emits `skill://`, which is the SHOULD.

Noticed via the MCP Inspector's SEP-2640 writeup, which grades the scheme as
deliberately unconstrained and reaches the same reading independently.
@panyam
panyam merged commit 7c37c31 into main Sep 10, 2026
10 checks passed
@panyam
panyam deleted the fix/skills-scheme-agnostic branch September 10, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant