Skip to content
Merged
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
120 changes: 46 additions & 74 deletions www/docs/pages/transports/http.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import (
func main() {
s := server.NewMCPServer("StreamableHTTP API Server", "1.0.0",
server.WithToolCapabilities(true),
server.WithResourceCapabilities(true),
server.WithResourceCapabilities(true, true),
)

// Add RESTful tools
Expand All @@ -60,7 +60,7 @@ func main() {
mcp.WithDescription("Create a new user"),
mcp.WithString("name", mcp.Required()),
mcp.WithString("email", mcp.Required()),
mcp.WithInteger("age", mcp.Minimum(0)),
mcp.WithNumber("age", mcp.Min(0)),
),
handleCreateUser,
)
Expand All @@ -69,8 +69,8 @@ func main() {
mcp.NewTool("search_users",
mcp.WithDescription("Search users with filters"),
mcp.WithString("query", mcp.Description("Search query")),
mcp.WithInteger("limit", mcp.Default(10), mcp.Maximum(100)),
mcp.WithInteger("offset", mcp.Default(0), mcp.Minimum(0)),
mcp.WithNumber("limit", mcp.DefaultNumber(10), mcp.Max(100)),
mcp.WithNumber("offset", mcp.DefaultNumber(0), mcp.Min(0)),
),
handleSearchUsers,
)
Expand All @@ -95,21 +95,29 @@ func main() {
}

func handleGetUser(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
userID := req.Params.Arguments["user_id"].(string)
userID := req.GetString("user_id", "")
if userID == "" {
return nil, fmt.Errorf("user_id is required")
}

// Simulate database lookup
user, err := getUserFromDB(userID)
if err != nil {
return nil, fmt.Errorf("user not found: %s", userID)
}

return mcp.NewToolResultJSON(user), nil
return mcp.NewToolResultText(fmt.Sprintf(`{"id":"%s","name":"%s","email":"%s","age":%d}`,
user.ID, user.Name, user.Email, user.Age)), nil
}

func handleCreateUser(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name := req.Params.Arguments["name"].(string)
email := req.Params.Arguments["email"].(string)
age := int(req.Params.Arguments["age"].(float64))
name := req.GetString("name", "")
email := req.GetString("email", "")
age := req.GetInt("age", 0)

if name == "" || email == "" {
return nil, fmt.Errorf("name and email are required")
}

// Validate input
if !isValidEmail(email) {
Expand All @@ -129,11 +137,8 @@ func handleCreateUser(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
return nil, fmt.Errorf("failed to create user: %w", err)
}

return mcp.NewToolResultJSON(map[string]interface{}{
"id": user.ID,
"message": "User created successfully",
"user": user,
}), nil
return mcp.NewToolResultText(fmt.Sprintf(`{"id":"%s","message":"User created successfully","user":{"id":"%s","name":"%s","email":"%s","age":%d}}`,
user.ID, user.ID, user.Name, user.Email, user.Age)), nil
}

// Helper functions and types for the examples
Expand All @@ -156,7 +161,6 @@ func getUserFromDB(userID string) (*User, error) {
}

func isValidEmail(email string) bool {
// Simple email validation
return strings.Contains(email, "@") && strings.Contains(email, ".")
}

Expand All @@ -171,54 +175,38 @@ func saveUserToDB(user *User) error {
}

func handleSearchUsers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query := getStringParam(req.Params.Arguments, "query", "")
limit := int(getFloatParam(req.Params.Arguments, "limit", 10))
offset := int(getFloatParam(req.Params.Arguments, "offset", 0))
query := req.GetString("query", "")
limit := req.GetInt("limit", 10)
offset := req.GetInt("offset", 0)

// Search users with pagination
users, total, err := searchUsersInDB(query, limit, offset)
if err != nil {
return nil, fmt.Errorf("search failed: %w", err)
}

return mcp.NewToolResultJSON(map[string]interface{}{
"users": users,
"total": total,
"limit": limit,
"offset": offset,
"query": query,
}), nil
return mcp.NewToolResultText(fmt.Sprintf(`{"users":[{"id":"1","name":"John Doe","email":"[email protected]","age":30},{"id":"2","name":"Jane Smith","email":"[email protected]","age":25}],"total":%d,"limit":%d,"offset":%d,"query":"%s"}`,
total, limit, offset, query)), nil
}

func handleUserResource(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
func handleUserResource(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
userID := extractUserIDFromURI(req.Params.URI)

user, err := getUserFromDB(userID)
if err != nil {
return nil, fmt.Errorf("user not found: %s", userID)
}

return mcp.NewResourceResultJSON(user), nil
}

// Additional helper functions for parameter handling
func getStringParam(args map[string]interface{}, key, defaultValue string) string {
if val, ok := args[key]; ok && val != nil {
if str, ok := val.(string); ok {
return str
}
}
return defaultValue
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: req.Params.URI,
MIMEType: "application/json",
Text: fmt.Sprintf(`{"id":"%s","name":"%s","email":"%s","age":%d}`, user.ID, user.Name, user.Email, user.Age),
},
}, nil
}

func getFloatParam(args map[string]interface{}, key string, defaultValue float64) float64 {
if val, ok := args[key]; ok && val != nil {
if f, ok := val.(float64); ok {
return f
}
}
return defaultValue
}
// Additional helper functions

func searchUsersInDB(query string, limit, offset int) ([]*User, int, error) {
// Placeholder implementation
Expand All @@ -231,9 +219,8 @@ func searchUsersInDB(query string, limit, offset int) ([]*User, int, error) {

func extractUserIDFromURI(uri string) string {
// Extract user ID from URI like "users://123"
parts := strings.Split(uri, "://")
if len(parts) > 1 {
return parts[1]
if len(uri) > 8 && uri[:8] == "users://" {
return uri[8:]
}
return uri
}
Expand All @@ -244,39 +231,24 @@ func extractUserIDFromURI(uri string) string {
```go
func main() {
s := server.NewMCPServer("Advanced StreamableHTTP Server", "1.0.0",
server.WithAllCapabilities(),
server.WithRecovery(),
server.WithHooks(&server.Hooks{
OnToolCall: logToolCall,
OnResourceRead: logResourceRead,
}),
server.WithResourceCapabilities(true, true),
server.WithPromptCapabilities(true),
server.WithToolCapabilities(true),
server.WithLogging(),
)

// Configure StreamableHTTP-specific options
streamableHTTPOptions := server.StreamableHTTPOptions{
BasePath: "/api/v1/mcp",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxBodySize: 10 * 1024 * 1024, // 10MB
EnableCORS: true,
AllowedOrigins: []string{"https://myapp.com", "http://localhost:3000"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
EnableGzip: true,
TrustedProxies: []string{"10.0.0.0/8", "172.16.0.0/12"},
}

// Add middleware
addStreamableHTTPMiddleware(s)

// Add comprehensive tools
// Add comprehensive tools and resources
addCRUDTools(s)
addBatchTools(s)
addAnalyticsTools(s)

log.Println("Starting advanced StreamableHTTP server on :8080")
httpServer := server.NewStreamableHTTPServer(s, streamableHTTPOptions...)
httpServer := server.NewStreamableHTTPServer(s,
server.WithEndpointPath("/api/v1/mcp"),
server.WithHeartbeatInterval(30*time.Second),
server.WithStateLess(false),
)

if err := httpServer.Start(":8080"); err != nil {
log.Fatal(err)
}
Expand Down
81 changes: 60 additions & 21 deletions www/docs/pages/transports/inprocess.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,34 @@ func main() {
)

// Create in-process client
client := client.NewInProcessClient(s)
defer client.Close()
mcpClient, err := client.NewInProcessClient(s)
if err != nil {
log.Fatal(err)
}
defer mcpClient.Close()

ctx := context.Background()

// Initialize
if err := client.Initialize(ctx); err != nil {
_, err = mcpClient.Initialize(ctx, mcp.InitializeRequest{
Params: mcp.InitializeRequestParams{
ProtocolVersion: "2024-11-05",
Capabilities: mcp.ClientCapabilities{
Tools: &mcp.ToolsCapability{},
},
ClientInfo: mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
},
},
})
if err != nil {
log.Fatal(err)
}

// Use the calculator
result, err := client.CallTool(ctx, mcp.CallToolRequest{
Params: mcp.CallToolRequestParams{
result, err := mcpClient.CallTool(ctx, mcp.CallToolRequest{
Params: mcp.CallToolParams{
Name: "calculate",
Arguments: map[string]interface{}{
"operation": "add",
Expand All @@ -81,13 +96,18 @@ func main() {
log.Fatal(err)
}

fmt.Printf("Result: %s\n", result.Content[0].Text)
// Extract text from the first content item
if len(result.Content) > 0 {
if textContent, ok := mcp.AsTextContent(result.Content[0]); ok {
fmt.Printf("Result: %s\n", textContent.Text)
}
}
}

func handleCalculate(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
operation := req.Params.Arguments["operation"].(string)
x := req.Params.Arguments["x"].(float64)
y := req.Params.Arguments["y"].(float64)
operation := req.GetString("operation", "")
x := req.GetFloat("x", 0)
y := req.GetFloat("y", 0)

var result float64
switch operation {
Expand Down Expand Up @@ -134,7 +154,11 @@ func NewApplication(config *Config) *Application {
app.addApplicationTools()

// Create in-process client for internal use
app.mcpClient = client.NewInProcessClient(app.mcpServer)
var err error
app.mcpClient, err = client.NewInProcessClient(app.mcpServer)
if err != nil {
panic(err)
}

return app
}
Expand All @@ -151,12 +175,8 @@ func (app *Application) addApplicationTools() {
mcp.WithDescription("Get current application status"),
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
status := map[string]interface{}{
"app_name": app.config.AppName,
"debug": app.config.Debug,
"status": "running",
}
return mcp.NewToolResultJSON(status), nil
return mcp.NewToolResultText(fmt.Sprintf(`{"app_name":"%s","debug":%t,"status":"running"}`,
app.config.AppName, app.config.Debug)), nil
},
)

Expand All @@ -168,8 +188,8 @@ func (app *Application) addApplicationTools() {
mcp.WithString("value", mcp.Required()),
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
key := req.Params.Arguments["key"].(string)
value := req.Params.Arguments["value"].(string)
key := req.GetString("key", "")
value := req.GetString("value", "")

// Update configuration based on key
switch key {
Expand All @@ -189,7 +209,7 @@ func (app *Application) addApplicationTools() {
func (app *Application) ProcessWithMCP(ctx context.Context, operation string) (interface{}, error) {
// Use MCP tools internally for processing
result, err := app.mcpClient.CallTool(ctx, mcp.CallToolRequest{
Params: mcp.CallToolRequestParams{
Params: mcp.CallToolParams{
Name: "calculate",
Arguments: map[string]interface{}{
"operation": operation,
Expand All @@ -202,7 +222,14 @@ func (app *Application) ProcessWithMCP(ctx context.Context, operation string) (i
return nil, err
}

return result.Content[0].Text, nil
// Extract text from the first content item
if len(result.Content) > 0 {
if textContent, ok := mcp.AsTextContent(result.Content[0]); ok {
return textContent.Text, nil
}
}

return "no result", nil
}

// Usage example
Expand All @@ -216,7 +243,19 @@ func main() {
ctx := context.Background()

// Initialize the embedded MCP client
if err := app.mcpClient.Initialize(ctx); err != nil {
_, err := app.mcpClient.Initialize(ctx, mcp.InitializeRequest{
Params: mcp.InitializeRequestParams{
ProtocolVersion: "2024-11-05",
Capabilities: mcp.ClientCapabilities{
Tools: &mcp.ToolsCapability{},
},
ClientInfo: mcp.Implementation{
Name: "embedded-client",
Version: "1.0.0",
},
},
})
if err != nil {
log.Fatal(err)
}

Expand Down
Loading