Skip to content
Merged
111 changes: 111 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -775,3 +775,114 @@ go generate ./...
You need `go` installed and the `goimports` tool available. The generator runs
`goimports` automatically to format and fix imports.

### Auto-completions

When users are filling in argument values for a specific prompt (identified by name) or resource template (identified by URI), servers can provide contextual suggestions.
To enable completion support, use the `server.WithCompletions()` option when creating your server.

#### Completion Providers

You can provide completion logic for both prompt arguments and resource template arguments by implementing the respective interfaces and passing them to the server as options.

<details>
<summary>Show Completion Provider Examples</summary>

```go
type MyPromptCompletionProvider struct{}

func (p *MyPromptCompletionProvider) CompletePromptArgument(
ctx context.Context,
promptName string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Example: provide style suggestions for a "code_review" prompt
if promptName == "code_review" && argument.Name == "style" {
styles := []string{"formal", "casual", "technical", "creative"}
var suggestions []string

// Filter based on current input
for _, style := range styles {
if strings.HasPrefix(style, argument.Value) {
suggestions = append(suggestions, style)
}
}

return &mcp.Completion{
Values: suggestions,
}, nil
}

// Return empty suggestions for unhandled cases
return &mcp.Completion{Values: []string{}}, nil
}

type MyResourceCompletionProvider struct{}

func (p *MyResourceCompletionProvider) CompleteResourceArgument(
ctx context.Context,
uri string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Example: provide file path completions
if uri == "file:///{path}" && argument.Name == "path" {
// You can access previously completed arguments from context.Arguments
// context.Arguments is a map[string]string of already-resolved arguments

paths := getMatchingPaths(argument.Value) // Your custom logic

return &mcp.Completion{
Values: paths[:min(len(paths), 100)], // Max 100 items
Total: len(paths), // Total available matches
HasMore: len(paths) > 100, // More results available
}, nil
}

return &mcp.Completion{Values: []string{}}, nil
}

// Register the provider
mcpServer := server.NewMCPServer(
"my-server",
"1.0.0",
server.WithCompletions(),
server.WithPromptCompletionProvider(&MyPromptCompletionProvider{}),
server.WithResourceCompletionProvider(&MyResourceCompletionProvider{}),
)
```

</details>

#### Completion Context

For prompts or resource templates with multiple arguments, the `CompleteContext` parameter provides access to previously completed arguments. This allows you to provide contextual suggestions based on earlier choices.

<details>
<summary>Show Completion Context Example</summary>

```go
func (p *MyProvider) CompleteResourceArgument(
ctx context.Context,
uri string,
argument mcp.CompleteArgument,
context mcp.CompleteContext,
) (*mcp.Completion, error) {
// Access previously completed arguments
if previousValue, ok := context.Arguments["previous_arg"]; ok {
// Provide suggestions based on previous_arg value
return getSuggestionsFor(argument.Value, previousValue), nil
}

return &mcp.Completion{Values: []string{}}, nil
}
```

</details>

#### Response Constraints

When returning completion results:
- Maximum 100 items per response
- Use `Total` to indicate the total number of available matches
- Use `HasMore` to signal if additional results exist beyond the returned values
42 changes: 42 additions & 0 deletions examples/everything/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import "strconv"

func pow(n int, p int) int {
result := 1
for p > 0 {
if (p & 1) == 1 {
result *= n
}
n *= n
p >>= 1
}
return result
}

// n < 10000 is 4 digits
var maxDigits = 4
var maxId = pow(10, maxDigits)

func isIdInRange(num int) bool {
return num >= 0 && num < maxId
}

// getIdSuggestions returns 10 suggestions for a given number.
// It returns the current argument value appended with 0-9.
func getIdSuggestions(num int) []string {
suggestions := make([]string, 0, 10)
for i := range 10 {
suggestions = append(suggestions, strconv.Itoa(num*10+i))
}
return suggestions
}

func getTotalIds(digits int) int {
// Leftover digits represent the total number of choices.
return pow(10, maxDigits-digits)
}

func hasMoreIds(digits int) bool {
return digits < maxDigits-1
}
71 changes: 71 additions & 0 deletions examples/everything/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"log"
"strconv"
"strings"
"time"

"github.com/mark3labs/mcp-go/mcp"
Expand Down Expand Up @@ -69,6 +70,9 @@ func NewMCPServer() *server.MCPServer {
server.WithToolCapabilities(true),
server.WithLogging(),
server.WithHooks(hooks),
server.WithCompletions(),
server.WithPromptCompletionProvider(&promptCompletionProvider{}),
server.WithResourceCompletionProvider(&resourceCompletionProvider{}),
)

mcpServer.AddResource(mcp.NewResource("test://static/resource",
Expand Down Expand Up @@ -501,13 +505,80 @@ func handleGetTinyImageTool(
}, nil
}

var styles = []string{"formal", "casual", "technical", "creative"}

func handleNotification(
ctx context.Context,
notification mcp.JSONRPCNotification,
) {
log.Printf("Received notification: %s", notification.Method)
}

type promptCompletionProvider struct{}

func (p *promptCompletionProvider) CompletePromptArgument(ctx context.Context, promptName string, argument mcp.CompleteArgument, context mcp.CompleteContext) (*mcp.Completion, error) {
switch promptName {
case string(COMPLEX):
if argument.Name == "style" {
var suggestions []string
for _, style := range styles {
if argument.Value == "" || strings.HasPrefix(style, argument.Value) {
suggestions = append(suggestions, style)
}
}
return &mcp.Completion{
Values: suggestions,
}, nil
}
fallthrough
default:
return &mcp.Completion{
Values: []string{},
}, nil
}
}

type resourceCompletionProvider struct{}

// CompleteResourceArgument here is implemented to return suggestions for "test://dynamic/resource/{id}" template, specifically for the "id" argument.
func (p *resourceCompletionProvider) CompleteResourceArgument(ctx context.Context, uri string, argument mcp.CompleteArgument, context mcp.CompleteContext) (*mcp.Completion, error) {
if uri == "test://dynamic/resource/{id}" && argument.Name == "id" {
if len(argument.Value) == 0 {
return &mcp.Completion{
Values: getIdSuggestions(0),
Total: maxId,
HasMore: true,
}, nil
}

// If the input is not a number, return empty suggestion.
intArgVal, err := strconv.Atoi(argument.Value)
if err != nil {
return &mcp.Completion{
Values: []string{},
}, nil
}

digits := len(argument.Value)

// If the input is out of range, return empty suggestion.
if !isIdInRange(intArgVal) {
return &mcp.Completion{
Values: []string{},
}, nil
}

return &mcp.Completion{
Values: getIdSuggestions(intArgVal),
Total: getTotalIds(digits),
HasMore: hasMoreIds(digits),
}, nil
}
return &mcp.Completion{
Values: []string{},
}, nil
}

func main() {
var transport string
flag.StringVar(&transport, "t", "stdio", "Transport type (stdio or http)")
Expand Down
Loading