@@ -25,6 +25,27 @@ const (
2525// property — digest verification governs content regardless.
2626const listTTLMs = 3_600_000
2727
28+ // resultTypeComplete is the base protocol's multi-round-trip result marker
29+ // (SEP-2322): results on 2026-07-28+ sessions declare themselves "complete".
30+ // The SDK stamps its own result types automatically but not custom methods',
31+ // so the extension's handlers set it themselves.
32+ const (
33+ resultTypeComplete = "complete"
34+ protocolVersion20260728 = "2026-07-28"
35+ )
36+
37+ // completeResultType returns the resultType value for a session: "complete"
38+ // when the session negotiated 2026-07-28 or later, empty (omitted) for older
39+ // clients, for which the field does not exist.
40+ func completeResultType (ss * mcp.ServerSession ) string {
41+ if ss != nil {
42+ if p := ss .InitializeParams (); p != nil && p .ProtocolVersion < protocolVersion20260728 {
43+ return ""
44+ }
45+ }
46+ return resultTypeComplete
47+ }
48+
2849// ListSkillsParams is the skills/list request payload.
2950type ListSkillsParams struct {
3051 mcp.ParamsBase
@@ -36,7 +57,10 @@ type ListSkillsParams struct {
3657type ListSkillsResult struct {
3758 mcp.ResultBase
3859 mcp.Cacheable
39- Skills []Entry `json:"skills"`
60+ // ResultType is the base protocol's completion marker on 2026-07-28+
61+ // sessions; see completeResultType.
62+ ResultType string `json:"resultType,omitempty"`
63+ Skills []Entry `json:"skills"`
4064 // NextCursor, when present, indicates more entries are available.
4165 NextCursor string `json:"nextCursor,omitempty"`
4266}
@@ -52,7 +76,8 @@ type GetSkillParams struct {
5276// shape and meaning to an entry of skills/list.
5377type GetSkillResult struct {
5478 mcp.ResultBase
55- Skill Entry `json:"skill"`
79+ ResultType string `json:"resultType,omitempty"`
80+ Skill Entry `json:"skill"`
5681}
5782
5883// DirectoryReadParams is the resources/directory/read request payload.
@@ -70,7 +95,8 @@ type DirectoryReadParams struct {
7095// mimeType "inode/directory".
7196type DirectoryReadResult struct {
7297 mcp.ResultBase
73- Resources []* mcp.Resource `json:"resources"`
98+ ResultType string `json:"resultType,omitempty"`
99+ Resources []* mcp.Resource `json:"resources"`
74100 // NextCursor, when present, indicates more children are available.
75101 NextCursor string `json:"nextCursor,omitempty"`
76102}
@@ -138,8 +164,8 @@ func serveFile(uri string, f File) mcp.ResourceHandler {
138164// so it always fits one page: any cursor is accepted and the full listing
139165// returned with no nextCursor, which terminates every conforming pagination
140166// loop.
141- func (p * Publisher ) listSkills (_ context.Context , _ * mcp.ServerSession , _ * ListSkillsParams ) (* ListSkillsResult , error ) {
142- res := & ListSkillsResult {Skills : p .Registry .Entries ()}
167+ func (p * Publisher ) listSkills (_ context.Context , ss * mcp.ServerSession , _ * ListSkillsParams ) (* ListSkillsResult , error ) {
168+ res := & ListSkillsResult {Skills : p .Registry .Entries (), ResultType : completeResultType ( ss ) }
143169 res .TTLMs = listTTLMs
144170 res .CacheScope = "public"
145171 return res , nil
@@ -148,42 +174,42 @@ func (p *Publisher) listSkills(_ context.Context, _ *mcp.ServerSession, _ *ListS
148174// getSkill implements skills/get. Per the SEP it answers for every skill the
149175// server serves — listed or not — and returns -32602 (the same code
150176// resources/read uses for unknown resources) otherwise.
151- func (p * Publisher ) getSkill (ctx context.Context , _ * mcp.ServerSession , params * GetSkillParams ) (* GetSkillResult , error ) {
177+ func (p * Publisher ) getSkill (ctx context.Context , ss * mcp.ServerSession , params * GetSkillParams ) (* GetSkillResult , error ) {
152178 if params == nil || params .URI == "" {
153179 return nil , & jsonrpc.Error {Code : jsonrpc .CodeInvalidParams , Message : "missing required parameter: uri" }
154180 }
155181 if s , ok := p .Registry .Get (params .URI ); ok {
156- return & GetSkillResult {Skill : s .Entry ()}, nil
182+ return & GetSkillResult {Skill : s .Entry (), ResultType : completeResultType ( ss ) }, nil
157183 }
158184 if p .DynamicGet != nil {
159185 entry , err := p .DynamicGet (ctx , params .URI )
160186 if err != nil {
161187 return nil , err
162188 }
163189 if entry != nil {
164- return & GetSkillResult {Skill : * entry }, nil
190+ return & GetSkillResult {Skill : * entry , ResultType : completeResultType ( ss ) }, nil
165191 }
166192 }
167193 return nil , mcp .ResourceNotFoundError (params .URI )
168194}
169195
170196// readDirectory implements resources/directory/read. Like listSkills, bundled
171197// directories are small enough to always fit one page.
172- func (p * Publisher ) readDirectory (ctx context.Context , _ * mcp.ServerSession , params * DirectoryReadParams ) (* DirectoryReadResult , error ) {
198+ func (p * Publisher ) readDirectory (ctx context.Context , ss * mcp.ServerSession , params * DirectoryReadParams ) (* DirectoryReadResult , error ) {
173199 if params == nil || params .URI == "" {
174200 return nil , & jsonrpc.Error {Code : jsonrpc .CodeInvalidParams , Message : "missing required parameter: uri" }
175201 }
176202 uri := strings .TrimSuffix (params .URI , "/" )
177203 if children , ok := p .Registry .Directory (uri ); ok {
178- return & DirectoryReadResult {Resources : children }, nil
204+ return & DirectoryReadResult {Resources : children , ResultType : completeResultType ( ss ) }, nil
179205 }
180206 if p .DynamicDirectoryRead != nil {
181207 children , err := p .DynamicDirectoryRead (ctx , uri )
182208 if err != nil {
183209 return nil , err
184210 }
185211 if children != nil {
186- return & DirectoryReadResult {Resources : children }, nil
212+ return & DirectoryReadResult {Resources : children , ResultType : completeResultType ( ss ) }, nil
187213 }
188214 }
189215 return nil , mcp .ResourceNotFoundError (params .URI )
0 commit comments