@aashari/boilerplate-mcp-server 1.14.0 → 1.16.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 ADDED
@@ -0,0 +1 @@
1
+ # CI/CD trigger Thu Sep 18 00:40:39 WIB 2025
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # [1.16.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.15.0...v1.16.0) (2025-12-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * regenerate package-lock.json to fix CI dependency resolution ([a0225a8](https://github.com/aashari/boilerplate-mcp-server/commit/a0225a8f9bb0bb5b27a436099d0b131fa47e8cbc))
7
+
8
+
9
+ ### Features
10
+
11
+ * modernize SDK usage and update dependencies ([#127](https://github.com/aashari/boilerplate-mcp-server/issues/127)) ([a36c9f6](https://github.com/aashari/boilerplate-mcp-server/commit/a36c9f644d842e9c5360d4ed2f207ada18d97ebd))
12
+
13
+ # [1.15.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.14.0...v1.15.0) (2025-12-01)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * update tests for TOON output format ([6054be3](https://github.com/aashari/boilerplate-mcp-server/commit/6054be313fd54c6678b39da214c13e17b04ec4a6))
19
+
20
+
21
+ ### Features
22
+
23
+ * 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))
24
+
1
25
  # [1.14.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.13.5...v1.14.0) (2025-09-09)
2
26
 
3
27
 
package/README.md CHANGED
@@ -9,7 +9,10 @@ A production-ready foundation for developing custom Model Context Protocol (MCP)
9
9
 
10
10
  - **Dual Transport Support**: STDIO and Streamable HTTP transports with automatic fallback
11
11
  - **5-Layer Architecture**: Clean separation between CLI, tools, controllers, services, and utilities
12
- - **Type Safety**: Full TypeScript implementation with Zod schema validation
12
+ - **Type Safety**: Full TypeScript implementation with Zod v4 schema validation
13
+ - **TOON Output Format**: Token-Oriented Object Notation for 30-60% fewer tokens than JSON
14
+ - **JMESPath Filtering**: Extract only needed fields from responses to reduce token costs
15
+ - **Modern SDK**: Uses MCP SDK v1.23.0 with `registerTool` API pattern
13
16
  - **Complete IP Address Example**: Tools, resources, and CLI commands for IP geolocation
14
17
  - **Comprehensive Testing**: Unit and integration tests with coverage reporting
15
18
  - **Production Tooling**: ESLint, Prettier, semantic-release, and MCP Inspector integration
@@ -43,6 +46,8 @@ npm run build
43
46
  npm run cli -- get-ip-details 8.8.8.8
44
47
  npm run cli -- get-ip-details # Get your current IP
45
48
  npm run cli -- get-ip-details 1.1.1.1 --include-extended-data
49
+ npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}" # JQ filter
50
+ npm run cli -- get-ip-details 8.8.8.8 --output-format json # JSON output
46
51
 
47
52
  # 2. STDIO Transport - For AI assistant integration (Claude Desktop, Cursor)
48
53
  npm run mcp:stdio
@@ -69,6 +74,52 @@ npm run mcp:inspect # Auto-opens browser with debugging
69
74
  - Health Check: `http://localhost:3000/` → Returns server version
70
75
  - Run with: `TRANSPORT_MODE=http node dist/index.js`
71
76
 
77
+ ## Output Formats
78
+
79
+ ### TOON Format (Default)
80
+
81
+ TOON (Token-Oriented Object Notation) is a human-readable format optimized for LLMs, reducing token usage by 30-60% compared to JSON:
82
+
83
+ ```
84
+ status: success
85
+ query: 8.8.8.8
86
+ country: United States
87
+ city: Ashburn
88
+ lat: 39.03
89
+ lon: -77.5
90
+ ```
91
+
92
+ ### JSON Format
93
+
94
+ Standard JSON output when `--output-format json` is specified:
95
+
96
+ ```json
97
+ {
98
+ "status": "success",
99
+ "query": "8.8.8.8",
100
+ "country": "United States",
101
+ "city": "Ashburn"
102
+ }
103
+ ```
104
+
105
+ ### JMESPath Filtering
106
+
107
+ Use `--jq` to extract only needed fields, reducing token costs:
108
+
109
+ ```bash
110
+ # Extract specific fields
111
+ npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}"
112
+
113
+ # Output:
114
+ # ip: 8.8.8.8
115
+ # country: United States
116
+
117
+ # Nested structure
118
+ npm run cli -- get-ip-details 8.8.8.8 --jq "{location: {city: city, coords: {lat: lat, lon: lon}}}"
119
+ ```
120
+
121
+ See [JMESPath documentation](https://jmespath.org) for more filter examples.
122
+
72
123
  ## Architecture Overview
73
124
 
74
125
  <details>
@@ -99,6 +150,8 @@ src/
99
150
  │ ├── config.util.ts # Environment configuration
100
151
  │ ├── constants.util.ts # Version and package constants
101
152
  │ ├── formatter.util.ts # Markdown formatting
153
+ │ ├── toon.util.ts # TOON format encoding
154
+ │ ├── jq.util.ts # JMESPath filtering
102
155
  │ └── transport.util.ts # HTTP transport utilities
103
156
  └── index.ts # Server entry point (dual transport)
104
157
  ```
@@ -126,9 +179,9 @@ The boilerplate follows a clean, layered architecture that promotes maintainabil
126
179
  ### 3. Resources Layer (`src/resources/`)
127
180
 
128
181
  - **Purpose**: MCP resources providing contextual data accessible via URIs
129
- - **Implementation**: Resource handlers that respond to URI-based requests
130
- - **Example**: `ip://8.8.8.8` resource providing IP geolocation data
131
- - **Pattern**: Register URI patternsParse requests → Return formatted content
182
+ - **Implementation**: Uses `registerResource` API with `ResourceTemplate` for parameterized URIs
183
+ - **Example**: `ip://{ipAddress}` resource template providing IP geolocation data
184
+ - **Pattern**: Register URI templateExtract variables → Return formatted content
132
185
 
133
186
  ### 4. Controllers Layer (`src/controllers/`)
134
187
 
@@ -147,11 +200,13 @@ The boilerplate follows a clean, layered architecture that promotes maintainabil
147
200
  ### 6. Utils Layer (`src/utils/`)
148
201
 
149
202
  - **Purpose**: Shared functionality across all layers
150
- - **Key Components**:
203
+ - **Key Components**:
151
204
  - `logger.util.ts`: Contextual logging (file:method context)
152
205
  - `error.util.ts`: MCP-specific error formatting
153
206
  - `transport.util.ts`: HTTP/API utilities with retry logic
154
207
  - `config.util.ts`: Environment configuration management
208
+ - `toon.util.ts`: TOON format encoding (token-efficient output)
209
+ - `jq.util.ts`: JMESPath filtering for response transformation
155
210
 
156
211
  ## Developer Guide
157
212
 
@@ -396,16 +451,20 @@ async function handleGetData(args: Record<string, unknown>) {
396
451
  }
397
452
  }
398
453
 
399
- // Registration function following the pattern used by existing tools
454
+ // Registration function using the modern registerTool API (SDK v1.22.0+)
400
455
  function registerTools(server: McpServer) {
401
456
  const registerLogger = logger.forMethod('registerTools');
402
457
  registerLogger.debug('Registering example tools...');
403
458
 
404
- server.tool(
459
+ // SDK best practices: 'title' for UI display name, 'description' for detailed info
460
+ server.registerTool(
405
461
  'example_get_data',
406
- `Gets data from the Example API with optional parameter.
462
+ {
463
+ title: 'Get Example Data', // Display name for UI (e.g., 'Get Example Data')
464
+ description: `Gets data from the Example API with optional parameter.
407
465
  Use this tool to fetch example data. Returns formatted data as Markdown.`,
408
- GetDataSchema.shape,
466
+ inputSchema: GetDataSchema,
467
+ },
409
468
  handleGetData
410
469
  );
411
470
 
@@ -484,6 +543,63 @@ import exampleTools from './tools/example.tool.js';
484
543
  exampleTools.registerTools(serverInstance);
485
544
  ```
486
545
 
546
+ ### 6. Add MCP Resource (Optional)
547
+
548
+ Create a resource in `src/resources/` using the modern `registerResource` API:
549
+
550
+ ```typescript
551
+ // src/resources/example.resource.ts
552
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
553
+ import { Logger } from '../utils/logger.util.js';
554
+ import exampleController from '../controllers/example.controller.js';
555
+ import { formatErrorForMcpResource } from '../utils/error.util.js';
556
+
557
+ const logger = Logger.forContext('resources/example.resource.ts');
558
+
559
+ function registerResources(server: McpServer) {
560
+ const registerLogger = logger.forMethod('registerResources');
561
+ registerLogger.debug('Registering example resources...');
562
+
563
+ // Use registerResource with ResourceTemplate for parameterized URIs (SDK v1.22.0+)
564
+ server.registerResource(
565
+ 'example-data',
566
+ new ResourceTemplate('example://{param}', { list: undefined }),
567
+ {
568
+ title: 'Example Data', // Display name for UI
569
+ description: 'Retrieve example data by parameter'
570
+ },
571
+ async (uri, variables) => {
572
+ const methodLogger = logger.forMethod('exampleResource');
573
+ try {
574
+ // Extract parameter from template variables
575
+ const param = variables.param as string | undefined;
576
+
577
+ methodLogger.debug('Example resource called', { uri: uri.href, param });
578
+
579
+ const result = await exampleController.getData({ param });
580
+
581
+ return {
582
+ contents: [
583
+ {
584
+ uri: uri.href,
585
+ text: result.content,
586
+ mimeType: 'text/markdown'
587
+ }
588
+ ]
589
+ };
590
+ } catch (error) {
591
+ methodLogger.error('Resource error', error);
592
+ return formatErrorForMcpResource(error, uri.href);
593
+ }
594
+ }
595
+ );
596
+
597
+ registerLogger.debug('Example resources registered successfully');
598
+ }
599
+
600
+ export default { registerResources };
601
+ ```
602
+
487
603
  </details>
488
604
 
489
605
  ## IP Address Example Implementation
@@ -494,26 +610,30 @@ The boilerplate includes a complete IP address geolocation example demonstrating
494
610
 
495
611
  **CLI Commands:**
496
612
  ```bash
497
- npm run cli -- get-ip-details # Get current public IP
498
- npm run cli -- get-ip-details 8.8.8.8 # Get details for specific IP
613
+ npm run cli -- get-ip-details # Get current public IP (TOON format)
614
+ npm run cli -- get-ip-details 8.8.8.8 # Get details for specific IP
499
615
  npm run cli -- get-ip-details 1.1.1.1 --include-extended-data # With extended data
500
- npm run cli -- get-ip-details 8.8.8.8 --no-use-https # Force HTTP (for free tier)
616
+ npm run cli -- get-ip-details 8.8.8.8 --no-use-https # Force HTTP (for free tier)
617
+ npm run cli -- get-ip-details 8.8.8.8 --output-format json # JSON output
618
+ npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}" # Filtered output
501
619
  ```
502
620
 
503
621
  **MCP Tools:**
504
622
  - `ip_get_details` - IP geolocation lookup for AI assistants
623
+ - Supports `outputFormat`: "toon" (default) or "json"
624
+ - Supports `jq`: JMESPath expression for filtering
505
625
 
506
626
  **MCP Resources:**
507
- - `ip://` - Current IP details
508
- - `ip://8.8.8.8` - Specific IP details
627
+ - `ip://{ipAddress}` - IP details resource template (e.g., `ip://8.8.8.8`)
509
628
 
510
629
  ### Features Demonstrated
511
630
 
631
+ - **TOON Output**: Token-efficient format (30-60% fewer tokens than JSON)
632
+ - **JMESPath Filtering**: Extract only needed fields to reduce costs
512
633
  - **Fallback Logic**: HTTPS → HTTP fallback for free tier users
513
634
  - **Environment Detection**: Different behavior in test vs production
514
635
  - **API Token Support**: Optional token for extended data (ASN, mobile detection, etc.)
515
636
  - **Error Handling**: Structured errors for private/reserved IP addresses
516
- - **Response Formatting**: Clean Markdown output with geolocation data
517
637
 
518
638
  ### Configuration Options
519
639
 
@@ -592,9 +712,10 @@ npm run test:cli # CLI-specific tests only
592
712
  ## Resources & Documentation
593
713
 
594
714
  ### MCP Protocol Resources
595
- - [MCP Specification](https://modelcontextprotocol.io/specification/2025-06-18)
596
- - [MCP SDK Documentation](https://github.com/modelcontextprotocol/sdk)
715
+ - [MCP Specification](https://modelcontextprotocol.io/specification) - Latest protocol specification
716
+ - [MCP SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk) - TypeScript SDK v1.23.0+
597
717
  - [MCP Inspector](https://github.com/modelcontextprotocol/inspector) - Visual debugging tool
718
+ - [MCP Concepts](https://modelcontextprotocol.io/docs/concepts) - Tools, resources, transports
598
719
 
599
720
  ### Implementation References
600
721
  - [Anthropic MCP Announcement](https://www.anthropic.com/news/model-context-protocol)
package/STYLE_GUIDE.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # MCP Server Style Guide
2
2
 
3
- Based on the patterns observed and best practices, I recommend adopting the following consistent style guide across all your MCP servers:
3
+ Based on the MCP SDK v1.22.0+ best practices and observed patterns, this guide ensures consistency across all MCP servers.
4
+
5
+ ## Naming Conventions
4
6
 
5
7
  | Element | Convention | Rationale / Examples |
6
8
  | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
7
9
  | **CLI Commands** | `verb-noun` in `kebab-case`. Use the shortest unambiguous verb (`ls`, `get`, `create`, `add`, `exec`, `search`). | `ls-repos`, `get-pr`, `create-comment`, `exec-command` |
8
10
  | **CLI Options** | `--kebab-case`. Be specific (e.g., `--workspace-slug`, not just `--slug`). | `--project-key-or-id`, `--source-branch` |
9
11
  | **MCP Tool Names** | `<namespace>_<verb>_<noun>` in `snake_case`. Use a concise 2-4 char namespace. Avoid noun repetition. | `bb_ls_repos` (Bitbucket list repos), `conf_get_page` (Confluence get page), `aws_exec_command` (AWS execute command). Avoid `ip_ip_get_details`. |
12
+ | **MCP Resource Names**| `kebab-case`. Descriptive identifier for the resource type. | `ip-lookup`, `user-profile`, `config-data` |
10
13
  | **MCP Arguments** | `camelCase`. Suffix identifiers consistently (e.g., `Id`, `Key`, `Slug`). Avoid abbreviations unless universal. | `workspaceSlug`, `pullRequestId`, `sourceBranch`, `pageId`. |
11
14
  | **Boolean Args** | Use verb prefixes for clarity (`includeXxx`, `launchBrowser`). Avoid bare adjectives (`--https`). | `includeExtendedData: boolean`, `launchBrowser: boolean` |
12
15
  | **Array Args** | Use plural names (`spaceIds`, `labels`, `statuses`). | `spaceIds: string[]`, `labels: string[]` |
@@ -14,4 +17,60 @@ Based on the patterns observed and best practices, I recommend adopting the foll
14
17
  | **Arg Descriptions** | Start lowercase, explain purpose clearly. Mention defaults or constraints. | `numeric ID of the page to retrieve (e.g., "456789"). Required.` |
15
18
  | **ID/Key Naming** | Use consistent suffixes like `Id`, `Key`, `Slug`, `KeyOrId` where appropriate. | `pageId`, `projectKeyOrId`, `workspaceSlug` |
16
19
 
20
+ ## SDK Best Practices (v1.22.0+)
21
+
22
+ ### Title vs Name
23
+
24
+ All registrations (`registerTool`, `registerResource`, `registerPrompt`) support both `name` and `title`:
25
+
26
+ | Field | Purpose | Example |
27
+ | :---- | :------ | :------ |
28
+ | `name` | Unique identifier for programmatic use | `ip_get_details` |
29
+ | `title` | Human-readable display name for UI | `IP Address Lookup` |
30
+
31
+ **Always provide both** - `name` for code, `title` for user interfaces.
32
+
33
+ ### Modern Registration APIs
34
+
35
+ Use the modern `register*` methods instead of deprecated alternatives:
36
+
37
+ | Deprecated | Modern (SDK v1.22.0+) |
38
+ | :--------- | :-------------------- |
39
+ | `server.tool()` | `server.registerTool()` |
40
+ | `server.resource()` | `server.registerResource()` |
41
+ | `server.prompt()` | `server.registerPrompt()` |
42
+
43
+ ### Resource Templates
44
+
45
+ Use `ResourceTemplate` for parameterized resource URIs:
46
+
47
+ ```typescript
48
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
49
+
50
+ // Static resource - fixed URI
51
+ server.registerResource('config', 'config://app', { ... }, handler);
52
+
53
+ // Dynamic resource - parameterized URI
54
+ server.registerResource(
55
+ 'user-profile',
56
+ new ResourceTemplate('users://{userId}/profile', { list: undefined }),
57
+ { title: 'User Profile', description: '...' },
58
+ async (uri, variables) => {
59
+ const userId = variables.userId as string;
60
+ // ...
61
+ }
62
+ );
63
+ ```
64
+
65
+ ### Error Handling
66
+
67
+ Use `isError: true` for tool execution failures:
68
+
69
+ ```typescript
70
+ return {
71
+ content: [{ type: 'text', text: 'Error: Something went wrong' }],
72
+ isError: true
73
+ };
74
+ ```
75
+
17
76
  Adopting this guide will make the tools more predictable and easier for both humans and AI agents to understand and use correctly.
@@ -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
- import { ControllerResponse } from '../types/common.types.js';
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
- * @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted IP details in Markdown.
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
- * @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted IP details in Markdown.
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
- const data = await vendor_ip_api_com_service_js_1.default.get(args.ipAddress, serviceOptions);
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
- const httpData = await vendor_ip_api_com_service_js_1.default.get(args.ipAddress, {
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`, httpData);
95
- const formattedContent = (0, ipaddress_formatter_js_1.formatIpDetails)(httpData);
96
- return { content: formattedContent };
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 }));
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  /**
3
3
  * Register an IP address lookup resource with the MCP server
4
+ * Uses the modern registerResource API (SDK v1.22.0+) with ResourceTemplate
4
5
  *
5
6
  * @param server The MCP server instance
6
7
  */
@@ -3,29 +3,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
7
  const logger_util_js_1 = require("../utils/logger.util.js");
7
8
  const ipaddress_controller_js_1 = __importDefault(require("../controllers/ipaddress.controller.js"));
8
9
  const error_util_js_1 = require("../utils/error.util.js");
9
10
  const logger = logger_util_js_1.Logger.forContext('resources/ipaddress.resource.ts');
10
11
  /**
11
12
  * Register an IP address lookup resource with the MCP server
13
+ * Uses the modern registerResource API (SDK v1.22.0+) with ResourceTemplate
12
14
  *
13
15
  * @param server The MCP server instance
14
16
  */
15
17
  function registerResources(server) {
16
18
  const registerLogger = logger.forMethod('registerResources');
17
19
  registerLogger.debug('Registering IP lookup resources...');
18
- // Register the IP lookup resource
19
- server.resource('ip-lookup', 'Lookup IP address details, returning formatted text result', async (uri) => {
20
+ // Register the IP lookup resource using modern registerResource API
21
+ // ResourceTemplate enables parameterized URIs with {ipAddress} placeholder
22
+ server.registerResource('ip-lookup', new mcp_js_1.ResourceTemplate('ip://{ipAddress}', { list: undefined }), {
23
+ title: 'IP Address Lookup', // Display name for UI
24
+ description: 'Retrieve geolocation and network information for a public IP address',
25
+ }, async (uri, variables) => {
20
26
  const methodLogger = logger.forMethod('ipLookupResource');
21
27
  try {
22
- // Extract the IP address from the request path (if present)
23
- // Format of the URI would be ip://<ip-address> or ip://
28
+ // Extract ipAddress from template variables
29
+ const ipAddress = variables.ipAddress;
24
30
  methodLogger.debug('IP lookup resource called', {
25
- uri: uri.toString(),
31
+ uri: uri.href,
32
+ ipAddress,
26
33
  });
27
- // Get everything after the ip:// protocol
28
- const ipAddress = uri.toString().replace(/^ip:\/\//, '');
29
34
  // Call the controller to get the IP details
30
35
  const result = await ipaddress_controller_js_1.default.get({
31
36
  ipAddress: ipAddress || undefined,
@@ -36,17 +41,16 @@ function registerResources(server) {
36
41
  return {
37
42
  contents: [
38
43
  {
39
- uri: uri.toString(),
44
+ uri: uri.href,
40
45
  text: result.content,
41
46
  mimeType: 'text/markdown',
42
- description: `IP Details for ${ipAddress || 'current'}`,
43
47
  },
44
48
  ],
45
49
  };
46
50
  }
47
51
  catch (error) {
48
52
  methodLogger.error('Resource error', error);
49
- return (0, error_util_js_1.formatErrorForMcpResource)(error, uri.toString());
53
+ return (0, error_util_js_1.formatErrorForMcpResource)(error, uri.href);
50
54
  }
51
55
  });
52
56
  registerLogger.debug('IP lookup resources registered successfully');
@@ -24,49 +24,7 @@ export declare const IPDetailSchema: z.ZodObject<{
24
24
  mobile: z.ZodOptional<z.ZodBoolean>;
25
25
  proxy: z.ZodOptional<z.ZodBoolean>;
26
26
  hosting: z.ZodOptional<z.ZodBoolean>;
27
- }, "strip", z.ZodTypeAny, {
28
- status: string;
29
- message?: string | undefined;
30
- query?: string | undefined;
31
- country?: string | undefined;
32
- countryCode?: string | undefined;
33
- region?: string | undefined;
34
- regionName?: string | undefined;
35
- city?: string | undefined;
36
- zip?: string | undefined;
37
- lat?: number | undefined;
38
- lon?: number | undefined;
39
- timezone?: string | undefined;
40
- isp?: string | undefined;
41
- org?: string | undefined;
42
- as?: string | undefined;
43
- asname?: string | undefined;
44
- reverse?: string | undefined;
45
- mobile?: boolean | undefined;
46
- proxy?: boolean | undefined;
47
- hosting?: boolean | undefined;
48
- }, {
49
- status: string;
50
- message?: string | undefined;
51
- query?: string | undefined;
52
- country?: string | undefined;
53
- countryCode?: string | undefined;
54
- region?: string | undefined;
55
- regionName?: string | undefined;
56
- city?: string | undefined;
57
- zip?: string | undefined;
58
- lat?: number | undefined;
59
- lon?: number | undefined;
60
- timezone?: string | undefined;
61
- isp?: string | undefined;
62
- org?: string | undefined;
63
- as?: string | undefined;
64
- asname?: string | undefined;
65
- reverse?: string | undefined;
66
- mobile?: boolean | undefined;
67
- proxy?: boolean | undefined;
68
- hosting?: boolean | undefined;
69
- }>;
27
+ }, z.core.$strip>;
70
28
  /**
71
29
  * TypeScript type inferred from the IPDetailSchema.
72
30
  * Represents the expected structure of a successful ip-api.com response.
@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  /**
3
3
  * @function registerTools
4
4
  * @description Registers the IP address lookup tool ('ip_get_details') with the MCP server.
5
+ * Uses the modern registerTool API (SDK v1.22.0+) instead of deprecated tool() method.
5
6
  *
6
7
  * @param {McpServer} server - The MCP server instance.
7
8
  */
@@ -19,10 +19,6 @@ const GetIpDetailsToolSchema = zod_1.z.object({
19
19
  .describe('IP address to lookup (omit for current IP)'),
20
20
  ...ipaddress_types_js_1.IpAddressToolArgs.shape, // Merge options schema
21
21
  });
22
- /**
23
- * TypeScript type inferred from the combined tool arguments schema.
24
- */
25
- // type GetIpDetailsToolArgsType = z.infer<typeof GetIpDetailsToolSchema>;
26
22
  /**
27
23
  * @function handleGetIpDetails
28
24
  * @description MCP Tool handler to retrieve details for a given IP address (or the current IP).
@@ -54,16 +50,49 @@ async function handleGetIpDetails(args) {
54
50
  return (0, error_util_js_1.formatErrorForMcpTool)(error);
55
51
  }
56
52
  }
53
+ /**
54
+ * Tool description for ip_get_details
55
+ */
56
+ const IP_GET_DETAILS_DESCRIPTION = `Retrieve geolocation and network information for a public IP address. Returns TOON format by default (30-60% fewer tokens than JSON).
57
+
58
+ **IMPORTANT - Cost Optimization:**
59
+ - Use \`jq\` param to extract only needed fields. Unfiltered responses are expensive!
60
+ - Example: \`jq: "{ip: query, country: country, city: city}"\` - extract specific fields
61
+ - If unsure about available fields, first call WITHOUT jq filter to see all fields, then use jq in subsequent calls
62
+
63
+ **Schema Discovery Pattern:**
64
+ 1. First call: \`ipAddress: "8.8.8.8"\` (no jq) - explore available fields
65
+ 2. Then use: \`jq: "{ip: query, location: {city: city, country: country}}"\` - extract only what you need
66
+
67
+ **Output format:** TOON (default, token-efficient) or JSON (\`outputFormat: "json"\`)
68
+
69
+ **Parameters:**
70
+ - \`ipAddress\` - IP to lookup (omit for current device's public IP)
71
+ - \`includeExtendedData\` - Include ASN, host, proxy detection (requires API token)
72
+ - \`useHttps\` - Use HTTPS (default: true)
73
+ - \`jq\` - JMESPath expression to filter response
74
+ - \`outputFormat\` - "toon" (default) or "json"
75
+
76
+ **JQ examples:** \`query\` (IP only), \`{ip: query, country: country}\`, \`{location: {lat: lat, lon: lon}}\`
77
+
78
+ **Note:** Cannot lookup private IPs (192.168.x.x, 10.x.x.x). Powered by ip-api.com.`;
57
79
  /**
58
80
  * @function registerTools
59
81
  * @description Registers the IP address lookup tool ('ip_get_details') with the MCP server.
82
+ * Uses the modern registerTool API (SDK v1.22.0+) instead of deprecated tool() method.
60
83
  *
61
84
  * @param {McpServer} server - The MCP server instance.
62
85
  */
63
86
  function registerTools(server) {
64
87
  const methodLogger = logger_util_js_1.Logger.forContext('tools/ipaddress.tool.ts', 'registerTools');
65
88
  methodLogger.debug(`Registering IP address tools...`);
66
- server.tool('ip_get_details', `Retrieves detailed geolocation and network information for a public IP address (\`ipAddress\`). If no IP is provided, automatically uses the server's current public IP. Returns comprehensive location data including country, region, city, coordinates, timezone, and network details like ISP and organization. Use \`includeExtendedData\` to get additional information such as ASN, mobile/proxy detection (requires API token). **Note:** Cannot lookup private IPs (e.g., 192.168.x.x, 10.x.x.x). Powered by ip-api.com. Enable \`useHttps\` for secure connections (required for paid tier).`, GetIpDetailsToolSchema.shape, handleGetIpDetails);
89
+ // Use the modern registerTool API (SDK v1.22.0+)
90
+ // Following SDK best practices: title for UI display, description for details
91
+ server.registerTool('ip_get_details', {
92
+ title: 'IP Address Lookup',
93
+ description: IP_GET_DETAILS_DESCRIPTION,
94
+ inputSchema: GetIpDetailsToolSchema,
95
+ }, handleGetIpDetails);
67
96
  methodLogger.debug('Successfully registered ip_get_details tool.');
68
97
  }
69
98
  exports.default = { registerTools };
@@ -1,17 +1,25 @@
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<{
8
+ toon: "toon";
9
+ json: "json";
10
+ }>>;
2
11
  /**
3
12
  * Zod schema for the IP address tool arguments.
4
13
  */
5
14
  export declare const IpAddressToolArgs: z.ZodObject<{
6
15
  includeExtendedData: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
7
16
  useHttps: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
8
- }, "strict", z.ZodTypeAny, {
9
- useHttps: boolean;
10
- includeExtendedData: boolean;
11
- }, {
12
- useHttps?: boolean | undefined;
13
- includeExtendedData?: boolean | undefined;
14
- }>;
17
+ jq: z.ZodOptional<z.ZodString>;
18
+ outputFormat: z.ZodOptional<z.ZodEnum<{
19
+ toon: "toon";
20
+ json: "json";
21
+ }>>;
22
+ }, z.core.$strict>;
15
23
  /**
16
24
  * TypeScript type inferred from the IpAddressToolArgs Zod schema.
17
25
  * This represents the optional arguments passed to the tool handler and controller.
@@ -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
@@ -8,7 +8,7 @@
8
8
  * Current application version
9
9
  * This should match the version in package.json
10
10
  */
11
- export declare const VERSION = "1.14.0";
11
+ export declare const VERSION = "1.16.0";
12
12
  /**
13
13
  * Package name with scope
14
14
  * Used for initialization and identification
@@ -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.0';
14
+ exports.VERSION = '1.16.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
+ }
@@ -134,7 +134,6 @@ async function fetchApi(url, options = {}) {
134
134
  try {
135
135
  const responseData = await response.json();
136
136
  methodLogger.debug('Response body successfully parsed as JSON.');
137
- // methodLogger.debug('Response Data:', responseData); // Uncomment for full response logging
138
137
  return responseData;
139
138
  }
140
139
  catch (parseError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "1.14.0",
3
+ "version": "1.16.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",
@@ -51,35 +51,38 @@
51
51
  "node": ">=18.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@eslint/js": "^9.35.0",
54
+ "@eslint/js": "^9.39.1",
55
55
  "@semantic-release/changelog": "^6.0.3",
56
56
  "@semantic-release/exec": "^7.1.0",
57
57
  "@semantic-release/git": "^10.0.1",
58
- "@semantic-release/github": "^11.0.5",
59
- "@semantic-release/npm": "^12.0.2",
58
+ "@semantic-release/github": "^12.0.2",
59
+ "@semantic-release/npm": "^13.1.2",
60
60
  "@types/cors": "^2.8.19",
61
- "@types/express": "^5.0.3",
61
+ "@types/express": "^5.0.5",
62
62
  "@types/jest": "^30.0.0",
63
- "@types/node": "^24.3.1",
64
- "@typescript-eslint/eslint-plugin": "^8.43.0",
65
- "@typescript-eslint/parser": "^8.43.0",
66
- "eslint": "^9.35.0",
63
+ "@types/jmespath": "^0.15.2",
64
+ "@types/node": "^24.10.1",
65
+ "@typescript-eslint/eslint-plugin": "^8.48.0",
66
+ "@typescript-eslint/parser": "^8.48.0",
67
+ "eslint": "^9.39.1",
67
68
  "eslint-config-prettier": "^10.1.8",
68
69
  "eslint-plugin-prettier": "^5.5.4",
69
- "jest": "^30.1.3",
70
- "prettier": "^3.6.2",
71
- "semantic-release": "^24.2.7",
72
- "ts-jest": "^29.4.1",
73
- "typescript": "^5.9.2",
74
- "typescript-eslint": "^8.43.0"
70
+ "jest": "^30.2.0",
71
+ "prettier": "^3.7.3",
72
+ "semantic-release": "^25.0.2",
73
+ "ts-jest": "^29.4.5",
74
+ "typescript": "^5.9.3",
75
+ "typescript-eslint": "^8.48.0"
75
76
  },
76
77
  "dependencies": {
77
- "@modelcontextprotocol/sdk": "^1.17.5",
78
- "commander": "^14.0.0",
78
+ "@modelcontextprotocol/sdk": "^1.23.0",
79
+ "@toon-format/toon": "^2.0.1",
80
+ "commander": "^14.0.2",
79
81
  "cors": "^2.8.5",
80
- "dotenv": "^17.2.2",
82
+ "dotenv": "^17.2.3",
81
83
  "express": "^5.1.0",
82
- "zod": "^3.25.76"
84
+ "jmespath": "^0.16.0",
85
+ "zod": "^4.1.13"
83
86
  },
84
87
  "publishConfig": {
85
88
  "registry": "https://registry.npmjs.org/",
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "1.13.5",
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",
@@ -51,35 +51,38 @@
51
51
  "node": ">=18.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@eslint/js": "^9.35.0",
54
+ "@eslint/js": "^9.39.1",
55
55
  "@semantic-release/changelog": "^6.0.3",
56
56
  "@semantic-release/exec": "^7.1.0",
57
57
  "@semantic-release/git": "^10.0.1",
58
- "@semantic-release/github": "^11.0.5",
59
- "@semantic-release/npm": "^12.0.2",
58
+ "@semantic-release/github": "^12.0.2",
59
+ "@semantic-release/npm": "^13.1.2",
60
60
  "@types/cors": "^2.8.19",
61
- "@types/express": "^5.0.3",
61
+ "@types/express": "^5.0.5",
62
62
  "@types/jest": "^30.0.0",
63
- "@types/node": "^24.3.1",
64
- "@typescript-eslint/eslint-plugin": "^8.43.0",
65
- "@typescript-eslint/parser": "^8.43.0",
66
- "eslint": "^9.35.0",
63
+ "@types/jmespath": "^0.15.2",
64
+ "@types/node": "^24.10.1",
65
+ "@typescript-eslint/eslint-plugin": "^8.48.0",
66
+ "@typescript-eslint/parser": "^8.48.0",
67
+ "eslint": "^9.39.1",
67
68
  "eslint-config-prettier": "^10.1.8",
68
69
  "eslint-plugin-prettier": "^5.5.4",
69
- "jest": "^30.1.3",
70
- "prettier": "^3.6.2",
71
- "semantic-release": "^24.2.7",
72
- "ts-jest": "^29.4.1",
73
- "typescript": "^5.9.2",
74
- "typescript-eslint": "^8.43.0"
70
+ "jest": "^30.2.0",
71
+ "prettier": "^3.7.3",
72
+ "semantic-release": "^25.0.2",
73
+ "ts-jest": "^29.4.5",
74
+ "typescript": "^5.9.3",
75
+ "typescript-eslint": "^8.48.0"
75
76
  },
76
77
  "dependencies": {
77
- "@modelcontextprotocol/sdk": "^1.17.5",
78
- "commander": "^14.0.0",
78
+ "@modelcontextprotocol/sdk": "^1.23.0",
79
+ "@toon-format/toon": "^2.0.1",
80
+ "commander": "^14.0.2",
79
81
  "cors": "^2.8.5",
80
- "dotenv": "^17.2.2",
82
+ "dotenv": "^17.2.3",
81
83
  "express": "^5.1.0",
82
- "zod": "^3.25.76"
84
+ "jmespath": "^0.16.0",
85
+ "zod": "^4.1.13"
83
86
  },
84
87
  "publishConfig": {
85
88
  "registry": "https://registry.npmjs.org/",