@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 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 JSON string for MCP tool return
95
+ * Convert an OutputResponse to a string for MCP tool return
89
96
  *
90
97
  * @param response - The response from handleOutput()
91
- * @returns JSON string suitable for MCP tool return value
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 JSON.stringify(toMCPResponse(response));
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 JSON string or content directly
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;
@@ -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;CAC1B;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;AAuCvE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CAwChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAwB9D;AAED;;;;;;;;;;;;;;GAcG;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"}
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 JSON string for MCP tool return
103
+ * Convert an OutputResponse to a string for MCP tool return
103
104
  *
104
105
  * @param response - The response from handleOutput()
105
- * @returns JSON string suitable for MCP tool return value
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 JSON.stringify(toMCPResponse(response));
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
- // For file type, return structured JSON
118
- const result = {
119
- type: "file",
120
- path: response.path,
121
- };
122
- if (response.size !== undefined) {
123
- result.size = response.size;
124
- }
125
- if (response.sizeFormatted !== undefined) {
126
- result.sizeFormatted = response.sizeFormatted;
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
- result.preview = response.preview;
147
+ lines.push(`--- PREVIEW (first 500 chars) ---`);
148
+ lines.push(response.preview);
149
+ lines.push(`--- END PREVIEW ---`);
150
+ lines.push(``);
130
151
  }
131
- return JSON.stringify(result, null, 2);
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 JSON string or content directly
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.3",
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
  }