Registers a sending middleware on each inner server that intercepts all outgoing messages and routes them through the front session using a session-swapped request wrapper. Replaces the previous proxy client handler approach which dropped log messages in stateless mode. ## Motivation and Context Tool handlers in variant servers use req.Session (the inner session) to send notifications. Previously, log messages were silently dropped in stateless mode because the inner session is not aware of the front session. This change injects the sendingMethodHandler from the frontSession into the context and adds a sending middleware to redirect all sending requests to the frontSession handler directly. A few considerations in this implementation: * Having frontSession in the context allows notifications to be sent to the correct frontSession, when the same backend session could be used by multiple front sessions. * This solution will work for synchronous notifications that happen within the same context (progress, log, sampling, elicitation) but not working for asynchronous notifications (tool list changed, resource updated etc), because asynchronous notifications are sent with a new context. * All the operations before the sending method will still happen in the backend. Most notable impact is in stateless mode, requests from one client could change the log level for other clients. For this reason, I default the logging level to debug for now. * This implementation relies on the MCP Go SDK sendingMethodHandler taking a generic `Request` not one that tied to specific function. Based on the current SDK implementation this is unlikely to change. * The same generic method forwarding mechanism could work for receiving methods too, but with some caveat. That can be addressed in separate PR. ## How Has This Been Tested? TestNotificationDelivery — verifies progress and log messages arrive at the front clients with multiple clients making concurrent request. New tests added for context preservation that was added in https://github.com/modelcontextprotocol/experimental-ext-variants/pull/6. Context preservation is required to make the notification forwarding work anyway. Since we use the context to carry frontSession handler to the backend session. All existing tests pass ## Breaking Changes None. Tool handlers continue to use req.Session normally. ## Types of changes Bug fix (non-breaking change which fixes an issue)
175 lines
5.9 KiB
Go
175 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
type SearchPapersInput struct {
|
|
Query string `json:"query" jsonschema:"search query for academic papers"`
|
|
MaxResults int `json:"maxResults,omitempty" jsonschema:"maximum number of results (1-50, default: 10)"`
|
|
Year int `json:"year,omitempty" jsonschema:"filter by publication year"`
|
|
Field string `json:"field,omitempty" jsonschema:"filter by field: cs, math, physics, biology, medicine"`
|
|
}
|
|
|
|
type Paper struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Authors []string `json:"authors"`
|
|
Year int `json:"year"`
|
|
Abstract string `json:"abstract"`
|
|
Citations int `json:"citations"`
|
|
}
|
|
|
|
type SearchPapersOutput struct {
|
|
Papers []Paper `json:"papers"`
|
|
TotalFound int `json:"totalFound"`
|
|
}
|
|
|
|
var databases = []string{"arXiv", "Semantic Scholar", "PubMed"}
|
|
|
|
func searchPapers(ctx context.Context, req *mcp.CallToolRequest, in SearchPapersInput) (*mcp.CallToolResult, SearchPapersOutput, error) {
|
|
token := req.Params.GetProgressToken()
|
|
total := len(databases)
|
|
|
|
for i, db := range databases {
|
|
req.Session.Log(ctx, &mcp.LoggingMessageParams{
|
|
Level: "info",
|
|
Logger: "search_papers",
|
|
Data: fmt.Sprintf("Searching %s for %q", db, in.Query),
|
|
})
|
|
req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
|
|
ProgressToken: token,
|
|
Progress: float64(i + 1),
|
|
Total: float64(total),
|
|
Message: fmt.Sprintf("Searching %s (%d/%d)", db, i+1, total),
|
|
})
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
req.Session.Log(ctx, &mcp.LoggingMessageParams{
|
|
Level: "info",
|
|
Logger: "search_papers",
|
|
Data: fmt.Sprintf("Found 1247 results for %q, returning top 2", in.Query),
|
|
})
|
|
|
|
return nil, SearchPapersOutput{
|
|
Papers: []Paper{
|
|
{
|
|
ID: "arxiv:2301.01234",
|
|
Title: "Attention Is All You Need: A Comprehensive Survey",
|
|
Authors: []string{"Smith, J.", "Chen, L.", "Kumar, A."},
|
|
Year: 2024,
|
|
Abstract: "We present a comprehensive survey of transformer architectures and their applications across natural language processing, computer vision, and scientific computing.",
|
|
Citations: 342,
|
|
},
|
|
{
|
|
ID: "arxiv:2302.05678",
|
|
Title: "Scaling Laws for Neural Language Models",
|
|
Authors: []string{"Johnson, R.", "Williams, K."},
|
|
Year: 2024,
|
|
Abstract: "This paper investigates empirical scaling laws governing the performance of language models as a function of model size, dataset size, and compute budget.",
|
|
Citations: 189,
|
|
},
|
|
},
|
|
TotalFound: 1247,
|
|
}, nil
|
|
}
|
|
|
|
type GetPaperInput struct {
|
|
ID string `json:"id" jsonschema:"paper identifier (e.g. arxiv:2301.01234, doi:10.1234/5678)"`
|
|
}
|
|
|
|
type PaperDetail struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Authors []string `json:"authors"`
|
|
Year int `json:"year"`
|
|
Abstract string `json:"abstract"`
|
|
Citations int `json:"citations"`
|
|
References []string `json:"references"`
|
|
Keywords []string `json:"keywords"`
|
|
Venue string `json:"venue"`
|
|
}
|
|
|
|
type GetPaperOutput struct {
|
|
Paper PaperDetail `json:"paper"`
|
|
}
|
|
|
|
func getPaper(ctx context.Context, req *mcp.CallToolRequest, in GetPaperInput) (*mcp.CallToolResult, GetPaperOutput, error) {
|
|
token := req.Params.GetProgressToken()
|
|
steps := []string{"Fetching metadata", "Resolving references", "Loading citations"}
|
|
|
|
for i, step := range steps {
|
|
req.Session.Log(ctx, &mcp.LoggingMessageParams{
|
|
Level: "info",
|
|
Logger: "get_paper",
|
|
Data: fmt.Sprintf("%s for %s", step, in.ID),
|
|
})
|
|
req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
|
|
ProgressToken: token,
|
|
Progress: float64(i + 1),
|
|
Total: float64(len(steps)),
|
|
Message: fmt.Sprintf("%s (%d/%d)", step, i+1, len(steps)),
|
|
})
|
|
time.Sleep(80 * time.Millisecond)
|
|
}
|
|
|
|
return nil, GetPaperOutput{
|
|
Paper: PaperDetail{
|
|
ID: in.ID,
|
|
Title: "Attention Is All You Need: A Comprehensive Survey",
|
|
Authors: []string{"Smith, J.", "Chen, L.", "Kumar, A."},
|
|
Year: 2024,
|
|
Abstract: "We present a comprehensive survey of transformer architectures and their applications across natural language processing, computer vision, and scientific computing.",
|
|
Citations: 342,
|
|
References: []string{"arxiv:1706.03762", "arxiv:1810.04805", "arxiv:2005.14165"},
|
|
Keywords: []string{"transformers", "attention mechanisms", "deep learning", "survey"},
|
|
Venue: "NeurIPS 2024",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type SummarizeInput struct {
|
|
Text string `json:"text" jsonschema:"text to summarize"`
|
|
Style string `json:"style,omitempty" jsonschema:"summary style: abstract, bullet-points, executive (default: abstract)"`
|
|
Length string `json:"length,omitempty" jsonschema:"summary length: short, medium, long (default: medium)"`
|
|
}
|
|
|
|
type SummarizeOutput struct {
|
|
Summary string `json:"summary"`
|
|
Style string `json:"style"`
|
|
}
|
|
|
|
func summarize(ctx context.Context, req *mcp.CallToolRequest, in SummarizeInput) (*mcp.CallToolResult, SummarizeOutput, error) {
|
|
style := in.Style
|
|
if style == "" {
|
|
style = "abstract"
|
|
}
|
|
|
|
token := req.Params.GetProgressToken()
|
|
steps := []string{"Analyzing text", "Generating summary"}
|
|
|
|
for i, step := range steps {
|
|
req.Session.Log(ctx, &mcp.LoggingMessageParams{
|
|
Level: "info",
|
|
Logger: "summarize",
|
|
Data: fmt.Sprintf("%s (%s style)", step, style),
|
|
})
|
|
req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
|
|
ProgressToken: token,
|
|
Progress: float64(i + 1),
|
|
Total: float64(len(steps)),
|
|
Message: fmt.Sprintf("%s (%d/%d)", step, i+1, len(steps)),
|
|
})
|
|
time.Sleep(80 * time.Millisecond)
|
|
}
|
|
|
|
return nil, SummarizeOutput{
|
|
Summary: "This paper presents key findings in the field, demonstrating significant improvements over prior work through novel methodology and comprehensive evaluation across multiple benchmarks.",
|
|
Style: style,
|
|
}, nil
|
|
}
|