11/**
22 * SEP-2663 Tasks Extension — wire-format / TTL conformance.
33 *
4- * Tests the renamed wire fields (ttlSeconds, pollIntervalMilliseconds ),
4+ * Tests the renamed wire fields (ttlMs, pollIntervalMs ),
55 * the no-early-TTL-expiry rule, and confirms the v1 `related-task` _meta
66 * key is absent on tasks/get's inlined result (taskId is at root level
77 * already, so the metadata is redundant).
@@ -37,18 +37,23 @@ export class TasksWireFieldsScenario implements ClientScenario {
3737**Server Implementation Requirements:**
3838
3939**Wire-field renames (SEP-2663):**
40- - The TTL field is named \`ttlSeconds\` on the wire (the v1 \`ttl\`
41- key is in milliseconds-by-convention; SEP-2663 puts the unit in the
42- field name).
43- - The poll-interval field is named \`pollIntervalMilliseconds\` (v1
44- used \`pollInterval\`).
40+ - The TTL field is named \`ttlMs\` on the wire (the v1 \`ttl\` key was
41+ in milliseconds-by-convention; SEP-2663 puts the unit in the field
42+ name and standardised on the \`Ms\` suffix in the 2026-05-07 spec
43+ commit aligning all duration fields).
44+ - The poll-interval field is named \`pollIntervalMs\` (v1 used
45+ \`pollInterval\`; an interim SEP-2663 draft used
46+ \`pollIntervalMilliseconds\` before the 2026-05-07 \`Ms\`-suffix
47+ alignment).
4548- A \`CreateTaskResult\` MUST NOT carry the legacy \`ttl\` or
4649 \`pollInterval\` keys — clients keying off v1 names on a v2 server
47- would silently miss the TTL guidance.
50+ would silently miss the TTL guidance. The interim
51+ \`ttlSeconds\` / \`pollIntervalMilliseconds\` keys MUST also be
52+ absent on a server tracking the post-2026-05-07 spec.
4853
4954**TTL non-expiry (SEP-2663):**
5055- A task MUST remain accessible via \`tasks/get\` for the duration of
51- its \`ttlSeconds \`; a server MUST NOT expire it earlier.
56+ its \`ttlMs \`; a server MUST NOT expire it earlier.
5257
5358**Inlined-result \`_meta\` (SEP-2663):**
5459- The v1 \`io.modelcontextprotocol/related-task\` \`_meta\` key MUST NOT
@@ -86,13 +91,13 @@ export class TasksWireFieldsScenario implements ClientScenario {
8691 return checks ;
8792 }
8893
89- // Check 1: ttlSeconds + pollIntervalMilliseconds wire shape.
94+ // Check 1: ttlMs + pollIntervalMs wire shape.
9095 let createdTaskId : string | undefined ;
9196 {
9297 const id = 'tasks-wire-field-renames' ;
9398 const name = 'TasksWireFieldRenames' ;
9499 const description =
95- 'CreateTaskResult uses ttlSeconds + pollIntervalMilliseconds ; legacy ttl / pollInterval keys absent' ;
100+ 'CreateTaskResult uses ttlMs + pollIntervalMs ; legacy ttl / pollInterval keys absent and the interim ttlSeconds / pollIntervalMilliseconds keys also absent' ;
96101 try {
97102 const result = ( await client . request (
98103 {
@@ -106,40 +111,52 @@ export class TasksWireFieldsScenario implements ClientScenario {
106111 ) ) as any ;
107112 createdTaskId = result . taskId ;
108113 const errs : string [ ] = [ ] ;
109- // ttlSeconds — required, positive (or null = unlimited; treat
110- // either as well-formed). Legacy `ttl` MUST be absent.
111- if ( ! ( 'ttlSeconds' in result ) ) {
114+ // ttlMs — required, positive integer (or null = unlimited; treat
115+ // either as well-formed). Legacy `ttl` MUST be absent. Interim
116+ // `ttlSeconds` MUST also be absent on a post-2026-05-07 server.
117+ if ( ! ( 'ttlMs' in result ) ) {
112118 errs . push (
113- 'CreateTaskResult MUST carry ttlSeconds (renamed from v1 `ttl`)'
119+ 'CreateTaskResult MUST carry ttlMs (renamed from v1 `ttl` and from the interim `ttlSeconds `)'
114120 ) ;
115121 } else if (
116- result . ttlSeconds !== null &&
117- ( typeof result . ttlSeconds !== 'number' || result . ttlSeconds <= 0 )
122+ result . ttlMs !== null &&
123+ ( typeof result . ttlMs !== 'number' || result . ttlMs <= 0 )
118124 ) {
119125 errs . push (
120- `ttlSeconds MUST be null or a positive number; got ${ JSON . stringify ( result . ttlSeconds ) } `
126+ `ttlMs MUST be null or a positive number (integer milliseconds) ; got ${ JSON . stringify ( result . ttlMs ) } `
121127 ) ;
122128 }
123129 if ( 'ttl' in result ) {
124130 errs . push (
125- 'CreateTaskResult MUST NOT carry the v1 `ttl` key (use ttlSeconds )'
131+ 'CreateTaskResult MUST NOT carry the v1 `ttl` key (use ttlMs )'
126132 ) ;
127133 }
128- // pollIntervalMilliseconds — optional. When present it MUST be
129- // a positive number and the legacy `pollInterval` key MUST NOT
130- // appear.
134+ if ( 'ttlSeconds' in result ) {
135+ errs . push (
136+ 'CreateTaskResult MUST NOT carry the interim `ttlSeconds` key (the 2026-05-07 SEP-2663 commit replaced it with ttlMs)'
137+ ) ;
138+ }
139+ // pollIntervalMs — optional. When present it MUST be a positive
140+ // integer (milliseconds), and the legacy `pollInterval` key MUST
141+ // NOT appear. The interim `pollIntervalMilliseconds` key MUST
142+ // also be absent on a post-2026-05-07 server.
131143 if (
132- result . pollIntervalMilliseconds !== undefined &&
133- ( typeof result . pollIntervalMilliseconds !== 'number' ||
134- result . pollIntervalMilliseconds <= 0 )
144+ result . pollIntervalMs !== undefined &&
145+ ( typeof result . pollIntervalMs !== 'number' ||
146+ result . pollIntervalMs <= 0 )
135147 ) {
136148 errs . push (
137- `pollIntervalMilliseconds MUST be a positive number when present; got ${ JSON . stringify ( result . pollIntervalMilliseconds ) } `
149+ `pollIntervalMs MUST be a positive number when present; got ${ JSON . stringify ( result . pollIntervalMs ) } `
138150 ) ;
139151 }
140152 if ( 'pollInterval' in result ) {
141153 errs . push (
142- 'CreateTaskResult MUST NOT carry the v1 `pollInterval` key (use pollIntervalMilliseconds)'
154+ 'CreateTaskResult MUST NOT carry the v1 `pollInterval` key (use pollIntervalMs)'
155+ ) ;
156+ }
157+ if ( 'pollIntervalMilliseconds' in result ) {
158+ errs . push (
159+ 'CreateTaskResult MUST NOT carry the interim `pollIntervalMilliseconds` key (the 2026-05-07 SEP-2663 commit replaced it with pollIntervalMs)'
143160 ) ;
144161 }
145162 checks . push ( {
@@ -151,10 +168,13 @@ export class TasksWireFieldsScenario implements ClientScenario {
151168 errorMessage : errs . length > 0 ? errs . join ( '; ' ) : undefined ,
152169 specReferences : [ SEP_2663_REF ] ,
153170 details : {
154- ttlSeconds : result . ttlSeconds ,
155- pollIntervalMilliseconds : result . pollIntervalMilliseconds ,
171+ ttlMs : result . ttlMs ,
172+ pollIntervalMs : result . pollIntervalMs ,
156173 hasLegacyTtl : 'ttl' in result ,
157- hasLegacyPollInterval : 'pollInterval' in result
174+ hasLegacyPollInterval : 'pollInterval' in result ,
175+ hasInterimTtlSeconds : 'ttlSeconds' in result ,
176+ hasInterimPollIntervalMilliseconds :
177+ 'pollIntervalMilliseconds' in result
158178 }
159179 } ) ;
160180 } catch ( error ) {
@@ -167,14 +187,15 @@ export class TasksWireFieldsScenario implements ClientScenario {
167187 const id = 'tasks-no-early-ttl-expiry' ;
168188 const name = 'TasksNoEarlyTtlExpiry' ;
169189 const description =
170- 'Task remains accessible via tasks/get for the duration of its ttlSeconds ' ;
190+ 'Task remains accessible via tasks/get for the duration of its ttlMs ' ;
171191 if ( ! createdTaskId ) {
172192 checks . push ( skipCheck ( id , name , description , 'no task created' ) ) ;
173193 } else {
174194 try {
175195 await waitForTerminal ( client , createdTaskId ) ;
176- // Sanity probe well before TTL (the unit is seconds; servers
177- // typically pick order-of-minutes defaults).
196+ // Sanity probe well before TTL elapses. ttlMs is integer
197+ // milliseconds and servers typically pick order-of-minutes
198+ // defaults, so a 500ms wait is comfortably inside any sane TTL.
178199 await new Promise ( ( r ) => setTimeout ( r , 500 ) ) ;
179200 const after = ( await client . request (
180201 {
0 commit comments