@aashari/boilerplate-mcp-server 1.14.0 → 1.15.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/.trigger-ci +1 -0
- package/CHANGELOG.md +12 -0
- package/dist/cli/ipaddress.cli.js +4 -0
- package/dist/controllers/ipaddress.controller.d.ts +15 -2
- package/dist/controllers/ipaddress.controller.js +19 -11
- package/dist/tools/ipaddress.tool.js +23 -1
- package/dist/tools/ipaddress.types.d.ts +12 -0
- package/dist/tools/ipaddress.types.js +15 -1
- package/dist/utils/cli.test.util.d.ts +6 -0
- package/dist/utils/cli.test.util.js +20 -0
- package/dist/utils/constants.util.d.ts +1 -1
- package/dist/utils/constants.util.js +1 -1
- package/dist/utils/jq.util.d.ts +42 -0
- package/dist/utils/jq.util.js +88 -0
- package/dist/utils/toon.util.d.ts +15 -0
- package/dist/utils/toon.util.js +65 -0
- package/package.json +4 -1
- package/package.json.bak +4 -1
package/.trigger-ci
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# CI/CD trigger Thu Sep 18 00:40:39 WIB 2025
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [1.15.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.14.0...v1.15.0) (2025-12-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update tests for TOON output format ([6054be3](https://github.com/aashari/boilerplate-mcp-server/commit/6054be313fd54c6678b39da214c13e17b04ec4a6))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add TOON output format with JMESPath filtering support ([#126](https://github.com/aashari/boilerplate-mcp-server/issues/126)) ([2b3d8d8](https://github.com/aashari/boilerplate-mcp-server/commit/2b3d8d8a6811a1296475b6971d840b9392ceff08))
|
|
12
|
+
|
|
1
13
|
# [1.14.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.13.5...v1.14.0) (2025-09-09)
|
|
2
14
|
|
|
3
15
|
|
|
@@ -21,6 +21,8 @@ function register(program) {
|
|
|
21
21
|
.option('-e, --include-extended-data', 'Include extended data (ASN, host, org). Requires API token.')
|
|
22
22
|
.option('--no-use-https', // commander creates a 'useHttps' boolean, defaulting to true
|
|
23
23
|
'Use HTTP instead of HTTPS for the API call.')
|
|
24
|
+
.option('--jq <expression>', 'JMESPath expression to filter/transform the response.')
|
|
25
|
+
.option('-o, --output-format <format>', 'Output format: "toon" (default, token-efficient) or "json".', 'toon')
|
|
24
26
|
.action(async (ipAddress, options) => {
|
|
25
27
|
const actionLogger = logger.forMethod('action:get-ip-details');
|
|
26
28
|
try {
|
|
@@ -33,6 +35,8 @@ function register(program) {
|
|
|
33
35
|
ipAddress,
|
|
34
36
|
includeExtendedData: options.includeExtendedData || false,
|
|
35
37
|
useHttps: options.useHttps, // commander handles the default via --no-use-https
|
|
38
|
+
jq: options.jq,
|
|
39
|
+
outputFormat: options.outputFormat,
|
|
36
40
|
};
|
|
37
41
|
const result = await ipaddress_controller_js_1.default.get(args);
|
|
38
42
|
console.log(result.content);
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Output format type
|
|
3
|
+
*/
|
|
4
|
+
type OutputFormat = 'toon' | 'json';
|
|
5
|
+
/**
|
|
6
|
+
* Controller response type
|
|
7
|
+
*/
|
|
8
|
+
interface ControllerResponse {
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* @namespace IpAddressController
|
|
4
13
|
* @description Controller responsible for handling IP address lookup logic.
|
|
@@ -14,13 +23,17 @@ import { ControllerResponse } from '../types/common.types.js';
|
|
|
14
23
|
* @param {string} [args.ipAddress] - Optional IP address to look up. If omitted, the service will fetch the current device's public IP.
|
|
15
24
|
* @param {boolean} [args.includeExtendedData=false] - Whether to include extended data fields requiring an API token
|
|
16
25
|
* @param {boolean} [args.useHttps=true] - Whether to use HTTPS for the API request
|
|
17
|
-
* @
|
|
26
|
+
* @param {string} [args.jq] - JMESPath expression to filter the response
|
|
27
|
+
* @param {OutputFormat} [args.outputFormat] - Output format (toon or json)
|
|
28
|
+
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted IP details.
|
|
18
29
|
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
|
|
19
30
|
*/
|
|
20
31
|
declare function get(args?: {
|
|
21
32
|
ipAddress?: string;
|
|
22
33
|
includeExtendedData?: boolean;
|
|
23
34
|
useHttps?: boolean;
|
|
35
|
+
jq?: string;
|
|
36
|
+
outputFormat?: OutputFormat;
|
|
24
37
|
}): Promise<ControllerResponse>;
|
|
25
38
|
declare const _default: {
|
|
26
39
|
get: typeof get;
|
|
@@ -5,11 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const logger_util_js_1 = require("../utils/logger.util.js");
|
|
7
7
|
const vendor_ip_api_com_service_js_1 = __importDefault(require("../services/vendor.ip-api.com.service.js"));
|
|
8
|
-
const ipaddress_formatter_js_1 = require("./ipaddress.formatter.js");
|
|
9
8
|
const error_handler_util_js_1 = require("../utils/error-handler.util.js");
|
|
10
9
|
const config_util_js_1 = require("../utils/config.util.js");
|
|
11
10
|
const error_util_js_1 = require("../utils/error.util.js");
|
|
12
11
|
const error_handler_util_js_2 = require("../utils/error-handler.util.js");
|
|
12
|
+
const jq_util_js_1 = require("../utils/jq.util.js");
|
|
13
13
|
/**
|
|
14
14
|
* @namespace IpAddressController
|
|
15
15
|
* @description Controller responsible for handling IP address lookup logic.
|
|
@@ -25,7 +25,9 @@ const error_handler_util_js_2 = require("../utils/error-handler.util.js");
|
|
|
25
25
|
* @param {string} [args.ipAddress] - Optional IP address to look up. If omitted, the service will fetch the current device's public IP.
|
|
26
26
|
* @param {boolean} [args.includeExtendedData=false] - Whether to include extended data fields requiring an API token
|
|
27
27
|
* @param {boolean} [args.useHttps=true] - Whether to use HTTPS for the API request
|
|
28
|
-
* @
|
|
28
|
+
* @param {string} [args.jq] - JMESPath expression to filter the response
|
|
29
|
+
* @param {OutputFormat} [args.outputFormat] - Output format (toon or json)
|
|
30
|
+
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted IP details.
|
|
29
31
|
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
|
|
30
32
|
*/
|
|
31
33
|
async function get(args = {}) {
|
|
@@ -71,12 +73,11 @@ async function get(args = {}) {
|
|
|
71
73
|
serviceOptions,
|
|
72
74
|
isTestEnvironment,
|
|
73
75
|
});
|
|
76
|
+
let data;
|
|
74
77
|
try {
|
|
75
78
|
// Call the service with ipAddress and the mapped serviceOptions
|
|
76
|
-
|
|
79
|
+
data = await vendor_ip_api_com_service_js_1.default.get(args.ipAddress, serviceOptions);
|
|
77
80
|
methodLogger.debug(`Got the response from the service`, data);
|
|
78
|
-
const formattedContent = (0, ipaddress_formatter_js_1.formatIpDetails)(data);
|
|
79
|
-
return { content: formattedContent };
|
|
80
81
|
}
|
|
81
82
|
catch (error) {
|
|
82
83
|
// If HTTPS fails with permission/SSL error and useHttps was true, try again with HTTP
|
|
@@ -87,17 +88,24 @@ async function get(args = {}) {
|
|
|
87
88
|
error.message.includes('Access denied'))) {
|
|
88
89
|
methodLogger.warn('HTTPS request failed, falling back to HTTP');
|
|
89
90
|
// Try again with HTTP
|
|
90
|
-
|
|
91
|
+
data = await vendor_ip_api_com_service_js_1.default.get(args.ipAddress, {
|
|
91
92
|
...serviceOptions,
|
|
92
93
|
useHttps: false,
|
|
93
94
|
});
|
|
94
|
-
methodLogger.debug(`Got the response from HTTP fallback`,
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
methodLogger.debug(`Got the response from HTTP fallback`, data);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// For other errors, rethrow
|
|
99
|
+
throw error;
|
|
97
100
|
}
|
|
98
|
-
// For other errors, rethrow
|
|
99
|
-
throw error;
|
|
100
101
|
}
|
|
102
|
+
// Apply JQ filter if provided
|
|
103
|
+
const filteredData = (0, jq_util_js_1.applyJqFilter)(data, args.jq);
|
|
104
|
+
// Determine output format (default to TOON)
|
|
105
|
+
const useToon = args.outputFormat !== 'json';
|
|
106
|
+
// Format the output
|
|
107
|
+
const content = await (0, jq_util_js_1.toOutputString)(filteredData, useToon);
|
|
108
|
+
return { content };
|
|
101
109
|
}
|
|
102
110
|
catch (error) {
|
|
103
111
|
throw (0, error_handler_util_js_1.handleControllerError)(error, (0, error_handler_util_js_2.buildErrorContext)('IP Address', 'get', 'controllers/ipaddress.controller.ts@get', args.ipAddress || 'current device', { args }));
|
|
@@ -63,7 +63,29 @@ async function handleGetIpDetails(args) {
|
|
|
63
63
|
function registerTools(server) {
|
|
64
64
|
const methodLogger = logger_util_js_1.Logger.forContext('tools/ipaddress.tool.ts', 'registerTools');
|
|
65
65
|
methodLogger.debug(`Registering IP address tools...`);
|
|
66
|
-
server.tool('ip_get_details', `
|
|
66
|
+
server.tool('ip_get_details', `Retrieve geolocation and network information for a public IP address. Returns TOON format by default (30-60% fewer tokens than JSON).
|
|
67
|
+
|
|
68
|
+
**IMPORTANT - Cost Optimization:**
|
|
69
|
+
- Use \`jq\` param to extract only needed fields. Unfiltered responses are expensive!
|
|
70
|
+
- Example: \`jq: "{ip: query, country: country, city: city}"\` - extract specific fields
|
|
71
|
+
- If unsure about available fields, first call WITHOUT jq filter to see all fields, then use jq in subsequent calls
|
|
72
|
+
|
|
73
|
+
**Schema Discovery Pattern:**
|
|
74
|
+
1. First call: \`ipAddress: "8.8.8.8"\` (no jq) - explore available fields
|
|
75
|
+
2. Then use: \`jq: "{ip: query, location: {city: city, country: country}}"\` - extract only what you need
|
|
76
|
+
|
|
77
|
+
**Output format:** TOON (default, token-efficient) or JSON (\`outputFormat: "json"\`)
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
- \`ipAddress\` - IP to lookup (omit for current device's public IP)
|
|
81
|
+
- \`includeExtendedData\` - Include ASN, host, proxy detection (requires API token)
|
|
82
|
+
- \`useHttps\` - Use HTTPS (default: true)
|
|
83
|
+
- \`jq\` - JMESPath expression to filter response
|
|
84
|
+
- \`outputFormat\` - "toon" (default) or "json"
|
|
85
|
+
|
|
86
|
+
**JQ examples:** \`query\` (IP only), \`{ip: query, country: country}\`, \`{location: {lat: lat, lon: lon}}\`
|
|
87
|
+
|
|
88
|
+
**Note:** Cannot lookup private IPs (192.168.x.x, 10.x.x.x). Powered by ip-api.com.`, GetIpDetailsToolSchema.shape, handleGetIpDetails);
|
|
67
89
|
methodLogger.debug('Successfully registered ip_get_details tool.');
|
|
68
90
|
}
|
|
69
91
|
exports.default = { registerTools };
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Output format options for API responses
|
|
4
|
+
* - toon: Token-Oriented Object Notation (default, more token-efficient for LLMs)
|
|
5
|
+
* - json: Standard JSON format
|
|
6
|
+
*/
|
|
7
|
+
export declare const OutputFormat: z.ZodOptional<z.ZodEnum<["toon", "json"]>>;
|
|
2
8
|
/**
|
|
3
9
|
* Zod schema for the IP address tool arguments.
|
|
4
10
|
*/
|
|
5
11
|
export declare const IpAddressToolArgs: z.ZodObject<{
|
|
6
12
|
includeExtendedData: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
13
|
useHttps: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
14
|
+
jq: z.ZodOptional<z.ZodString>;
|
|
15
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["toon", "json"]>>;
|
|
8
16
|
}, "strict", z.ZodTypeAny, {
|
|
9
17
|
useHttps: boolean;
|
|
10
18
|
includeExtendedData: boolean;
|
|
19
|
+
jq?: string | undefined;
|
|
20
|
+
outputFormat?: "toon" | "json" | undefined;
|
|
11
21
|
}, {
|
|
12
22
|
useHttps?: boolean | undefined;
|
|
13
23
|
includeExtendedData?: boolean | undefined;
|
|
24
|
+
jq?: string | undefined;
|
|
25
|
+
outputFormat?: "toon" | "json" | undefined;
|
|
14
26
|
}>;
|
|
15
27
|
/**
|
|
16
28
|
* TypeScript type inferred from the IpAddressToolArgs Zod schema.
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IpAddressToolArgs = void 0;
|
|
3
|
+
exports.IpAddressToolArgs = exports.OutputFormat = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Output format options for API responses
|
|
7
|
+
* - toon: Token-Oriented Object Notation (default, more token-efficient for LLMs)
|
|
8
|
+
* - json: Standard JSON format
|
|
9
|
+
*/
|
|
10
|
+
exports.OutputFormat = zod_1.z
|
|
11
|
+
.enum(['toon', 'json'])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Output format: "toon" (default, 30-60% fewer tokens) or "json". TOON is optimized for LLMs with tabular arrays and minimal syntax.');
|
|
5
14
|
/**
|
|
6
15
|
* Zod schema for the IP address tool arguments.
|
|
7
16
|
*/
|
|
@@ -19,5 +28,10 @@ exports.IpAddressToolArgs = zod_1.z
|
|
|
19
28
|
.optional()
|
|
20
29
|
.default(true)
|
|
21
30
|
.describe('Whether to use HTTPS for the API call (recommended).'),
|
|
31
|
+
jq: zod_1.z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('JMESPath expression to filter/transform the response. IMPORTANT: Always use this to extract only needed fields and reduce token costs. Examples: "{ip: query, country: country}" (extract specific fields), "lat" (single field). See https://jmespath.org'),
|
|
35
|
+
outputFormat: exports.OutputFormat,
|
|
22
36
|
})
|
|
23
37
|
.strict();
|
|
@@ -21,8 +21,14 @@ export declare class CliTestUtil {
|
|
|
21
21
|
* Validates that stdout contains expected strings/patterns
|
|
22
22
|
*/
|
|
23
23
|
static validateOutputContains(output: string, expectedPatterns: (string | RegExp)[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Validates TOON output format
|
|
26
|
+
* TOON uses "key: value" syntax for objects
|
|
27
|
+
*/
|
|
28
|
+
static validateToonOutput(output: string): void;
|
|
24
29
|
/**
|
|
25
30
|
* Validates Markdown output format
|
|
31
|
+
* @deprecated Use validateToonOutput for new code - output is now TOON by default
|
|
26
32
|
*/
|
|
27
33
|
static validateMarkdownOutput(output: string): void;
|
|
28
34
|
}
|
|
@@ -99,8 +99,28 @@ class CliTestUtil {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Validates TOON output format
|
|
104
|
+
* TOON uses "key: value" syntax for objects
|
|
105
|
+
*/
|
|
106
|
+
static validateToonOutput(output) {
|
|
107
|
+
// Filter out debug log lines for cleaner validation
|
|
108
|
+
const cleanOutput = output
|
|
109
|
+
.split('\n')
|
|
110
|
+
.filter((line) => !line.match(/^\[\d{2}:\d{2}:\d{2}\]/))
|
|
111
|
+
.join('\n');
|
|
112
|
+
// TOON format characteristics:
|
|
113
|
+
// - Key-value pairs in "key: value" format
|
|
114
|
+
// - No curly braces for objects
|
|
115
|
+
// - Arrays use [count]{fields}: notation
|
|
116
|
+
const toonPatterns = [
|
|
117
|
+
/^\w+:\s*.+/m, // key: value pattern
|
|
118
|
+
];
|
|
119
|
+
expect(toonPatterns.some((pattern) => pattern.test(cleanOutput))).toBe(true);
|
|
120
|
+
}
|
|
102
121
|
/**
|
|
103
122
|
* Validates Markdown output format
|
|
123
|
+
* @deprecated Use validateToonOutput for new code - output is now TOON by default
|
|
104
124
|
*/
|
|
105
125
|
static validateMarkdownOutput(output) {
|
|
106
126
|
// Filter out debug log lines for cleaner validation
|
|
@@ -11,7 +11,7 @@ exports.CLI_NAME = exports.PACKAGE_NAME = exports.VERSION = void 0;
|
|
|
11
11
|
* Current application version
|
|
12
12
|
* This should match the version in package.json
|
|
13
13
|
*/
|
|
14
|
-
exports.VERSION = '1.
|
|
14
|
+
exports.VERSION = '1.15.0';
|
|
15
15
|
/**
|
|
16
16
|
* Package name with scope
|
|
17
17
|
* Used for initialization and identification
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply a JMESPath filter to JSON data
|
|
3
|
+
*
|
|
4
|
+
* @param data - The data to filter (any JSON-serializable value)
|
|
5
|
+
* @param filter - JMESPath expression to apply
|
|
6
|
+
* @returns Filtered data or original data if filter is empty/invalid
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Get single field
|
|
10
|
+
* applyJqFilter(data, "name")
|
|
11
|
+
*
|
|
12
|
+
* // Get nested field
|
|
13
|
+
* applyJqFilter(data, "body.storage.value")
|
|
14
|
+
*
|
|
15
|
+
* // Get multiple fields as object
|
|
16
|
+
* applyJqFilter(data, "{id: id, title: title}")
|
|
17
|
+
*
|
|
18
|
+
* // Array operations
|
|
19
|
+
* applyJqFilter(data, "results[*].title")
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyJqFilter(data: unknown, filter?: string): unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Convert data to JSON string for MCP response
|
|
24
|
+
*
|
|
25
|
+
* @param data - The data to stringify
|
|
26
|
+
* @param pretty - Whether to pretty-print the JSON (default: true)
|
|
27
|
+
* @returns JSON string
|
|
28
|
+
*/
|
|
29
|
+
export declare function toJsonString(data: unknown, pretty?: boolean): string;
|
|
30
|
+
/**
|
|
31
|
+
* Convert data to output string for MCP response
|
|
32
|
+
*
|
|
33
|
+
* By default, converts to TOON format (Token-Oriented Object Notation)
|
|
34
|
+
* for improved LLM token efficiency (30-60% fewer tokens).
|
|
35
|
+
* Falls back to JSON if TOON conversion fails or if useToon is false.
|
|
36
|
+
*
|
|
37
|
+
* @param data - The data to convert
|
|
38
|
+
* @param useToon - Whether to use TOON format (default: true)
|
|
39
|
+
* @param pretty - Whether to pretty-print JSON (default: true)
|
|
40
|
+
* @returns TOON formatted string (default), or JSON string
|
|
41
|
+
*/
|
|
42
|
+
export declare function toOutputString(data: unknown, useToon?: boolean, pretty?: boolean): Promise<string>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyJqFilter = applyJqFilter;
|
|
7
|
+
exports.toJsonString = toJsonString;
|
|
8
|
+
exports.toOutputString = toOutputString;
|
|
9
|
+
const jmespath_1 = __importDefault(require("jmespath"));
|
|
10
|
+
const logger_util_js_1 = require("./logger.util.js");
|
|
11
|
+
const toon_util_js_1 = require("./toon.util.js");
|
|
12
|
+
const logger = logger_util_js_1.Logger.forContext('utils/jq.util.ts');
|
|
13
|
+
/**
|
|
14
|
+
* Apply a JMESPath filter to JSON data
|
|
15
|
+
*
|
|
16
|
+
* @param data - The data to filter (any JSON-serializable value)
|
|
17
|
+
* @param filter - JMESPath expression to apply
|
|
18
|
+
* @returns Filtered data or original data if filter is empty/invalid
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Get single field
|
|
22
|
+
* applyJqFilter(data, "name")
|
|
23
|
+
*
|
|
24
|
+
* // Get nested field
|
|
25
|
+
* applyJqFilter(data, "body.storage.value")
|
|
26
|
+
*
|
|
27
|
+
* // Get multiple fields as object
|
|
28
|
+
* applyJqFilter(data, "{id: id, title: title}")
|
|
29
|
+
*
|
|
30
|
+
* // Array operations
|
|
31
|
+
* applyJqFilter(data, "results[*].title")
|
|
32
|
+
*/
|
|
33
|
+
function applyJqFilter(data, filter) {
|
|
34
|
+
const methodLogger = logger.forMethod('applyJqFilter');
|
|
35
|
+
// Return original data if no filter provided
|
|
36
|
+
if (!filter || filter.trim() === '') {
|
|
37
|
+
methodLogger.debug('No filter provided, returning original data');
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
methodLogger.debug(`Applying JMESPath filter: ${filter}`);
|
|
42
|
+
const result = jmespath_1.default.search(data, filter);
|
|
43
|
+
methodLogger.debug('Filter applied successfully');
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
methodLogger.error(`Invalid JMESPath expression: ${filter}`, error);
|
|
48
|
+
// Return original data with error info if filter is invalid
|
|
49
|
+
return {
|
|
50
|
+
_jqError: `Invalid JMESPath expression: ${filter}`,
|
|
51
|
+
_originalData: data,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert data to JSON string for MCP response
|
|
57
|
+
*
|
|
58
|
+
* @param data - The data to stringify
|
|
59
|
+
* @param pretty - Whether to pretty-print the JSON (default: true)
|
|
60
|
+
* @returns JSON string
|
|
61
|
+
*/
|
|
62
|
+
function toJsonString(data, pretty = true) {
|
|
63
|
+
if (pretty) {
|
|
64
|
+
return JSON.stringify(data, null, 2);
|
|
65
|
+
}
|
|
66
|
+
return JSON.stringify(data);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Convert data to output string for MCP response
|
|
70
|
+
*
|
|
71
|
+
* By default, converts to TOON format (Token-Oriented Object Notation)
|
|
72
|
+
* for improved LLM token efficiency (30-60% fewer tokens).
|
|
73
|
+
* Falls back to JSON if TOON conversion fails or if useToon is false.
|
|
74
|
+
*
|
|
75
|
+
* @param data - The data to convert
|
|
76
|
+
* @param useToon - Whether to use TOON format (default: true)
|
|
77
|
+
* @param pretty - Whether to pretty-print JSON (default: true)
|
|
78
|
+
* @returns TOON formatted string (default), or JSON string
|
|
79
|
+
*/
|
|
80
|
+
async function toOutputString(data, useToon = true, pretty = true) {
|
|
81
|
+
const jsonString = toJsonString(data, pretty);
|
|
82
|
+
// Return JSON directly if TOON is not requested
|
|
83
|
+
if (!useToon) {
|
|
84
|
+
return jsonString;
|
|
85
|
+
}
|
|
86
|
+
// Try TOON conversion with JSON fallback
|
|
87
|
+
return (0, toon_util_js_1.toToonOrJson)(data, jsonString);
|
|
88
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert data to TOON format with JSON fallback.
|
|
3
|
+
*
|
|
4
|
+
* TOON (Token-Oriented Object Notation) is 30-60% more token-efficient than JSON
|
|
5
|
+
* for tabular data, making it ideal for LLM responses.
|
|
6
|
+
*
|
|
7
|
+
* @param data - The data to convert
|
|
8
|
+
* @param jsonFallback - JSON string to return if TOON encoding fails
|
|
9
|
+
* @returns TOON formatted string, or JSON fallback on failure
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const json = JSON.stringify(data, null, 2);
|
|
13
|
+
* const output = await toToonOrJson(data, json);
|
|
14
|
+
*/
|
|
15
|
+
export declare function toToonOrJson(data: unknown, jsonFallback: string): Promise<string>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toToonOrJson = toToonOrJson;
|
|
4
|
+
const logger_util_js_1 = require("./logger.util.js");
|
|
5
|
+
const logger = logger_util_js_1.Logger.forContext('utils/toon.util.ts');
|
|
6
|
+
/**
|
|
7
|
+
* Cached TOON encoder to avoid repeated dynamic imports
|
|
8
|
+
*/
|
|
9
|
+
let toonEncode = null;
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically loads the TOON encoder module.
|
|
12
|
+
* Uses dynamic import because @toon-format/toon is an ESM-only package.
|
|
13
|
+
*
|
|
14
|
+
* @returns Promise resolving to the encode function or null if loading fails
|
|
15
|
+
*/
|
|
16
|
+
async function loadToonEncoder() {
|
|
17
|
+
const methodLogger = logger.forMethod('loadToonEncoder');
|
|
18
|
+
// Return cached encoder if available
|
|
19
|
+
if (toonEncode) {
|
|
20
|
+
return toonEncode;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
methodLogger.debug('Loading TOON encoder module...');
|
|
24
|
+
// Dynamic import for ESM module in CommonJS project
|
|
25
|
+
const toon = await import('@toon-format/toon');
|
|
26
|
+
toonEncode = toon.encode;
|
|
27
|
+
methodLogger.debug('TOON encoder loaded successfully');
|
|
28
|
+
return toonEncode;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
methodLogger.error('Failed to load TOON encoder', error);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convert data to TOON format with JSON fallback.
|
|
37
|
+
*
|
|
38
|
+
* TOON (Token-Oriented Object Notation) is 30-60% more token-efficient than JSON
|
|
39
|
+
* for tabular data, making it ideal for LLM responses.
|
|
40
|
+
*
|
|
41
|
+
* @param data - The data to convert
|
|
42
|
+
* @param jsonFallback - JSON string to return if TOON encoding fails
|
|
43
|
+
* @returns TOON formatted string, or JSON fallback on failure
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const json = JSON.stringify(data, null, 2);
|
|
47
|
+
* const output = await toToonOrJson(data, json);
|
|
48
|
+
*/
|
|
49
|
+
async function toToonOrJson(data, jsonFallback) {
|
|
50
|
+
const methodLogger = logger.forMethod('toToonOrJson');
|
|
51
|
+
try {
|
|
52
|
+
const encode = await loadToonEncoder();
|
|
53
|
+
if (!encode) {
|
|
54
|
+
methodLogger.debug('TOON encoder not available, using JSON fallback');
|
|
55
|
+
return jsonFallback;
|
|
56
|
+
}
|
|
57
|
+
const result = encode(data, { indent: 2 });
|
|
58
|
+
methodLogger.debug('Successfully converted to TOON format');
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
methodLogger.error('TOON encoding failed, using JSON fallback', error);
|
|
63
|
+
return jsonFallback;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aashari/boilerplate-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "TypeScript MCP server boilerplate with STDIO and HTTP transport support, CLI tools, and extensible architecture",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@types/cors": "^2.8.19",
|
|
61
61
|
"@types/express": "^5.0.3",
|
|
62
62
|
"@types/jest": "^30.0.0",
|
|
63
|
+
"@types/jmespath": "^0.15.2",
|
|
63
64
|
"@types/node": "^24.3.1",
|
|
64
65
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
65
66
|
"@typescript-eslint/parser": "^8.43.0",
|
|
@@ -75,10 +76,12 @@
|
|
|
75
76
|
},
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
79
|
+
"@toon-format/toon": "^2.0.1",
|
|
78
80
|
"commander": "^14.0.0",
|
|
79
81
|
"cors": "^2.8.5",
|
|
80
82
|
"dotenv": "^17.2.2",
|
|
81
83
|
"express": "^5.1.0",
|
|
84
|
+
"jmespath": "^0.16.0",
|
|
82
85
|
"zod": "^3.25.76"
|
|
83
86
|
},
|
|
84
87
|
"publishConfig": {
|
package/package.json.bak
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aashari/boilerplate-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "TypeScript MCP server boilerplate with STDIO and HTTP transport support, CLI tools, and extensible architecture",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@types/cors": "^2.8.19",
|
|
61
61
|
"@types/express": "^5.0.3",
|
|
62
62
|
"@types/jest": "^30.0.0",
|
|
63
|
+
"@types/jmespath": "^0.15.2",
|
|
63
64
|
"@types/node": "^24.3.1",
|
|
64
65
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
65
66
|
"@typescript-eslint/parser": "^8.43.0",
|
|
@@ -75,10 +76,12 @@
|
|
|
75
76
|
},
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
79
|
+
"@toon-format/toon": "^2.0.1",
|
|
78
80
|
"commander": "^14.0.0",
|
|
79
81
|
"cors": "^2.8.5",
|
|
80
82
|
"dotenv": "^17.2.2",
|
|
81
83
|
"express": "^5.1.0",
|
|
84
|
+
"jmespath": "^0.16.0",
|
|
82
85
|
"zod": "^3.25.76"
|
|
83
86
|
},
|
|
84
87
|
"publishConfig": {
|