@aashari/boilerplate-mcp-server 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.releaserc.json CHANGED
@@ -1,5 +1,7 @@
1
1
  {
2
- "branches": ["main"],
2
+ "branches": [
3
+ "main"
4
+ ],
3
5
  "plugins": [
4
6
  "@semantic-release/commit-analyzer",
5
7
  "@semantic-release/release-notes-generator",
@@ -24,7 +26,8 @@
24
26
  "package.json",
25
27
  "CHANGELOG.md",
26
28
  "src/index.ts",
27
- "src/cli/index.ts"
29
+ "src/cli/index.ts",
30
+ "src/utils/constants.util.ts"
28
31
  ],
29
32
  "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
30
33
  }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [3.2.0](https://github.com/aashari/boilerplate-mcp-server/compare/v3.1.0...v3.2.0) (2026-02-17)
2
+
3
+
4
+ ### Features
5
+
6
+ * harden HTTP transport security and add tool annotations ([5919f9f](https://github.com/aashari/boilerplate-mcp-server/commit/5919f9f4c1e416da1a6f5c9ce01174a56624ba90))
7
+
8
+ # [3.1.0](https://github.com/aashari/boilerplate-mcp-server/compare/v3.0.0...v3.1.0) (2026-02-17)
9
+
10
+
11
+ ### Features
12
+
13
+ * modernize HTTP transport and standardize MCP tool contracts ([615b373](https://github.com/aashari/boilerplate-mcp-server/commit/615b3732c10ad2c607a899de0f6632221b6f7bc3))
14
+
1
15
  # [3.0.0](https://github.com/aashari/boilerplate-mcp-server/compare/v2.0.0...v3.0.0) (2026-02-04)
2
16
 
3
17
 
package/README.md CHANGED
@@ -5,7 +5,7 @@ A production-ready foundation for developing custom Model Context Protocol (MCP)
5
5
  [![NPM Version](https://img.shields.io/npm/v/@aashari/boilerplate-mcp-server)](https://www.npmjs.com/package/@aashari/boilerplate-mcp-server)
6
6
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
7
 
8
- > **Latest Update (Feb 2026)**: Updated to MCP SDK 1.25.3 and Zod 4.3.6 with new features like `z.fromJSONSchema()`, `z.xor()`, and improved validation. See [docs/MODERNIZATION.md](docs/MODERNIZATION.md) for details.
8
+ > **Latest Update (Feb 2026)**: Updated to MCP SDK 1.26.0 and Zod 4.3.6 with continued modern `registerTool` patterns and streamable HTTP improvements. See [docs/MODERNIZATION.md](docs/MODERNIZATION.md) for details.
9
9
 
10
10
  ## Features
11
11
 
@@ -18,7 +18,7 @@ A production-ready foundation for developing custom Model Context Protocol (MCP)
18
18
  - **TOON Output Format**: Token-Oriented Object Notation for 30-60% fewer tokens than JSON
19
19
  - **JMESPath Filtering**: Extract only needed fields from responses to reduce token costs
20
20
  - **Raw Response Logging**: Automatic logging of large API responses to `/tmp/mcp/<project>/` with truncation guidance
21
- - **Modern SDK**: Uses MCP SDK v1.25.3 with `registerTool` API pattern (ready for v2 migration)
21
+ - **Modern SDK**: Uses MCP SDK v1.26.0 with `registerTool` API pattern (ready for v2 migration)
22
22
  - **Complete IP Address Example**: Tools, resources, prompts, and CLI commands for IP geolocation
23
23
  - **Comprehensive Testing**: Unit and integration tests with coverage reporting (47 tests passing)
24
24
  - **Production Tooling**: ESLint, Prettier, semantic-release, and MCP Inspector integration
@@ -31,7 +31,7 @@ Model Context Protocol (MCP) is an open standard for securely connecting AI syst
31
31
 
32
32
  ## Prerequisites
33
33
 
34
- - **Node.js** (>=18.x): [Download](https://nodejs.org/)
34
+ - **Node.js** (>=20.x): [Download](https://nodejs.org/)
35
35
  - **Git**: For version control
36
36
 
37
37
  ## Quick Start
@@ -263,10 +263,6 @@ npm run mcp:stdio # STDIO transport for AI assistants
263
263
  npm run mcp:http # HTTP transport on port 3000
264
264
  npm run mcp:inspect # HTTP + auto-open MCP Inspector
265
265
 
266
- # Development with Debugging
267
- npm run dev:stdio # STDIO with MCP Inspector integration
268
- npm run dev:http # HTTP with debug logging enabled
269
-
270
266
  # Testing
271
267
  npm test # Run all tests (Jest)
272
268
  npm run test:coverage # Generate coverage report
@@ -275,7 +271,6 @@ npm run test:cli # Run CLI-specific tests
275
271
  # Code Quality
276
272
  npm run lint # ESLint with TypeScript rules
277
273
  npm run format # Prettier formatting
278
- npm run update:deps # Update dependencies
279
274
  ```
280
275
 
281
276
  ### Environment Variables
@@ -665,12 +660,18 @@ npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}" # JM
665
660
 
666
661
  **MCP Tools:**
667
662
  - `ip_get_details` - IP geolocation lookup for AI assistants
668
- - Parameters:
669
- - `ipAddress` (optional): IP address to lookup (omit for current device's public IP)
670
- - `includeExtendedData` (optional, default: `false`): Include ASN, host, organization data (requires API token)
671
- - `useHttps` (optional, default: `true`): Use HTTPS for API calls
672
- - `jq` (optional): JMESPath expression to filter/transform response
673
- - `outputFormat` (optional, default: `"toon"`): Output format - "toon" or "json"
663
+ - `ip_get_details_link` - Same lookup + resource link output for resource-aware clients
664
+
665
+ Both tools share the same parameters:
666
+ - `ipAddress` (optional): IP address to lookup (omit for current device's public IP)
667
+ - `includeExtendedData` (optional, default: `false`): Include ASN, host, organization data (requires API token)
668
+ - `useHttps` (optional, default: `true`): Use HTTPS for API calls
669
+ - `jq` (optional): JMESPath expression to filter/transform response
670
+ - `outputFormat` (optional, default: `"toon"`): Output format - "toon" or "json"
671
+
672
+ Output behavior:
673
+ - `ip_get_details`: returns one `text` content block
674
+ - `ip_get_details_link`: returns the same first `text` block plus a `resource_link` block (`ip://<resolved-ip>`)
674
675
 
675
676
  **MCP Resources:**
676
677
  - `ip://{ipAddress}` - IP details resource template (e.g., `ip://8.8.8.8`, `ip://1.1.1.1`)
@@ -718,7 +719,7 @@ PORT=3001 # Custom port
718
719
  npm run cli -- your-command
719
720
  npm run mcp:stdio # Test with MCP Inspector
720
721
  ```
721
- 4. **Publish:** `npm publish` (requires npm login)
722
+ 4. **Release:** Push conventional commits to `main` and let semantic-release publish automatically via GitHub Actions (OIDC trusted publishing).
722
723
 
723
724
  ## Testing Strategy
724
725
 
package/SECURITY.md CHANGED
@@ -322,8 +322,8 @@ logger.warn('Failed authentication attempt', {
322
322
  ### 5. Regular Updates
323
323
  Keep dependencies up-to-date:
324
324
  ```bash
325
- npm run update:check
326
- npm run update:deps
325
+ npm outdated
326
+ npx npm-check-updates
327
327
  ```
328
328
 
329
329
  ---
@@ -110,7 +110,8 @@ async function get(args = {}) {
110
110
  const useToon = args.outputFormat !== 'json';
111
111
  // Format the output
112
112
  const content = await (0, jq_util_js_1.toOutputString)(filteredData, useToon);
113
- return { content, rawResponsePath };
113
+ // Expose canonical resolved IP so callers do not need to parse rendered output
114
+ return { content, rawResponsePath, resolvedIp: data.query };
114
115
  }
115
116
  catch (error) {
116
117
  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 }));
package/dist/index.js CHANGED
@@ -5,9 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.startServer = startServer;
8
+ const node_crypto_1 = require("node:crypto");
8
9
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
9
10
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
10
11
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
12
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
11
13
  const logger_util_js_1 = require("./utils/logger.util.js");
12
14
  const config_util_js_1 = require("./utils/config.util.js");
13
15
  const constants_util_js_1 = require("./utils/constants.util.js");
@@ -22,6 +24,29 @@ const analysis_prompt_js_1 = __importDefault(require("./prompts/analysis.prompt.
22
24
  const logger = logger_util_js_1.Logger.forContext('index.ts');
23
25
  let serverInstance = null;
24
26
  let transportInstance = null;
27
+ let httpServerInstance = null;
28
+ const httpSessions = new Map();
29
+ function createMcpServer() {
30
+ const server = new mcp_js_1.McpServer({
31
+ name: constants_util_js_1.PACKAGE_NAME,
32
+ version: constants_util_js_1.VERSION,
33
+ });
34
+ ipaddress_tool_js_1.default.registerTools(server);
35
+ ipaddress_link_tool_js_1.default.registerTools(server);
36
+ ipaddress_resource_js_1.default.registerResources(server);
37
+ analysis_prompt_js_1.default.registerPrompts(server);
38
+ return server;
39
+ }
40
+ function getSessionId(req) {
41
+ const rawSessionId = req.headers['mcp-session-id'];
42
+ if (Array.isArray(rawSessionId)) {
43
+ return rawSessionId[0] ?? null;
44
+ }
45
+ if (typeof rawSessionId === 'string' && rawSessionId.length > 0) {
46
+ return rawSessionId;
47
+ }
48
+ return null;
49
+ }
25
50
  /**
26
51
  * Start the MCP server with the specified transport mode
27
52
  */
@@ -34,19 +59,11 @@ async function startServer(mode = 'stdio') {
34
59
  if (config_util_js_1.config.getBoolean('DEBUG')) {
35
60
  serverLogger.debug('Debug mode enabled');
36
61
  }
37
- serverLogger.info(`Initializing Boilerplate MCP server v${constants_util_js_1.VERSION}`);
38
- serverInstance = new mcp_js_1.McpServer({
39
- name: constants_util_js_1.PACKAGE_NAME,
40
- version: constants_util_js_1.VERSION,
41
- });
42
- // Register tools, resources, and prompts
43
- serverLogger.info('Registering MCP tools, resources, and prompts...');
44
- ipaddress_tool_js_1.default.registerTools(serverInstance);
45
- ipaddress_link_tool_js_1.default.registerTools(serverInstance);
46
- ipaddress_resource_js_1.default.registerResources(serverInstance);
47
- analysis_prompt_js_1.default.registerPrompts(serverInstance);
48
- serverLogger.debug('All tools, resources, and prompts registered');
49
62
  if (mode === 'stdio') {
63
+ serverLogger.info(`Initializing Boilerplate MCP server v${constants_util_js_1.VERSION}`);
64
+ serverLogger.info('Registering MCP tools, resources, and prompts...');
65
+ serverInstance = createMcpServer();
66
+ serverLogger.debug('All tools, resources, and prompts registered');
50
67
  serverLogger.info('Using STDIO transport');
51
68
  transportInstance = new stdio_js_1.StdioServerTransport();
52
69
  try {
@@ -76,8 +93,10 @@ async function startServer(mode = 'stdio') {
76
93
  const allowedOrigins = [
77
94
  'http://localhost',
78
95
  'http://127.0.0.1',
96
+ 'http://[::1]',
79
97
  'https://localhost',
80
98
  'https://127.0.0.1',
99
+ 'https://[::1]',
81
100
  ];
82
101
  const isAllowed = allowedOrigins.some((allowed) => origin === allowed || origin.startsWith(`${allowed}:`));
83
102
  if (!isAllowed) {
@@ -90,30 +109,138 @@ async function startServer(mode = 'stdio') {
90
109
  }
91
110
  next();
92
111
  });
93
- app.use((0, cors_1.default)());
94
- app.use(express_1.default.json());
112
+ app.use((0, cors_1.default)({ origin: true }));
113
+ app.use(express_1.default.json({ limit: '1mb' }));
95
114
  const mcpEndpoint = '/mcp';
96
115
  serverLogger.debug(`MCP endpoint: ${mcpEndpoint}`);
97
- // Create transport instance
98
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
99
- // sessionIdGenerator is optional
100
- sessionIdGenerator: undefined,
101
- });
102
- // Connect server to transport
103
- await serverInstance.connect(transport);
104
- transportInstance = transport;
105
- // Handle all MCP requests
106
- app.all(mcpEndpoint, (req, res) => {
107
- transport
108
- .handleRequest(req, res, req.body)
109
- .catch((err) => {
110
- serverLogger.error('Error in transport.handleRequest', err);
116
+ // Handle MCP POST requests (initialize + normal RPC calls)
117
+ app.post(mcpEndpoint, async (req, res) => {
118
+ const sessionId = getSessionId(req);
119
+ try {
120
+ if (sessionId) {
121
+ const session = httpSessions.get(sessionId);
122
+ if (!session) {
123
+ res.status(404).json({
124
+ jsonrpc: '2.0',
125
+ error: {
126
+ code: -32001,
127
+ message: 'Session not found for provided Mcp-Session-Id',
128
+ },
129
+ id: null,
130
+ });
131
+ return;
132
+ }
133
+ session.lastActivity = Date.now();
134
+ await session.transport.handleRequest(req, res, req.body);
135
+ return;
136
+ }
137
+ if (!(0, types_js_1.isInitializeRequest)(req.body)) {
138
+ res.status(400).json({
139
+ jsonrpc: '2.0',
140
+ error: {
141
+ code: -32000,
142
+ message: 'Bad Request: Missing Mcp-Session-Id header',
143
+ },
144
+ id: null,
145
+ });
146
+ return;
147
+ }
148
+ serverLogger.info('Creating new HTTP MCP session for initialize request');
149
+ let initializedSessionId = null;
150
+ const sessionServer = createMcpServer();
151
+ const sessionTransport = new streamableHttp_js_1.StreamableHTTPServerTransport({
152
+ sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
153
+ onsessioninitialized: (newSessionId) => {
154
+ initializedSessionId = newSessionId;
155
+ httpSessions.set(newSessionId, {
156
+ server: sessionServer,
157
+ transport: sessionTransport,
158
+ lastActivity: Date.now(),
159
+ });
160
+ serverLogger.info(`Initialized HTTP MCP session ${newSessionId}`);
161
+ },
162
+ onsessionclosed: (closedSessionId) => {
163
+ const session = httpSessions.get(closedSessionId);
164
+ if (!session) {
165
+ return;
166
+ }
167
+ httpSessions.delete(closedSessionId);
168
+ void session.server.close().catch((err) => {
169
+ serverLogger.error(`Error closing server for session ${closedSessionId}`, err);
170
+ });
171
+ serverLogger.info(`Closed HTTP MCP session ${closedSessionId}`);
172
+ },
173
+ });
174
+ await sessionServer.connect(sessionTransport);
175
+ try {
176
+ await sessionTransport.handleRequest(req, res, req.body);
177
+ }
178
+ catch (err) {
179
+ if (initializedSessionId) {
180
+ httpSessions.delete(initializedSessionId);
181
+ }
182
+ await sessionTransport.close();
183
+ await sessionServer.close();
184
+ throw err;
185
+ }
186
+ }
187
+ catch (err) {
188
+ serverLogger.error('Error in HTTP MCP handler', err);
111
189
  if (!res.headersSent) {
112
190
  res.status(500).json({
113
- error: 'Internal Server Error',
191
+ jsonrpc: '2.0',
192
+ error: {
193
+ code: -32603,
194
+ message: 'Internal server error',
195
+ },
196
+ id: null,
114
197
  });
115
198
  }
116
- });
199
+ }
200
+ });
201
+ // Handle optional GET requests for streamable HTTP SSE
202
+ app.get(mcpEndpoint, async (req, res) => {
203
+ const sessionId = getSessionId(req);
204
+ if (!sessionId) {
205
+ res.status(400).send('Missing Mcp-Session-Id header');
206
+ return;
207
+ }
208
+ const session = httpSessions.get(sessionId);
209
+ if (!session) {
210
+ res.status(404).send('Session not found');
211
+ return;
212
+ }
213
+ try {
214
+ await session.transport.handleRequest(req, res);
215
+ }
216
+ catch (err) {
217
+ serverLogger.error('Error in HTTP MCP GET handler', err);
218
+ if (!res.headersSent) {
219
+ res.status(500).send('Internal Server Error');
220
+ }
221
+ }
222
+ });
223
+ // Handle session termination requests
224
+ app.delete(mcpEndpoint, async (req, res) => {
225
+ const sessionId = getSessionId(req);
226
+ if (!sessionId) {
227
+ res.status(400).send('Missing Mcp-Session-Id header');
228
+ return;
229
+ }
230
+ const session = httpSessions.get(sessionId);
231
+ if (!session) {
232
+ res.status(404).send('Session not found');
233
+ return;
234
+ }
235
+ try {
236
+ await session.transport.handleRequest(req, res);
237
+ }
238
+ catch (err) {
239
+ serverLogger.error('Error in HTTP MCP DELETE handler', err);
240
+ if (!res.headersSent) {
241
+ res.status(500).send('Internal Server Error');
242
+ }
243
+ }
117
244
  });
118
245
  // Health check endpoint
119
246
  app.get('/', (_req, res) => {
@@ -123,14 +250,34 @@ async function startServer(mode = 'stdio') {
123
250
  const PORT = Number(process.env.PORT ?? 3000);
124
251
  const HOST = '127.0.0.1'; // Explicit localhost binding for security
125
252
  await new Promise((resolve) => {
126
- app.listen(PORT, HOST, () => {
253
+ httpServerInstance = app.listen(PORT, HOST, () => {
127
254
  serverLogger.info(`HTTP transport listening on http://${HOST}:${PORT}${mcpEndpoint}`);
128
255
  serverLogger.info('Server bound to localhost only for security');
129
256
  resolve();
130
257
  });
131
258
  });
259
+ // Reap idle sessions every 5 minutes (TTL: 30 minutes)
260
+ const SESSION_TTL_MS = 30 * 60 * 1000;
261
+ const REAP_INTERVAL_MS = 5 * 60 * 1000;
262
+ const reapInterval = setInterval(() => {
263
+ const now = Date.now();
264
+ for (const [sessionId, session] of httpSessions.entries()) {
265
+ if (now - session.lastActivity > SESSION_TTL_MS) {
266
+ serverLogger.info(`Reaping idle HTTP session ${sessionId}`);
267
+ httpSessions.delete(sessionId);
268
+ void session.transport
269
+ .close()
270
+ .catch((err) => serverLogger.debug(`Error closing transport for reaped session ${sessionId}`, err))
271
+ .then(() => session.server.close())
272
+ .catch((err) => serverLogger.debug(`Error closing server for reaped session ${sessionId}`, err));
273
+ }
274
+ }
275
+ }, REAP_INTERVAL_MS);
276
+ reapInterval.unref();
132
277
  setupGracefulShutdown();
133
- return serverInstance;
278
+ // HTTP mode uses per-session servers (managed in httpSessions map).
279
+ // Return a reference server for API compatibility; not connected to any transport.
280
+ return createMcpServer();
134
281
  }
135
282
  }
136
283
  /**
@@ -174,9 +321,36 @@ if (require.main === module) {
174
321
  */
175
322
  function setupGracefulShutdown() {
176
323
  const shutdownLogger = logger_util_js_1.Logger.forContext('index.ts', 'shutdown');
324
+ let shuttingDown = false;
177
325
  const shutdown = async () => {
326
+ if (shuttingDown)
327
+ return;
328
+ shuttingDown = true;
178
329
  try {
179
330
  shutdownLogger.info('Shutting down gracefully...');
331
+ if (httpSessions.size > 0) {
332
+ shutdownLogger.info(`Closing ${httpSessions.size} active HTTP session(s)`);
333
+ }
334
+ for (const [sessionId, session] of httpSessions.entries()) {
335
+ try {
336
+ await session.transport.close();
337
+ }
338
+ catch (err) {
339
+ shutdownLogger.error(`Error closing transport for session ${sessionId}`, err);
340
+ }
341
+ try {
342
+ await session.server.close();
343
+ }
344
+ catch (err) {
345
+ shutdownLogger.error(`Error closing server for session ${sessionId}`, err);
346
+ }
347
+ }
348
+ httpSessions.clear();
349
+ if (httpServerInstance) {
350
+ await new Promise((resolve) => {
351
+ httpServerInstance.close(() => resolve());
352
+ });
353
+ }
180
354
  if (transportInstance &&
181
355
  'close' in transportInstance &&
182
356
  typeof transportInstance.close === 'function') {
@@ -5,68 +5,61 @@ 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 error_util_js_1 = require("../utils/error.util.js");
8
+ const formatter_util_js_1 = require("../utils/formatter.util.js");
8
9
  const zod_1 = require("zod");
9
10
  const ipaddress_controller_js_1 = __importDefault(require("../controllers/ipaddress.controller.js"));
11
+ const ipaddress_types_js_1 = require("./ipaddress.types.js");
10
12
  const logger = logger_util_js_1.Logger.forContext('tools/ipaddress-link.tool.ts');
11
13
  /**
12
14
  * Zod schema for the resource-link tool arguments
15
+ * Keep argument shape aligned with ip_get_details for consistency.
13
16
  */
14
17
  const GetIpDetailsLinkToolSchema = zod_1.z.object({
15
18
  ipAddress: zod_1.z
16
19
  .string()
17
20
  .optional()
18
21
  .describe('IP address to lookup (omit for current IP)'),
19
- includeExtendedData: zod_1.z
20
- .boolean()
21
- .optional()
22
- .describe('Include extended data (ASN, host, proxy detection)'),
22
+ ...ipaddress_types_js_1.IpAddressToolArgs.shape,
23
23
  });
24
24
  /**
25
25
  * Tool description for ip_get_details_link
26
26
  */
27
- const IP_GET_DETAILS_LINK_DESCRIPTION = `Retrieve IP address details and return as a resource reference (ResourceLink pattern).
28
-
29
- **This demonstrates the ResourceLink pattern** - instead of embedding full data inline, the tool returns a reference to a resource. This is useful for:
30
- - Large responses that would consume many tokens
31
- - Data that clients may cache and reuse
32
- - Responses that other tools can reference
33
-
34
- **When to use ResourceLink vs inline content:**
35
- - ResourceLink: Large data, cacheable, reusable across multiple tools
36
- - Inline: Small data, one-time use, immediate context
27
+ const IP_GET_DETAILS_LINK_DESCRIPTION = `Retrieve IP address details and return both direct content and a resource link.
37
28
 
38
- **Parameters:**
39
- - \`ipAddress\` - IP to lookup (omit for current device's public IP)
40
- - \`includeExtendedData\` - Include ASN, host, proxy detection (requires API token)
29
+ **Consistency with ip_get_details:**
30
+ - Uses the same arguments: \`ipAddress\`, \`includeExtendedData\`, \`useHttps\`, \`jq\`, \`outputFormat\`
31
+ - Uses the same output rendering pipeline (TOON by default, JSON when \`outputFormat: "json"\`)
41
32
 
42
- **Returns:** A resource reference (resourceLink) instead of inline text.
33
+ **What this returns:**
34
+ - A normal text response (same rendered output as \`ip_get_details\`)
35
+ - A \`resource_link\` entry pointing to \`ip://<resolved-ip>\` for resource-style clients
43
36
 
44
37
  **Note:** Cannot lookup private IPs (192.168.x.x, 10.x.x.x). Powered by ip-api.com.`;
45
38
  /**
46
- * Handle IP lookup with ResourceLink pattern
39
+ * Handle IP lookup with resource-link + text output consistency.
47
40
  */
48
41
  async function handleGetIpDetailsLink(args) {
49
42
  const methodLogger = logger.forMethod('handleGetIpDetailsLink');
50
43
  methodLogger.debug(`Getting IP address details link for ${args.ipAddress || 'current IP'}...`, args);
51
44
  try {
52
- // First, verify we can get the data
53
45
  const result = await ipaddress_controller_js_1.default.get(args);
54
- // Extract the actual IP from the result
55
- // The result.content includes the IP in TOON or JSON format
56
- const ipMatch = result.content.match(/query[:\s]+([0-9.]+)/);
57
- const actualIp = ipMatch?.[1] || args.ipAddress || 'current';
58
- methodLogger.debug(`IP resolved to: ${actualIp}`);
59
- // Return a ResourceLink instead of inline content
60
- // This tells the client to fetch the resource separately
46
+ const actualIp = result.resolvedIp ||
47
+ (typeof args.ipAddress === 'string' ? args.ipAddress : 'current');
48
+ methodLogger.debug(`Resolved IP for resource URI: ${actualIp}`);
49
+ const textContent = (0, formatter_util_js_1.truncateForAI)(result.content, result.rawResponsePath);
50
+ const mimeType = args.outputFormat === 'json' ? 'application/json' : 'text/plain';
61
51
  return {
62
52
  content: [
63
53
  {
64
- type: 'resource',
65
- resource: {
66
- uri: `ip://${actualIp}`,
67
- text: `IP lookup result available at resource ip://${actualIp}`,
68
- mimeType: 'text/markdown',
69
- },
54
+ type: 'text',
55
+ text: textContent,
56
+ },
57
+ {
58
+ type: 'resource_link',
59
+ uri: `ip://${actualIp}`,
60
+ name: `IP lookup ${actualIp}`,
61
+ description: `Resource link for IP lookup result ${actualIp}`,
62
+ mimeType,
70
63
  },
71
64
  ],
72
65
  };
@@ -86,6 +79,12 @@ function registerTools(server) {
86
79
  title: 'IP Address Lookup (ResourceLink)',
87
80
  description: IP_GET_DETAILS_LINK_DESCRIPTION,
88
81
  inputSchema: GetIpDetailsLinkToolSchema,
82
+ annotations: {
83
+ readOnlyHint: true,
84
+ destructiveHint: false,
85
+ idempotentHint: true,
86
+ openWorldHint: true,
87
+ },
89
88
  }, handleGetIpDetailsLink);
90
89
  methodLogger.debug('Successfully registered ip_get_details_link tool.');
91
90
  }
@@ -93,6 +93,12 @@ function registerTools(server) {
93
93
  title: 'IP Address Lookup',
94
94
  description: IP_GET_DETAILS_DESCRIPTION,
95
95
  inputSchema: GetIpDetailsToolSchema,
96
+ annotations: {
97
+ readOnlyHint: true,
98
+ destructiveHint: false,
99
+ idempotentHint: true,
100
+ openWorldHint: true,
101
+ },
96
102
  }, handleGetIpDetails);
97
103
  methodLogger.debug('Successfully registered ip_get_details tool.');
98
104
  }
@@ -50,4 +50,9 @@ export interface ControllerResponse {
50
50
  * When the response is truncated, this path allows AI to access the full data.
51
51
  */
52
52
  rawResponsePath?: string | null;
53
+ /**
54
+ * Canonical resolved IP from the upstream API response.
55
+ * Use this to avoid parsing formatted output when consumers need the queried IP value.
56
+ */
57
+ resolvedIp?: string;
53
58
  }
@@ -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 = "3.0.0";
11
+ export declare const VERSION = "3.2.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 = '3.0.0';
14
+ exports.VERSION = '3.2.0';
15
15
  /**
16
16
  * Package name with scope
17
17
  * 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.19.0';
14
+ exports.VERSION = '3.1.0';
15
15
  /**
16
16
  * Package name with scope
17
17
  * Used for initialization and identification
@@ -162,7 +162,7 @@ function createUserFriendlyErrorMessage(code, context = {}, originalMessage) {
162
162
  const entity = entityType
163
163
  ? `${entityType}${entityIdStr ? ` ${entityIdStr}` : ''}`
164
164
  : 'Resource';
165
- let message = '';
165
+ let message;
166
166
  switch (code) {
167
167
  case ErrorCode.NOT_FOUND:
168
168
  message = `${entity} not found${entityIdStr ? `: ${entityIdStr}` : ''}. Verify the ID is correct and that you have access to this ${entityType?.toLowerCase() || 'resource'}.`;
@@ -0,0 +1,306 @@
1
+ # MCP Modernization + Release Runbook
2
+
3
+ ## Purpose
4
+
5
+ This runbook documents the exact modernization + release approach used in this repository so other MCP servers can follow the same pattern with predictable outcomes.
6
+
7
+ Scope covered:
8
+
9
+ - Baseline audit (history, diffs, dependencies, CI)
10
+ - Streamable HTTP transport hardening
11
+ - Tool input/output contract standardization
12
+ - Inspector-based validation
13
+ - Semantic-release driven publishing
14
+
15
+ ---
16
+
17
+ ## Case Study Window (This Repo)
18
+
19
+ Reference range analyzed:
20
+
21
+ - From: `v3.0.0` (`fadd464`)
22
+ - To: `HEAD` (`08f8a02`)
23
+
24
+ Key commits:
25
+
26
+ - `615b373` `feat: modernize HTTP transport and standardize MCP tool contracts`
27
+ - `287cb4c` `chore(release): 3.1.0 [skip ci]`
28
+ - `08f8a02` `docs: sync README and security guidance with current tooling`
29
+
30
+ ### File-Level Change Summary (`v3.0.0..HEAD`)
31
+
32
+ - `.releaserc.json`
33
+ - `CHANGELOG.md`
34
+ - `README.md`
35
+ - `SECURITY.md`
36
+ - `package-lock.json`
37
+ - `package.json`
38
+ - `src/controllers/ipaddress.controller.ts`
39
+ - `src/index.ts`
40
+ - `src/tools/ipaddress-link.tool.ts`
41
+ - `src/types/common.types.ts`
42
+ - `src/utils/constants.util.ts`
43
+ - `src/utils/error-handler.util.ts`
44
+
45
+ High-impact deltas:
46
+
47
+ - `src/index.ts`: moved HTTP handling to session-aware streamable transport flow.
48
+ - `src/tools/ipaddress-link.tool.ts`: aligned input schema with `ip_get_details`, standardized output to `text` + `resource_link`.
49
+ - `src/controllers/ipaddress.controller.ts` + `src/types/common.types.ts`: exposed canonical `resolvedIp` to avoid parsing rendered output.
50
+ - `package.json` + `package-lock.json`: dependency upgrades and script surface simplification.
51
+ - `.releaserc.json`: added `src/utils/constants.util.ts` to git release assets to prevent version drift.
52
+
53
+ ---
54
+
55
+ ## Phase 1: Baseline Audit
56
+
57
+ ### 1.1 Capture release baseline
58
+
59
+ ```bash
60
+ git tag --sort=-v:refname | head -n 10
61
+ git log --oneline --decorate vX.Y.Z..HEAD
62
+ git diff --name-status vX.Y.Z..HEAD
63
+ git diff --stat vX.Y.Z..HEAD
64
+ ```
65
+
66
+ ### 1.2 Validate release pipeline wiring
67
+
68
+ Check:
69
+
70
+ - `.releaserc.json`
71
+ - `.github/workflows/ci-semantic-release.yml`
72
+ - `package.json` `engines`, scripts, dependencies
73
+
74
+ Release prerequisites:
75
+
76
+ - Conventional commits
77
+ - Semantic-release workflow on push to `main`
78
+ - OIDC trusted publishing (no `NPM_TOKEN` required)
79
+
80
+ ---
81
+
82
+ ## Phase 2: Modernize Streamable HTTP Transport
83
+
84
+ ## Problem Pattern
85
+
86
+ Stateless `StreamableHTTPServerTransport` reuse across requests causes failures after `initialize`.
87
+
88
+ ## Correct Pattern
89
+
90
+ Use stateful session management:
91
+
92
+ - Create a transport on initialize
93
+ - Generate `Mcp-Session-Id`
94
+ - Store per-session `{ server, transport }`
95
+ - Route POST/GET/DELETE by session
96
+ - Cleanup on session close + process shutdown
97
+
98
+ ## Implementation Checklist
99
+
100
+ - Add session map keyed by `Mcp-Session-Id`
101
+ - Validate initialize requests with `isInitializeRequest`
102
+ - Reject non-initialize requests without session header
103
+ - Implement:
104
+ - `POST /mcp` for initialize + rpc
105
+ - `GET /mcp` for SSE stream channel if used
106
+ - `DELETE /mcp` for session termination
107
+ - Add graceful shutdown over all sessions
108
+
109
+ ---
110
+
111
+ ## Phase 3: Standardize Tool Contracts
112
+
113
+ Target rule for related tools:
114
+
115
+ - Same input schema unless there is a strong functional reason to diverge.
116
+ - Same base output shape for primary payload.
117
+ - Additive behavior only where required (e.g., extra link item).
118
+
119
+ ## Applied Pattern
120
+
121
+ For `ip_get_details` and `ip_get_details_link`:
122
+
123
+ - Input parity:
124
+ - `ipAddress`
125
+ - `includeExtendedData`
126
+ - `useHttps`
127
+ - `jq`
128
+ - `outputFormat`
129
+ - Output parity:
130
+ - First content block is always `text` from the same toon/json renderer.
131
+ - Link tool extension:
132
+ - Second block is `resource_link` (`ip://<resolved-ip>`).
133
+
134
+ ## Critical anti-pattern to avoid
135
+
136
+ Do not parse rendered text (regex on TOON/JSON output) to recover structured fields.
137
+
138
+ Instead:
139
+
140
+ - expose canonical data from controller/service boundary (`resolvedIp`) and consume that.
141
+
142
+ ---
143
+
144
+ ## Phase 4: Test Matrix (Required)
145
+
146
+ ### 4.1 Static quality gates
147
+
148
+ ```bash
149
+ npm run lint
150
+ npm run build
151
+ npm test -- --runInBand
152
+ ```
153
+
154
+ ### 4.2 Streamable HTTP protocol flow test (curl)
155
+
156
+ Required sequence:
157
+
158
+ 1. `initialize`
159
+ 2. `notifications/initialized`
160
+ 3. `tools/list`
161
+ 4. `tools/call`
162
+
163
+ Assertions:
164
+
165
+ - `initialize` -> `200`, has `mcp-session-id`
166
+ - `notifications/initialized` -> `202`
167
+ - `tools/list` -> `200`
168
+ - `tools/call` -> `200`
169
+
170
+ ### 4.3 SDK client compatibility test
171
+
172
+ Use official transport:
173
+
174
+ - `StreamableHTTPClientTransport`
175
+ - `Client` from MCP TS SDK
176
+
177
+ Assertions:
178
+
179
+ - `connect()` succeeds
180
+ - `tools/list` succeeds
181
+ - representative `tools/call` succeeds
182
+
183
+ ### 4.4 Inspector validation
184
+
185
+ - CLI check:
186
+
187
+ ```bash
188
+ npx @modelcontextprotocol/inspector --cli "http://127.0.0.1:PORT/mcp" --transport http --method tools/list
189
+ ```
190
+
191
+ - UI check via proxy token URL.
192
+
193
+ For remote access:
194
+
195
+ - `HOST=0.0.0.0`
196
+ - `ALLOWED_ORIGINS` includes remote UI origin
197
+ - set `MCP_PROXY_FULL_ADDRESS` to reachable host:port
198
+
199
+ Security note: keep auth enabled (`MCP_PROXY_AUTH_TOKEN`), do not use `DANGEROUSLY_OMIT_AUTH`.
200
+
201
+ ---
202
+
203
+ ## Phase 5: Semantic Release Procedure
204
+
205
+ ### 5.1 Pre-release checks
206
+
207
+ ```bash
208
+ git status --short
209
+ npm run lint && npm run build && npm test -- --runInBand
210
+ ```
211
+
212
+ ### 5.2 Commit strategy
213
+
214
+ Use conventional commits:
215
+
216
+ - `fix:` -> patch
217
+ - `feat:` -> minor
218
+ - `feat!:` or `BREAKING CHANGE:` -> major
219
+
220
+ ### 5.3 Push and monitor
221
+
222
+ ```bash
223
+ git push origin main
224
+ gh run list --repo <owner>/<repo> --limit 5
225
+ gh run watch <run-id> --repo <owner>/<repo>
226
+ ```
227
+
228
+ ### 5.4 Verify artifacts
229
+
230
+ ```bash
231
+ git fetch --tags origin
232
+ git tag --sort=-v:refname | head
233
+ gh release list --repo <owner>/<repo> --limit 5
234
+ npm view <package-name> version
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Release Config Guardrails
240
+
241
+ ### Guardrail 1: Keep version files aligned
242
+
243
+ If custom prepare scripts update files, ensure `@semantic-release/git` assets include them.
244
+
245
+ In this repo:
246
+
247
+ - `scripts/update-version.js` updates `src/utils/constants.util.ts`
248
+ - therefore `src/utils/constants.util.ts` must be in `.releaserc.json` git assets.
249
+
250
+ ### Guardrail 2: Avoid script sprawl
251
+
252
+ Keep `package.json` scripts minimal and operationally relevant:
253
+
254
+ - build/lint/test/format
255
+ - core run paths (`cli`, `mcp:stdio`, `mcp:http`, `mcp:inspect`)
256
+
257
+ Remove duplicate aliases that do not add behavior.
258
+
259
+ ### Guardrail 3: Update active docs only
260
+
261
+ When behavior changes, update:
262
+
263
+ - `README.md`
264
+ - `SECURITY.md`
265
+ - active setup docs
266
+
267
+ Do not rewrite historical/audit snapshots unless they claim current state.
268
+
269
+ ---
270
+
271
+ ## Reusable Execution Checklist
272
+
273
+ - [ ] Identify release baseline tag and commit window
274
+ - [ ] Review commit history + per-file diff stats
275
+ - [ ] Validate semantic-release + CI workflow config
276
+ - [ ] Modernize streamable HTTP transport to session-safe pattern
277
+ - [ ] Standardize related tool input/output contracts
278
+ - [ ] Remove output parsing hacks (regex/content scraping)
279
+ - [ ] Run lint/build/tests
280
+ - [ ] Run curl protocol flow tests
281
+ - [ ] Run official SDK client tests
282
+ - [ ] Run Inspector CLI + UI tests
283
+ - [ ] Commit with conventional commit type
284
+ - [ ] Push to `main`
285
+ - [ ] Watch semantic-release workflow to completion
286
+ - [ ] Verify git tag, GitHub release, npm version
287
+ - [ ] Sync active docs with final behavior
288
+
289
+ ---
290
+
291
+ ## Appendix: Commands Used in This Repo
292
+
293
+ ```bash
294
+ # History/diff tracing
295
+ git log --oneline --decorate v3.0.0..HEAD
296
+ git diff --name-status v3.0.0..HEAD
297
+ git diff --stat v3.0.0..HEAD
298
+
299
+ # Semantic-release dry check (local repo URL fallback)
300
+ npx semantic-release --dry-run --no-ci --branches main \
301
+ --repository-url file:///absolute/path/to/repo \
302
+ --plugins @semantic-release/commit-analyzer @semantic-release/release-notes-generator
303
+
304
+ # Inspector CLI against streamable HTTP
305
+ npx @modelcontextprotocol/inspector --cli "http://127.0.0.1:3330/mcp" --transport http --method tools/list
306
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "3.0.0",
3
+ "version": "3.2.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",
@@ -14,28 +14,18 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsc",
17
+ "clean": "rm -rf dist coverage",
17
18
  "prepare": "npm run build && node scripts/ensure-executable.js",
18
19
  "postinstall": "node scripts/ensure-executable.js",
19
- "clean": "rm -rf dist coverage",
20
+ "lint": "eslint src --ext .ts --config eslint.config.mjs",
21
+ "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
20
22
  "test": "jest",
21
23
  "test:coverage": "jest --coverage",
22
24
  "test:cli": "jest src/cli/.*\\.cli\\.test\\.ts --runInBand --testTimeout=60000",
23
- "lint": "eslint src --ext .ts --config eslint.config.mjs",
24
- "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
25
- "publish:npm": "npm publish",
26
- "update:check": "npx npm-check-updates",
27
- "update:deps": "npx npm-check-updates -u && npm install --legacy-peer-deps",
28
- "update:version": "node scripts/update-version.js",
25
+ "cli": "npm run build && node dist/index.js",
29
26
  "mcp:stdio": "TRANSPORT_MODE=stdio npm run build && node dist/index.js",
30
27
  "mcp:http": "TRANSPORT_MODE=http npm run build && node dist/index.js",
31
- "mcp:inspect": "TRANSPORT_MODE=http npm run build && (node dist/index.js &) && sleep 2 && npx @modelcontextprotocol/inspector http://localhost:3000/mcp",
32
- "dev:stdio": "npm run build && npx @modelcontextprotocol/inspector -e TRANSPORT_MODE=stdio -e DEBUG=true node dist/index.js",
33
- "dev:http": "DEBUG=true TRANSPORT_MODE=http npm run build && node dist/index.js",
34
- "dev:server": "DEBUG=true npm run build && npx @modelcontextprotocol/inspector -e DEBUG=true node dist/index.js",
35
- "dev:cli": "DEBUG=true npm run build && DEBUG=true node dist/index.js",
36
- "start:server": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js",
37
- "start:cli": "npm run build && node dist/index.js",
38
- "cli": "npm run build && node dist/index.js"
28
+ "mcp:inspect": "TRANSPORT_MODE=http npm run build && (node dist/index.js &) && sleep 2 && npx @modelcontextprotocol/inspector http://localhost:3000/mcp"
39
29
  },
40
30
  "keywords": [
41
31
  "mcp",
@@ -56,42 +46,42 @@
56
46
  "author": "Andi Ashari",
57
47
  "license": "ISC",
58
48
  "engines": {
59
- "node": ">=18.0.0"
49
+ "node": ">=20.0.0"
60
50
  },
61
51
  "devDependencies": {
62
- "@eslint/js": "^9.39.1",
52
+ "@eslint/js": "^10.0.1",
63
53
  "@semantic-release/changelog": "^6.0.3",
64
54
  "@semantic-release/exec": "^7.1.0",
65
55
  "@semantic-release/git": "^10.0.1",
66
- "@semantic-release/github": "^12.0.2",
67
- "@semantic-release/npm": "^13.1.2",
56
+ "@semantic-release/github": "^12.0.6",
57
+ "@semantic-release/npm": "^13.1.4",
68
58
  "@types/cors": "^2.8.19",
69
- "@types/express": "^5.0.5",
59
+ "@types/express": "^5.0.6",
70
60
  "@types/jest": "^30.0.0",
71
61
  "@types/jmespath": "^0.15.2",
72
- "@types/node": "^24.10.10",
73
- "@typescript-eslint/eslint-plugin": "^8.48.0",
74
- "@typescript-eslint/parser": "^8.48.0",
75
- "eslint": "^9.39.1",
62
+ "@types/node": "^25.2.3",
63
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
64
+ "@typescript-eslint/parser": "^8.56.0",
65
+ "eslint": "^10.0.0",
76
66
  "eslint-config-prettier": "^10.1.8",
77
67
  "eslint-plugin-filenames": "^1.3.2",
78
- "eslint-plugin-prettier": "^5.5.4",
68
+ "eslint-plugin-prettier": "^5.5.5",
79
69
  "jest": "^30.2.0",
80
70
  "nodemon": "^3.1.11",
81
- "npm-check-updates": "^19.1.2",
82
- "prettier": "^3.7.3",
83
- "semantic-release": "^25.0.2",
84
- "ts-jest": "^29.4.5",
71
+ "npm-check-updates": "^19.3.2",
72
+ "prettier": "^3.8.1",
73
+ "semantic-release": "^25.0.3",
74
+ "ts-jest": "^29.4.6",
85
75
  "ts-node": "^10.9.2",
86
76
  "typescript": "^5.9.3",
87
- "typescript-eslint": "^8.48.0"
77
+ "typescript-eslint": "^8.56.0"
88
78
  },
89
79
  "dependencies": {
90
- "@modelcontextprotocol/sdk": "^1.25.3",
80
+ "@modelcontextprotocol/sdk": "^1.26.0",
91
81
  "@toon-format/toon": "^2.1.0",
92
82
  "commander": "^14.0.3",
93
83
  "cors": "^2.8.6",
94
- "dotenv": "^17.2.3",
84
+ "dotenv": "^17.3.1",
95
85
  "express": "^5.2.1",
96
86
  "jmespath": "^0.16.0",
97
87
  "zod": "^4.3.6"
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "2.0.0",
3
+ "version": "3.1.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",
@@ -14,28 +14,18 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsc",
17
+ "clean": "rm -rf dist coverage",
17
18
  "prepare": "npm run build && node scripts/ensure-executable.js",
18
19
  "postinstall": "node scripts/ensure-executable.js",
19
- "clean": "rm -rf dist coverage",
20
+ "lint": "eslint src --ext .ts --config eslint.config.mjs",
21
+ "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
20
22
  "test": "jest",
21
23
  "test:coverage": "jest --coverage",
22
24
  "test:cli": "jest src/cli/.*\\.cli\\.test\\.ts --runInBand --testTimeout=60000",
23
- "lint": "eslint src --ext .ts --config eslint.config.mjs",
24
- "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
25
- "publish:npm": "npm publish",
26
- "update:check": "npx npm-check-updates",
27
- "update:deps": "npx npm-check-updates -u && npm install --legacy-peer-deps",
28
- "update:version": "node scripts/update-version.js",
25
+ "cli": "npm run build && node dist/index.js",
29
26
  "mcp:stdio": "TRANSPORT_MODE=stdio npm run build && node dist/index.js",
30
27
  "mcp:http": "TRANSPORT_MODE=http npm run build && node dist/index.js",
31
- "mcp:inspect": "TRANSPORT_MODE=http npm run build && (node dist/index.js &) && sleep 2 && npx @modelcontextprotocol/inspector http://localhost:3000/mcp",
32
- "dev:stdio": "npm run build && npx @modelcontextprotocol/inspector -e TRANSPORT_MODE=stdio -e DEBUG=true node dist/index.js",
33
- "dev:http": "DEBUG=true TRANSPORT_MODE=http npm run build && node dist/index.js",
34
- "dev:server": "DEBUG=true npm run build && npx @modelcontextprotocol/inspector -e DEBUG=true node dist/index.js",
35
- "dev:cli": "DEBUG=true npm run build && DEBUG=true node dist/index.js",
36
- "start:server": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js",
37
- "start:cli": "npm run build && node dist/index.js",
38
- "cli": "npm run build && node dist/index.js"
28
+ "mcp:inspect": "TRANSPORT_MODE=http npm run build && (node dist/index.js &) && sleep 2 && npx @modelcontextprotocol/inspector http://localhost:3000/mcp"
39
29
  },
40
30
  "keywords": [
41
31
  "mcp",
@@ -56,42 +46,42 @@
56
46
  "author": "Andi Ashari",
57
47
  "license": "ISC",
58
48
  "engines": {
59
- "node": ">=18.0.0"
49
+ "node": ">=20.0.0"
60
50
  },
61
51
  "devDependencies": {
62
- "@eslint/js": "^9.39.1",
52
+ "@eslint/js": "^10.0.1",
63
53
  "@semantic-release/changelog": "^6.0.3",
64
54
  "@semantic-release/exec": "^7.1.0",
65
55
  "@semantic-release/git": "^10.0.1",
66
- "@semantic-release/github": "^12.0.2",
67
- "@semantic-release/npm": "^13.1.2",
56
+ "@semantic-release/github": "^12.0.6",
57
+ "@semantic-release/npm": "^13.1.4",
68
58
  "@types/cors": "^2.8.19",
69
- "@types/express": "^5.0.5",
59
+ "@types/express": "^5.0.6",
70
60
  "@types/jest": "^30.0.0",
71
61
  "@types/jmespath": "^0.15.2",
72
- "@types/node": "^24.10.10",
73
- "@typescript-eslint/eslint-plugin": "^8.48.0",
74
- "@typescript-eslint/parser": "^8.48.0",
75
- "eslint": "^9.39.1",
62
+ "@types/node": "^25.2.3",
63
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
64
+ "@typescript-eslint/parser": "^8.56.0",
65
+ "eslint": "^10.0.0",
76
66
  "eslint-config-prettier": "^10.1.8",
77
67
  "eslint-plugin-filenames": "^1.3.2",
78
- "eslint-plugin-prettier": "^5.5.4",
68
+ "eslint-plugin-prettier": "^5.5.5",
79
69
  "jest": "^30.2.0",
80
70
  "nodemon": "^3.1.11",
81
- "npm-check-updates": "^19.1.2",
82
- "prettier": "^3.7.3",
83
- "semantic-release": "^25.0.2",
84
- "ts-jest": "^29.4.5",
71
+ "npm-check-updates": "^19.3.2",
72
+ "prettier": "^3.8.1",
73
+ "semantic-release": "^25.0.3",
74
+ "ts-jest": "^29.4.6",
85
75
  "ts-node": "^10.9.2",
86
76
  "typescript": "^5.9.3",
87
- "typescript-eslint": "^8.48.0"
77
+ "typescript-eslint": "^8.56.0"
88
78
  },
89
79
  "dependencies": {
90
- "@modelcontextprotocol/sdk": "^1.25.3",
80
+ "@modelcontextprotocol/sdk": "^1.26.0",
91
81
  "@toon-format/toon": "^2.1.0",
92
82
  "commander": "^14.0.3",
93
83
  "cors": "^2.8.6",
94
- "dotenv": "^17.2.3",
84
+ "dotenv": "^17.3.1",
95
85
  "express": "^5.2.1",
96
86
  "jmespath": "^0.16.0",
97
87
  "zod": "^4.3.6"