testmcp

package
v0.4.0-rc1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2025 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const ToolAddOrDeleteDummyResourceName = "add_or_delete_dummy_resource"
View Source
const ToolAddPromptName = "add_prompt"

ToolAddPromptName is the name of the tool that adds a prompt dynamically which triggers the prompt handler.

View Source
const ToolNotificationCountsName = "notification_counts"
View Source
const ToolResourceUpdateNotificationName = "resource_update_notification"

Variables

View Source
var (
	DummyResource = &mcp.Resource{
		Name:     "dummy-resource",
		MIMEType: "text/plain",
		URI:      "file:///dummy.txt",
	}

	AnotherDummyResource = &mcp.Resource{
		Name:     "another-dummy-resource",
		MIMEType: "text/plain",
		URI:      "file:///another-dummy.txt",
	}

	DummyResourceTemplate = &mcp.ResourceTemplate{
		Name:        "dummy-template",
		Description: "A dummy resource template for testing",
		MIMEType:    "text/plain",
		Title:       "Dummy Template",
		URITemplate: "file:///{name}.txt",
	}
)
View Source
var CodeReviewPrompt = &mcp.Prompt{
	Name:        "code_review",
	Description: "do a code review",
	Arguments:   []*mcp.PromptArgument{{Name: "Code", Required: true}},
}
View Source
var DummyPrompt = &mcp.Prompt{
	Name:        "dummy",
	Description: "a dummy prompt that does nothing",
	Arguments:   []*mcp.PromptArgument{},
}
View Source
var ToolContainsRootTool = TestTool[ToolContainsRootToolArgs, any]{
	Tool: &mcp.Tool{
		Name:        "contains_root",
		Description: "Check if a root with the given name exists",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"expected_root_name": {Type: "string", Description: "Expression to filter root names"},
			},
			Required: []string{"expected_root_name"},
		},
	},
	Handler: func(ctx context.Context, request *mcp.CallToolRequest, args ToolContainsRootToolArgs) (*mcp.CallToolResult, any, error) {
		rs, err := request.Session.ListRoots(ctx, &mcp.ListRootsParams{})
		if err != nil {
			return nil, nil, err
		}
		has := false
		for _, r := range rs.Roots {
			if r.Name == args.ExpectedRootName {
				has = true
				break
			}
		}
		if !has {
			return &mcp.CallToolResult{
				IsError: true,
				Content: []mcp.Content{
					&mcp.TextContent{Text: fmt.Sprintf("root %q not found", args.ExpectedRootName)},
				},
			}, nil, nil
		}
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: fmt.Sprintf("root %q found", args.ExpectedRootName)},
			},
		}, nil, nil
	},
}

ToolContainsRootTool - contains_root { expected_root_name: string } -> text.

This calls the server->client ListRoots method to check if a root with the given name exists. That will issue client->server JsonRPC response on the request path.

View Source
var ToolCountDown = TestTool[ToolCountDownArgs, any]{
	Tool: &mcp.Tool{
		Name:        "countdown",
		Description: "Count down from a given number to zero",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"from": {Type: "integer", Description: "Number to count down from"},
			},
			Required: []string{"from"},
		},
	},
	Handler: func(ctx context.Context, request *mcp.CallToolRequest, args ToolCountDownArgs) (*mcp.CallToolResult, any, error) {
		interval, err := time.ParseDuration(args.Interval)
		if err != nil {
			return nil, nil, err
		}

		go func() {
			for i := args.From; i >= 0; i-- {

				err := request.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
					Message:       fmt.Sprintf("count down: %d", i),
					ProgressToken: args.From - i,
				})
				if err != nil {
					return
				}

				err = request.Session.Log(ctx, &mcp.LoggingMessageParams{
					Level: "debug",
					Data:  `debug count down: ` + fmt.Sprint(i),
				})
				if err != nil {
					return
				}
				err = request.Session.Log(ctx, &mcp.LoggingMessageParams{
					Level: "error",
					Data:  `count down: ` + fmt.Sprint(i),
				})
				if err != nil {
					return
				}
				time.Sleep(interval)
			}
		}()

		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: "Done!"},
			},
		}, nil, nil
	},
}
View Source
var ToolCreateMessage = TestTool[struct{}, any]{
	Tool: &mcp.Tool{
		Name:        "create_message",
		Description: "Create a message",
		InputSchema: &jsonschema.Schema{
			Type:       "object",
			Properties: map[string]*jsonschema.Schema{},
		},
	},
	Handler: func(ctx context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
		_, err := req.Session.CreateMessage(ctx, &mcp.CreateMessageParams{
			Messages: []*mcp.SamplingMessage{
				{
					Content: &mcp.TextContent{
						Text: "You are a coding AI assistant.",
					},
					Role: "system",
				},
				{
					Content: &mcp.TextContent{
						Text: "Build a MCP Gateway using Envoy AI Gateway.",
					},
					Role: "user",
				},
			},
			Meta: map[string]any{
				"progressToken": "sampling-foo",
			},
		})
		if err != nil {
			return nil, nil, err
		}
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: "Please create a message."},
			},
		}, nil, nil
	},
}

ToolCreateMessage - create_message {} -> text.

This tool simply asks the model to create a message, which will trigger a CreateMessageRequest.

View Source
var ToolDelay = TestTool[ToolDelayArgs, any]{
	Tool: &mcp.Tool{
		Name:        "delay",
		Description: "Delay for a given duration",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"duration": {Type: "string", Description: "Duration to delay (e.g.}, '2s', '500ms')"},
			},
			Required: []string{"duration"},
		},
	},
	Handler: func(_ context.Context, _ *mcp.CallToolRequest, args ToolDelayArgs) (*mcp.CallToolResult, any, error) {
		d, err := time.ParseDuration(args.Duration)
		if err != nil {
			return nil, nil, err
		}
		time.Sleep(d)
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: fmt.Sprintf("Done after %s!", d)},
			},
		}, nil, nil
	},
}

ToolDelay - delay { duration: string } -> text.

This tool simply waits for the specified duration before returning.

View Source
var ToolDumbEcho = TestTool[ToolEchoArgs, any]{
	Tool: &mcp.Tool{
		Name:        "dumb_echo",
		Description: "Echo back the provided text with an unnecessary prefix",
		InputSchema: &jsonschema.Schema{
			Type:       "object",
			Properties: map[string]*jsonschema.Schema{"text": {Type: "string", Description: "Text to echo"}},
			Required:   []string{"text"},
		},
	},
	Handler: func(_ context.Context, _ *mcp.CallToolRequest, args ToolEchoArgs) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "dumb echo: " + args.Text}}}, nil, nil
	},
}
View Source
var ToolEcho = TestTool[ToolEchoArgs, any]{
	Tool: &mcp.Tool{
		Name:        "echo",
		Description: "Echo back the provided text",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"text": {Type: "string", Description: "Text to echo"},
			},
			Required: []string{"text"},
		},
	},
	Handler: func(_ context.Context, _ *mcp.CallToolRequest, args ToolEchoArgs) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: args.Text},
			},
		}, nil, nil
	},
}

ToolEcho - echo { text: string } -> text.

View Source
var ToolElicitEmail = TestTool[struct{}, any]{
	Tool: &mcp.Tool{
		Name:        "elicit_email",
		Description: "Log the provided email address",
		InputSchema: &jsonschema.Schema{
			Type:       "object",
			Properties: map[string]*jsonschema.Schema{},
		},
	},
	Handler: func(ctx context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
		_, err := req.Session.Elicit(ctx, &mcp.ElicitParams{
			Message: "Please collect the user name and email.",
		})
		if err != nil {
			return nil, nil, err
		}
		return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: "done"}},
		}, nil, nil
	},
}

ToolElicitEmail - elicit_email {} -> text.

This tool simply logs the provided email address.

View Source
var ToolError = TestTool[ToolErrorArgs, any]{
	Tool: &mcp.Tool{
		Name:        "error",
		Description: "Return an error",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"error": {
					Type:        "string",
					Description: "Error message to return from the tool",
					MinLength:   ptr(2),
				},
			},
			Required: []string{"error"},
		},
	},
	Handler: func(_ context.Context, _ *mcp.CallToolRequest, args ToolErrorArgs) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{
			IsError: true,
			Content: []mcp.Content{
				&mcp.TextContent{Text: args.Error},
			},
		}, nil, nil
	},
}

ToolError - error { tool_error: string, mcp_error: string } -> error.

View Source
var ToolSum = TestTool[ToolSumArgs, any]{
	Tool: &mcp.Tool{
		Name:        "sum",
		Description: "Return a + b",
		InputSchema: &jsonschema.Schema{
			Type: "object",
			Properties: map[string]*jsonschema.Schema{
				"a": {Type: "number", Description: "First addend"},
				"b": {Type: "number", Description: "Second addend"},
			},
			Required: []string{"a", "b"},
		},
	},
	Handler: func(_ context.Context, _ *mcp.CallToolRequest, args ToolSumArgs) (*mcp.CallToolResult, any, error) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				&mcp.TextContent{Text: fmt.Sprintf("%g", args.A+args.B)},
			},
		}, nil, nil
	},
}

ToolSum - sum { a: number, b: number } -> number.

Functions

func DummyResourceHandler

func DummyResourceHandler() mcp.ResourceHandler

func NewServer

func NewServer(opts *Options) *http.Server

NewServer starts a demo MCP server with two tools: echo and sum.

When forceJSONResponse true, the server will respond with JSON responses instead of using text/even-stream. The spec allows both so it is useful for us to test both scenarios.

When dumbEchoServer is true, the server will only implement the echo tool, and will not implement any prompts or resources. This is useful for testing basic routing.

Types

type Options

type Options struct {
	Port                              int
	ForceJSONResponse, DumbEchoServer bool
	WriteTimeout                      time.Duration
}

type TestTool

type TestTool[In, Out any] struct {
	Tool    *mcp.Tool
	Handler mcp.ToolHandlerFor[In, Out]
}

TestTool combines a tool definition and its handler for testing.

type ToolAddOrDeleteAnotherDummyResourceArgs

type ToolAddOrDeleteAnotherDummyResourceArgs struct {
	Delete bool `json:"delete,omitempty"`
}

ToolAddOrDeleteAnotherDummyResourceArgs defines the arguments for the add_or_delete_dummy_resource tool.

type ToolContainsRootToolArgs

type ToolContainsRootToolArgs struct {
	ExpectedRootName string `json:"expected_root_name,omitempty"`
}

type ToolCountDownArgs

type ToolCountDownArgs struct {
	From     int    `json:"from,omitempty"`
	Interval string `json:"Interval,omitempty"`
}

type ToolDelayArgs

type ToolDelayArgs struct {
	Duration string `json:"duration,omitempty"`
}

type ToolEchoArgs

type ToolEchoArgs struct {
	Text string `json:"text,omitempty"`
}

ToolEchoArgs defines the arguments for the echo tool.

type ToolErrorArgs

type ToolErrorArgs struct {
	Error string `json:"error,omitempty"`
}

ToolErrorArgs defines the arguments for the error tool.

type ToolResourceUpdateNotificationArgs

type ToolResourceUpdateNotificationArgs struct {
	URI string `json:"uri"`
}

type ToolSumArgs

type ToolSumArgs struct {
	A float64 `json:"a,omitempty"`
	B float64 `json:"b,omitempty"`
}

ToolSumArgs defines the arguments for the sum tool.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL