@ebowwa/large-output 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -20
- package/package.json +22 -4
- package/rust/Cargo.lock +690 -0
- package/rust/Cargo.toml +29 -0
- package/rust/README.md +185 -0
- package/rust/src/lib.rs +596 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @ebowwa/large-output
|
|
2
|
+
|
|
3
|
+
Shared utility for handling large MCP outputs with automatic file fallback and **actionable LLM prompts**.
|
|
4
|
+
|
|
5
|
+
**Available in TypeScript and Rust with full API parity.**
|
|
6
|
+
|
|
7
|
+
## Problem Solved
|
|
8
|
+
|
|
9
|
+
When MCP tools return large outputs, they typically hit Claude's ~20,000 character tool output limit. Common solutions like pagination fragment the context and require multiple round-trips.
|
|
10
|
+
|
|
11
|
+
**This library solves it by:**
|
|
12
|
+
- Returning content inline if under the threshold
|
|
13
|
+
- Automatically writing to a temp file if over the threshold
|
|
14
|
+
- **Returning actionable text that prompts the LLM to read the file**
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## TypeScript
|
|
19
|
+
|
|
20
|
+
### Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add @ebowwa/large-output
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { handleMCPOutput } from "@ebowwa/large-output";
|
|
30
|
+
|
|
31
|
+
// In your MCP tool handler:
|
|
32
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
33
|
+
const results = await fetchData();
|
|
34
|
+
const content = JSON.stringify(results, null, 2);
|
|
35
|
+
|
|
36
|
+
// Return with automatic file fallback (actionable format by default)
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: handleMCPOutput(content) }],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### API
|
|
44
|
+
|
|
45
|
+
| Function | Description |
|
|
46
|
+
|----------|-------------|
|
|
47
|
+
| `handleOutput(content, options?)` | Handle output, returns `OutputResponse` |
|
|
48
|
+
| `handleMCPOutput(content, options?)` | Convenience: handle + return MCP string |
|
|
49
|
+
| `toMCPResponse(response, format)` | Convert `OutputResponse` to string |
|
|
50
|
+
| `handleBatch(contents, options?)` | Batch handler for multiple outputs |
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
| Option | Type | Default | Description |
|
|
55
|
+
|--------|------|---------|-------------|
|
|
56
|
+
| `threshold` | `number` | `15000` | Character threshold for file fallback |
|
|
57
|
+
| `previewLength` | `number` | `500` | Preview chars when written to file |
|
|
58
|
+
| `tempDir` | `string` | `os.tmpdir()` | Custom temp directory |
|
|
59
|
+
| `filenamePrefix` | `string` | `"mcp_output"` | Filename prefix |
|
|
60
|
+
| `includeSize` | `boolean` | `true` | Include size in file response |
|
|
61
|
+
| `includePreview` | `boolean` | `true` | Include preview in file response |
|
|
62
|
+
| `responseFormat` | `"actionable"` \| `"json"` | `"actionable"` | Response format |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Rust
|
|
67
|
+
|
|
68
|
+
Located in `./rust/` directory.
|
|
69
|
+
|
|
70
|
+
### Installation
|
|
71
|
+
|
|
72
|
+
Add to your `Cargo.toml`:
|
|
73
|
+
|
|
74
|
+
```toml
|
|
75
|
+
[dependencies]
|
|
76
|
+
large-output = "1.2.0"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Quick Start
|
|
80
|
+
|
|
81
|
+
```rust
|
|
82
|
+
use large_output::{handle_output, handle_mcp_output, OutputResponse};
|
|
83
|
+
|
|
84
|
+
let content = "very large content...".repeat(1000);
|
|
85
|
+
let response = handle_output(&content, None);
|
|
86
|
+
|
|
87
|
+
match response {
|
|
88
|
+
OutputResponse::Inline { content, .. } => println!("{}", content),
|
|
89
|
+
OutputResponse::File { path, size, .. } => {
|
|
90
|
+
println!("Written {} bytes to {:?}", size, path);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MCP convenience function
|
|
95
|
+
let mcp_text = handle_mcp_output(&content, None);
|
|
96
|
+
println!("{}", mcp_text);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### API
|
|
100
|
+
|
|
101
|
+
| Function | Description |
|
|
102
|
+
|----------|-------------|
|
|
103
|
+
| `handle_output(content, options)` | Handle output, returns `OutputResponse` |
|
|
104
|
+
| `handle_mcp_output(content, options)` | Convenience: handle + return MCP string |
|
|
105
|
+
| `to_mcp_response(response, format)` | Convert `OutputResponse` to string |
|
|
106
|
+
| `handle_batch(contents, options)` | Batch handler for multiple outputs |
|
|
107
|
+
| `OptionsBuilder::new()...build()` | Builder for `LargeOutputOptions` |
|
|
108
|
+
|
|
109
|
+
### Options
|
|
110
|
+
|
|
111
|
+
```rust
|
|
112
|
+
use large_output::{OptionsBuilder, ResponseFormat};
|
|
113
|
+
|
|
114
|
+
let options = OptionsBuilder::new()
|
|
115
|
+
.threshold(20000)
|
|
116
|
+
.preview_length(1000)
|
|
117
|
+
.filename_prefix("github_search")
|
|
118
|
+
.response_format(ResponseFormat::Json)
|
|
119
|
+
.build();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Feature Flags
|
|
123
|
+
|
|
124
|
+
- `default` - Synchronous file operations
|
|
125
|
+
- `async` - Async file operations with tokio
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Response Formats
|
|
130
|
+
|
|
131
|
+
### Inline (under threshold)
|
|
132
|
+
|
|
133
|
+
Content returned directly.
|
|
134
|
+
|
|
135
|
+
### File - Actionable format (default)
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Large output (126.5 KB) saved to file.
|
|
139
|
+
|
|
140
|
+
ACTION REQUIRED: Use the Read tool to read this file:
|
|
141
|
+
|
|
142
|
+
/tmp/mcp_output_2025-02-19T12-38-00_abc123.txt
|
|
143
|
+
|
|
144
|
+
--- PREVIEW (first 500 chars) ---
|
|
145
|
+
{...preview...}
|
|
146
|
+
--- END PREVIEW ---
|
|
147
|
+
|
|
148
|
+
File contains the complete data. Read it to proceed.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### File - JSON format
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"type": "file",
|
|
156
|
+
"path": "/tmp/mcp_output_2025-02-19T12-38-00_abc123.txt",
|
|
157
|
+
"size": 126507,
|
|
158
|
+
"sizeFormatted": "126.5 KB",
|
|
159
|
+
"preview": "..."
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ export interface LargeOutputOptions {
|
|
|
40
40
|
* @default true
|
|
41
41
|
*/
|
|
42
42
|
includePreview?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Response format for file outputs
|
|
45
|
+
* - "actionable" (default): Returns actionable text prompting LLM to read the file
|
|
46
|
+
* - "json": Returns structured JSON object
|
|
47
|
+
* @default "actionable"
|
|
48
|
+
*/
|
|
49
|
+
responseFormat?: "actionable" | "json";
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
45
52
|
* Response when output is returned inline (under threshold)
|
|
@@ -85,31 +92,35 @@ export type OutputResponse = InlineOutputResponse | FileOutputResponse;
|
|
|
85
92
|
*/
|
|
86
93
|
export declare function handleOutput(content: string, options?: LargeOutputOptions): OutputResponse;
|
|
87
94
|
/**
|
|
88
|
-
* Convert an OutputResponse to a
|
|
95
|
+
* Convert an OutputResponse to a string for MCP tool return
|
|
89
96
|
*
|
|
90
97
|
* @param response - The response from handleOutput()
|
|
91
|
-
* @
|
|
98
|
+
* @param format - Response format: "actionable" (default) or "json"
|
|
99
|
+
* @returns Text or JSON string suitable for MCP tool return value
|
|
92
100
|
*
|
|
93
101
|
* @example
|
|
94
102
|
* ```ts
|
|
95
103
|
* const response = handleOutput(largeContent);
|
|
96
|
-
* return
|
|
104
|
+
* return toMCPResponse(response);
|
|
97
105
|
* ```
|
|
98
106
|
*/
|
|
99
|
-
export declare function toMCPResponse(response: OutputResponse): string;
|
|
107
|
+
export declare function toMCPResponse(response: OutputResponse, format?: "actionable" | "json"): string;
|
|
100
108
|
/**
|
|
101
109
|
* Convenience function: handle output and return MCP-formatted string
|
|
102
110
|
*
|
|
103
111
|
* @param content - The content to return
|
|
104
|
-
* @param options - Configuration options
|
|
105
|
-
* @returns
|
|
112
|
+
* @param options - Configuration options (including responseFormat)
|
|
113
|
+
* @returns Text string with actionable instructions (default) or JSON
|
|
106
114
|
*
|
|
107
115
|
* @example
|
|
108
116
|
* ```ts
|
|
109
117
|
* import { handleMCPOutput } from "@ebowwa/large-output";
|
|
110
118
|
*
|
|
111
|
-
* // In your MCP tool handler:
|
|
119
|
+
* // In your MCP tool handler (default: actionable format):
|
|
112
120
|
* return handleMCPOutput(JSON.stringify(results));
|
|
121
|
+
*
|
|
122
|
+
* // For JSON format:
|
|
123
|
+
* return handleMCPOutput(JSON.stringify(results), { responseFormat: "json" });
|
|
113
124
|
* ```
|
|
114
125
|
*/
|
|
115
126
|
export declare function handleMCPOutput(content: string, options?: LargeOutputOptions): string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG,kBAAkB,CAAC;AAwCvE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CAwChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,YAAY,GAAG,MAAqB,GAC3C,MAAM,CAgDR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAGR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,GAAE,kBAAuB,GAC/B,cAAc,EAAE,CAElB"}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const DEFAULT_OPTIONS = {
|
|
|
17
17
|
previewLength: 500,
|
|
18
18
|
includeSize: true,
|
|
19
19
|
includePreview: true,
|
|
20
|
+
responseFormat: "actionable",
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* Format bytes to human-readable string
|
|
@@ -99,55 +100,79 @@ export function handleOutput(content, options = {}) {
|
|
|
99
100
|
return response;
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
|
-
* Convert an OutputResponse to a
|
|
103
|
+
* Convert an OutputResponse to a string for MCP tool return
|
|
103
104
|
*
|
|
104
105
|
* @param response - The response from handleOutput()
|
|
105
|
-
* @
|
|
106
|
+
* @param format - Response format: "actionable" (default) or "json"
|
|
107
|
+
* @returns Text or JSON string suitable for MCP tool return value
|
|
106
108
|
*
|
|
107
109
|
* @example
|
|
108
110
|
* ```ts
|
|
109
111
|
* const response = handleOutput(largeContent);
|
|
110
|
-
* return
|
|
112
|
+
* return toMCPResponse(response);
|
|
111
113
|
* ```
|
|
112
114
|
*/
|
|
113
|
-
export function toMCPResponse(response) {
|
|
115
|
+
export function toMCPResponse(response, format = "actionable") {
|
|
114
116
|
if (response.type === "inline") {
|
|
115
117
|
return response.content;
|
|
116
118
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// JSON format for programmatic consumers
|
|
120
|
+
if (format === "json") {
|
|
121
|
+
const result = {
|
|
122
|
+
type: "file",
|
|
123
|
+
path: response.path,
|
|
124
|
+
};
|
|
125
|
+
if (response.size !== undefined) {
|
|
126
|
+
result.size = response.size;
|
|
127
|
+
}
|
|
128
|
+
if (response.sizeFormatted !== undefined) {
|
|
129
|
+
result.sizeFormatted = response.sizeFormatted;
|
|
130
|
+
}
|
|
131
|
+
if (response.preview !== undefined) {
|
|
132
|
+
result.preview = response.preview;
|
|
133
|
+
}
|
|
134
|
+
return JSON.stringify(result, null, 2);
|
|
127
135
|
}
|
|
136
|
+
// Actionable format - prompts LLM to read the file (DEFAULT)
|
|
137
|
+
// This is critical - JSON alone gets ignored by LLMs
|
|
138
|
+
const lines = [
|
|
139
|
+
`⚠️ Large output (${response.sizeFormatted || response.size} characters) saved to file.`,
|
|
140
|
+
``,
|
|
141
|
+
`📖 **ACTION REQUIRED**: Use the Read tool to read this file:`,
|
|
142
|
+
``,
|
|
143
|
+
` ${response.path}`,
|
|
144
|
+
``,
|
|
145
|
+
];
|
|
128
146
|
if (response.preview !== undefined) {
|
|
129
|
-
|
|
147
|
+
lines.push(`--- PREVIEW (first 500 chars) ---`);
|
|
148
|
+
lines.push(response.preview);
|
|
149
|
+
lines.push(`--- END PREVIEW ---`);
|
|
150
|
+
lines.push(``);
|
|
130
151
|
}
|
|
131
|
-
|
|
152
|
+
lines.push(`✅ File contains the complete data. Read it to proceed.`);
|
|
153
|
+
return lines.join("\n");
|
|
132
154
|
}
|
|
133
155
|
/**
|
|
134
156
|
* Convenience function: handle output and return MCP-formatted string
|
|
135
157
|
*
|
|
136
158
|
* @param content - The content to return
|
|
137
|
-
* @param options - Configuration options
|
|
138
|
-
* @returns
|
|
159
|
+
* @param options - Configuration options (including responseFormat)
|
|
160
|
+
* @returns Text string with actionable instructions (default) or JSON
|
|
139
161
|
*
|
|
140
162
|
* @example
|
|
141
163
|
* ```ts
|
|
142
164
|
* import { handleMCPOutput } from "@ebowwa/large-output";
|
|
143
165
|
*
|
|
144
|
-
* // In your MCP tool handler:
|
|
166
|
+
* // In your MCP tool handler (default: actionable format):
|
|
145
167
|
* return handleMCPOutput(JSON.stringify(results));
|
|
168
|
+
*
|
|
169
|
+
* // For JSON format:
|
|
170
|
+
* return handleMCPOutput(JSON.stringify(results), { responseFormat: "json" });
|
|
146
171
|
* ```
|
|
147
172
|
*/
|
|
148
173
|
export function handleMCPOutput(content, options = {}) {
|
|
149
174
|
const response = handleOutput(content, options);
|
|
150
|
-
return toMCPResponse(response);
|
|
175
|
+
return toMCPResponse(response, options.responseFormat);
|
|
151
176
|
}
|
|
152
177
|
/**
|
|
153
178
|
* Batch handler for multiple outputs
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/large-output",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Shared utility for handling large MCP outputs with automatic file fallback",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Shared utility for handling large MCP outputs with automatic file fallback and actionable LLM prompts. Includes TypeScript and Rust implementations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -12,22 +12,40 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"rust"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|
|
20
|
+
"build:rust": "cd rust && cargo build --release",
|
|
21
|
+
"test": "tsc --noEmit && cd rust && cargo test",
|
|
19
22
|
"prepublishOnly": "bun run build"
|
|
20
23
|
},
|
|
21
24
|
"keywords": [
|
|
22
25
|
"mcp",
|
|
23
26
|
"output",
|
|
24
27
|
"pagination",
|
|
25
|
-
"file-fallback"
|
|
28
|
+
"file-fallback",
|
|
29
|
+
"llm-actionable",
|
|
30
|
+
"rust",
|
|
31
|
+
"typescript"
|
|
26
32
|
],
|
|
27
33
|
"author": "ebowwa",
|
|
28
34
|
"license": "MIT",
|
|
29
35
|
"devDependencies": {
|
|
30
36
|
"@types/node": "^22.10.2",
|
|
31
37
|
"typescript": "^5.7.2"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ebowwa/codespaces",
|
|
42
|
+
"directory": "packages/src/ai/large-output"
|
|
43
|
+
},
|
|
44
|
+
"ownership": {
|
|
45
|
+
"domain": "tooling",
|
|
46
|
+
"responsibilities": [
|
|
47
|
+
"output-handling",
|
|
48
|
+
"large-response-management"
|
|
49
|
+
]
|
|
32
50
|
}
|
|
33
51
|
}
|