@aashari/boilerplate-mcp-server 1.5.0 → 1.5.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [1.5.1](https://github.com/aashari/boilerplate-mcp-server/compare/v1.5.0...v1.5.1) (2025-05-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Improve cross-platform compatibility for npx execution ([d840c51](https://github.com/aashari/boilerplate-mcp-server/commit/d840c51cc94ad3d54e5c38670b774cdb7d52b8a7))
7
+
8
+
9
+ ### Performance Improvements
10
+
11
+ * Update dependencies ([cbc63fe](https://github.com/aashari/boilerplate-mcp-server/commit/cbc63fe692f563a3e1b68bb136b8f567408fbf9c))
12
+
1
13
  # [1.5.0](https://github.com/aashari/boilerplate-mcp-server/compare/v1.4.9...v1.5.0) (2025-05-05)
2
14
 
3
15
 
package/dist/index.js CHANGED
@@ -105,6 +105,17 @@ async function main() {
105
105
  await startServer();
106
106
  mainLogger.info('Server is now running');
107
107
  }
108
+ // --- Start: Add Graceful Shutdown ---
109
+ const shutdown = async (signal) => {
110
+ mainLogger.info(`Received ${signal}. Shutting down gracefully...`);
111
+ // Add any specific cleanup logic here if needed in the future
112
+ // For now, just exiting cleanly is the main goal
113
+ // No need to explicitly call server disconnect if process exits
114
+ process.exit(0);
115
+ };
116
+ process.on('SIGINT', () => shutdown('SIGINT')); // Ctrl+C
117
+ process.on('SIGTERM', () => shutdown('SIGTERM')); // kill command
118
+ // --- End: Add Graceful Shutdown ---
108
119
  }
109
120
  // If this file is being executed directly (not imported), run the main function
110
121
  if (require.main === module) {
@@ -3,27 +3,6 @@
3
3
  * These types provide a standard interface for controller interactions.
4
4
  * Centralized here to ensure consistency across the codebase.
5
5
  */
6
- /**
7
- * Common pagination information for API responses.
8
- * This is used for providing consistent pagination details to clients.
9
- */
10
- export interface ResponsePagination {
11
- /**
12
- * Cursor for the next page of results, if available.
13
- * This should be passed to subsequent requests to retrieve the next page.
14
- */
15
- nextCursor?: string;
16
- /**
17
- * Whether more results are available beyond the current page.
18
- * When true, clients should use the nextCursor to retrieve more results.
19
- */
20
- hasMore: boolean;
21
- /**
22
- * The number of items in the current result set.
23
- * This helps clients track how many items they've received.
24
- */
25
- count?: number;
26
- }
27
6
  /**
28
7
  * Common response structure for controller operations.
29
8
  * All controller methods should return this structure.
@@ -35,8 +14,7 @@ export interface ControllerResponse {
35
14
  */
36
15
  content: string;
37
16
  /**
38
- * Optional pagination information for list operations.
39
- * If present, indicates that more results are available.
17
+ * Optional metadata for any extra information associated with the response.
40
18
  */
41
- pagination?: ResponsePagination;
19
+ metadata?: Record<string, unknown>;
42
20
  }
@@ -8,7 +8,7 @@
8
8
  * Current application version
9
9
  * This should match the version in package.json
10
10
  */
11
- export declare const VERSION = "1.5.0";
11
+ export declare const VERSION = "1.5.1";
12
12
  /**
13
13
  * Package name with scope
14
14
  * Used for initialization and identification
@@ -11,7 +11,7 @@ exports.CLI_NAME = exports.PACKAGE_NAME = exports.VERSION = void 0;
11
11
  * Current application version
12
12
  * This should match the version in package.json
13
13
  */
14
- exports.VERSION = '1.5.0';
14
+ exports.VERSION = '1.5.1';
15
15
  /**
16
16
  * Package name with scope
17
17
  * Used for initialization and identification
@@ -2,7 +2,6 @@
2
2
  * Standardized formatting utilities for consistent output across all CLI and Tool interfaces.
3
3
  * These functions should be used by all formatters to ensure consistent formatting.
4
4
  */
5
- import type { ResponsePagination } from './pagination.util.js';
6
5
  /**
7
6
  * Format a date in a standardized way: YYYY-MM-DD HH:MM:SS UTC
8
7
  * @param dateString - ISO date string or Date object
@@ -35,10 +34,3 @@ export declare function formatBulletList(items: Record<string, unknown>, keyForm
35
34
  * @returns Separator line
36
35
  */
37
36
  export declare function formatSeparator(): string;
38
- /**
39
- * Formats the pagination information into a standardized footer string for CLI output.
40
- *
41
- * @param {ResponsePagination | undefined} pagination - The standardized pagination object.
42
- * @returns {string} A formatted string summarizing pagination and next steps, or an empty string if no pagination.
43
- */
44
- export declare function formatPagination(pagination: ResponsePagination | undefined): string;
@@ -9,7 +9,6 @@ exports.formatUrl = formatUrl;
9
9
  exports.formatHeading = formatHeading;
10
10
  exports.formatBulletList = formatBulletList;
11
11
  exports.formatSeparator = formatSeparator;
12
- exports.formatPagination = formatPagination;
13
12
  /**
14
13
  * Format a date in a standardized way: YYYY-MM-DD HH:MM:SS UTC
15
14
  * @param dateString - ISO date string or Date object
@@ -115,39 +114,3 @@ function formatValue(value) {
115
114
  function formatSeparator() {
116
115
  return '---';
117
116
  }
118
- /**
119
- * Formats the pagination information into a standardized footer string for CLI output.
120
- *
121
- * @param {ResponsePagination | undefined} pagination - The standardized pagination object.
122
- * @returns {string} A formatted string summarizing pagination and next steps, or an empty string if no pagination.
123
- */
124
- function formatPagination(pagination) {
125
- if (!pagination) {
126
- return '';
127
- }
128
- const { count, hasMore, total, nextCursor, startAt, limit, nextPage, entityType, } = pagination;
129
- const entity = entityType ? ` ${entityType.toLowerCase()}` : ' items'; // Default to 'items'
130
- const lines = [formatSeparator()];
131
- if (total !== undefined && total !== null) {
132
- lines.push(`*Showing ${count} of ${total}${entity}.*`);
133
- }
134
- else {
135
- lines.push(`*Showing ${count}${entity}.*`);
136
- }
137
- if (hasMore) {
138
- lines.push('More results are available.');
139
- if (nextCursor) {
140
- lines.push(`*Use --cursor "${nextCursor}" to view more.*`);
141
- }
142
- else if (nextPage !== undefined) {
143
- lines.push(`*Use --page ${nextPage} to view more.*`);
144
- }
145
- else if (startAt !== undefined && limit !== undefined) {
146
- const nextStartAt = startAt + limit;
147
- lines.push(`*Use --start-at ${nextStartAt} to view more.*`);
148
- }
149
- }
150
- // Add the standard timestamp footer at the very end
151
- lines.push(`*Information retrieved at: ${formatDate(new Date())}*`);
152
- return lines.join('\n');
153
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "TypeScript Model Context Protocol (MCP) server boilerplate providing IP lookup tools/resources. Includes CLI support and extensible structure for connecting AI systems (LLMs) to external data sources like ip-api.com. Ideal template for creating new MCP integrations via Node.js.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,11 +14,12 @@
14
14
  "mcp-server": "./dist/index.js"
15
15
  },
16
16
  "scripts": {
17
- "build": "tsc",
17
+ "build": "rimraf dist && tsc",
18
18
  "prepare": "npm run build && chmod +x dist/index.js",
19
- "postinstall": "chmod +x dist/index.js || true",
19
+ "postinstall": "node scripts/ensure-executable.js || true",
20
20
  "test": "jest",
21
21
  "test:coverage": "jest --coverage",
22
+ "test:cli": "jest src/cli/.*\\.cli\\.test\\.ts --runInBand --testTimeout=60000",
22
23
  "lint": "eslint src --ext .ts --config eslint.config.mjs",
23
24
  "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
24
25
  "publish:npm": "npm publish",
@@ -70,6 +71,7 @@
70
71
  "nodemon": "^3.1.10",
71
72
  "npm-check-updates": "^18.0.1",
72
73
  "prettier": "^3.5.3",
74
+ "rimraf": "^6.0.1",
73
75
  "semantic-release": "^24.2.3",
74
76
  "ts-jest": "^29.3.2",
75
77
  "ts-node": "^10.9.2",
@@ -84,7 +86,7 @@
84
86
  "@modelcontextprotocol/sdk": "^1.11.0",
85
87
  "commander": "^13.1.0",
86
88
  "dotenv": "^16.5.0",
87
- "zod": "^3.24.3"
89
+ "zod": "^3.24.4"
88
90
  },
89
91
  "directories": {
90
92
  "example": "examples"
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aashari/boilerplate-mcp-server",
3
- "version": "1.4.9",
3
+ "version": "1.5.0",
4
4
  "description": "TypeScript Model Context Protocol (MCP) server boilerplate providing IP lookup tools/resources. Includes CLI support and extensible structure for connecting AI systems (LLMs) to external data sources like ip-api.com. Ideal template for creating new MCP integrations via Node.js.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,11 +14,12 @@
14
14
  "mcp-server": "./dist/index.js"
15
15
  },
16
16
  "scripts": {
17
- "build": "tsc",
17
+ "build": "rimraf dist && tsc",
18
18
  "prepare": "npm run build && chmod +x dist/index.js",
19
- "postinstall": "chmod +x dist/index.js || true",
19
+ "postinstall": "node scripts/ensure-executable.js || true",
20
20
  "test": "jest",
21
21
  "test:coverage": "jest --coverage",
22
+ "test:cli": "jest src/cli/.*\\.cli\\.test\\.ts --runInBand --testTimeout=60000",
22
23
  "lint": "eslint src --ext .ts --config eslint.config.mjs",
23
24
  "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.js'",
24
25
  "publish:npm": "npm publish",
@@ -70,6 +71,7 @@
70
71
  "nodemon": "^3.1.10",
71
72
  "npm-check-updates": "^18.0.1",
72
73
  "prettier": "^3.5.3",
74
+ "rimraf": "^6.0.1",
73
75
  "semantic-release": "^24.2.3",
74
76
  "ts-jest": "^29.3.2",
75
77
  "ts-node": "^10.9.2",
@@ -84,7 +86,7 @@
84
86
  "@modelcontextprotocol/sdk": "^1.11.0",
85
87
  "commander": "^13.1.0",
86
88
  "dotenv": "^16.5.0",
87
- "zod": "^3.24.3"
89
+ "zod": "^3.24.4"
88
90
  },
89
91
  "directories": {
90
92
  "example": "examples"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ // Use dynamic import meta for ESM compatibility
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const rootDir = path.resolve(__dirname, '..');
10
+ const entryPoint = path.join(rootDir, 'dist', 'index.js');
11
+
12
+ try {
13
+ if (fs.existsSync(entryPoint)) {
14
+ // Ensure the file is executable (cross-platform)
15
+ const currentMode = fs.statSync(entryPoint).mode;
16
+ // Check if executable bits are set (user, group, or other)
17
+ // Mode constants differ slightly across platforms, checking broadly
18
+ const isExecutable =
19
+ currentMode & fs.constants.S_IXUSR ||
20
+ currentMode & fs.constants.S_IXGRP ||
21
+ currentMode & fs.constants.S_IXOTH;
22
+
23
+ if (!isExecutable) {
24
+ // Set permissions to 755 (rwxr-xr-x) if not executable
25
+ fs.chmodSync(entryPoint, 0o755);
26
+ console.log(
27
+ `Made ${path.relative(rootDir, entryPoint)} executable`,
28
+ );
29
+ } else {
30
+ // console.log(`${path.relative(rootDir, entryPoint)} is already executable`);
31
+ }
32
+ } else {
33
+ // console.warn(`${path.relative(rootDir, entryPoint)} not found, skipping chmod`);
34
+ }
35
+ } catch (err) {
36
+ // console.warn(`Failed to set executable permissions: ${err.message}`);
37
+ // We use '|| true' in package.json, so no need to exit here
38
+ }
@@ -1,24 +0,0 @@
1
- "use strict";
2
- /**
3
- * Application constants
4
- *
5
- * This file contains constants used throughout the application.
6
- * Centralizing these values makes them easier to maintain and update.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.CLI_NAME = exports.PACKAGE_NAME = exports.VERSION = void 0;
10
- /**
11
- * Current application version
12
- * This should match the version in package.json
13
- */
14
- exports.VERSION = '1.1.3';
15
- /**
16
- * Package name with scope
17
- * Used for initialization and identification
18
- */
19
- exports.PACKAGE_NAME = '@aashari/boilerplate-mcp-server';
20
- /**
21
- * CLI command name
22
- * Used for binary name and CLI help text
23
- */
24
- exports.CLI_NAME = 'mcp-boilerplate';
@@ -1,31 +0,0 @@
1
- /**
2
- * Enum for different pagination types used by APIs.
3
- */
4
- export declare enum PaginationType {
5
- CURSOR = "CURSOR",// Cursor-based (e.g., Confluence v2)
6
- OFFSET = "OFFSET",// Offset-based (e.g., Jira)
7
- PAGE = "PAGE"
8
- }
9
- /**
10
- * Standardized pagination information returned by controllers.
11
- */
12
- export interface ResponsePagination {
13
- count: number;
14
- hasMore: boolean;
15
- limit?: number;
16
- startAt?: number;
17
- total?: number;
18
- nextCursor?: string;
19
- nextPage?: number;
20
- entityType?: string;
21
- }
22
- /**
23
- * Extracts standardized pagination information from various API response formats.
24
- *
25
- * @param data - The raw API response data. Expected to contain pagination fields.
26
- * @param type - The type of pagination the API uses (CURSOR, OFFSET, PAGE).
27
- * @param entityType - Optional name of the entity being paginated (for messages).
28
- * @returns A standardized ResponsePagination object, or undefined if no pagination info found.
29
- */
30
- export declare function extractPaginationInfo(data: any, // Using 'any' here as API responses vary greatly
31
- type: PaginationType, entityType?: string): ResponsePagination | undefined;
@@ -1,124 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PaginationType = void 0;
4
- exports.extractPaginationInfo = extractPaginationInfo;
5
- const logger_util_js_1 = require("./logger.util.js");
6
- const logger = logger_util_js_1.Logger.forContext('utils/pagination.util.ts');
7
- logger.debug('Pagination utility initialized');
8
- /**
9
- * Enum for different pagination types used by APIs.
10
- */
11
- var PaginationType;
12
- (function (PaginationType) {
13
- PaginationType["CURSOR"] = "CURSOR";
14
- PaginationType["OFFSET"] = "OFFSET";
15
- PaginationType["PAGE"] = "PAGE";
16
- })(PaginationType || (exports.PaginationType = PaginationType = {}));
17
- /**
18
- * Extracts standardized pagination information from various API response formats.
19
- *
20
- * @param data - The raw API response data. Expected to contain pagination fields.
21
- * @param type - The type of pagination the API uses (CURSOR, OFFSET, PAGE).
22
- * @param entityType - Optional name of the entity being paginated (for messages).
23
- * @returns A standardized ResponsePagination object, or undefined if no pagination info found.
24
- */
25
- function extractPaginationInfo(
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- data, // Using 'any' here as API responses vary greatly
28
- type, entityType) {
29
- if (!data) {
30
- return undefined;
31
- }
32
- let count = 0;
33
- let hasMore = false;
34
- let total = undefined;
35
- let nextCursor = undefined;
36
- let nextPage = undefined;
37
- let startAt = undefined;
38
- let limit = undefined;
39
- // Common count extraction
40
- if (Array.isArray(data.results)) {
41
- count = data.results.length;
42
- }
43
- else if (Array.isArray(data.values)) {
44
- count = data.values.length;
45
- }
46
- else if (Array.isArray(data.issues)) {
47
- count = data.issues.length;
48
- }
49
- else if (Array.isArray(data)) {
50
- // Handle cases where the response is just an array (e.g., global statuses)
51
- count = data.length;
52
- // Assume no more pages if the response is just an array without metadata
53
- hasMore = false;
54
- return { count, hasMore, entityType };
55
- }
56
- switch (type) {
57
- case PaginationType.CURSOR:
58
- // Confluence v2 style (_links.next)
59
- if (data._links?.next) {
60
- hasMore = true;
61
- // Extract cursor from the 'next' link query parameters
62
- try {
63
- const nextUrl = new URL(data._links.next, 'http://dummy.base');
64
- nextCursor =
65
- nextUrl.searchParams.get('cursor') || undefined;
66
- }
67
- catch (e) {
68
- logger.warn('Failed to parse next link URL for cursor:', e);
69
- }
70
- }
71
- // Limit might be present in the main response or derivable
72
- limit = data.limit || data.size || count; // Use count as fallback limit if not explicit
73
- break;
74
- case PaginationType.OFFSET:
75
- // Jira style (startAt, maxResults, total, isLast)
76
- startAt = data.startAt ?? 0;
77
- limit = data.maxResults;
78
- total = data.total;
79
- hasMore = data.isLast === false;
80
- // Calculate next startAt if possible
81
- if (hasMore && limit !== undefined) {
82
- // Keep startAt as is, formatter will calculate next step
83
- }
84
- break;
85
- case PaginationType.PAGE:
86
- // Bitbucket style (page, pagelen, size, next)
87
- limit = data.pagelen;
88
- total = data.size; // Bitbucket uses 'size' for total
89
- hasMore = !!data.next;
90
- if (hasMore) {
91
- // Try to parse the page number from the next URL
92
- try {
93
- const nextUrl = new URL(data.next);
94
- nextPage = parseInt(nextUrl.searchParams.get('page') || '', 10);
95
- if (isNaN(nextPage)) {
96
- nextPage = (data.page || 1) + 1; // Fallback calculation
97
- }
98
- }
99
- catch (e) {
100
- logger.warn('Failed to parse next link URL for page:', e);
101
- nextPage = (data.page || 1) + 1; // Fallback calculation
102
- }
103
- }
104
- break;
105
- default:
106
- logger.warn('Unknown pagination type provided:', type);
107
- return undefined; // Unknown type
108
- }
109
- // If count is zero and there's no indication of more pages, return undefined
110
- if (count === 0 && !hasMore && total === 0) {
111
- return undefined;
112
- }
113
- // Return standardized pagination object
114
- return {
115
- count,
116
- hasMore,
117
- limit,
118
- startAt,
119
- total,
120
- nextCursor,
121
- nextPage,
122
- entityType,
123
- };
124
- }