Skip to content

Commit b38a961

Browse files
Copilotlpcox
andcommitted
Add configurable payload size threshold with comprehensive tests
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
1 parent 1f5e3c8 commit b38a961

6 files changed

Lines changed: 525 additions & 34 deletions

File tree

internal/config/config_core.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ type GatewayConfig struct {
5454

5555
// PayloadDir is the directory for storing large payloads
5656
PayloadDir string `toml:"payload_dir" json:"payload_dir,omitempty"`
57+
58+
// PayloadSizeThreshold is the size threshold (in bytes) for storing payloads to disk.
59+
// Payloads larger than this threshold are stored to disk, smaller ones are returned inline.
60+
// Default: 1024 bytes (1KB)
61+
PayloadSizeThreshold int `toml:"payload_size_threshold" json:"payload_size_threshold,omitempty"`
5762
}
5863

5964
// ServerConfig represents an individual MCP server configuration.

internal/config/config_payload.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@ package config
55
// DefaultPayloadDir is the default directory for storing large payloads.
66
const DefaultPayloadDir = "/tmp/jq-payloads"
77

8+
// DefaultPayloadSizeThreshold is the default size threshold (in bytes) for storing payloads to disk.
9+
// Payloads larger than this threshold are stored to disk, smaller ones are returned inline.
10+
// Default: 1024 bytes (1KB)
11+
const DefaultPayloadSizeThreshold = 1024
12+
813
func init() {
9-
// Register default setter for PayloadDir
14+
// Register default setter for PayloadDir and PayloadSizeThreshold
1015
RegisterDefaults(func(cfg *Config) {
11-
if cfg.Gateway != nil && cfg.Gateway.PayloadDir == "" {
12-
cfg.Gateway.PayloadDir = DefaultPayloadDir
16+
if cfg.Gateway != nil {
17+
if cfg.Gateway.PayloadDir == "" {
18+
cfg.Gateway.PayloadDir = DefaultPayloadDir
19+
}
20+
if cfg.Gateway.PayloadSizeThreshold == 0 {
21+
cfg.Gateway.PayloadSizeThreshold = DefaultPayloadSizeThreshold
22+
}
1323
}
1424
})
1525
}

internal/middleware/jqschema.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ func savePayload(baseDir, sessionID, queryID string, payload []byte) (string, er
167167
// This middleware:
168168
// 1. Generates a random ID for the query
169169
// 2. Extracts session ID from context (or uses "default")
170-
// 3. Saves the response payload to {baseDir}/{sessionID}/{queryID}/payload.json
171-
// 4. Returns first 500 chars of payload + jq inferred schema
170+
// 3. If payload size > sizeThreshold: saves to {baseDir}/{sessionID}/{queryID}/payload.json and returns metadata
171+
// 4. If payload size <= sizeThreshold: returns original response directly (no file storage)
172+
// 5. For large payloads: returns first 500 chars of payload + jq inferred schema
172173
func WrapToolHandler(
173174
handler func(context.Context, *sdk.CallToolRequest, interface{}) (*sdk.CallToolResult, interface{}, error),
174175
toolName string,
175176
baseDir string,
177+
sizeThreshold int,
176178
getSessionID func(context.Context) string,
177179
) func(context.Context, *sdk.CallToolRequest, interface{}) (*sdk.CallToolResult, interface{}, error) {
178180
return func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
@@ -224,12 +226,24 @@ func WrapToolHandler(
224226
}
225227

226228
payloadSize := len(payloadJSON)
227-
logger.LogInfo("payload", "Response data marshaled to JSON: tool=%s, queryID=%s, size=%d bytes (%.2f KB, %.2f MB)",
228-
toolName, queryID, payloadSize, float64(payloadSize)/1024, float64(payloadSize)/(1024*1024))
229+
logger.LogInfo("payload", "Response data marshaled to JSON: tool=%s, queryID=%s, size=%d bytes (%.2f KB, %.2f MB), threshold=%d bytes",
230+
toolName, queryID, payloadSize, float64(payloadSize)/1024, float64(payloadSize)/(1024*1024), sizeThreshold)
231+
232+
// Check if payload size is within threshold - if so, return original response directly
233+
if payloadSize <= sizeThreshold {
234+
logger.LogInfo("payload", "Payload size (%d bytes) is within threshold (%d bytes), returning inline without file storage: tool=%s, queryID=%s",
235+
payloadSize, sizeThreshold, toolName, queryID)
236+
logMiddleware.Printf("Payload within threshold: tool=%s, queryID=%s, size=%d bytes, threshold=%d bytes, returning inline",
237+
toolName, queryID, payloadSize, sizeThreshold)
238+
// Return the original result without modification
239+
return result, data, err
240+
}
229241

230-
// Save the payload
231-
logger.LogInfo("payload", "Starting payload storage to filesystem: tool=%s, queryID=%s, session=%s, baseDir=%s",
232-
toolName, queryID, sessionID, baseDir)
242+
// Payload is larger than threshold - save to filesystem
243+
logger.LogInfo("payload", "Payload size (%d bytes) exceeds threshold (%d bytes), saving to filesystem: tool=%s, queryID=%s, session=%s, baseDir=%s",
244+
payloadSize, sizeThreshold, toolName, queryID, sessionID, baseDir)
245+
logMiddleware.Printf("Payload exceeds threshold: tool=%s, queryID=%s, size=%d bytes, threshold=%d bytes, saving to disk",
246+
toolName, queryID, payloadSize, sizeThreshold)
233247

234248
filePath, saveErr := savePayload(baseDir, sessionID, queryID, payloadJSON)
235249
if saveErr != nil {

internal/middleware/jqschema_integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestMiddlewareIntegration(t *testing.T) {
6767
}
6868

6969
// Wrap with middleware
70-
wrappedHandler := WrapToolHandler(mockHandler, "github___search_repositories", baseDir, testGetSessionID)
70+
wrappedHandler := WrapToolHandler(mockHandler, "github___search_repositories", baseDir, 5, testGetSessionID)
7171

7272
// Call the wrapped handler
7373
result, data, err := wrappedHandler(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{
@@ -211,7 +211,7 @@ func TestMiddlewareWithLargePayload(t *testing.T) {
211211
}, nil
212212
}
213213

214-
wrappedHandler := WrapToolHandler(mockHandler, "test_tool", baseDir, testGetSessionID)
214+
wrappedHandler := WrapToolHandler(mockHandler, "test_tool", baseDir, 5, testGetSessionID)
215215
result, data, err := wrappedHandler(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
216216

217217
require.NoError(t, err)
@@ -273,7 +273,7 @@ func TestMiddlewareDirectoryCreation(t *testing.T) {
273273
return &sdk.CallToolResult{IsError: false}, map[string]interface{}{"test": "data"}, nil
274274
}
275275

276-
wrappedHandler := WrapToolHandler(mockHandler, "test_tool", baseDir, testGetSessionID)
276+
wrappedHandler := WrapToolHandler(mockHandler, "test_tool", baseDir, 5, testGetSessionID)
277277
result, data, err := wrappedHandler(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
278278

279279
require.NoError(t, err)

0 commit comments

Comments
 (0)