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
17 changes: 8 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,31 +49,30 @@ go work init ./project ./go-sdk

### Conformance tests

The SDK includes a script to run the official MCP conformance tests against the
SDK's conformance server:
The SDK includes scripts to run the official MCP server and client conformance tests:

```sh
./scripts/conformance.sh
./scripts/server-conformance.sh
./scripts/client-conformance.sh
```

By default, results are cleaned up after the script runs. To save results to a
specific directory:
To save server results to a specific directory:

```sh
./scripts/conformance.sh --result_dir ./conformance-results
./scripts/server-conformance.sh --result_dir ./conformance-results
```

To run against a local checkout of the
[conformance repo](https://github.com/modelcontextprotocol/conformance) instead
of the latest npm release:

```sh
./scripts/conformance.sh --conformance_repo ~/src/conformance
./scripts/server-conformance.sh --conformance_repo ~/src/conformance
```

Note: you must run `npm install` in the conformance repo first.

Run `./scripts/conformance.sh --help` for more options.
Run either script with `--help` for more options.

## Filing issues

Expand Down Expand Up @@ -200,7 +199,7 @@ change therefore cannot reach existing users by accident; they have to change
their import path to receive one.

This policy covers the exported API of the SDK's importable packages — `mcp`,
`jsonrpc`, `auth`, `auth/extauth` and `oauthex`. Everything under `internal/`
`jsonrpc`, `auth`, `auth/extauth`, `oauthex` and `skills`. Everything under `internal/`
is not importable outside the module and may change in any release.

Which MCP spec revisions each SDK version speaks is documented in the
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ The SDK consists of several importable packages:
- The
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
package provides some primitives for supporting OAuth.
- The
[`github.com/modelcontextprotocol/go-sdk/auth/extauth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth/extauth)
package provides OAuth handlers for authorization extensions.
- The
[`github.com/modelcontextprotocol/go-sdk/oauthex`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/oauthex)
package provides extensions to the OAuth protocol, such as ProtectedResourceMetadata.
- The
[`github.com/modelcontextprotocol/go-sdk/skills`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/skills)
package provides opt-in Skills extension support for discovery, directory
browsing, and content verification. See the [client](docs/client.md#skills-extension)
and [server](docs/server.md#skills-extension) examples.

The SDK endeavors to implement the full MCP spec. The [`docs/`](/docs/) directory
contains feature documentation, mapping the MCP spec to the packages above.
Expand Down
98 changes: 98 additions & 0 deletions conformance/skills-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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.

// This fixture exercises the generic Skills API without a filesystem provider.
// Run it against the three sep-2640-skills-* server conformance scenarios.
package main

import (
"context"
"crypto/sha256"
"flag"
"fmt"
"log"
"net/http"
"path"
"strings"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/go-sdk/skills"
)

func main() {
addr := flag.String("http", "localhost:18299", "HTTP listen address")
stateless := flag.Bool("stateless", true, "Use the modern stateless protocol")
flag.Parse()
server := mcp.NewServer(&mcp.Implementation{Name: "skills-conformance", Version: "v1"}, nil)
files := map[string]string{
"skill://demo/SKILL.md": "---\nname: demo\ndescription: A demonstration skill.\nmetadata:\n author: go-sdk\n---\n# Demo\nRead references/guide.md as needed.\n",
"skill://demo/references/guide.md": "# Guide\nSupporting content.\n",
"skill://demo/nested/SKILL.md": "---\nname: nested\ndescription: A nested skill.\n---\n# Nested\n",
"skill://other/SKILL.md": "---\nname: other\ndescription: Another skill.\n---\n# Other\n",
}
var entries []*skills.Skill
byURI := map[string]*skills.Skill{}
for _, item := range []struct{ uri, name, description string }{
{"skill://demo/SKILL.md", "demo", "A demonstration skill."},
{"skill://demo/nested/SKILL.md", "nested", "A nested skill."},
{"skill://other/SKILL.md", "other", "Another skill."},
} {
var resources []*skills.Resource
prefix := strings.TrimSuffix(item.uri, "SKILL.md")
for uri, content := range files {
if strings.HasPrefix(uri, prefix) {
resources = append(resources, &skills.Resource{URI: uri, Digest: fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(content))), Size: int64(len(content))})
}
}
entry := &skills.Skill{URI: item.uri, Frontmatter: skills.Frontmatter{"name": item.name, "description": item.description}, Resources: skills.StaticResources(resources...)}
if item.name == "demo" {
entry.Frontmatter["metadata"] = map[string]string{"author": "go-sdk"}
}
entries = append(entries, entry)
byURI[item.uri] = entry
}
directories := map[string][]*mcp.Resource{"skill://demo/empty": {}}
for uri, content := range files {
resource := &mcp.Resource{URI: uri, Name: path.Base(uri), MIMEType: "text/markdown"}
if entry, ok := byURI[uri]; ok {
resource.Name = entry.Frontmatter["name"].(string)
resource.Description = entry.Frontmatter["description"].(string)
}
server.AddResource(resource, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{{URI: uri, MIMEType: "text/markdown", Text: content}}}, nil
})
// path.Dir would clean "skill://" down to "skill:/".
parent := uri[:strings.LastIndex(uri, "/")]
directories[parent] = append(directories[parent], resource)
}
for _, uri := range []string{"skill://demo/references", "skill://demo/nested", "skill://demo/empty"} {
directories["skill://demo"] = append(directories["skill://demo"], &mcp.Resource{URI: uri, Name: path.Base(uri), MIMEType: "inode/directory"})
}
if err := skills.AddHandlers(server, &skills.Handlers{
List: func(_ context.Context, _ *mcp.ServerSession, p *skills.ListSkillsParams) (*skills.ListSkillsResult, error) {
page, next, err := skills.PaginateSkills(entries, p.Cursor, 1)
return &skills.ListSkillsResult{Skills: page, NextCursor: next}, err
},
Get: func(_ context.Context, _ *mcp.ServerSession, p *skills.GetSkillParams) (*skills.GetSkillResult, error) {
for _, entry := range entries {
if entry.URI == p.URI {
return &skills.GetSkillResult{Skill: entry}, nil
}
}
return nil, nil
},
ReadDirectory: func(_ context.Context, _ *mcp.ServerSession, p *skills.ReadDirectoryParams) (*skills.ReadDirectoryResult, error) {
children, ok := directories[p.URI]
if !ok {
return nil, nil
}
page, next, err := skills.PaginateDirectoryResources(children, p.Cursor, 1)
return &skills.ReadDirectoryResult{Resources: page, NextCursor: next}, err
},
}, nil); err != nil {
log.Fatal(err)
}
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, &mcp.StreamableHTTPOptions{Stateless: *stateless})
log.Fatal(http.ListenAndServe(*addr, handler))
}
19 changes: 16 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ The SDK consists of several importable packages:
- The
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
package provides some primitives for supporting OAuth.
- The
[`github.com/modelcontextprotocol/go-sdk/auth/extauth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth/extauth)
package provides OAuth handlers for authorization extensions.
- The
[`github.com/modelcontextprotocol/go-sdk/oauthex`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/oauthex)
package provides extensions to the OAuth protocol, such as ProtectedResourceMetadata.
- The
[`github.com/modelcontextprotocol/go-sdk/skills`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/skills)
package provides opt-in Skills extension support for discovery, directory
browsing, and content verification.


These docs mirror the [official MCP spec](https://modelcontextprotocol.io/specification/2025-06-18).
Use the index below to learn how the SDK implements a particular aspect of the
protocol.
These docs describe the SDK's implementation of the
[MCP specification](https://modelcontextprotocol.io/specification/2026-07-28)
and optional extensions. See the [version compatibility table](../README.md#version-compatibility)
for supported protocol revisions. Use the index below to learn how the SDK
implements a particular feature.

## Base Protocol

Expand All @@ -41,12 +50,16 @@ protocol.
1. [Roots](client.md#roots)
1. [Sampling](client.md#sampling)
1. [Elicitation](client.md#elicitation)
1. [Extensions](client.md#extensions)
1. [Skills](client.md#skills-extension)

## Server Features

1. [Prompts](server.md#prompts)
1. [Resources](server.md#resources)
1. [Tools](server.md#tools)
1. [Extensions](server.md#extensions)
1. [Skills](server.md#skills-extension)
1. [Utilities](server.md#utilities)
1. [Completion](server.md#completion)
1. [Logging](server.md#logging)
Expand Down
113 changes: 112 additions & 1 deletion docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,5 +544,116 @@ client := mcp.NewClient(impl, &mcp.ClientOptions{
adds an `extensions` map to `ClientCapabilities` and `ServerCapabilities` so
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.
are per-extension settings objects. Extensions require explicit opt-in.

#### Skills extension

The [`skills`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/skills)
package provides typed calls for the
[Skills extension](https://github.com/modelcontextprotocol/ext-skills/blob/main/specification/stable/skills.mdx).
Register methods before connecting, then bind the skills client to the connected
session. This example connects to the server from the
[server example](server.md#skills-extension) over an in-memory transport:

```go
ctx := context.Background()
client := mcp.NewClient(&mcp.Implementation{Name: "skills-client", Version: "v1.0.0"}, nil)
if err := skills.AddMethods(client); err != nil {
log.Fatal(err)
}

serverTransport, clientTransport := mcp.NewInMemoryTransports()
serverSession, err := server.Connect(ctx, serverTransport, nil)
if err != nil {
log.Fatal(err)
}
defer serverSession.Close()
session, err := client.Connect(ctx, clientTransport, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()

skillClient := &skills.Client{Session: session}
for skill, err := range skillClient.All(ctx, nil) {
if err != nil {
log.Fatal(err)
}
fmt.Println(skill.URI, skill.Frontmatter["description"])
}
```

`List`, `Get`, and `All` share `skillClient.Limits`:

| Configuration | Manifest limits |
| --- | --- |
| Omitted or `Limits: skills.Limits{}` | No count or size caps |
| `Limits: skills.BaselineLimits()` | 512 resources and 16 MiB per skill |
| Positive fields in a supplied `Limits` | Exact caps for those dimensions |
| Zero fields in a supplied `Limits` | Those dimensions are unlimited |
| Negative fields | Configuration error |

Structural validation always runs. The spec's limits are an interoperability
baseline: hosts must support at least that much and may support more. They are
not mandatory rejection thresholds. To opt into caps based on that baseline:

```go
limits := skills.BaselineLimits()
limits.MaxTotalSize = 32 << 20
skillClient = &skills.Client{Session: session, Limits: limits}
```

A literal containing only `MaxTotalSize` leaves resource count unlimited.
`BaselineLimits()` follows the spec supported by the installed SDK version.
Supply explicit numeric values to pin application policy across upgrades.
Caps below the baseline reduce what the host can accept.

Servers and clients configure these limits independently. Each call captures the
configured limits before sending its request, and `All` captures them when the
iterator is created. Do not mutate the client during use.

These caps apply to static manifests. For dynamic skills, applications manage
their own download, storage, and context budgets; the SDK does not retrieve files
or maintain cumulative size or file counts.

`ReadDirectory` and `DirectoryEntries` expose optional
directory browsing when the server advertises `directoryRead: true`. Calls fail
if the required server capabilities are absent. Iterators follow cursors without
modifying request parameters and stop after the first error.

Listing does not fetch content. Read files on demand with `session.ReadResource`
and check them with `skills.VerifyResource` or `skills.VerifySkillMD` before use.
A listed entry is complete; `Get` also retrieves a skill directly by URI even
when it was not listed. For example, when the user chooses to load a known skill:

```go
result, err := skillClient.Get(ctx, &skills.GetSkillParams{URI: "skill://greeting/SKILL.md"})
if err != nil {
log.Fatal(err)
}
resource, err := session.ReadResource(ctx, &mcp.ReadResourceParams{URI: result.Skill.URI})
if err != nil {
log.Fatal(err)
}
if len(resource.Contents) != 1 || resource.Contents[0] == nil || resource.Contents[0].URI != result.Skill.URI || resource.Contents[0].Blob != nil {
log.Fatal("expected one text resource for SKILL.md")
}
if err := skills.VerifySkillMD(result.Skill, []byte(resource.Contents[0].Text)); err != nil {
log.Fatal(err)
}
fmt.Println("verified", result.Skill.URI)
```

Keep skill entries scoped to their originating session: equal URIs from different
servers are different skills. Use a host-assigned server identity when persisting
entries or approvals. Directory results are live observations; they do not expand
the files authorized by a held manifest.

`VerifyResource` checks manifest membership, byte length, and SHA-256 digest.
`VerifySkillMD` also compares every frontmatter field. JSON frontmatter numbers
are decoded as `json.Number` to preserve integer precision. For dynamic manifests,
`VerifySkillMD` still checks frontmatter and returns `skills.ErrDynamicResources`
only when it matches; malformed or mismatched frontmatter returns a different error.
Applications decide whether to accept content without integrity verification and
own skill approval and execution policy. A digest match alone does not make remote
instructions trustworthy.
Loading
Loading