Skip to content

Commit 2f363d5

Browse files
author
vijay
committed
Add end-to-end dynamic-skill and cursor-resume client tests
Two behavioral cases the existing suite left unpinned: - A "dynamic" skill now round-trips through the real server extension, the wire, and the client wrapper (validated on both ends), proving the resources union survives intact rather than only in an isolated model round-trip. - list_skills honours a caller-supplied starting cursor, skipping the pages before it — the resume-from-a-saved-cursor contract.
1 parent b845490 commit 2f363d5

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

‎tests/client/test_skills.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,32 @@ async def test_get_skill_rejects_a_mismatched_uri_from_a_non_conformant_server()
224224
async with Client(server) as client:
225225
with pytest.raises(ValueError, match="returned skill"):
226226
await get_skill(client.session, "skill://other/SKILL.md")
227+
228+
229+
async def test_get_skill_round_trips_a_dynamic_skill() -> None:
230+
"""SEP-2640 Resources: the `"dynamic"` marker survives the full server -> wire -> client
231+
path — validated on both ends — with the union type intact, not coerced to a list or null."""
232+
dynamic = Skill(
233+
uri="skill://generated/SKILL.md",
234+
frontmatter={"name": "generated", "description": "instructions generated on demand"},
235+
resources="dynamic",
236+
)
237+
238+
async def get_dynamic(ctx: ServerRequestContext[Any, Any], params: GetSkillParams) -> GetSkillResult:
239+
# The server's own _handle_get already enforces the requested-uri match; this test only
240+
# ever asks for `dynamic.uri`, so the handler just returns it.
241+
return GetSkillResult(skill=dynamic)
242+
243+
server = MCPServer("catalog", extensions=[Skills(list_skills=_paginated_list_handler(), get_skill=get_dynamic)])
244+
async with Client(server) as client:
245+
skill = await get_skill(client.session, dynamic.uri)
246+
assert skill.resources == "dynamic"
247+
assert skill.frontmatter["name"] == "generated"
248+
249+
250+
async def test_list_skills_starts_from_a_caller_supplied_cursor() -> None:
251+
"""A host resuming from a saved cursor: `list_skills` begins at that cursor rather than the
252+
top, so only the pages after it come back (here, page 1's skill is skipped)."""
253+
async with Client(_server()) as client:
254+
skills = await list_skills(client.session, ListSkillsParams(cursor="page-2"))
255+
assert [s.uri for s in skills] == ["skill://other/SKILL.md"]

0 commit comments

Comments
 (0)