mcp-ts-template 1.6.1 → 1.6.3
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 +1 -1
- package/dist/agent/agent-core/agent.js +8 -2
- package/dist/config/index.js +4 -2
- package/dist/index.js +1 -1
- package/dist/mcp-client/core/clientConnectionLogic.js +10 -2
- package/dist/mcp-client/core/clientManager.d.ts +2 -2
- package/dist/mcp-client/core/clientManager.js +13 -3
- package/dist/mcp-server/tools/imageTest/registration.js +6 -1
- package/dist/mcp-server/transports/httpTransport.js +40 -38
- package/dist/services/duck-db/duckDBQueryExecutor.d.ts +3 -3
- package/dist/services/duck-db/duckDBQueryExecutor.js +5 -32
- package/dist/services/duck-db/duckDBService.d.ts +4 -3
- package/dist/services/duck-db/duckDBService.js +15 -4
- package/dist/services/duck-db/types.d.ts +7 -7
- package/dist/services/llm-providers/openRouterProvider.d.ts +1 -1
- package/dist/services/llm-providers/openRouterProvider.js +11 -5
- package/dist/services/supabase/supabaseClient.d.ts +1 -1
- package/dist/types-global/errors.d.ts +2 -0
- package/dist/types-global/errors.js +2 -0
- package/dist/utils/internal/errorHandler.js +1 -1
- package/dist/utils/internal/logger.d.ts +2 -2
- package/dist/utils/parsing/dateParser.d.ts +2 -3
- package/dist/utils/parsing/dateParser.js +2 -2
- package/dist/utils/parsing/jsonParser.d.ts +1 -1
- package/dist/utils/parsing/jsonParser.js +2 -1
- package/dist/utils/scheduling/scheduler.js +2 -4
- package/dist/utils/security/sanitization.js +3 -5
- package/package.json +11 -5
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)
|
|
@@ -177,7 +177,6 @@ Begin the task. Your response must be only the JSON object.`;
|
|
|
177
177
|
{ role: "system", content: systemPrompt },
|
|
178
178
|
{ role: "user", content: initialPrompt },
|
|
179
179
|
];
|
|
180
|
-
// eslint-disable-next-line no-constant-condition
|
|
181
180
|
while (true) {
|
|
182
181
|
const llmParams = {
|
|
183
182
|
messages,
|
|
@@ -191,7 +190,7 @@ Begin the task. Your response must be only the JSON object.`;
|
|
|
191
190
|
try {
|
|
192
191
|
commandJson = jsonParser.parse(llmResponse);
|
|
193
192
|
}
|
|
194
|
-
catch (
|
|
193
|
+
catch (_e) {
|
|
195
194
|
logger.warning("LLM response was not valid JSON. Treating as a conversational message.", { ...runContext, llmResponse });
|
|
196
195
|
if (onStreamChunk) {
|
|
197
196
|
onStreamChunk(`\n[AGENT_NOTE]: The AI responded with conversational text instead of a command. I will remind it of the protocol.\n[AI]: ${llmResponse}\n`);
|
|
@@ -205,6 +204,9 @@ Begin the task. Your response must be only the JSON object.`;
|
|
|
205
204
|
const { command, arguments: args } = commandJson;
|
|
206
205
|
if (command === "mcp_tool_call") {
|
|
207
206
|
const toolResult = await this._executeToolCall(args, runContext);
|
|
207
|
+
if (typeof args.name !== "string") {
|
|
208
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Tool call name is missing or not a string.", runContext);
|
|
209
|
+
}
|
|
208
210
|
const toolMessage = {
|
|
209
211
|
role: "tool",
|
|
210
212
|
tool_call_id: args.name,
|
|
@@ -315,6 +317,10 @@ Begin the task. Your response must be only the JSON object.`;
|
|
|
315
317
|
args,
|
|
316
318
|
});
|
|
317
319
|
const client = await this.mcpClientManager.connectMcpClient(serverName, context);
|
|
320
|
+
if (args !== undefined &&
|
|
321
|
+
(typeof args !== "object" || args === null || Array.isArray(args))) {
|
|
322
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Tool arguments for '${toolName}' must be a plain object or undefined.`, context);
|
|
323
|
+
}
|
|
318
324
|
const toolResult = await client.callTool({
|
|
319
325
|
name: toolName,
|
|
320
326
|
arguments: args,
|
package/dist/config/index.js
CHANGED
|
@@ -49,7 +49,8 @@ try {
|
|
|
49
49
|
projectRoot = findProjectRoot(currentModuleDir);
|
|
50
50
|
}
|
|
51
51
|
catch (error) {
|
|
52
|
-
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
53
|
+
console.error(`FATAL: Error determining project root: ${errorMessage}`);
|
|
53
54
|
// Fallback to process.cwd() if project root cannot be determined.
|
|
54
55
|
// This might happen in unusual execution environments.
|
|
55
56
|
projectRoot = process.cwd();
|
|
@@ -215,7 +216,8 @@ const ensureDirectory = (dirPath, rootDir, dirName) => {
|
|
|
215
216
|
}
|
|
216
217
|
catch (statError) {
|
|
217
218
|
if (process.stdout.isTTY) {
|
|
218
|
-
|
|
219
|
+
const statErrorMessage = statError instanceof Error ? statError.message : String(statError);
|
|
220
|
+
console.error(`Error accessing ${dirName} path ${resolvedDirPath}: ${statErrorMessage}`);
|
|
219
221
|
}
|
|
220
222
|
return null;
|
|
221
223
|
}
|
package/dist/index.js
CHANGED
|
@@ -178,7 +178,7 @@ const start = async () => {
|
|
|
178
178
|
logger.error("FATAL: Uncaught exception detected. This indicates a bug or unexpected state. Initiating shutdown...", errorContext);
|
|
179
179
|
await shutdown("uncaughtException");
|
|
180
180
|
});
|
|
181
|
-
process.on("unhandledRejection", async (reason,
|
|
181
|
+
process.on("unhandledRejection", async (reason, _promise) => {
|
|
182
182
|
const rejectionContext = {
|
|
183
183
|
...startupContext,
|
|
184
184
|
triggerEvent: "unhandledRejection",
|
|
@@ -68,8 +68,16 @@ export async function establishNewMcpConnection(serverName, operationContext, di
|
|
|
68
68
|
// These handlers are crucial for reacting to errors and closures.
|
|
69
69
|
// They will call the main disconnectMcpClient from clientManager.ts for cleanup.
|
|
70
70
|
client.onerror = (clientError) => {
|
|
71
|
-
const errorCode = clientError
|
|
72
|
-
|
|
71
|
+
const errorCode = typeof clientError === "object" &&
|
|
72
|
+
clientError !== null &&
|
|
73
|
+
"code" in clientError
|
|
74
|
+
? clientError.code
|
|
75
|
+
: "UNKNOWN";
|
|
76
|
+
const errorData = typeof clientError === "object" &&
|
|
77
|
+
clientError !== null &&
|
|
78
|
+
"data" in clientError
|
|
79
|
+
? clientError.data
|
|
80
|
+
: undefined;
|
|
73
81
|
logger.error(`MCP SDK Client error for server ${serverName}`, {
|
|
74
82
|
...operationContext,
|
|
75
83
|
error: clientError.message,
|
|
@@ -59,7 +59,7 @@ export declare class McpClientManager {
|
|
|
59
59
|
* @param parentContext - The context of the calling operation.
|
|
60
60
|
* @returns A promise that resolves to a map where keys are tool names and values are their definitions.
|
|
61
61
|
*/
|
|
62
|
-
getAllTools(parentContext?: RequestContext | null): Promise<Map<string,
|
|
62
|
+
getAllTools(parentContext?: RequestContext | null): Promise<Map<string, unknown>>;
|
|
63
63
|
/**
|
|
64
64
|
* Finds the server name for a given tool from the cached tool map.
|
|
65
65
|
* This is a synchronous method and relies on `getAllTools` having been called first.
|
|
@@ -67,5 +67,5 @@ export declare class McpClientManager {
|
|
|
67
67
|
* @param allTools - The map of all available tools.
|
|
68
68
|
* @returns The server name, or null if the tool is not found.
|
|
69
69
|
*/
|
|
70
|
-
getServerForTool(toolName: string, allTools: Map<string,
|
|
70
|
+
getServerForTool(toolName: string, allTools: Map<string, unknown>): string | null;
|
|
71
71
|
}
|
|
@@ -166,11 +166,18 @@ export class McpClientManager {
|
|
|
166
166
|
const toolPromises = Array.from(this.connectedClients.entries()).map(async ([serverName, client]) => {
|
|
167
167
|
try {
|
|
168
168
|
const result = await client.listTools();
|
|
169
|
-
const tools = result
|
|
169
|
+
const tools = result && typeof result === "object" && "tools" in result
|
|
170
|
+
? result.tools
|
|
171
|
+
: [];
|
|
170
172
|
if (Array.isArray(tools)) {
|
|
171
173
|
logger.debug(`Successfully fetched ${tools.length} tools from server: ${serverName}`, { ...context, serverName });
|
|
172
174
|
for (const tool of tools) {
|
|
173
|
-
|
|
175
|
+
if (tool && typeof tool === "object" && "name" in tool) {
|
|
176
|
+
allTools.set(tool.name, {
|
|
177
|
+
...tool,
|
|
178
|
+
server: serverName,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
else {
|
|
@@ -198,6 +205,9 @@ export class McpClientManager {
|
|
|
198
205
|
*/
|
|
199
206
|
getServerForTool(toolName, allTools) {
|
|
200
207
|
const tool = allTools.get(toolName);
|
|
201
|
-
|
|
208
|
+
if (tool && typeof tool === "object" && "server" in tool) {
|
|
209
|
+
return tool.server;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
202
212
|
}
|
|
203
213
|
}
|
|
@@ -18,8 +18,13 @@ export function registerFetchImageTestTool(server) {
|
|
|
18
18
|
});
|
|
19
19
|
ErrorHandler.tryCatch(async () => {
|
|
20
20
|
server.tool(toolName, toolDescription, FetchImageTestInputSchema.shape, async (input, mcpProvidedContext) => {
|
|
21
|
+
const parentRequestId = mcpProvidedContext &&
|
|
22
|
+
typeof mcpProvidedContext === "object" &&
|
|
23
|
+
"requestId" in mcpProvidedContext
|
|
24
|
+
? mcpProvidedContext.requestId
|
|
25
|
+
: registrationContext.requestId;
|
|
21
26
|
const handlerContext = requestContextService.createRequestContext({
|
|
22
|
-
parentRequestId
|
|
27
|
+
parentRequestId,
|
|
23
28
|
operation: "fetchImageTestToolHandler",
|
|
24
29
|
toolName: toolName,
|
|
25
30
|
input,
|
|
@@ -25,7 +25,7 @@ import { randomUUID } from "node:crypto";
|
|
|
25
25
|
import { config } from "../../config/index.js";
|
|
26
26
|
import { BaseErrorCode, McpError } from "../../types-global/errors.js";
|
|
27
27
|
import { logger, rateLimiter, requestContextService, } from "../../utils/index.js";
|
|
28
|
-
import { jwtAuthMiddleware, oauthMiddleware
|
|
28
|
+
import { jwtAuthMiddleware, oauthMiddleware } from "./auth/index.js";
|
|
29
29
|
import { httpErrorHandler } from "./httpErrorHandler.js";
|
|
30
30
|
const HTTP_PORT = config.mcpHttpPort;
|
|
31
31
|
const HTTP_HOST = config.mcpHttpHost;
|
|
@@ -37,13 +37,7 @@ const MAX_PORT_RETRIES = 15;
|
|
|
37
37
|
// For a scalable deployment, this would need to be replaced with a distributed
|
|
38
38
|
// store like Redis or Memcached.
|
|
39
39
|
const transports = {};
|
|
40
|
-
async function isPortInUse(port, host,
|
|
41
|
-
const checkContext = requestContextService.createRequestContext({
|
|
42
|
-
...parentContext,
|
|
43
|
-
operation: "isPortInUse",
|
|
44
|
-
port,
|
|
45
|
-
host,
|
|
46
|
-
});
|
|
40
|
+
async function isPortInUse(port, host, _parentContext) {
|
|
47
41
|
return new Promise((resolve) => {
|
|
48
42
|
const tempServer = http.createServer();
|
|
49
43
|
tempServer
|
|
@@ -61,40 +55,48 @@ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentCont
|
|
|
61
55
|
...parentContext,
|
|
62
56
|
operation: "startHttpServerWithRetry",
|
|
63
57
|
});
|
|
64
|
-
return new Promise(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
...startContext,
|
|
69
|
-
port: currentPort,
|
|
70
|
-
attempt: i + 1,
|
|
71
|
-
};
|
|
72
|
-
if (await isPortInUse(currentPort, host, attemptContext)) {
|
|
73
|
-
logger.warning(`Port ${currentPort} is in use, retrying...`, attemptContext);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const serverInstance = serve({ fetch: app.fetch, port: currentPort, hostname: host }, (info) => {
|
|
78
|
-
const serverAddress = `http://${info.address}:${info.port}${MCP_ENDPOINT_PATH}`;
|
|
79
|
-
logger.info(`HTTP transport listening at ${serverAddress}`, {
|
|
80
|
-
...attemptContext,
|
|
81
|
-
address: serverAddress,
|
|
82
|
-
});
|
|
83
|
-
if (process.stdout.isTTY) {
|
|
84
|
-
console.log(`\n🚀 MCP Server running at: ${serverAddress}\n`);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
resolve(serverInstance);
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const tryBind = (port, attempt) => {
|
|
60
|
+
if (attempt > maxRetries + 1) {
|
|
61
|
+
reject(new Error("Failed to bind to any port after multiple retries."));
|
|
88
62
|
return;
|
|
89
63
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
64
|
+
const attemptContext = { ...startContext, port, attempt };
|
|
65
|
+
isPortInUse(port, host, attemptContext)
|
|
66
|
+
.then((inUse) => {
|
|
67
|
+
if (inUse) {
|
|
68
|
+
logger.warning(`Port ${port} is in use, retrying...`, attemptContext);
|
|
69
|
+
setTimeout(() => tryBind(port + 1, attempt + 1), 50); // Small delay
|
|
93
70
|
return;
|
|
94
71
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
72
|
+
try {
|
|
73
|
+
const serverInstance = serve({ fetch: app.fetch, port, hostname: host }, (info) => {
|
|
74
|
+
const serverAddress = `http://${info.address}:${info.port}${MCP_ENDPOINT_PATH}`;
|
|
75
|
+
logger.info(`HTTP transport listening at ${serverAddress}`, {
|
|
76
|
+
...attemptContext,
|
|
77
|
+
address: serverAddress,
|
|
78
|
+
});
|
|
79
|
+
if (process.stdout.isTTY) {
|
|
80
|
+
console.log(`\n🚀 MCP Server running at: ${serverAddress}\n`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
resolve(serverInstance);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err &&
|
|
87
|
+
typeof err === "object" &&
|
|
88
|
+
"code" in err &&
|
|
89
|
+
err.code !== "EADDRINUSE") {
|
|
90
|
+
reject(err);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
setTimeout(() => tryBind(port + 1, attempt + 1), 50);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.catch((err) => reject(err));
|
|
98
|
+
};
|
|
99
|
+
tryBind(initialPort, 1);
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
export async function startHttpTransport(createServerInstanceFn, parentContext) {
|
|
@@ -7,9 +7,9 @@ import { DuckDBQueryResult } from "./types.js";
|
|
|
7
7
|
export declare class DuckDBQueryExecutor {
|
|
8
8
|
private dbConnection;
|
|
9
9
|
constructor(connection: duckdb.DuckDBConnection);
|
|
10
|
-
run(sql: string, params?: duckdb.DuckDBValue[]
|
|
11
|
-
query<T = Record<string, unknown>>(sql: string, params?: duckdb.DuckDBValue[]
|
|
12
|
-
stream(sql: string, params?: duckdb.DuckDBValue[]
|
|
10
|
+
run(sql: string, params?: duckdb.DuckDBValue[]): Promise<void>;
|
|
11
|
+
query<T = Record<string, unknown>>(sql: string, params?: duckdb.DuckDBValue[]): Promise<DuckDBQueryResult<T>>;
|
|
12
|
+
stream(sql: string, params?: duckdb.DuckDBValue[]): Promise<duckdb.DuckDBResult>;
|
|
13
13
|
prepare(sql: string): Promise<duckdb.DuckDBPreparedStatement>;
|
|
14
14
|
beginTransaction(): Promise<void>;
|
|
15
15
|
commitTransaction(): Promise<void>;
|
|
@@ -18,11 +18,8 @@ export class DuckDBQueryExecutor {
|
|
|
18
18
|
if (params === undefined) {
|
|
19
19
|
await this.dbConnection.run(sql);
|
|
20
20
|
}
|
|
21
|
-
else if (Array.isArray(params)) {
|
|
22
|
-
await this.dbConnection.run(sql, params); // Pass array directly
|
|
23
|
-
}
|
|
24
21
|
else {
|
|
25
|
-
await this.dbConnection.run(sql, params);
|
|
22
|
+
await this.dbConnection.run(sql, params);
|
|
26
23
|
}
|
|
27
24
|
}, {
|
|
28
25
|
operation: "DuckDBQueryExecutor.run",
|
|
@@ -38,32 +35,11 @@ export class DuckDBQueryExecutor {
|
|
|
38
35
|
});
|
|
39
36
|
return ErrorHandler.tryCatch(async () => {
|
|
40
37
|
logger.debug(`Executing SQL (query): ${sql}`, { ...context, params });
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
else if (Array.isArray(params)) {
|
|
46
|
-
resultObject = await this.dbConnection.stream(sql,
|
|
47
|
-
// According to docs, params for stream are passed as subsequent arguments
|
|
48
|
-
// However, the DuckDBConnection type definition might expect a single array/object.
|
|
49
|
-
// The original code passed `params` directly, which is typical for many drivers
|
|
50
|
-
// if the method signature is `stream(sql: string, params?: any[])`
|
|
51
|
-
// Let's stick to the original way params were passed to stream, as the docs
|
|
52
|
-
// show `connection.stream(sql, values, types)` where values could be an array/object.
|
|
53
|
-
// The most robust way for array params is often `stream(sql, ...params_array)`.
|
|
54
|
-
// Let's assume the type defs are `stream(sql: string, ...args: any[])` for array params.
|
|
55
|
-
// Or `stream(sql: string, namedParams: object)` for named.
|
|
56
|
-
// The previous code `params` directly for array, and `params as any` for object.
|
|
57
|
-
// This seems reasonable.
|
|
58
|
-
params);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
resultObject = await this.dbConnection.stream(sql, params); // Cast to any for object
|
|
62
|
-
}
|
|
63
|
-
const rows = (await resultObject.getRows()); // Use getRows() as per docs, and it's async
|
|
64
|
-
const columnNames = resultObject.columnNames(); // Sync as per docs
|
|
38
|
+
const resultObject = await this.stream(sql, params);
|
|
39
|
+
const rows = (await resultObject.getRows());
|
|
40
|
+
const columnNames = resultObject.columnNames();
|
|
65
41
|
const columnTypes = resultObject
|
|
66
|
-
.columnTypes()
|
|
42
|
+
.columnTypes()
|
|
67
43
|
.map((ct) => ct.typeId);
|
|
68
44
|
return {
|
|
69
45
|
rows: rows,
|
|
@@ -88,9 +64,6 @@ export class DuckDBQueryExecutor {
|
|
|
88
64
|
if (params === undefined) {
|
|
89
65
|
return this.dbConnection.stream(sql);
|
|
90
66
|
}
|
|
91
|
-
else if (Array.isArray(params)) {
|
|
92
|
-
return this.dbConnection.stream(sql, params);
|
|
93
|
-
}
|
|
94
67
|
else {
|
|
95
68
|
return this.dbConnection.stream(sql, params);
|
|
96
69
|
}
|
|
@@ -11,9 +11,10 @@ export declare class DuckDBService implements IDuckDBService {
|
|
|
11
11
|
constructor();
|
|
12
12
|
initialize(config?: DuckDBServiceConfig): Promise<void>;
|
|
13
13
|
private ensureInitialized;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
private validateParams;
|
|
15
|
+
run(sql: string, params?: unknown[]): Promise<void>;
|
|
16
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<DuckDBQueryResult<T>>;
|
|
17
|
+
stream(sql: string, params?: unknown[]): Promise<duckdb.DuckDBResult>;
|
|
17
18
|
prepare(sql: string): Promise<duckdb.DuckDBPreparedStatement>;
|
|
18
19
|
beginTransaction(): Promise<void>;
|
|
19
20
|
commitTransaction(): Promise<void>;
|
|
@@ -42,14 +42,23 @@ export class DuckDBService {
|
|
|
42
42
|
throw new McpError(BaseErrorCode.SERVICE_NOT_INITIALIZED, "DuckDBQueryExecutor not available. DuckDBService may not be fully initialized.", context);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
validateParams(params, context) {
|
|
46
|
+
if (params === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(params)) {
|
|
50
|
+
return params;
|
|
51
|
+
}
|
|
52
|
+
throw new McpError(BaseErrorCode.INVALID_INPUT, "DuckDB service only supports array-style parameters, not named objects.", context);
|
|
53
|
+
}
|
|
45
54
|
async run(sql, params) {
|
|
46
55
|
const context = requestContextService.createRequestContext({
|
|
47
56
|
operation: "DuckDBService.run",
|
|
48
57
|
initialData: { sql, params },
|
|
49
58
|
});
|
|
50
59
|
this.ensureInitialized(context);
|
|
51
|
-
|
|
52
|
-
return this.queryExecutor.run(sql,
|
|
60
|
+
const validatedParams = this.validateParams(params, context);
|
|
61
|
+
return this.queryExecutor.run(sql, validatedParams);
|
|
53
62
|
}
|
|
54
63
|
async query(sql, params) {
|
|
55
64
|
const context = requestContextService.createRequestContext({
|
|
@@ -57,7 +66,8 @@ export class DuckDBService {
|
|
|
57
66
|
initialData: { sql, params },
|
|
58
67
|
});
|
|
59
68
|
this.ensureInitialized(context);
|
|
60
|
-
|
|
69
|
+
const validatedParams = this.validateParams(params, context);
|
|
70
|
+
return this.queryExecutor.query(sql, validatedParams);
|
|
61
71
|
}
|
|
62
72
|
async stream(sql, params) {
|
|
63
73
|
const context = requestContextService.createRequestContext({
|
|
@@ -65,7 +75,8 @@ export class DuckDBService {
|
|
|
65
75
|
initialData: { sql, params },
|
|
66
76
|
});
|
|
67
77
|
this.ensureInitialized(context);
|
|
68
|
-
|
|
78
|
+
const validatedParams = this.validateParams(params, context);
|
|
79
|
+
return this.queryExecutor.stream(sql, validatedParams);
|
|
69
80
|
}
|
|
70
81
|
async prepare(sql) {
|
|
71
82
|
const context = requestContextService.createRequestContext({
|
|
@@ -30,7 +30,7 @@ export interface DuckDBServiceConfig {
|
|
|
30
30
|
*/
|
|
31
31
|
export interface DuckDBQuery {
|
|
32
32
|
sql: string;
|
|
33
|
-
params?: unknown[]
|
|
33
|
+
params?: unknown[];
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Represents the result of a query execution.
|
|
@@ -56,31 +56,31 @@ export interface IDuckDBService {
|
|
|
56
56
|
/**
|
|
57
57
|
* Executes a SQL query that does not return a large result set (e.g., CREATE, INSERT, UPDATE, DELETE).
|
|
58
58
|
* @param {string} sql - The SQL query string.
|
|
59
|
-
* @param {unknown[]
|
|
59
|
+
* @param {unknown[]} [params] - Optional parameters for the query.
|
|
60
60
|
* @returns {Promise<void>}
|
|
61
61
|
* @throws {McpError} If the query fails or the service is not initialized.
|
|
62
62
|
*/
|
|
63
|
-
run(sql: string, params?: unknown[]
|
|
63
|
+
run(sql: string, params?: unknown[]): Promise<void>;
|
|
64
64
|
/**
|
|
65
65
|
* Executes a SQL query and returns all resulting rows.
|
|
66
66
|
* Suitable for queries that return a manageable number of rows.
|
|
67
67
|
* @template T - The expected type of the row objects.
|
|
68
68
|
* @param {string} sql - The SQL query string.
|
|
69
|
-
* @param {unknown[]
|
|
69
|
+
* @param {unknown[]} [params] - Optional parameters for the query.
|
|
70
70
|
* @returns {Promise<DuckDBQueryResult<T>>} The query result.
|
|
71
71
|
* @throws {McpError} If the query fails or the service is not initialized.
|
|
72
72
|
*/
|
|
73
|
-
query<T = Record<string, unknown>>(sql: string, params?: unknown[]
|
|
73
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<DuckDBQueryResult<T>>;
|
|
74
74
|
/**
|
|
75
75
|
* Executes a SQL query and provides a streaming result reader.
|
|
76
76
|
* Suitable for queries that return very large result sets.
|
|
77
77
|
* The caller is responsible for closing the stream.
|
|
78
78
|
* @param {string} sql - The SQL query string.
|
|
79
|
-
* @param {unknown[]
|
|
79
|
+
* @param {unknown[]} [params] - Optional parameters for the query.
|
|
80
80
|
* @returns {Promise<duckdb.DuckDBStreamingResult>} A streaming result object.
|
|
81
81
|
* @throws {McpError} If the query fails or the service is not initialized.
|
|
82
82
|
*/
|
|
83
|
-
stream(sql: string, params?: unknown[]
|
|
83
|
+
stream(sql: string, params?: unknown[]): Promise<duckdb.DuckDBResult>;
|
|
84
84
|
/**
|
|
85
85
|
* Creates a prepared statement.
|
|
86
86
|
* @param {string} sql - The SQL query string for the prepared statement.
|
|
@@ -19,7 +19,7 @@ export type OpenRouterChatParams = (ChatCompletionCreateParamsNonStreaming | Cha
|
|
|
19
19
|
transforms?: string[];
|
|
20
20
|
models?: string[];
|
|
21
21
|
route?: "fallback";
|
|
22
|
-
provider?: Record<string,
|
|
22
|
+
provider?: Record<string, unknown>;
|
|
23
23
|
};
|
|
24
24
|
declare class OpenRouterProvider {
|
|
25
25
|
private client?;
|
|
@@ -65,9 +65,13 @@ function _prepareApiParameters(params) {
|
|
|
65
65
|
if (extraBody.min_p === undefined && config.llmDefaultMinP !== undefined) {
|
|
66
66
|
extraBody.min_p = config.llmDefaultMinP;
|
|
67
67
|
}
|
|
68
|
-
if (extraBody.provider &&
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (extraBody.provider &&
|
|
69
|
+
typeof extraBody.provider === "object" &&
|
|
70
|
+
extraBody.provider !== null) {
|
|
71
|
+
const provider = extraBody.provider;
|
|
72
|
+
if (!provider.sort) {
|
|
73
|
+
provider.sort = "throughput";
|
|
74
|
+
}
|
|
71
75
|
}
|
|
72
76
|
else if (extraBody.provider === undefined) {
|
|
73
77
|
extraBody.provider = { sort: "throughput" };
|
|
@@ -108,7 +112,8 @@ async function _openRouterChatCompletionLogic(client, params, context) {
|
|
|
108
112
|
});
|
|
109
113
|
return response;
|
|
110
114
|
}
|
|
111
|
-
catch (
|
|
115
|
+
catch (e) {
|
|
116
|
+
const error = e;
|
|
112
117
|
logger.logInteraction("OpenRouterError", {
|
|
113
118
|
context,
|
|
114
119
|
error: {
|
|
@@ -164,7 +169,8 @@ class OpenRouterProvider {
|
|
|
164
169
|
this.status = "ready";
|
|
165
170
|
logger.info("OpenRouter Service Initialized and Ready", opContext);
|
|
166
171
|
}
|
|
167
|
-
catch (
|
|
172
|
+
catch (e) {
|
|
173
|
+
const error = e;
|
|
168
174
|
this.status = "error";
|
|
169
175
|
this.initializationError = error;
|
|
170
176
|
logger.error("Failed to initialize OpenRouter client", {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module src/services/supabase/supabaseClient
|
|
8
8
|
*/
|
|
9
9
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
10
|
-
type Database =
|
|
10
|
+
type Database = Record<string, unknown>;
|
|
11
11
|
/**
|
|
12
12
|
* Returns the singleton Supabase client instance.
|
|
13
13
|
* Throws an McpError if the client is not initialized.
|
|
@@ -23,6 +23,8 @@ export declare enum BaseErrorCode {
|
|
|
23
23
|
CONFLICT = "CONFLICT",
|
|
24
24
|
/** The request failed due to invalid input parameters or data. */
|
|
25
25
|
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
26
|
+
/** The provided input is invalid for the operation. */
|
|
27
|
+
INVALID_INPUT = "INVALID_INPUT",
|
|
26
28
|
/** An error occurred while parsing input data (e.g., date string, JSON). */
|
|
27
29
|
PARSING_ERROR = "PARSING_ERROR",
|
|
28
30
|
/** The request was rejected because the client has exceeded rate limits. */
|
|
@@ -24,6 +24,8 @@ export var BaseErrorCode;
|
|
|
24
24
|
BaseErrorCode["CONFLICT"] = "CONFLICT";
|
|
25
25
|
/** The request failed due to invalid input parameters or data. */
|
|
26
26
|
BaseErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
27
|
+
/** The provided input is invalid for the operation. */
|
|
28
|
+
BaseErrorCode["INVALID_INPUT"] = "INVALID_INPUT";
|
|
27
29
|
/** An error occurred while parsing input data (e.g., date string, JSON). */
|
|
28
30
|
BaseErrorCode["PARSING_ERROR"] = "PARSING_ERROR";
|
|
29
31
|
/** The request was rejected because the client has exceeded rate limits. */
|
|
@@ -126,7 +126,7 @@ function getErrorMessage(error) {
|
|
|
126
126
|
try {
|
|
127
127
|
return `Non-Error object encountered: ${JSON.stringify(error)}`;
|
|
128
128
|
}
|
|
129
|
-
catch
|
|
129
|
+
catch {
|
|
130
130
|
return `Unstringifyable non-Error object encountered (constructor: ${error.constructor?.name || "Unknown"})`;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -17,7 +17,7 @@ export interface McpLogPayload {
|
|
|
17
17
|
message: string;
|
|
18
18
|
stack?: string;
|
|
19
19
|
};
|
|
20
|
-
[key: string]:
|
|
20
|
+
[key: string]: unknown;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Type for the `data` parameter of the `McpNotificationSender` function.
|
|
@@ -139,7 +139,7 @@ export declare class Logger {
|
|
|
139
139
|
* @param interactionName - A name for the interaction type (e.g., 'OpenRouterIO').
|
|
140
140
|
* @param data - The structured data to log.
|
|
141
141
|
*/
|
|
142
|
-
logInteraction(interactionName: string, data: Record<string,
|
|
142
|
+
logInteraction(interactionName: string, data: Record<string, unknown>): void;
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
145
|
* The singleton instance of the Logger.
|
|
@@ -16,7 +16,7 @@ import { RequestContext } from "../index.js";
|
|
|
16
16
|
* @throws {McpError} If an unexpected error occurs during parsing.
|
|
17
17
|
* @private
|
|
18
18
|
*/
|
|
19
|
-
declare function parseDateString(text: string, context: RequestContext, refDate?: Date): Promise<Date | null>;
|
|
19
|
+
export declare function parseDateString(text: string, context: RequestContext, refDate?: Date): Promise<Date | null>;
|
|
20
20
|
/**
|
|
21
21
|
* Parses a natural language date string and returns detailed parsing results.
|
|
22
22
|
* Provides more information than just the Date object, including matched text and components.
|
|
@@ -28,7 +28,7 @@ declare function parseDateString(text: string, context: RequestContext, refDate?
|
|
|
28
28
|
* @throws {McpError} If an unexpected error occurs during parsing.
|
|
29
29
|
* @private
|
|
30
30
|
*/
|
|
31
|
-
declare function parseDateStringDetailed(text: string, context: RequestContext, refDate?: Date): Promise<chrono.ParsedResult[]>;
|
|
31
|
+
export declare function parseDateStringDetailed(text: string, context: RequestContext, refDate?: Date): Promise<chrono.ParsedResult[]>;
|
|
32
32
|
/**
|
|
33
33
|
* An object providing date parsing functionalities.
|
|
34
34
|
*
|
|
@@ -70,4 +70,3 @@ export declare const dateParser: {
|
|
|
70
70
|
*/
|
|
71
71
|
parseDate: typeof parseDateString;
|
|
72
72
|
};
|
|
73
|
-
export {};
|
|
@@ -17,7 +17,7 @@ import { ErrorHandler, logger } from "../index.js";
|
|
|
17
17
|
* @throws {McpError} If an unexpected error occurs during parsing.
|
|
18
18
|
* @private
|
|
19
19
|
*/
|
|
20
|
-
async function parseDateString(text, context, refDate) {
|
|
20
|
+
export async function parseDateString(text, context, refDate) {
|
|
21
21
|
const operation = "parseDateString";
|
|
22
22
|
const logContext = { ...context, operation, inputText: text, refDate };
|
|
23
23
|
logger.debug(`Attempting to parse date string: "${text}"`, logContext);
|
|
@@ -49,7 +49,7 @@ async function parseDateString(text, context, refDate) {
|
|
|
49
49
|
* @throws {McpError} If an unexpected error occurs during parsing.
|
|
50
50
|
* @private
|
|
51
51
|
*/
|
|
52
|
-
async function parseDateStringDetailed(text, context, refDate) {
|
|
52
|
+
export async function parseDateStringDetailed(text, context, refDate) {
|
|
53
53
|
const operation = "parseDateStringDetailed";
|
|
54
54
|
const logContext = { ...context, operation, inputText: text, refDate };
|
|
55
55
|
logger.debug(`Attempting detailed parse of date string: "${text}"`, logContext);
|
|
@@ -56,7 +56,7 @@ export declare class JsonParser {
|
|
|
56
56
|
* @returns The parsed JavaScript value.
|
|
57
57
|
* @throws {McpError} If the string is empty after processing or if `partial-json` fails.
|
|
58
58
|
*/
|
|
59
|
-
parse<T =
|
|
59
|
+
parse<T = unknown>(jsonString: string, allowPartial?: number, context?: RequestContext): T;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Singleton instance of the `JsonParser`.
|
|
@@ -83,7 +83,8 @@ export class JsonParser {
|
|
|
83
83
|
try {
|
|
84
84
|
return parsePartialJson(stringToParse, allowPartial);
|
|
85
85
|
}
|
|
86
|
-
catch (
|
|
86
|
+
catch (e) {
|
|
87
|
+
const error = e;
|
|
87
88
|
const errorLogContext = context ||
|
|
88
89
|
requestContextService.createRequestContext({
|
|
89
90
|
operation: "JsonParser.parseError",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* defining, starting, stopping, and listing recurring tasks within the application.
|
|
5
5
|
* @module src/utils/scheduling/scheduler
|
|
6
6
|
*/
|
|
7
|
-
import cron from "node-cron";
|
|
7
|
+
import cron, { createTask } from "node-cron";
|
|
8
8
|
import { logger } from "../internal/index.js";
|
|
9
9
|
import { requestContextService } from "../internal/requestContext.js";
|
|
10
10
|
/**
|
|
@@ -45,7 +45,7 @@ export class SchedulerService {
|
|
|
45
45
|
if (!cron.validate(schedule)) {
|
|
46
46
|
throw new Error(`Invalid cron schedule: ${schedule}`);
|
|
47
47
|
}
|
|
48
|
-
const task =
|
|
48
|
+
const task = createTask(schedule, async () => {
|
|
49
49
|
const job = this.jobs.get(id);
|
|
50
50
|
if (job && job.isRunning) {
|
|
51
51
|
logger.warning(`Job '${id}' is already running. Skipping this execution.`, {
|
|
@@ -74,8 +74,6 @@ export class SchedulerService {
|
|
|
74
74
|
job.isRunning = false;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
}, {
|
|
78
|
-
scheduled: false, // Do not start immediately
|
|
79
77
|
});
|
|
80
78
|
const newJob = {
|
|
81
79
|
id,
|
|
@@ -233,7 +233,6 @@ export class Sanitization {
|
|
|
233
233
|
rootDir: options.rootDir ? path.resolve(options.rootDir) : undefined,
|
|
234
234
|
};
|
|
235
235
|
let wasAbsoluteInitially = false;
|
|
236
|
-
let convertedToRelative = false;
|
|
237
236
|
try {
|
|
238
237
|
if (!input || typeof input !== "string")
|
|
239
238
|
throw new Error("Invalid path input: must be a non-empty string.");
|
|
@@ -263,7 +262,6 @@ export class Sanitization {
|
|
|
263
262
|
if (path.isAbsolute(normalized)) {
|
|
264
263
|
if (!effectiveOptions.allowAbsolute) {
|
|
265
264
|
finalSanitizedPath = normalized.replace(/^(?:[A-Za-z]:)?[/\\]+/, "");
|
|
266
|
-
convertedToRelative = true;
|
|
267
265
|
}
|
|
268
266
|
else {
|
|
269
267
|
finalSanitizedPath = normalized;
|
|
@@ -355,7 +353,7 @@ export class Sanitization {
|
|
|
355
353
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid number value (NaN or Infinity).", { input });
|
|
356
354
|
}
|
|
357
355
|
let clamped = false;
|
|
358
|
-
|
|
356
|
+
const originalValueForLog = value;
|
|
359
357
|
if (min !== undefined && value < min) {
|
|
360
358
|
value = min;
|
|
361
359
|
clamped = true;
|
|
@@ -399,8 +397,8 @@ export class Sanitization {
|
|
|
399
397
|
try {
|
|
400
398
|
if (!input || typeof input !== "object")
|
|
401
399
|
return input;
|
|
402
|
-
const clonedInput = typeof structuredClone === "function"
|
|
403
|
-
? structuredClone(input)
|
|
400
|
+
const clonedInput = typeof globalThis.structuredClone === "function"
|
|
401
|
+
? globalThis.structuredClone(input)
|
|
404
402
|
: JSON.parse(JSON.stringify(input));
|
|
405
403
|
this.redactSensitiveFields(clonedInput);
|
|
406
404
|
return clonedInput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ts-template",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "Jumpstart Model Context Protocol (MCP) development with this production-ready TypeScript template. Build robust MCP servers and clients with built-in utilities, authentication, and service integrations. Agent framework utilizing MCP Client included.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"rebuild": "ts-node --esm scripts/clean.ts && npm run build",
|
|
30
30
|
"docs:generate": "typedoc --tsconfig ./tsconfig.typedoc.json",
|
|
31
31
|
"depcheck": "npx depcheck",
|
|
32
|
+
"lint": "eslint .",
|
|
33
|
+
"lint:fix": "eslint . --fix",
|
|
32
34
|
"tree": "ts-node --esm scripts/tree.ts",
|
|
33
35
|
"fetch-spec": "ts-node --esm scripts/fetch-openapi-spec.ts",
|
|
34
36
|
"format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
|
|
@@ -44,22 +46,24 @@
|
|
|
44
46
|
"@types/sanitize-html": "^2.16.0",
|
|
45
47
|
"@types/validator": "13.15.2",
|
|
46
48
|
"chrono-node": "^2.8.0",
|
|
47
|
-
"dotenv": "^16.
|
|
49
|
+
"dotenv": "^16.6.1",
|
|
50
|
+
"eslint": "^9.30.1",
|
|
48
51
|
"hono": "^4.8.4",
|
|
49
52
|
"ignore": "^7.0.5",
|
|
50
53
|
"jose": "^6.0.11",
|
|
54
|
+
"js-yaml": "^4.1.0",
|
|
55
|
+
"node-cron": "^4.2.0",
|
|
51
56
|
"openai": "^5.8.2",
|
|
52
57
|
"partial-json": "^0.1.7",
|
|
53
58
|
"sanitize-html": "^2.17.0",
|
|
54
59
|
"tiktoken": "^1.0.21",
|
|
55
60
|
"ts-node": "^10.9.2",
|
|
56
61
|
"typescript": "^5.8.3",
|
|
62
|
+
"typescript-eslint": "^8.35.1",
|
|
57
63
|
"validator": "13.15.15",
|
|
58
64
|
"winston": "^3.17.0",
|
|
59
65
|
"winston-transport": "^4.9.0",
|
|
60
|
-
"
|
|
61
|
-
"zod": "^3.25.74",
|
|
62
|
-
"js-yaml": "^4.1.0"
|
|
66
|
+
"zod": "^3.25.74"
|
|
63
67
|
},
|
|
64
68
|
"keywords": [
|
|
65
69
|
"typescript",
|
|
@@ -101,10 +105,12 @@
|
|
|
101
105
|
"node": ">=20.0.0"
|
|
102
106
|
},
|
|
103
107
|
"devDependencies": {
|
|
108
|
+
"@eslint/js": "^9.30.1",
|
|
104
109
|
"@types/js-yaml": "^4.0.9",
|
|
105
110
|
"@types/node-cron": "^3.0.11",
|
|
106
111
|
"axios": "^1.10.0",
|
|
107
112
|
"depcheck": "^1.4.7",
|
|
113
|
+
"globals": "^16.3.0",
|
|
108
114
|
"prettier": "^3.6.2",
|
|
109
115
|
"typedoc": "^0.28.7"
|
|
110
116
|
}
|