Skip to content

Commit 1cd5306

Browse files
authored
Centralize jq payload preview size configuration (#1066)
The jq middleware payload preview size (500 characters) was hardcoded in multiple locations, making it difficult to adjust consistently. ## Changes - **Added `PayloadPreviewSize` constant** in `internal/middleware/jqschema.go` - Replaces hardcoded `500` in preview truncation logic - Replaces hardcoded `500` in logging statements - Updates function documentation - **Updated references** in documentation (`README.md`, `AGENTS.md`) and tests to reference the constant ## Example Before: ```go truncated := len(payloadStr) > 500 if truncated { preview = payloadStr[:500] + "..." } ``` After: ```go truncated := len(payloadStr) > PayloadPreviewSize if truncated { preview = payloadStr[:PayloadPreviewSize] + "..." } ``` The constant provides a single point of configuration for payload preview behavior across the middleware package. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `example.com` > - Triggering command: `/tmp/go-build3360590955/b279/launcher.test /tmp/go-build3360590955/b279/launcher.test -test.testlogfile=/tmp/go-build3360590955/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 4047529/b162/_pkg_.a 64/src/runtime/cgo x_amd64/vet` (dns block) > - `invalid-host-that-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build3360590955/b264/config.test /tmp/go-build3360590955/b264/config.test -test.testlogfile=/tmp/go-build3360590955/b264/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo 3E2y/lfIxJsLvVk-kDynw3E2y .12/x64/bin/as` (dns block) > - `nonexistent.local` > - Triggering command: `/tmp/go-build3360590955/b279/launcher.test /tmp/go-build3360590955/b279/launcher.test -test.testlogfile=/tmp/go-build3360590955/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 4047529/b162/_pkg_.a 64/src/runtime/cgo x_amd64/vet` (dns block) > - `slow.example.com` > - Triggering command: `/tmp/go-build3360590955/b279/launcher.test /tmp/go-build3360590955/b279/launcher.test -test.testlogfile=/tmp/go-build3360590955/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 4047529/b162/_pkg_.a 64/src/runtime/cgo x_amd64/vet` (dns block) > - `this-host-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build3360590955/b288/mcp.test /tmp/go-build3360590955/b288/mcp.test -test.testlogfile=/tmp/go-build3360590955/b288/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true se 4047529/b025/vet.cfg rgo/bin/as -p internal/msan -lang=go1.25 /opt/hostedtoolcache/go/1.25.7/x-trimpath go_.�� lcache/go/1.25.7/x64=/_/GOROOT Rd x_amd64/vet -p hash/maphash -lang=go1.25 x_amd64/vet` (dns block) > - Triggering command: `/tmp/go-build1404549538/b001/mcp.test /tmp/go-build1404549538/b001/mcp.test -test.testlogfile=/tmp/go-build1404549538/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -tes�� io.containerd.runtime.v2.task/moby/63201cdb6593143a21722511515a1/run/containerd/io.containerd.rugofmt -test.timeout=10m0s k/gh-aw-mcpg/gh-aw-mcpg/awmg se 4047529/b025/vet--norc ffbfb0094a8a3294--noprofile k/gh-aw-mcpg/gh-aw-mcpg/awmg -tes�� -test.paniconexit0 y by/71af78b18fce5832ab10e180bbc3a77cfee5908e386c9517dcf943eeea458c34/log.json ntime.v2.task/mo/opt/hostedtoolcache/go/1.25.7/x64/pkg/tool/linux_amd64/vet Rd t&#34;, &#34;REQUESTS_CA_B-c iginal` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/github/gh-aw-mcpg/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
2 parents 751d20e + e95f024 commit 1cd5306

4 files changed

Lines changed: 18 additions & 14 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,14 @@ DEBUG_COLORS=0 DEBUG=* ./awmg --config config.toml
398398
- Default payload directory: `/tmp/jq-payloads` (configurable via `--payload-dir` flag, `MCP_GATEWAY_PAYLOAD_DIR` env var, or `payload_dir` in config)
399399
- Payloads are organized by session ID: `{payload_dir}/{sessionID}/{queryID}/payload.json`
400400
- This allows agents to mount their session-specific subdirectory to access full payloads
401-
- The jq middleware returns: preview (first 500 chars), schema, payloadPath, queryID, originalSize, truncated flag
401+
- The jq middleware returns: preview (first `PayloadPreviewSize` chars, default 500), schema, payloadPath, queryID, originalSize, truncated flag
402402

403403
**Understanding the payload.json File:**
404404
- The `payload.json` file contains the **complete original response data** in valid JSON format
405405
- You can read and parse this file directly using standard JSON parsing tools (e.g., `cat payload.json | jq .` or `JSON.parse(fs.readFileSync(path))`)
406406
- The `payloadSchema` in the metadata response shows the **structure and types** of fields (e.g., "string", "number", "boolean", "array", "object")
407407
- The `payloadSchema` does NOT contain the actual data values - those are only in the `payload.json` file
408-
- The `payloadPreview` shows the first 500 characters of the JSON for quick reference
408+
- The `payloadPreview` shows the first `PayloadPreviewSize` characters (default 500) of the JSON for quick reference
409409
- To access the full data with all actual values, read the JSON file at `payloadPath`
410410

411411
**Tools Catalog (tools.json):**

internal/middleware/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This middleware package implements the jqschema functionality from the gh-aw sha
77
- **Automatic JSON Schema Inference**: Uses the jq schema transformation logic to automatically infer the structure and types of JSON responses
88
- **Payload Storage**: Stores complete response payloads in `/tmp/gh-awmg/tools-calls/{randomID}/payload.json`
99
- **Response Rewriting**: Returns a transformed response containing:
10-
- First 500 characters of the payload (for quick preview)
10+
- First `PayloadPreviewSize` (500) characters of the payload (for quick preview)
1111
- Inferred JSON schema showing structure and types
1212
- Query ID for tracking
1313
- File path to complete payload
@@ -71,7 +71,7 @@ The middleware is automatically applied to all backend MCP server tools (except
7171
**Understanding the response:**
7272
- `payloadPath`: Points to a JSON file containing the **complete original response data**
7373
- `payloadSchema`: Shows the **structure and types** (e.g., "string", "number", "boolean") but NOT the actual values
74-
- `payloadPreview`: First 500 characters of the JSON for quick reference
74+
- `payloadPreview`: First `PayloadPreviewSize` (500) characters of the JSON for quick reference
7575
- `originalSize`: Size of the full response in bytes
7676

7777
**Reading the payload.json file:**
@@ -130,15 +130,15 @@ func ShouldApplyMiddleware(toolName string) bool {
130130
**Selective Middleware Mounting**: A configuration system could be added to:
131131
- Enable/disable middleware per backend server
132132
- Configure which tools get middleware applied
133-
- Set custom truncation limits
133+
- Set custom truncation limits (currently `PayloadPreviewSize = 500`)
134134
- Configure storage locations
135135
- Add multiple middleware types with ordering
136136

137137
Example future config structure:
138138
```toml
139139
[middleware.jqschema]
140140
enabled = true
141-
truncate_at = 500
141+
preview_size = 500
142142
storage_path = "/tmp/gh-awmg/tools-calls"
143143
exclude_tools = ["sys___*"]
144144
include_backends = ["github", "tavily"]

internal/middleware/jqschema.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ var logMiddleware = logger.New("middleware:jqschema")
2222
// This prevents malformed queries or large payloads from causing hangs
2323
const DefaultJqTimeout = 5 * time.Second
2424

25+
// PayloadPreviewSize is the maximum number of characters to include in the payload preview
26+
// This controls how much of the original payload is returned inline when a payload is stored to disk
27+
const PayloadPreviewSize = 500
28+
2529
// PayloadTruncatedInstructions is the message returned to clients when a payload
2630
// has been truncated and saved to the filesystem
2731
const PayloadTruncatedInstructions = "The payload was too large for an MCP response. The complete original response data is saved as a JSON file at payloadPath. The file contains valid JSON that can be parsed directly. The payloadSchema shows the structure and types of fields in the full response, but not the actual values. To access the full data with all values, read and parse the JSON file at payloadPath."
@@ -219,7 +223,7 @@ func savePayload(baseDir, sessionID, queryID string, payload []byte) (string, er
219223
// 2. Extracts session ID from context (or uses "default")
220224
// 3. If payload size > sizeThreshold: saves to {baseDir}/{sessionID}/{queryID}/payload.json and returns metadata
221225
// 4. If payload size <= sizeThreshold: returns original response directly (no file storage)
222-
// 5. For large payloads: returns first 500 chars of payload + jq inferred schema
226+
// 5. For large payloads: returns first PayloadPreviewSize chars of payload + jq inferred schema
223227
func WrapToolHandler(
224228
handler func(context.Context, *sdk.CallToolRequest, interface{}) (*sdk.CallToolResult, interface{}, error),
225229
toolName string,
@@ -337,14 +341,14 @@ func WrapToolHandler(
337341
logger.LogDebug("payload", "Schema transformation completed: tool=%s, queryID=%s, schemaSize=%d bytes",
338342
toolName, queryID, len(schemaBytes))
339343

340-
// Build the transformed response: first 500 chars + schema
344+
// Build the transformed response: first PayloadPreviewSize chars + schema
341345
payloadStr := string(payloadJSON)
342346
var preview string
343-
truncated := len(payloadStr) > 500
347+
truncated := len(payloadStr) > PayloadPreviewSize
344348
if truncated {
345-
preview = payloadStr[:500] + "..."
346-
logger.LogInfo("payload", "Payload truncated for preview: tool=%s, queryID=%s, originalSize=%d bytes, previewSize=500 bytes",
347-
toolName, queryID, len(payloadStr))
349+
preview = payloadStr[:PayloadPreviewSize] + "..."
350+
logger.LogInfo("payload", "Payload truncated for preview: tool=%s, queryID=%s, originalSize=%d bytes, previewSize=%d bytes",
351+
toolName, queryID, len(payloadStr), PayloadPreviewSize)
348352
} else {
349353
preview = payloadStr
350354
logger.LogDebug("payload", "Payload small enough for full preview: tool=%s, queryID=%s, size=%d bytes",

internal/middleware/jqschema_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func TestWrapToolHandler_LongPayload(t *testing.T) {
253253

254254
// Verify preview truncation in Content field
255255
preview := contentMap["payloadPreview"].(string)
256-
assert.LessOrEqual(t, len(preview), 503, "Preview should be truncated to ~500 chars + '...'")
256+
assert.LessOrEqual(t, len(preview), PayloadPreviewSize+3, "Preview should be truncated to PayloadPreviewSize chars + '...'")
257257
assert.True(t, strings.HasSuffix(preview, "..."), "Preview should end with '...'")
258258
}
259259

@@ -321,7 +321,7 @@ func TestPayloadStorage_SessionIsolation(t *testing.T) {
321321
func TestPayloadStorage_LargePayloadPreserved(t *testing.T) {
322322
baseDir := t.TempDir()
323323

324-
// Create a large payload (> 500 chars to trigger truncation)
324+
// Create a large payload (> PayloadPreviewSize chars to trigger truncation)
325325
largeContent := strings.Repeat("This is a large payload content. ", 100) // ~3400 chars
326326
largePayload := map[string]interface{}{
327327
"total_count": 1000,

0 commit comments

Comments
 (0)