mcp-ts-template 1.4.0 → 1.4.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/README.md +6 -4
- package/dist/mcp-server/server.js +2 -0
- package/dist/mcp-server/tools/catFactFetcher/catFactFetcherLogic.d.ts +39 -0
- package/dist/mcp-server/tools/catFactFetcher/catFactFetcherLogic.js +103 -0
- package/dist/mcp-server/tools/catFactFetcher/catFactFetcherRegistration.d.ts +15 -0
- package/dist/mcp-server/tools/catFactFetcher/catFactFetcherRegistration.js +80 -0
- package/dist/mcp-server/tools/catFactFetcher/index.d.ts +7 -0
- package/dist/mcp-server/tools/catFactFetcher/index.js +7 -0
- package/dist/services/duck-db/duckDBQueryExecutor.js +3 -2
- package/dist/storage/duckdbExample.js +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/internal/logger.js +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +21 -0
- package/dist/utils/network/fetchWithTimeout.js +59 -0
- package/dist/utils/network/index.d.ts +6 -0
- package/dist/utils/network/index.js +5 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
4
|
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
5
5
|
[](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/changelog.mdx)
|
|
6
|
-
[](./CHANGELOG.md)
|
|
7
7
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
8
8
|
[](https://github.com/cyanheads/mcp-ts-template/issues)
|
|
9
9
|
[](https://github.com/cyanheads/mcp-ts-template)
|
|
@@ -18,7 +18,7 @@ Whether you're creating a new MCP server to extend an AI's capabilities or integ
|
|
|
18
18
|
|
|
19
19
|
| Feature Area | Description | Key Components / Location |
|
|
20
20
|
| :-------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
|
|
21
|
-
| **🔌 MCP Server** | Functional server example
|
|
21
|
+
| **🔌 MCP Server** | Functional server with example tools (`EchoTool`, `CatFactFetcher` for async/Promise API example) and an `EchoResource`. Supports `stdio` and **Streamable HTTP** transports. | `src/mcp-server/` |
|
|
22
22
|
| **💻 MCP Client** | Working client aligned with **MCP 2025-03-26 spec**. Connects via `mcp-config.json`. Includes detailed comments. | `src/mcp-client/` |
|
|
23
23
|
| **🚀 Production Utilities** | Logging, Error Handling, ID Generation, Rate Limiting, Request Context tracking, Input Sanitization. | `src/utils/` |
|
|
24
24
|
| **🔒 Type Safety/Security** | Strong type checking via TypeScript & Zod validation. Built-in security utilities (sanitization, auth middleware stub for HTTP). | Throughout, `src/utils/security/`, `src/mcp-server/transports/authentication/` |
|
|
@@ -180,8 +180,8 @@ The `src/` directory is organized for clarity:
|
|
|
180
180
|
- `index.ts`: Barrel file exporting key client functionalities.
|
|
181
181
|
- `mcp-server/`: Logic for the MCP server _provided by this template_.
|
|
182
182
|
- `server.ts`: Initializes the server, registers tools/resources.
|
|
183
|
-
- `resources/`: Example resource implementations (e.g., EchoResource).
|
|
184
|
-
- `tools/`: Example tool implementations (e.g., EchoTool).
|
|
183
|
+
- `resources/`: Example resource implementations (e.g., `EchoResource`).
|
|
184
|
+
- `tools/`: Example tool implementations (e.g., `EchoTool`, and `CatFactFetcher` demonstrating async/Promise API calls).
|
|
185
185
|
- `transports/`: Handles `stdio` and `http` communication for the server.
|
|
186
186
|
- `services/`: Contains service integrations.
|
|
187
187
|
- `duck-db/`: Service for interacting with DuckDB, an in-process analytical data management system.
|
|
@@ -222,6 +222,8 @@ This template is designed for extension! Follow the high-level SDK patterns:
|
|
|
222
222
|
|
|
223
223
|
Refer to the included `EchoTool` and `EchoResource` examples and the [.clinerules](.clinerules) cheatsheet for detailed patterns.
|
|
224
224
|
|
|
225
|
+
For an example of a tool that performs asynchronous operations, such as making an external API call, see the `get_random_cat_fact` tool located in `src/mcp-server/tools/catFactFetcher/`. This tool demonstrates how to use `async/await` within your tool's logic and handler, manage Promises, and integrate external data sources.
|
|
226
|
+
|
|
225
227
|
## 🌍 Explore More MCP Resources
|
|
226
228
|
|
|
227
229
|
Looking for more examples, guides, and pre-built MCP servers? Check out the companion repository:
|
|
@@ -18,6 +18,7 @@ import { config, environment } from "../config/index.js";
|
|
|
18
18
|
import { ErrorHandler, logger, requestContextService } from "../utils/index.js";
|
|
19
19
|
import { registerEchoResource } from "./resources/echoResource/index.js";
|
|
20
20
|
import { registerEchoTool } from "./tools/echoTool/index.js";
|
|
21
|
+
import { registerCatFactFetcherTool } from "./tools/catFactFetcher/index.js";
|
|
21
22
|
import { startHttpTransport } from "./transports/httpTransport.js";
|
|
22
23
|
import { connectStdioTransport } from "./transports/stdioTransport.js";
|
|
23
24
|
/**
|
|
@@ -70,6 +71,7 @@ async function createMcpServerInstance() {
|
|
|
70
71
|
logger.debug("Registering resources and tools...", context);
|
|
71
72
|
await registerEchoResource(server);
|
|
72
73
|
await registerEchoTool(server);
|
|
74
|
+
await registerCatFactFetcherTool(server);
|
|
73
75
|
logger.info("Resources and tools registered successfully", context);
|
|
74
76
|
}
|
|
75
77
|
catch (err) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the core logic, schemas, and types for the `get_random_cat_fact` tool.
|
|
3
|
+
* This tool fetches a random cat fact from the public Cat Fact Ninja API.
|
|
4
|
+
* It demonstrates an asynchronous API call within an MCP tool.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/catFactFetcherLogic
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { type RequestContext } from "../../../utils/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Zod schema for validating input arguments for the `get_random_cat_fact` tool.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CatFactFetcherInputSchema: z.ZodObject<{
|
|
13
|
+
maxLength: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
maxLength?: number | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
maxLength?: number | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* TypeScript type inferred from `CatFactFetcherInputSchema`.
|
|
21
|
+
*/
|
|
22
|
+
export type CatFactFetcherInput = z.infer<typeof CatFactFetcherInputSchema>;
|
|
23
|
+
/**
|
|
24
|
+
* Defines the structure of the JSON payload returned by the `get_random_cat_fact` tool handler.
|
|
25
|
+
*/
|
|
26
|
+
export interface CatFactFetcherResponse {
|
|
27
|
+
fact: string;
|
|
28
|
+
length: number;
|
|
29
|
+
requestedMaxLength?: number;
|
|
30
|
+
timestamp: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Processes the core logic for the `get_random_cat_fact` tool.
|
|
34
|
+
* It calls the Cat Fact Ninja API and returns the fetched fact.
|
|
35
|
+
* @param params - The validated input parameters for the tool.
|
|
36
|
+
* @param context - The request context for logging and tracing.
|
|
37
|
+
* @returns A promise that resolves to an object containing the cat fact data.
|
|
38
|
+
*/
|
|
39
|
+
export declare const processCatFactFetcher: (params: CatFactFetcherInput, context: RequestContext) => Promise<CatFactFetcherResponse>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the core logic, schemas, and types for the `get_random_cat_fact` tool.
|
|
3
|
+
* This tool fetches a random cat fact from the public Cat Fact Ninja API.
|
|
4
|
+
* It demonstrates an asynchronous API call within an MCP tool.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/catFactFetcherLogic
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { logger, fetchWithTimeout, // Added import
|
|
9
|
+
} from "../../../utils/index.js";
|
|
10
|
+
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
11
|
+
/**
|
|
12
|
+
* Asynchronously fetches a random cat fact from the Cat Fact Ninja API.
|
|
13
|
+
* @param maxLength - Optional maximum length for the cat fact.
|
|
14
|
+
* @param context - The request context for logging.
|
|
15
|
+
* @returns A promise that resolves to the CatFactApiResponse.
|
|
16
|
+
* @throws {McpError} If the API request fails or returns an error.
|
|
17
|
+
*/
|
|
18
|
+
async function fetchRandomCatFactFromApi(maxLength, context) {
|
|
19
|
+
let apiUrl = "https://catfact.ninja/fact";
|
|
20
|
+
if (maxLength !== undefined) {
|
|
21
|
+
apiUrl += `?max_length=${maxLength}`;
|
|
22
|
+
}
|
|
23
|
+
logger.info(`Fetching random cat fact from: ${apiUrl}`, context);
|
|
24
|
+
const CAT_FACT_API_TIMEOUT_MS = 5000;
|
|
25
|
+
try {
|
|
26
|
+
// Use the fetchWithTimeout utility
|
|
27
|
+
const response = await fetchWithTimeout(apiUrl, CAT_FACT_API_TIMEOUT_MS, context);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const errorText = await response.text();
|
|
30
|
+
logger.error(`Cat Fact API request to ${apiUrl} failed with status ${response.status}: ${errorText}`, context);
|
|
31
|
+
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Cat Fact API request to ${apiUrl} failed: ${response.status} ${response.statusText}`, {
|
|
32
|
+
...context,
|
|
33
|
+
httpStatusCode: response.status,
|
|
34
|
+
responseBodyBrief: errorText.substring(0, 200), // Log a snippet of the response
|
|
35
|
+
errorSource: "CatFactApiNonOkResponse",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
logger.info(`Successfully fetched cat fact (length: ${data.length}): "${data.fact.substring(0, 50)}..."`, context);
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// The fetchWithTimeout utility handles AbortError and throws McpError with BaseErrorCode.TIMEOUT.
|
|
44
|
+
// It also wraps other fetch-related network errors in McpError.
|
|
45
|
+
// So, we primarily need to catch McpErrors here or wrap any truly unexpected errors.
|
|
46
|
+
if (error instanceof McpError) {
|
|
47
|
+
// Log McpErrors specifically if needed, or just re-throw
|
|
48
|
+
// If it's a TIMEOUT error from fetchWithTimeout, it's already logged by the utility.
|
|
49
|
+
// If it's a SERVICE_UNAVAILABLE from fetchWithTimeout (generic network error), also logged.
|
|
50
|
+
// If it's from response.ok check above, it's also an McpError.
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
// Fallback for any other unexpected errors not already wrapped by McpError
|
|
54
|
+
logger.error(`Unexpected error during Cat Fact API processing for ${apiUrl}: ${error instanceof Error ? error.message : String(error)}`, context);
|
|
55
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, // Or UNKNOWN_ERROR if more appropriate
|
|
56
|
+
`Unexpected error processing response from Cat Fact API (${apiUrl}): ${error instanceof Error ? error.message : String(error)}`, {
|
|
57
|
+
...context,
|
|
58
|
+
originalErrorName: error instanceof Error ? error.name : "UnknownError",
|
|
59
|
+
errorSource: "CatFactApiUnexpectedCatch",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Zod schema for validating input arguments for the `get_random_cat_fact` tool.
|
|
65
|
+
*/
|
|
66
|
+
export const CatFactFetcherInputSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
maxLength: z
|
|
69
|
+
.number()
|
|
70
|
+
.int("Max length must be an integer.")
|
|
71
|
+
.min(1, "Max length must be at least 1.")
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("Optional: The maximum character length of the cat fact to retrieve."),
|
|
74
|
+
})
|
|
75
|
+
.describe("Input schema for the get_random_cat_fact tool. Allows specifying a maximum length for the fact.");
|
|
76
|
+
/**
|
|
77
|
+
* Processes the core logic for the `get_random_cat_fact` tool.
|
|
78
|
+
* It calls the Cat Fact Ninja API and returns the fetched fact.
|
|
79
|
+
* @param params - The validated input parameters for the tool.
|
|
80
|
+
* @param context - The request context for logging and tracing.
|
|
81
|
+
* @returns A promise that resolves to an object containing the cat fact data.
|
|
82
|
+
*/
|
|
83
|
+
export const processCatFactFetcher = async (params, context) => {
|
|
84
|
+
logger.debug("Processing get_random_cat_fact logic with input parameters.", {
|
|
85
|
+
...context,
|
|
86
|
+
toolInput: { maxLength: params.maxLength },
|
|
87
|
+
});
|
|
88
|
+
const apiResponse = await fetchRandomCatFactFromApi(params.maxLength, context);
|
|
89
|
+
const toolResponse = {
|
|
90
|
+
fact: apiResponse.fact,
|
|
91
|
+
length: apiResponse.length,
|
|
92
|
+
requestedMaxLength: params.maxLength,
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
logger.debug("Random cat fact fetched and processed successfully.", {
|
|
96
|
+
...context,
|
|
97
|
+
toolResponseSummary: {
|
|
98
|
+
factLength: toolResponse.length,
|
|
99
|
+
requestedMaxLength: toolResponse.requestedMaxLength,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
return toolResponse;
|
|
103
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handles the registration of the `get_random_cat_fact` tool
|
|
3
|
+
* with an MCP server instance. This tool fetches a random cat fact from the
|
|
4
|
+
* Cat Fact Ninja API.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/catFactFetcherRegistration
|
|
6
|
+
*/
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
/**
|
|
9
|
+
* Registers the 'get_random_cat_fact' tool and its handler with the MCP server.
|
|
10
|
+
*
|
|
11
|
+
* @param server - The MCP server instance to register the tool with.
|
|
12
|
+
* @returns A promise that resolves when tool registration is complete.
|
|
13
|
+
* @throws {McpError} If registration fails critically.
|
|
14
|
+
*/
|
|
15
|
+
export declare const registerCatFactFetcherTool: (server: McpServer) => Promise<void>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handles the registration of the `get_random_cat_fact` tool
|
|
3
|
+
* with an MCP server instance. This tool fetches a random cat fact from the
|
|
4
|
+
* Cat Fact Ninja API.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/catFactFetcherRegistration
|
|
6
|
+
*/
|
|
7
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
8
|
+
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
9
|
+
import { CatFactFetcherInputSchema, processCatFactFetcher, } from "./catFactFetcherLogic.js";
|
|
10
|
+
/**
|
|
11
|
+
* Registers the 'get_random_cat_fact' tool and its handler with the MCP server.
|
|
12
|
+
*
|
|
13
|
+
* @param server - The MCP server instance to register the tool with.
|
|
14
|
+
* @returns A promise that resolves when tool registration is complete.
|
|
15
|
+
* @throws {McpError} If registration fails critically.
|
|
16
|
+
*/
|
|
17
|
+
export const registerCatFactFetcherTool = async (server) => {
|
|
18
|
+
const toolName = "get_random_cat_fact";
|
|
19
|
+
const toolDescription = "Fetches a random cat fact from the Cat Fact Ninja API. Optionally, a maximum length for the fact can be specified.";
|
|
20
|
+
const registrationContext = requestContextService.createRequestContext({
|
|
21
|
+
operation: "RegisterTool",
|
|
22
|
+
toolName: toolName,
|
|
23
|
+
moduleName: "CatFactFetcherRegistration",
|
|
24
|
+
});
|
|
25
|
+
logger.info(`Attempting to register tool: '${toolName}'`, registrationContext);
|
|
26
|
+
await ErrorHandler.tryCatch(async () => {
|
|
27
|
+
server.tool(toolName, toolDescription, CatFactFetcherInputSchema.shape, // SDK uses this for schema generation & validation
|
|
28
|
+
async (params) => {
|
|
29
|
+
const handlerContext = requestContextService.createRequestContext({
|
|
30
|
+
parentContext: registrationContext,
|
|
31
|
+
operation: "HandleToolRequest",
|
|
32
|
+
toolName: toolName,
|
|
33
|
+
inputSummary: { maxLength: params.maxLength },
|
|
34
|
+
});
|
|
35
|
+
logger.debug(`Handling '${toolName}' tool request.`, handlerContext);
|
|
36
|
+
return await ErrorHandler.tryCatch(async () => {
|
|
37
|
+
const responsePayload = await processCatFactFetcher(params, handlerContext);
|
|
38
|
+
logger.debug(`'${toolName}' tool processed successfully. Preparing result.`, handlerContext);
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: JSON.stringify(responsePayload, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: false,
|
|
47
|
+
};
|
|
48
|
+
}, {
|
|
49
|
+
operation: `ExecutingCoreLogicFor_${toolName}`,
|
|
50
|
+
context: handlerContext,
|
|
51
|
+
input: params, // Input is sanitized by ErrorHandler for logging
|
|
52
|
+
errorMapper: (error) => {
|
|
53
|
+
// Ensure a specific McpError is created if not already one
|
|
54
|
+
if (error instanceof McpError)
|
|
55
|
+
return error;
|
|
56
|
+
const errorMessage = `Error processing '${toolName}' tool: ${error instanceof Error ? error.message : "An unknown error occurred"}`;
|
|
57
|
+
return new McpError(BaseErrorCode.INTERNAL_ERROR, errorMessage, {
|
|
58
|
+
...handlerContext,
|
|
59
|
+
originalErrorName: error instanceof Error ? error.name : typeof error,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
logger.info(`Tool '${toolName}' registered successfully with the MCP server.`, registrationContext);
|
|
65
|
+
}, {
|
|
66
|
+
operation: `RegisteringTool_${toolName}`,
|
|
67
|
+
context: registrationContext,
|
|
68
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
69
|
+
errorMapper: (error) => {
|
|
70
|
+
if (error instanceof McpError)
|
|
71
|
+
return error;
|
|
72
|
+
const errorMessage = `Failed to register tool '${toolName}': ${error instanceof Error ? error.message : "An unknown error occurred during registration."}`;
|
|
73
|
+
return new McpError(BaseErrorCode.INITIALIZATION_FAILED, errorMessage, {
|
|
74
|
+
...registrationContext,
|
|
75
|
+
originalErrorName: error instanceof Error ? error.name : typeof error,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
critical: true, // Registration failure is critical
|
|
79
|
+
});
|
|
80
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for the `catFactFetcher` tool.
|
|
3
|
+
* This file serves as the public interface for the cat fact fetcher tool module,
|
|
4
|
+
* primarily exporting the `registerCatFactFetcherTool` function.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/index
|
|
6
|
+
*/
|
|
7
|
+
export { registerCatFactFetcherTool } from "./catFactFetcherRegistration.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for the `catFactFetcher` tool.
|
|
3
|
+
* This file serves as the public interface for the cat fact fetcher tool module,
|
|
4
|
+
* primarily exporting the `registerCatFactFetcherTool` function.
|
|
5
|
+
* @module src/mcp-server/tools/catFactFetcher/index
|
|
6
|
+
*/
|
|
7
|
+
export { registerCatFactFetcherTool } from "./catFactFetcherRegistration.js";
|
|
@@ -60,9 +60,10 @@ export class DuckDBQueryExecutor {
|
|
|
60
60
|
else {
|
|
61
61
|
resultObject = await this.dbConnection.stream(sql, params); // Cast to any for object
|
|
62
62
|
}
|
|
63
|
-
const rows = await resultObject.getRows(); // Use getRows() as per docs, and it's async
|
|
63
|
+
const rows = (await resultObject.getRows()); // Use getRows() as per docs, and it's async
|
|
64
64
|
const columnNames = resultObject.columnNames(); // Sync as per docs
|
|
65
|
-
const columnTypes = resultObject
|
|
65
|
+
const columnTypes = resultObject
|
|
66
|
+
.columnTypes() // Sync as per docs
|
|
66
67
|
.map((ct) => ct.typeId);
|
|
67
68
|
return {
|
|
68
69
|
rows: rows,
|
|
@@ -83,7 +83,7 @@ async function runDuckDBExample() {
|
|
|
83
83
|
{ name: "Alice Wonderland", email: "alice@example.com" },
|
|
84
84
|
{ name: "Bob The Builder", email: "bob@example.com" },
|
|
85
85
|
{ name: "Charlie Chaplin", email: "charlie@example.com" },
|
|
86
|
-
].map(user => ({
|
|
86
|
+
].map((user) => ({
|
|
87
87
|
id: idGenerator.generateRandomString(6), // Generate 6-digit alphanumeric ID directly
|
|
88
88
|
...user,
|
|
89
89
|
}));
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./internal/index.js";
|
|
|
9
9
|
export * from "./metrics/index.js";
|
|
10
10
|
export * from "./parsing/index.js";
|
|
11
11
|
export * from "./security/index.js";
|
|
12
|
+
export * from "./network/index.js";
|
|
12
13
|
// It's good practice to have index.ts files in each subdirectory
|
|
13
14
|
// that export the contents of that directory.
|
|
14
15
|
// Assuming those will be created or already exist.
|
|
@@ -61,7 +61,7 @@ function createWinstonConsoleFormat() {
|
|
|
61
61
|
}
|
|
62
62
|
if (Object.keys(metaCopy).length > 0) {
|
|
63
63
|
try {
|
|
64
|
-
const replacer = (key, value) => typeof value ===
|
|
64
|
+
const replacer = (key, value) => typeof value === "bigint" ? value.toString() : value;
|
|
65
65
|
const remainingMetaJson = JSON.stringify(metaCopy, replacer, 2);
|
|
66
66
|
if (remainingMetaJson !== "{}")
|
|
67
67
|
metaString += `\n Meta: ${remainingMetaJson}`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides a utility function to make fetch requests with a specified timeout.
|
|
3
|
+
* @module src/utils/network/fetchWithTimeout
|
|
4
|
+
*/
|
|
5
|
+
import type { RequestContext } from "../internal/requestContext.js";
|
|
6
|
+
/**
|
|
7
|
+
* Options for the fetchWithTimeout utility.
|
|
8
|
+
* Extends standard RequestInit but omits 'signal' as it's handled internally.
|
|
9
|
+
*/
|
|
10
|
+
export type FetchWithTimeoutOptions = Omit<RequestInit, "signal">;
|
|
11
|
+
/**
|
|
12
|
+
* Fetches a resource with a specified timeout.
|
|
13
|
+
*
|
|
14
|
+
* @param url - The URL to fetch.
|
|
15
|
+
* @param timeoutMs - The timeout duration in milliseconds.
|
|
16
|
+
* @param context - The request context for logging.
|
|
17
|
+
* @param options - Optional fetch options (RequestInit), excluding 'signal'.
|
|
18
|
+
* @returns A promise that resolves to the Response object.
|
|
19
|
+
* @throws {McpError} If the request times out or another fetch-related error occurs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchWithTimeout(url: string | URL, timeoutMs: number, context: RequestContext, options?: FetchWithTimeoutOptions): Promise<Response>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides a utility function to make fetch requests with a specified timeout.
|
|
3
|
+
* @module src/utils/network/fetchWithTimeout
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from "../internal/logger.js"; // Adjusted import path
|
|
6
|
+
import { McpError, BaseErrorCode } from "../../types-global/errors.js";
|
|
7
|
+
/**
|
|
8
|
+
* Fetches a resource with a specified timeout.
|
|
9
|
+
*
|
|
10
|
+
* @param url - The URL to fetch.
|
|
11
|
+
* @param timeoutMs - The timeout duration in milliseconds.
|
|
12
|
+
* @param context - The request context for logging.
|
|
13
|
+
* @param options - Optional fetch options (RequestInit), excluding 'signal'.
|
|
14
|
+
* @returns A promise that resolves to the Response object.
|
|
15
|
+
* @throws {McpError} If the request times out or another fetch-related error occurs.
|
|
16
|
+
*/
|
|
17
|
+
export async function fetchWithTimeout(url, timeoutMs, context, options) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
20
|
+
const urlString = url.toString();
|
|
21
|
+
const operationDescription = `fetch ${options?.method || "GET"} ${urlString}`;
|
|
22
|
+
logger.debug(`Attempting ${operationDescription} with ${timeoutMs}ms timeout.`, context);
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
...options,
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
});
|
|
28
|
+
clearTimeout(timeoutId);
|
|
29
|
+
logger.debug(`Successfully fetched ${urlString}. Status: ${response.status}`, context);
|
|
30
|
+
return response;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
clearTimeout(timeoutId);
|
|
34
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
35
|
+
logger.error(`${operationDescription} timed out after ${timeoutMs}ms.`, {
|
|
36
|
+
...context,
|
|
37
|
+
errorSource: "FetchTimeout",
|
|
38
|
+
});
|
|
39
|
+
throw new McpError(BaseErrorCode.TIMEOUT, `${operationDescription} timed out.`, { ...context, errorSource: "FetchTimeout" });
|
|
40
|
+
}
|
|
41
|
+
// Log and re-throw other errors as McpError
|
|
42
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
43
|
+
logger.error(`Network error during ${operationDescription}: ${errorMessage}`, {
|
|
44
|
+
...context,
|
|
45
|
+
originalErrorName: error instanceof Error ? error.name : "UnknownError",
|
|
46
|
+
errorSource: "FetchNetworkError",
|
|
47
|
+
});
|
|
48
|
+
if (error instanceof McpError) {
|
|
49
|
+
// If it's already an McpError, re-throw it
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, // Generic error for network/service issues
|
|
53
|
+
`Network error during ${operationDescription}: ${errorMessage}`, {
|
|
54
|
+
...context,
|
|
55
|
+
originalErrorName: error instanceof Error ? error.name : "UnknownError",
|
|
56
|
+
errorSource: "FetchNetworkErrorWrapper",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ts-template",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "TypeScript template for building Model Context Protocol (MCP) Servers & Clients. Features extensive utilities (logger, requestContext, etc.), STDIO & Streamable HTTP (with authMiddleware), examples, and type safety. Ideal starting point for creating production-ready MCP Servers & Clients.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -30,14 +30,13 @@
|
|
|
30
30
|
"fetch-spec": "ts-node --esm scripts/fetch-openapi-spec.ts",
|
|
31
31
|
"format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
|
|
32
32
|
"inspector": "mcp-inspector --config mcp.json --server mcp-ts-template",
|
|
33
|
-
"start:client-cli": "node dist/mcp-client/cli/mcp-client-cli.js",
|
|
34
33
|
"db:generate": "MCP_LOG_LEVEL=debug tsc && node dist/storage/duckdbExample.js"
|
|
35
34
|
},
|
|
36
35
|
"dependencies": {
|
|
37
36
|
"@duckdb/node-api": "^1.3.0-alpha.21",
|
|
38
37
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
39
38
|
"@types/jsonwebtoken": "^9.0.9",
|
|
40
|
-
"@types/node": "^22.15.
|
|
39
|
+
"@types/node": "^22.15.29",
|
|
41
40
|
"@types/sanitize-html": "^2.16.0",
|
|
42
41
|
"@types/validator": "13.15.1",
|
|
43
42
|
"chalk": "^5.4.1",
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
"cli-table3": "^0.6.5",
|
|
46
45
|
"dotenv": "^16.5.0",
|
|
47
46
|
"express": "^5.1.0",
|
|
48
|
-
"ignore": "^7.0.
|
|
47
|
+
"ignore": "^7.0.5",
|
|
49
48
|
"jsonwebtoken": "^9.0.2",
|
|
50
49
|
"openai": "^5.0.1",
|
|
51
50
|
"partial-json": "^0.1.7",
|