first commit
This commit is contained in:
72
examples/mcps/edge-case-server/README.md
Normal file
72
examples/mcps/edge-case-server/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Edge Case MCP Server
|
||||
|
||||
MCP STDIO server optimized for testing edge cases and unusual scenarios.
|
||||
|
||||
## Tools
|
||||
|
||||
- **unicode_tool** - Returns Unicode text including emojis and right-to-left characters
|
||||
- **binary_data** - Returns binary-like data in various encodings (base64, hex, raw)
|
||||
- **empty_response** - Returns various types of empty responses (empty string, object, array, null)
|
||||
- **null_fields** - Returns responses with configurable null fields
|
||||
- **deeply_nested** - Returns deeply nested data structures up to specified depth
|
||||
- **special_chars** - Returns text with special characters (quotes, backslashes, newlines, control chars)
|
||||
- **zero_length** - Returns zero-length content
|
||||
- **extreme_sizes** - Returns data of various extreme sizes (tiny, normal, huge)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Run
|
||||
node dist/index.js
|
||||
```
|
||||
|
||||
## Integration Testing
|
||||
|
||||
This server is designed to test edge case handling in Bifrost's MCP integration via STDIO transport.
|
||||
|
||||
### Example Tool Calls
|
||||
|
||||
```typescript
|
||||
// Test Unicode handling
|
||||
{
|
||||
"name": "unicode_tool",
|
||||
"arguments": {
|
||||
"id": "test-1",
|
||||
"include_emojis": true,
|
||||
"include_rtl": true
|
||||
}
|
||||
}
|
||||
|
||||
// Test binary data
|
||||
{
|
||||
"name": "binary_data",
|
||||
"arguments": {
|
||||
"id": "test-2",
|
||||
"encoding": "base64"
|
||||
}
|
||||
}
|
||||
|
||||
// Test deeply nested structures
|
||||
{
|
||||
"name": "deeply_nested",
|
||||
"arguments": {
|
||||
"id": "test-3",
|
||||
"depth": 20
|
||||
}
|
||||
}
|
||||
|
||||
// Test special characters
|
||||
{
|
||||
"name": "special_chars",
|
||||
"arguments": {
|
||||
"id": "test-4",
|
||||
"char_type": "all"
|
||||
}
|
||||
}
|
||||
```
|
||||
17
examples/mcps/edge-case-server/go.mod
Normal file
17
examples/mcps/edge-case-server/go.mod
Normal file
@@ -0,0 +1,17 @@
|
||||
module github.com/maximhq/bifrost/examples/mcps/edge-case-server
|
||||
|
||||
go 1.26.2
|
||||
|
||||
require github.com/mark3labs/mcp-go v0.43.2
|
||||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
39
examples/mcps/edge-case-server/go.sum
Normal file
39
examples/mcps/edge-case-server/go.sum
Normal file
@@ -0,0 +1,39 @@
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
325
examples/mcps/edge-case-server/main.go
Normal file
325
examples/mcps/edge-case-server/main.go
Normal file
@@ -0,0 +1,325 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create MCP server
|
||||
s := server.NewMCPServer(
|
||||
"edge-case-server",
|
||||
"1.0.0",
|
||||
server.WithToolCapabilities(true),
|
||||
)
|
||||
|
||||
// Register all tools
|
||||
registerReturnUnicodeTool(s)
|
||||
registerReturnBinaryTool(s)
|
||||
registerReturnLargePayloadTool(s)
|
||||
registerReturnNestedStructureTool(s)
|
||||
registerReturnNullTool(s)
|
||||
registerReturnSpecialCharsTool(s)
|
||||
|
||||
// Start STDIO server
|
||||
if err := server.ServeStdio(s); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 1: return_unicode
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnUnicodeTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_unicode",
|
||||
mcp.WithDescription("Returns unicode strings of various types"),
|
||||
mcp.WithString("type",
|
||||
mcp.Required(),
|
||||
mcp.Description("Type of unicode to return"),
|
||||
mcp.Enum("emoji"),
|
||||
),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var args struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Get arguments using the proper method
|
||||
argsInterface := request.GetArguments()
|
||||
|
||||
// Marshal and unmarshal to convert to our struct
|
||||
argsBytes, err := json.Marshal(argsInterface)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(argsBytes, &args); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Invalid arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
var text string
|
||||
switch args.Type {
|
||||
case "emoji":
|
||||
text = "Hello 👋 World 🌍! Testing emoji: 🎉 🚀 💻 ❤️ 🔥"
|
||||
default:
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Unknown type: %s", args.Type)), nil
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"type": args.Type,
|
||||
"text": text,
|
||||
"length": len([]rune(text)),
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 2: return_binary
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnBinaryTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_binary",
|
||||
mcp.WithDescription("Returns binary data in specified encoding"),
|
||||
mcp.WithNumber("size",
|
||||
mcp.Required(),
|
||||
mcp.Description("Size of binary data in bytes"),
|
||||
),
|
||||
mcp.WithString("encoding",
|
||||
mcp.Required(),
|
||||
mcp.Description("Encoding for binary data"),
|
||||
mcp.Enum("base64", "hex"),
|
||||
),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var args struct {
|
||||
Size int `json:"size"`
|
||||
Encoding string `json:"encoding"`
|
||||
}
|
||||
|
||||
// Get arguments using the proper method
|
||||
argsInterface := request.GetArguments()
|
||||
|
||||
// Marshal and unmarshal to convert to our struct
|
||||
argsBytes, err := json.Marshal(argsInterface)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(argsBytes, &args); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Invalid arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
// Generate binary data (repeating pattern)
|
||||
data := make([]byte, args.Size)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
var encoded string
|
||||
switch args.Encoding {
|
||||
case "base64":
|
||||
encoded = base64.StdEncoding.EncodeToString(data)
|
||||
case "hex":
|
||||
encoded = hex.EncodeToString(data)
|
||||
default:
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Unknown encoding: %s", args.Encoding)), nil
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"size": args.Size,
|
||||
"encoding": args.Encoding,
|
||||
"data": encoded,
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 3: return_large_payload
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnLargePayloadTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_large_payload",
|
||||
mcp.WithDescription("Returns a large JSON payload"),
|
||||
mcp.WithNumber("size_kb",
|
||||
mcp.Required(),
|
||||
mcp.Description("Approximate size in kilobytes"),
|
||||
),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var args struct {
|
||||
SizeKB int `json:"size_kb"`
|
||||
}
|
||||
|
||||
// Get arguments using the proper method
|
||||
argsInterface := request.GetArguments()
|
||||
|
||||
// Marshal and unmarshal to convert to our struct
|
||||
argsBytes, err := json.Marshal(argsInterface)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(argsBytes, &args); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Invalid arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
// Generate array of objects to reach target size
|
||||
targetSize := args.SizeKB * 1024
|
||||
items := []map[string]interface{}{}
|
||||
currentSize := 0
|
||||
|
||||
for currentSize < targetSize {
|
||||
item := map[string]interface{}{
|
||||
"id": len(items),
|
||||
"name": fmt.Sprintf("Item-%d", len(items)),
|
||||
"description": "This is a test item with some text to increase the payload size.",
|
||||
"value": len(items) * 100,
|
||||
"active": len(items)%2 == 0,
|
||||
"tags": []string{"tag1", "tag2", "tag3"},
|
||||
}
|
||||
items = append(items, item)
|
||||
|
||||
// Rough estimate of current size
|
||||
itemJSON, _ := json.Marshal(item)
|
||||
currentSize += len(itemJSON)
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"requested_size_kb": args.SizeKB,
|
||||
"item_count": len(items),
|
||||
"items": items,
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 4: return_nested_structure
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnNestedStructureTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_nested_structure",
|
||||
mcp.WithDescription("Returns deeply nested JSON structure"),
|
||||
mcp.WithNumber("depth",
|
||||
mcp.Required(),
|
||||
mcp.Description("Depth of nesting"),
|
||||
),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var args struct {
|
||||
Depth int `json:"depth"`
|
||||
}
|
||||
|
||||
// Get arguments using the proper method
|
||||
argsInterface := request.GetArguments()
|
||||
|
||||
// Marshal and unmarshal to convert to our struct
|
||||
argsBytes, err := json.Marshal(argsInterface)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(argsBytes, &args); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Invalid arguments: %v", err)), nil
|
||||
}
|
||||
|
||||
// Build nested structure
|
||||
nested := buildNestedStructure(args.Depth)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"depth": args.Depth,
|
||||
"data": nested,
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
|
||||
func buildNestedStructure(depth int) map[string]interface{} {
|
||||
if depth <= 0 {
|
||||
return map[string]interface{}{
|
||||
"level": 0,
|
||||
"value": "leaf node",
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"level": depth,
|
||||
"child": buildNestedStructure(depth - 1),
|
||||
"data": map[string]interface{}{
|
||||
"id": depth,
|
||||
"name": fmt.Sprintf("Level %d", depth),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 5: return_null
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnNullTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_null",
|
||||
mcp.WithDescription("Returns null/empty values in various forms"),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
response := map[string]interface{}{
|
||||
"null_value": nil,
|
||||
"empty_string": "",
|
||||
"empty_array": []interface{}{},
|
||||
"empty_object": map[string]interface{}{},
|
||||
"zero": 0,
|
||||
"false": false,
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOOL 6: return_special_chars
|
||||
// ============================================================================
|
||||
|
||||
func registerReturnSpecialCharsTool(s *server.MCPServer) {
|
||||
tool := mcp.NewTool("return_special_chars",
|
||||
mcp.WithDescription("Returns strings with special characters and escape sequences"),
|
||||
)
|
||||
|
||||
s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
response := map[string]interface{}{
|
||||
"quotes": `He said "Hello" and she said 'Hi'`,
|
||||
"backslashes": `C:\Users\Test\Path`,
|
||||
"newlines": "Line 1\nLine 2\nLine 3",
|
||||
"tabs": "Col1\tCol2\tCol3",
|
||||
"mixed": "Special: \t\n\r\\ \" ' / @ # $ % & * ( )",
|
||||
"unicode_escape": "\u0041\u0042\u0043", // ABC
|
||||
"control_chars": "\x00\x01\x02",
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(response)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
})
|
||||
}
|
||||
1161
examples/mcps/edge-case-server/package-lock.json
generated
Normal file
1161
examples/mcps/edge-case-server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
examples/mcps/edge-case-server/package.json
Normal file
24
examples/mcps/edge-case-server/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "edge-case-server",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP STDIO server optimized for testing edge cases and unusual scenarios",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"edge-case-server": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && chmod +x dist/index.js",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"overrides": {
|
||||
"hono": "4.12.14"
|
||||
}
|
||||
}
|
||||
456
examples/mcps/edge-case-server/src/index.ts
Normal file
456
examples/mcps/edge-case-server/src/index.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
|
||||
// Schemas for edge case test tools
|
||||
const UnicodeToolSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
include_emojis: z.boolean().optional().describe("Include emoji characters"),
|
||||
include_rtl: z.boolean().optional().describe("Include right-to-left text"),
|
||||
});
|
||||
|
||||
const BinaryDataSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
encoding: z.enum(["base64", "hex", "raw"]).optional(),
|
||||
});
|
||||
|
||||
const EmptyResponseSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
type: z.enum(["empty_string", "empty_object", "empty_array", "null"]).optional(),
|
||||
});
|
||||
|
||||
const NullFieldsSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
null_count: z.number().optional().describe("Number of null fields to include"),
|
||||
});
|
||||
|
||||
const DeeplyNestedSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
depth: z.number().optional().describe("Nesting depth (default 10)"),
|
||||
});
|
||||
|
||||
const SpecialCharsSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
char_type: z.enum(["quotes", "backslashes", "newlines", "control_chars", "all"]).optional(),
|
||||
});
|
||||
|
||||
const ZeroLengthSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
});
|
||||
|
||||
const ExtremeSizesSchema = z.object({
|
||||
id: z.string().describe("Tool invocation ID"),
|
||||
size_type: z.enum(["tiny", "normal", "huge"]).optional(),
|
||||
});
|
||||
|
||||
const server = new Server(
|
||||
{ name: "edge-case-server", version: "1.0.0" },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
{
|
||||
name: "unicode_tool",
|
||||
description: "Returns Unicode text including emojis and RTL characters",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
include_emojis: {
|
||||
type: "boolean",
|
||||
description: "Include emoji characters",
|
||||
},
|
||||
include_rtl: {
|
||||
type: "boolean",
|
||||
description: "Include right-to-left text",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary_data",
|
||||
description: "Returns binary-like data in various encodings",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
encoding: {
|
||||
type: "string",
|
||||
enum: ["base64", "hex", "raw"],
|
||||
description: "Data encoding format",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty_response",
|
||||
description: "Returns various types of empty responses",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
type: {
|
||||
type: "string",
|
||||
enum: ["empty_string", "empty_object", "empty_array", "null"],
|
||||
description: "Type of empty response",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "null_fields",
|
||||
description: "Returns responses with null fields",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
null_count: {
|
||||
type: "number",
|
||||
description: "Number of null fields to include",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deeply_nested",
|
||||
description: "Returns deeply nested data structures",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
depth: {
|
||||
type: "number",
|
||||
description: "Nesting depth (default 10)",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "special_chars",
|
||||
description: "Returns text with special characters that need escaping",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
char_type: {
|
||||
type: "string",
|
||||
enum: ["quotes", "backslashes", "newlines", "control_chars", "all"],
|
||||
description: "Type of special characters to include",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zero_length",
|
||||
description: "Returns zero-length content",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extreme_sizes",
|
||||
description: "Returns data of various extreme sizes",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
size_type: {
|
||||
type: "string",
|
||||
enum: ["tiny", "normal", "huge"],
|
||||
description: "Size category",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const toolName = request.params.name;
|
||||
|
||||
try {
|
||||
switch (toolName) {
|
||||
case "unicode_tool": {
|
||||
const args = UnicodeToolSchema.parse(request.params.arguments);
|
||||
let text = "Unicode test: ";
|
||||
|
||||
// Basic Unicode characters
|
||||
text += "Ω α β γ δ ε ζ η θ ";
|
||||
|
||||
if (args.include_emojis) {
|
||||
text += "😀 😎 🔧 🚀 🎉 🌟 💻 🐍 ";
|
||||
}
|
||||
|
||||
if (args.include_rtl) {
|
||||
text += "مرحبا 你好 שלום ";
|
||||
}
|
||||
|
||||
// Additional Unicode ranges
|
||||
text += "© ® ™ € £ ¥ ";
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "unicode_tool",
|
||||
id: args.id,
|
||||
unicode_text: text,
|
||||
include_emojis: args.include_emojis ?? false,
|
||||
include_rtl: args.include_rtl ?? false,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "binary_data": {
|
||||
const args = BinaryDataSchema.parse(request.params.arguments);
|
||||
const encoding = args.encoding || "base64";
|
||||
|
||||
const binaryData = Buffer.from("This is binary data \x00\x01\x02\x03\xff\xfe");
|
||||
let encodedData: string;
|
||||
|
||||
switch (encoding) {
|
||||
case "base64":
|
||||
encodedData = binaryData.toString("base64");
|
||||
break;
|
||||
case "hex":
|
||||
encodedData = binaryData.toString("hex");
|
||||
break;
|
||||
case "raw":
|
||||
encodedData = binaryData.toString("binary");
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "binary_data",
|
||||
id: args.id,
|
||||
encoding,
|
||||
data: encodedData,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "empty_response": {
|
||||
const args = EmptyResponseSchema.parse(request.params.arguments);
|
||||
const type = args.type || "empty_string";
|
||||
|
||||
let responseData: any;
|
||||
switch (type) {
|
||||
case "empty_string":
|
||||
responseData = "";
|
||||
break;
|
||||
case "empty_object":
|
||||
responseData = {};
|
||||
break;
|
||||
case "empty_array":
|
||||
responseData = [];
|
||||
break;
|
||||
case "null":
|
||||
responseData = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "empty_response",
|
||||
id: args.id,
|
||||
type,
|
||||
data: responseData,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "null_fields": {
|
||||
const args = NullFieldsSchema.parse(request.params.arguments);
|
||||
const nullCount = args.null_count || 3;
|
||||
|
||||
const response: any = {
|
||||
tool: "null_fields",
|
||||
id: args.id,
|
||||
};
|
||||
|
||||
// Add null fields
|
||||
for (let i = 0; i < nullCount; i++) {
|
||||
response[`null_field_${i + 1}`] = null;
|
||||
}
|
||||
|
||||
response.non_null_field = "This is not null";
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(response),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "deeply_nested": {
|
||||
const args = DeeplyNestedSchema.parse(request.params.arguments);
|
||||
const depth = args.depth || 10;
|
||||
|
||||
// Create deeply nested structure
|
||||
let nested: any = { value: "leaf" };
|
||||
for (let i = 0; i < depth; i++) {
|
||||
nested = {
|
||||
level: depth - i,
|
||||
child: nested,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "deeply_nested",
|
||||
id: args.id,
|
||||
depth,
|
||||
data: nested,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "special_chars": {
|
||||
const args = SpecialCharsSchema.parse(request.params.arguments);
|
||||
const charType = args.char_type || "all";
|
||||
|
||||
let text = "";
|
||||
|
||||
if (charType === "quotes" || charType === "all") {
|
||||
text += 'Text with "double quotes" and \'single quotes\' ';
|
||||
}
|
||||
|
||||
if (charType === "backslashes" || charType === "all") {
|
||||
text += "Path: C:\\Users\\Test\\file.txt ";
|
||||
}
|
||||
|
||||
if (charType === "newlines" || charType === "all") {
|
||||
text += "Line 1\nLine 2\r\nLine 3\tTabbed ";
|
||||
}
|
||||
|
||||
if (charType === "control_chars" || charType === "all") {
|
||||
text += "Control: \x00 \x01 \x1F ";
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "special_chars",
|
||||
id: args.id,
|
||||
char_type: charType,
|
||||
text,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "zero_length": {
|
||||
const args = ZeroLengthSchema.parse(request.params.arguments);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "extreme_sizes": {
|
||||
const args = ExtremeSizesSchema.parse(request.params.arguments);
|
||||
const sizeType = args.size_type || "normal";
|
||||
|
||||
let data: string;
|
||||
switch (sizeType) {
|
||||
case "tiny":
|
||||
data = "x";
|
||||
break;
|
||||
case "normal":
|
||||
data = "x".repeat(1000);
|
||||
break;
|
||||
case "huge":
|
||||
data = "x".repeat(1000000); // 1MB
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: "extreme_sizes",
|
||||
id: args.id,
|
||||
size_type: sizeType,
|
||||
data_length: data.length,
|
||||
data,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Edge Case MCP Server running on stdio");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Fatal error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
17
examples/mcps/edge-case-server/tsconfig.json
Normal file
17
examples/mcps/edge-case-server/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user