188 lines
5 KiB
Go
188 lines
5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/joho/godotenv"
|
|
|
|
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
|
|
x402 "github.com/x402-foundation/x402/go/v2"
|
|
x402mcp "github.com/x402-foundation/x402/go/v2/mcp"
|
|
evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client"
|
|
uptoclient "github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/client"
|
|
evmsigners "github.com/x402-foundation/x402/go/v2/signers/evm"
|
|
)
|
|
|
|
const USDCContractAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
|
|
type LoggingRoundTripper struct {
|
|
Proxied http.RoundTripper
|
|
}
|
|
|
|
func (lrt *LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
var err error
|
|
|
|
if req.GetBody != nil {
|
|
rc, _ := req.GetBody()
|
|
req.Body = rc
|
|
}
|
|
|
|
reqDump, err := httputil.DumpRequestOut(req, true)
|
|
if err != nil {
|
|
log.Printf("Error dumping request: %v", err)
|
|
} else {
|
|
fmt.Printf("\n--- REQUEST ---\n%s\n----------------\n", reqDump)
|
|
}
|
|
|
|
resp, err := lrt.Proxied.RoundTrip(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
respDump, err := httputil.DumpResponse(resp, true)
|
|
if err != nil {
|
|
log.Printf("Error dumping response: %v", err)
|
|
} else {
|
|
fmt.Printf("\n--- RESPONSE ---\n%s\n----------------\n", respDump)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func callTool(x402Mcp *x402mcp.X402MCPClient, ctx context.Context, name string, args map[string]any) error {
|
|
result, err := x402Mcp.CallTool(ctx, name, args)
|
|
if err != nil {
|
|
return fmt.Errorf("%s MCP call failed: %w", name, err)
|
|
}
|
|
|
|
fmt.Printf("%s Response:\n", name)
|
|
for _, item := range result.Content {
|
|
fmt.Printf(" %s\n", item.Text)
|
|
}
|
|
if result.PaymentMade {
|
|
fmt.Printf("%s Payment made\n", name)
|
|
} else {
|
|
fmt.Printf("%s No payment required\n", name)
|
|
}
|
|
|
|
if result.IsError {
|
|
return fmt.Errorf("%s: tool returned an error result", name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if value, ok := os.LookupEnv(key); ok {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func main() {
|
|
_ = godotenv.Load()
|
|
privateKey := os.Getenv("PRIVATE_KEY")
|
|
serverURL := getEnv("TEST_SERVER_URL", "http://localhost:8080")
|
|
|
|
if privateKey == "" {
|
|
log.Fatal("PRIVATE_KEY environment variable is required")
|
|
}
|
|
|
|
signer, err := evmsigners.NewClientSignerFromPrivateKey(privateKey)
|
|
if err != nil {
|
|
log.Fatal("Failed to create EVM signer:", err)
|
|
}
|
|
|
|
debugClient := &http.Client{
|
|
Transport: &LoggingRoundTripper{
|
|
Proxied: http.DefaultTransport,
|
|
},
|
|
Timeout: 600 * time.Second,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
endpoint := strings.TrimRight(serverURL, "/") + "/mcp"
|
|
transport := &mcpsdk.StreamableClientTransport{
|
|
Endpoint: endpoint,
|
|
HTTPClient: debugClient,
|
|
DisableStandaloneSSE: true,
|
|
}
|
|
|
|
mcpClient := mcpsdk.NewClient(&mcpsdk.Implementation{
|
|
Name: "test-mcp-endpoints",
|
|
Version: "1.0.0",
|
|
}, nil)
|
|
|
|
session, err := mcpClient.Connect(ctx, transport, nil)
|
|
if err != nil {
|
|
log.Fatal("Failed to connect MCP client:", err)
|
|
}
|
|
defer func() { _ = session.Close() }()
|
|
|
|
x402Mcp := x402mcp.NewX402MCPClientFromConfig(session, []x402mcp.SchemeRegistration{
|
|
{Network: x402.Network("eip155:84532"), Client: evm.NewExactEvmScheme(signer, nil)},
|
|
{Network: x402.Network("eip155:84532"), Client: uptoclient.NewUptoEvmScheme(signer, nil)},
|
|
}, x402mcp.Options{})
|
|
|
|
log.Printf("Connected to MCP endpoint %s", endpoint)
|
|
|
|
log.Printf("Starting MCP endpoint tests against %s...", serverURL)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
args map[string]any
|
|
}{
|
|
{"getFeed", map[string]any{"uri": "https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur/feed/whats-hot"}},
|
|
{"getActor", map[string]any{"author": "did:plc:z72i7hdynmk6r22z27h6tvur"}},
|
|
{"searchActors", map[string]any{"query": "test", "limit": 5}},
|
|
{"getPost", map[string]any{"author": "sharkgamiart.bsky.social", "rkey": "3m47y27c67c2b"}},
|
|
{"getLatestPosts", map[string]any{"author": "sharkgamiart.bsky.social", "limit": 5}},
|
|
{"getPostsInTime", map[string]any{"author": "sharkgamiart.bsky.social", "startTime": "2024-01-01T00:00:00Z", "endTime": "2024-12-31T23:59:59Z", "limit": 5}},
|
|
{"getStyleProfile", map[string]any{"author": "did:plc:z72i7hdynmk6r22z27h6tvur"}},
|
|
{"findSimilarUsers", map[string]any{"author": "did:plc:z72i7hdynmk6r22z27h6tvur", "limit": 5}},
|
|
}
|
|
|
|
passed := 0
|
|
failed := 0
|
|
|
|
fmt.Printf("\n=== Testing listTools ===\n")
|
|
toolsResponse, err := session.ListTools(ctx, nil)
|
|
if err != nil {
|
|
log.Printf("FAIL listTools: %v\n", err)
|
|
failed++
|
|
} else {
|
|
for _, tool := range toolsResponse.Tools {
|
|
fmt.Printf(" tool: %s\n", tool.Name)
|
|
}
|
|
fmt.Printf("PASS listTools (%d tools listed)\n", len(toolsResponse.Tools))
|
|
passed++
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
fmt.Printf("\n=== Testing %s ===\n", tc.name)
|
|
err := callTool(x402Mcp, ctx, tc.name, tc.args)
|
|
if err != nil {
|
|
log.Printf("FAIL %s: %v\n", tc.name, err)
|
|
failed++
|
|
} else {
|
|
fmt.Printf("PASS %s\n", tc.name)
|
|
passed++
|
|
}
|
|
}
|
|
|
|
log.Printf("\n=== Test Summary ===")
|
|
log.Printf("Passed: %d", passed)
|
|
log.Printf("Failed: %d", failed)
|
|
|
|
if failed > 0 {
|
|
os.Exit(1)
|
|
}
|
|
}
|