Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,43 @@ that optional capabilities outside the core protocol can be declared on the
wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values
are per-extension settings objects.

Use `AddExtension` to declare one and `HasExtension` to test for one:

```go
caps := &mcp.ClientCapabilities{}
caps.AddExtension("io.example/my-extension", nil)
client := mcp.NewClient(impl, &mcp.ClientOptions{Capabilities: caps})

cs, err := client.Connect(ctx, transport, nil)
...
if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") {
// The server declared it too.
}
```

#### Tasks

[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md)
moved tasks out of the core protocol and into the
[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md),
identified by the `mcp.ExtensionTasks` constant. A server that has negotiated
it may answer a request with a durable task handle instead of the result that
was asked for, which the client then polls to completion.

**The SDK does not implement task execution**, and does not declare the
extension by default. Declaring it is a promise to the peer: a client that
declares it must be prepared for any eligible request to return a task handle
instead of a result. Only declare it if you implement that polling flow
yourself.

If a server returns a task handle anyway, decoding fails with
`*mcp.UnsupportedTaskResultError`, which carries the task ID:

```go
res, err := cs.CallTool(ctx, params)
var terr *mcp.UnsupportedTaskResultError
if errors.As(err, &terr) {
log.Printf("server created task %s, which this SDK cannot resolve", terr.TaskID)
}
```

30 changes: 30 additions & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,36 @@ capabilities outside the core protocol can be declared on the wire. Keys
are namespaced as `"{vendor-prefix}/{extension-name}"`; values are
per-extension settings objects.

Use `AddExtension` to declare one, and `HasExtension` to test what the client
declared. Client capabilities are read from the request, since as of protocol
version 2026-07-28 they travel in each request's `_meta` rather than in the
initialize handshake:

```go
caps := &mcp.ServerCapabilities{}
caps.AddExtension("io.example/my-extension", nil)
server := mcp.NewServer(impl, &mcp.ServerOptions{Capabilities: caps})

// Inside a tool handler:
if req.ClientCapabilities().HasExtension("io.example/my-extension") {
// The client declared it too.
}
```

#### Tasks

[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md)
moved tasks out of the core protocol and into the
[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md),
identified by the `mcp.ExtensionTasks` constant. A server that has negotiated
it may answer a request with a durable task handle instead of the result that
was asked for, which the client then polls to completion.

**The SDK does not implement task execution**, and does not declare the
extension by default. Declaring it is a promise to the peer: a server that
declares it must serve `tasks/get`, `tasks/update` and `tasks/cancel`. Only
declare it if you implement those yourself.

### Pagination

Server-side feature lists may be
Expand Down
40 changes: 40 additions & 0 deletions internal/docs/client.src.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,43 @@ that optional capabilities outside the core protocol can be declared on the
wire. Keys are namespaced as `"{vendor-prefix}/{extension-name}"`; values
are per-extension settings objects.

Use `AddExtension` to declare one and `HasExtension` to test for one:

```go
caps := &mcp.ClientCapabilities{}
caps.AddExtension("io.example/my-extension", nil)
client := mcp.NewClient(impl, &mcp.ClientOptions{Capabilities: caps})

cs, err := client.Connect(ctx, transport, nil)
...
if cs.InitializeResult().Capabilities.HasExtension("io.example/my-extension") {
// The server declared it too.
}
```

#### Tasks

[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md)
moved tasks out of the core protocol and into the
[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md),
identified by the `mcp.ExtensionTasks` constant. A server that has negotiated
it may answer a request with a durable task handle instead of the result that
was asked for, which the client then polls to completion.

**The SDK does not implement task execution**, and does not declare the
extension by default. Declaring it is a promise to the peer: a client that
declares it must be prepared for any eligible request to return a task handle
instead of a result. Only declare it if you implement that polling flow
yourself.

If a server returns a task handle anyway, decoding fails with
`*mcp.UnsupportedTaskResultError`, which carries the task ID:

```go
res, err := cs.CallTool(ctx, params)
var terr *mcp.UnsupportedTaskResultError
if errors.As(err, &terr) {
log.Printf("server created task %s, which this SDK cannot resolve", terr.TaskID)
}
```

30 changes: 30 additions & 0 deletions internal/docs/server.src.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,36 @@ capabilities outside the core protocol can be declared on the wire. Keys
are namespaced as `"{vendor-prefix}/{extension-name}"`; values are
per-extension settings objects.

Use `AddExtension` to declare one, and `HasExtension` to test what the client
declared. Client capabilities are read from the request, since as of protocol
version 2026-07-28 they travel in each request's `_meta` rather than in the
initialize handshake:

```go
caps := &mcp.ServerCapabilities{}
caps.AddExtension("io.example/my-extension", nil)
server := mcp.NewServer(impl, &mcp.ServerOptions{Capabilities: caps})

// Inside a tool handler:
if req.ClientCapabilities().HasExtension("io.example/my-extension") {
// The client declared it too.
}
```

#### Tasks

[SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md)
moved tasks out of the core protocol and into the
[tasks extension](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md),
identified by the `mcp.ExtensionTasks` constant. A server that has negotiated
it may answer a request with a durable task handle instead of the result that
was asked for, which the client then polls to completion.

**The SDK does not implement task execution**, and does not declare the
extension by default. Declaring it is a promise to the peer: a server that
declares it must serve `tasks/get`, `tasks/update` and `tasks/cancel`. Only
declare it if you implement those yourself.

### Pagination

Server-side feature lists may be
Expand Down
37 changes: 37 additions & 0 deletions mcp/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const (
// input before it can complete the request. The client should fulfill the
// InputRequests and retry the call with the responses.
resultTypeInputRequired resultType = "input_required"

// resultTypeTask is reserved by the io.modelcontextprotocol/tasks
// extension to discriminate a CreateTaskResult from a standard result.
// See [ExtensionTasks].
resultTypeTask resultType = "task"
)

type completeResultWithType struct {
Expand Down Expand Up @@ -403,10 +408,14 @@ func (x *CallToolResult) UnmarshalJSON(data []byte) error {
res
Content []*wireContent `json:"content"`
ResultType resultType `json:"resultType"`
TaskID string `json:"taskId"`
}
if err := internaljson.Unmarshal(data, &wire); err != nil {
return err
}
if wire.ResultType == resultTypeTask {
return &UnsupportedTaskResultError{TaskID: wire.TaskID}
}
var err error
if wire.res.Content, err = contentsFromWire(wire.Content, nil); err != nil {
return err
Expand Down Expand Up @@ -518,6 +527,16 @@ func (c *ClientCapabilities) AddExtension(name string, settings map[string]any)
c.Extensions[name] = settings
}

// HasExtension reports whether c declares the extension with the given name.
// It is safe to call on a nil *ClientCapabilities.
func (c *ClientCapabilities) HasExtension(name string) bool {
if c == nil {
return false
}
_, ok := c.Extensions[name]
return ok
}

// clone returns a copy of the ClientCapabilities.
// Values in the Extensions and Experimental maps are shallow-copied.
func (c *ClientCapabilities) clone() *ClientCapabilities {
Expand Down Expand Up @@ -1033,10 +1052,14 @@ func (x *GetPromptResult) UnmarshalJSON(data []byte) error {
var wire struct {
res
ResultType resultType `json:"resultType"`
TaskID string `json:"taskId"`
}
if err := internaljson.Unmarshal(data, &wire); err != nil {
return err
}
if wire.ResultType == resultTypeTask {
return &UnsupportedTaskResultError{TaskID: wire.TaskID}
}
wire.res.resultType = wire.ResultType
*x = GetPromptResult(wire.res)
return nil
Expand Down Expand Up @@ -1650,10 +1673,14 @@ func (x *ReadResourceResult) UnmarshalJSON(data []byte) error {
var wire struct {
res
ResultType resultType `json:"resultType"`
TaskID string `json:"taskId"`
}
if err := internaljson.Unmarshal(data, &wire); err != nil {
return err
}
if wire.ResultType == resultTypeTask {
return &UnsupportedTaskResultError{TaskID: wire.TaskID}
}
wire.res.resultType = wire.ResultType
*x = ReadResourceResult(wire.res)
return nil
Expand Down Expand Up @@ -2307,6 +2334,16 @@ func (c *ServerCapabilities) AddExtension(name string, settings map[string]any)
c.Extensions[name] = settings
}

// HasExtension reports whether c declares the extension with the given name.
// It is safe to call on a nil *ServerCapabilities.
func (c *ServerCapabilities) HasExtension(name string) bool {
if c == nil {
return false
}
_, ok := c.Extensions[name]
return ok
}

// clone returns a copy of the ServerCapabilities.
// Values in the Extensions and Experimental maps are shallow-copied.
func (c *ServerCapabilities) clone() *ServerCapabilities {
Expand Down
28 changes: 28 additions & 0 deletions mcp/tasks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.

package mcp

import "fmt"

// ExtensionTasks identifies the MCP Tasks extension, which lets a server answer
// a request with a durable task handle instead of the request's normal result.
//
// This SDK does not implement task execution, and does not declare the
// extension by default: declaring it obliges a client to poll a task handle to
// completion, and a server to serve the tasks/* methods.
//
// See https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/draft/tasks.md.
const ExtensionTasks = "io.modelcontextprotocol/tasks"

// UnsupportedTaskResultError reports that a peer answered a request with a task
// handle from the [ExtensionTasks] extension, which this SDK cannot resolve.
type UnsupportedTaskResultError struct {
// TaskID identifies the created task, for manual polling or cancellation.
TaskID string
}

func (e *UnsupportedTaskResultError) Error() string {
return fmt.Sprintf("peer created task %q: the %s extension is not implemented", e.TaskID, ExtensionTasks)
}
Loading