@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 +1 -0
- package/CHANGELOG.md +24 -0
- package/README.md +138 -17
- package/STYLE_GUIDE.md +60 -1
- 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/resources/ipaddress.resource.d.ts +1 -0
- package/dist/resources/ipaddress.resource.js +14 -10
- package/dist/services/vendor.ip-api.com.types.d.ts +1 -43
- package/dist/tools/ipaddress.tool.d.ts +1 -0
- package/dist/tools/ipaddress.tool.js +34 -5
- package/dist/tools/ipaddress.types.d.ts +15 -7
- 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/dist/utils/transport.util.js +0 -1
- package/package.json +22 -19
- package/package.json.bak +22 -19
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**:
|
|
130
|
-
- **Example**: `ip://
|
|
131
|
-
- **Pattern**: Register URI
|
|
182
|
+
- **Implementation**: Uses `registerResource` API with `ResourceTemplate` for parameterized URIs
|
|
183
|
+
- **Example**: `ip://{ipAddress}` resource template providing IP geolocation data
|
|
184
|
+
- **Pattern**: Register URI template → Extract 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
|
|
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
|
-
|
|
459
|
+
// SDK best practices: 'title' for UI display name, 'description' for detailed info
|
|
460
|
+
server.registerTool(
|
|
405
461
|
'example_get_data',
|
|
406
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 }));
|
|
@@ -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
|
-
|
|
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
|
|
23
|
-
|
|
28
|
+
// Extract ipAddress from template variables
|
|
29
|
+
const ipAddress = variables.ipAddress;
|
|
24
30
|
methodLogger.debug('IP lookup resource called', {
|
|
25
|
-
uri: uri.
|
|
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.
|
|
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.
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
@@ -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.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.
|
|
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.
|
|
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": "^
|
|
59
|
-
"@semantic-release/npm": "^
|
|
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.
|
|
61
|
+
"@types/express": "^5.0.5",
|
|
62
62
|
"@types/jest": "^30.0.0",
|
|
63
|
-
"@types/
|
|
64
|
-
"@
|
|
65
|
-
"@typescript-eslint/
|
|
66
|
-
"eslint": "^
|
|
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.
|
|
70
|
-
"prettier": "^3.
|
|
71
|
-
"semantic-release": "^
|
|
72
|
-
"ts-jest": "^29.4.
|
|
73
|
-
"typescript": "^5.9.
|
|
74
|
-
"typescript-eslint": "^8.
|
|
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.
|
|
78
|
-
"
|
|
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.
|
|
82
|
+
"dotenv": "^17.2.3",
|
|
81
83
|
"express": "^5.1.0",
|
|
82
|
-
"
|
|
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.
|
|
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.
|
|
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": "^
|
|
59
|
-
"@semantic-release/npm": "^
|
|
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.
|
|
61
|
+
"@types/express": "^5.0.5",
|
|
62
62
|
"@types/jest": "^30.0.0",
|
|
63
|
-
"@types/
|
|
64
|
-
"@
|
|
65
|
-
"@typescript-eslint/
|
|
66
|
-
"eslint": "^
|
|
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.
|
|
70
|
-
"prettier": "^3.
|
|
71
|
-
"semantic-release": "^
|
|
72
|
-
"ts-jest": "^29.4.
|
|
73
|
-
"typescript": "^5.9.
|
|
74
|
-
"typescript-eslint": "^8.
|
|
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.
|
|
78
|
-
"
|
|
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.
|
|
82
|
+
"dotenv": "^17.2.3",
|
|
81
83
|
"express": "^5.1.0",
|
|
82
|
-
"
|
|
84
|
+
"jmespath": "^0.16.0",
|
|
85
|
+
"zod": "^4.1.13"
|
|
83
86
|
},
|
|
84
87
|
"publishConfig": {
|
|
85
88
|
"registry": "https://registry.npmjs.org/",
|