@cyanheads/pubmed-mcp-server 1.0.16 → 1.1.2

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.
Files changed (42) hide show
  1. package/dist/config/index.d.ts +12 -22
  2. package/dist/config/index.js +29 -51
  3. package/dist/index.js +77 -29
  4. package/dist/mcp-server/server.d.ts +3 -2
  5. package/dist/mcp-server/server.js +16 -14
  6. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -1
  7. package/dist/mcp-server/tools/generatePubMedChart/logic.js +2 -1
  8. package/dist/mcp-server/tools/generatePubMedChart/registration.js +2 -2
  9. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -1
  10. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +2 -1
  11. package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +1 -1
  12. package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +19 -10
  13. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -1
  14. package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
  15. package/dist/mcp-server/transports/authentication/authContext.js +24 -0
  16. package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +21 -15
  17. package/dist/mcp-server/transports/authentication/authMiddleware.js +51 -69
  18. package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
  19. package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
  20. package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
  21. package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
  22. package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
  23. package/dist/mcp-server/transports/authentication/types.js +5 -0
  24. package/dist/mcp-server/transports/httpTransport.d.ts +5 -4
  25. package/dist/mcp-server/transports/httpTransport.js +177 -143
  26. package/dist/services/NCBI/ncbiCoreApiClient.js +0 -5
  27. package/dist/services/NCBI/ncbiRequestQueueManager.js +2 -4
  28. package/dist/services/NCBI/ncbiResponseHandler.js +0 -3
  29. package/dist/services/NCBI/ncbiService.d.ts +1 -1
  30. package/dist/services/NCBI/ncbiService.js +11 -4
  31. package/dist/utils/internal/logger.js +52 -74
  32. package/package.json +21 -9
  33. package/dist/services/index.d.ts +0 -7
  34. package/dist/services/index.js +0 -7
  35. package/dist/services/llm-providers/index.d.ts +0 -7
  36. package/dist/services/llm-providers/index.js +0 -7
  37. package/dist/services/llm-providers/llmFactory.d.ts +0 -69
  38. package/dist/services/llm-providers/llmFactory.js +0 -132
  39. package/dist/services/llm-providers/openRouter/index.d.ts +0 -6
  40. package/dist/services/llm-providers/openRouter/index.js +0 -7
  41. package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +0 -99
  42. package/dist/services/llm-providers/openRouter/openRouterProvider.js +0 -329
@@ -4,7 +4,6 @@
4
4
  * It handles different log levels compliant with RFC 5424 and MCP specifications.
5
5
  * @module src/utils/internal/logger
6
6
  */
7
- import fs from "fs";
8
7
  import path from "path";
9
8
  import winston from "winston";
10
9
  import { config } from "../../config/index.js";
@@ -36,11 +35,8 @@ const mcpToWinstonLevel = {
36
35
  alert: "error",
37
36
  emerg: "error",
38
37
  };
39
- // The logsPath from config is already resolved and validated by src/config/index.ts
40
- const resolvedLogsDir = config.logsPath;
41
- const isLogsDirSafe = !!resolvedLogsDir; // If logsPath is set, it's considered safe by config logic.
42
38
  /**
43
- * Creates the Winston console log format.
39
+ * Creates the Winston console log format for interactive TTY sessions.
44
40
  * @returns The Winston log format for console output.
45
41
  * @private
46
42
  */
@@ -61,7 +57,8 @@ function createWinstonConsoleFormat() {
61
57
  }
62
58
  if (Object.keys(metaCopy).length > 0) {
63
59
  try {
64
- const remainingMetaJson = JSON.stringify(metaCopy, null, 2);
60
+ const replacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
61
+ const remainingMetaJson = JSON.stringify(metaCopy, replacer, 2);
65
62
  if (remainingMetaJson !== "{}")
66
63
  metaString += `\n Meta: ${remainingMetaJson}`;
67
64
  }
@@ -103,64 +100,45 @@ export class Logger {
103
100
  });
104
101
  return;
105
102
  }
103
+ this.initialized = true;
106
104
  this.currentMcpLevel = level;
107
105
  this.currentWinstonLevel = mcpToWinstonLevel[level];
108
- let logsDirCreatedMessage = null; // This message is now informational as creation is handled by config
109
- if (isLogsDirSafe) {
110
- // Directory creation is handled by config/index.ts ensureDirectory.
111
- // We can log if it was newly created by checking if it existed before config ran,
112
- // but that's complex. For now, we assume config handled it.
113
- // If resolvedLogsDir is set, config ensures it exists.
114
- if (!fs.existsSync(resolvedLogsDir)) {
115
- // This case should ideally not be hit if config.logsPath is correctly set up and validated.
116
- // However, if it somehow occurs (e.g. dir deleted after config init but before logger init),
117
- // we attempt to create it.
118
- try {
119
- await fs.promises.mkdir(resolvedLogsDir, { recursive: true });
120
- logsDirCreatedMessage = `Re-created logs directory (should have been created by config): ${resolvedLogsDir}`;
121
- }
122
- catch (err) {
123
- if (process.stdout.isTTY) {
124
- const errorMessage = err instanceof Error ? err.message : String(err);
125
- console.error(`Error creating logs directory at ${resolvedLogsDir}: ${errorMessage}. File logging disabled.`);
126
- }
127
- throw err; // Critical if logs dir cannot be ensured
128
- }
129
- }
130
- }
131
- const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
132
106
  const transports = [];
133
- const fileTransportOptions = {
134
- format: fileFormat,
135
- maxsize: this.LOG_FILE_MAX_SIZE,
136
- maxFiles: this.LOG_MAX_FILES,
137
- tailable: true,
138
- };
139
- if (isLogsDirSafe) {
140
- transports.push(new winston.transports.File({
141
- filename: path.join(resolvedLogsDir, "error.log"),
142
- level: "error",
143
- ...fileTransportOptions,
144
- }), new winston.transports.File({
145
- filename: path.join(resolvedLogsDir, "warn.log"),
146
- level: "warn",
147
- ...fileTransportOptions,
148
- }), new winston.transports.File({
149
- filename: path.join(resolvedLogsDir, "info.log"),
150
- level: "info",
151
- ...fileTransportOptions,
152
- }), new winston.transports.File({
153
- filename: path.join(resolvedLogsDir, "debug.log"),
154
- level: "debug",
155
- ...fileTransportOptions,
156
- }), new winston.transports.File({
157
- filename: path.join(resolvedLogsDir, "combined.log"),
158
- ...fileTransportOptions,
107
+ if (config.logOutputMode === "stdout") {
108
+ transports.push(new winston.transports.Console({
109
+ format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()),
159
110
  }));
160
111
  }
161
112
  else {
162
- if (process.stdout.isTTY) {
163
- console.warn("File logging disabled as logsPath is not configured or invalid.");
113
+ const resolvedLogsDir = config.logsPath;
114
+ if (resolvedLogsDir) {
115
+ const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
116
+ const fileTransportOptions = {
117
+ format: fileFormat,
118
+ maxsize: this.LOG_FILE_MAX_SIZE,
119
+ maxFiles: this.LOG_MAX_FILES,
120
+ tailable: true,
121
+ };
122
+ transports.push(new winston.transports.File({
123
+ filename: path.join(resolvedLogsDir, "error.log"),
124
+ level: "error",
125
+ ...fileTransportOptions,
126
+ }), new winston.transports.File({
127
+ filename: path.join(resolvedLogsDir, "warn.log"),
128
+ level: "warn",
129
+ ...fileTransportOptions,
130
+ }), new winston.transports.File({
131
+ filename: path.join(resolvedLogsDir, "info.log"),
132
+ level: "info",
133
+ ...fileTransportOptions,
134
+ }), new winston.transports.File({
135
+ filename: path.join(resolvedLogsDir, "debug.log"),
136
+ level: "debug",
137
+ ...fileTransportOptions,
138
+ }), new winston.transports.File({
139
+ filename: path.join(resolvedLogsDir, "combined.log"),
140
+ ...fileTransportOptions,
141
+ }));
164
142
  }
165
143
  }
166
144
  this.winstonLogger = winston.createLogger({
@@ -168,26 +146,20 @@ export class Logger {
168
146
  transports,
169
147
  exitOnError: false,
170
148
  });
171
- // Configure console transport after Winston logger is created
172
149
  const consoleStatus = this._configureConsoleTransport();
173
150
  const initialContext = {
174
151
  loggerSetup: true,
175
152
  requestId: "logger-init-deferred",
176
153
  timestamp: new Date().toISOString(),
177
154
  };
178
- if (logsDirCreatedMessage) {
179
- // Log if we had to re-create it
180
- this.info(logsDirCreatedMessage, initialContext);
181
- }
182
155
  if (consoleStatus.message) {
183
156
  this.info(consoleStatus.message, initialContext);
184
157
  }
185
- this.initialized = true;
186
- this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, {
158
+ this.info(`Logger initialized. Mode: ${config.logOutputMode}. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}.`, {
187
159
  loggerSetup: true,
188
160
  requestId: "logger-post-init",
189
161
  timestamp: new Date().toISOString(),
190
- logsPathUsed: resolvedLogsDir,
162
+ logsPathUsed: config.logsPath,
191
163
  });
192
164
  }
193
165
  /**
@@ -227,7 +199,6 @@ export class Logger {
227
199
  this.currentMcpLevel = newLevel;
228
200
  this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
229
201
  if (this.winstonLogger) {
230
- // Ensure winstonLogger is defined
231
202
  this.winstonLogger.level = this.currentWinstonLevel;
232
203
  }
233
204
  const consoleStatus = this._configureConsoleTransport();
@@ -246,6 +217,12 @@ export class Logger {
246
217
  * @private
247
218
  */
248
219
  _configureConsoleTransport() {
220
+ if (config.logOutputMode === "stdout") {
221
+ return {
222
+ enabled: true,
223
+ message: "Stdout logging is enabled by configuration.",
224
+ };
225
+ }
249
226
  if (!this.winstonLogger) {
250
227
  return {
251
228
  enabled: false,
@@ -258,17 +235,19 @@ export class Logger {
258
235
  if (shouldHaveConsole && !consoleTransport) {
259
236
  const consoleFormat = createWinstonConsoleFormat();
260
237
  this.winstonLogger.add(new winston.transports.Console({
261
- level: "debug", // Console always logs debug if enabled
238
+ level: "debug",
262
239
  format: consoleFormat,
263
240
  }));
264
- message = "Console logging enabled (level: debug, stdout is TTY).";
241
+ message =
242
+ "Interactive console logging enabled (level: debug, stdout is TTY).";
265
243
  }
266
244
  else if (!shouldHaveConsole && consoleTransport) {
267
245
  this.winstonLogger.remove(consoleTransport);
268
- message = "Console logging disabled (level not debug or stdout not TTY).";
246
+ message =
247
+ "Interactive console logging disabled (level not debug or stdout not TTY).";
269
248
  }
270
249
  else {
271
- message = "Console logging status unchanged.";
250
+ message = "Interactive console logging status unchanged.";
272
251
  }
273
252
  return { enabled: shouldHaveConsole, message };
274
253
  }
@@ -308,7 +287,7 @@ export class Logger {
308
287
  if (!this.ensureInitialized())
309
288
  return;
310
289
  if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
311
- return; // Do not log if message level is less severe than currentMcpLevel
290
+ return;
312
291
  }
313
292
  const logData = { ...context };
314
293
  const winstonLevel = mcpToWinstonLevel[level];
@@ -324,7 +303,6 @@ export class Logger {
324
303
  mcpDataPayload.context = context;
325
304
  if (error) {
326
305
  mcpDataPayload.error = { message: error.message };
327
- // Include stack trace in debug mode for MCP notifications, truncated for brevity
328
306
  if (this.currentMcpLevel === "debug" && error.stack) {
329
307
  mcpDataPayload.error.stack = error.stack.substring(0, this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH);
330
308
  }
@@ -341,7 +319,7 @@ export class Logger {
341
319
  originalLevel: level,
342
320
  originalMessage: msg,
343
321
  sendError: errorMessage,
344
- mcpPayload: JSON.stringify(mcpDataPayload).substring(0, 500), // Log a preview
322
+ mcpPayload: JSON.stringify(mcpDataPayload).substring(0, 500),
345
323
  };
346
324
  this.winstonLogger.error("Failed to send MCP log notification", internalErrorContext);
347
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/pubmed-mcp-server",
3
- "version": "1.0.16",
3
+ "version": "1.1.2",
4
4
  "description": "A Model Context Protocol (MCP) server enabling AI agents to intelligently search, retrieve, and analyze biomedical literature from PubMed via NCBI E-utilities. Built on the mcp-ts-template for robust, production-ready performance.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -29,14 +29,14 @@
29
29
  "tree": "ts-node --esm scripts/tree.ts",
30
30
  "fetch-spec": "ts-node --esm scripts/fetch-openapi-spec.ts",
31
31
  "format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
32
- "inspector": "mcp-inspector --config mcp.json --server pubmed-mcp-server",
33
- "start:client-cli": "node dist/mcp-client/cli/mcp-client-cli.js"
32
+ "inspector": "mcp-inspector --config mcp.json --server pubmed-mcp-server"
34
33
  },
35
34
  "dependencies": {
36
- "@google/genai": "^1.3.0",
35
+ "@hono/node-server": "^1.14.4",
37
36
  "@modelcontextprotocol/sdk": "^1.12.1",
37
+ "@node-oauth/oauth2-server": "^5.2.0",
38
38
  "@types/jsonwebtoken": "^9.0.9",
39
- "@types/node": "^22.15.29",
39
+ "@types/node": "^24.0.1",
40
40
  "@types/sanitize-html": "^2.16.0",
41
41
  "@types/validator": "13.15.1",
42
42
  "axios": "^1.9.0",
@@ -46,10 +46,12 @@
46
46
  "cli-table3": "^0.6.5",
47
47
  "dotenv": "^16.5.0",
48
48
  "express": "^5.1.0",
49
- "fast-xml-parser": "^5.2.3",
49
+ "fast-xml-parser": "^5.2.5",
50
+ "hono": "^4.7.11",
50
51
  "ignore": "^7.0.5",
52
+ "jose": "^6.0.11",
51
53
  "jsonwebtoken": "^9.0.2",
52
- "openai": "^5.1.0",
54
+ "openai": "^5.3.0",
53
55
  "partial-json": "^0.1.7",
54
56
  "sanitize-html": "^2.17.0",
55
57
  "tiktoken": "^1.0.21",
@@ -61,7 +63,7 @@
61
63
  "winston": "^3.17.0",
62
64
  "winston-daily-rotate-file": "^5.0.0",
63
65
  "yargs": "^18.0.0",
64
- "zod": "^3.25.50"
66
+ "zod": "^3.25.64"
65
67
  },
66
68
  "keywords": [
67
69
  "mcp",
@@ -84,6 +86,16 @@
84
86
  ],
85
87
  "author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/pubmed-mcp-server#readme)",
86
88
  "license": "Apache-2.0",
89
+ "funding": [
90
+ {
91
+ "type": "github",
92
+ "url": "https://github.com/sponsors/cyanheads"
93
+ },
94
+ {
95
+ "type": "buy_me_a_coffee",
96
+ "url": "https://www.buymeacoffee.com/cyanheads"
97
+ }
98
+ ],
87
99
  "publishConfig": {
88
100
  "access": "public"
89
101
  },
@@ -91,7 +103,7 @@
91
103
  "node": ">=16.0.0"
92
104
  },
93
105
  "devDependencies": {
94
- "@types/express": "^5.0.2",
106
+ "@types/express": "^5.0.3",
95
107
  "@types/js-yaml": "^4.0.9",
96
108
  "js-yaml": "^4.1.0",
97
109
  "prettier": "^3.5.3",
@@ -1,7 +0,0 @@
1
- /**
2
- * @fileoverview Main barrel file for all services.
3
- * This file re-exports all service modules, providing a single entry point
4
- * for accessing various services within the application.
5
- * @module src/services/index
6
- */
7
- export * from "./llm-providers/index.js";
@@ -1,7 +0,0 @@
1
- /**
2
- * @fileoverview Main barrel file for all services.
3
- * This file re-exports all service modules, providing a single entry point
4
- * for accessing various services within the application.
5
- * @module src/services/index
6
- */
7
- export * from "./llm-providers/index.js";
@@ -1,7 +0,0 @@
1
- /**
2
- * @fileoverview Barrel file for LLM provider services.
3
- * This file re-exports all services related to different Large Language Model providers,
4
- * making them easily accessible from a single import path.
5
- * @module src/services/llm-providers/index
6
- */
7
- export * from "./openRouter/index.js";
@@ -1,7 +0,0 @@
1
- /**
2
- * @fileoverview Barrel file for LLM provider services.
3
- * This file re-exports all services related to different Large Language Model providers,
4
- * making them easily accessible from a single import path.
5
- * @module src/services/llm-providers/index
6
- */
7
- export * from "./openRouter/index.js"; // Changed to export from the new barrel file
@@ -1,69 +0,0 @@
1
- /**
2
- * @fileoverview Factory for creating LLM client instances.
3
- * Provides a centralized way to instantiate clients for different LLM providers
4
- * like OpenRouter and Google Gemini, handling API key configuration and
5
- * basic client setup.
6
- * @module src/services/llm-providers/llmFactory
7
- */
8
- import { GoogleGenAI } from "@google/genai";
9
- import OpenAI from "openai";
10
- import { RequestContext } from "../../utils/index.js";
11
- /**
12
- * Defines the supported LLM providers.
13
- */
14
- export type LlmProviderType = "openrouter" | "gemini";
15
- /**
16
- * Options for configuring the OpenRouter client.
17
- */
18
- export interface OpenRouterClientOptions {
19
- apiKey?: string;
20
- baseURL?: string;
21
- siteUrl?: string;
22
- siteName?: string;
23
- }
24
- /**
25
- * Options for configuring the Gemini client using @google/genai.
26
- * The factory will return a GoogleGenAI instance.
27
- * Vertex AI specific options are included here.
28
- */
29
- export interface GeminiClientOptions {
30
- apiKey?: string;
31
- useVertexAi?: boolean;
32
- project?: string;
33
- location?: string;
34
- }
35
- /**
36
- * Union type for all LLM client options.
37
- */
38
- export type LlmClientOptions = OpenRouterClientOptions | GeminiClientOptions;
39
- /**
40
- * LLM Factory class to create and configure LLM clients.
41
- */
42
- declare class LlmFactory {
43
- /**
44
- * Creates and returns an LLM client instance for the specified provider.
45
- *
46
- * @param provider - The LLM provider to create a client for.
47
- * @param context - The request context for logging.
48
- * @param options - Optional provider-specific configuration options.
49
- * @returns A Promise resolving to an instance of OpenAI (for OpenRouter)
50
- * or GoogleGenAI (for Gemini).
51
- * @throws {McpError} If the provider is unsupported or API key/config is missing.
52
- */
53
- getLlmClient(provider: LlmProviderType, context: RequestContext, options?: LlmClientOptions): Promise<OpenAI | GoogleGenAI>;
54
- /**
55
- * Creates an OpenAI client configured for OpenRouter.
56
- * @private
57
- */
58
- private createOpenRouterClient;
59
- /**
60
- * Creates a GoogleGenAI client for Gemini, supporting standard API key or Vertex AI.
61
- * @private
62
- */
63
- private createGeminiClient;
64
- }
65
- /**
66
- * Singleton instance of the LlmFactory.
67
- */
68
- export declare const llmFactory: LlmFactory;
69
- export {};
@@ -1,132 +0,0 @@
1
- /**
2
- * @fileoverview Factory for creating LLM client instances.
3
- * Provides a centralized way to instantiate clients for different LLM providers
4
- * like OpenRouter and Google Gemini, handling API key configuration and
5
- * basic client setup.
6
- * @module src/services/llm-providers/llmFactory
7
- */
8
- import { GoogleGenAI } from "@google/genai"; // Updated import path
9
- import OpenAI from "openai";
10
- import { config } from "../../config/index.js";
11
- import { BaseErrorCode, McpError } from "../../types-global/errors.js";
12
- import { logger } from "../../utils/index.js";
13
- /**
14
- * LLM Factory class to create and configure LLM clients.
15
- */
16
- class LlmFactory {
17
- /**
18
- * Creates and returns an LLM client instance for the specified provider.
19
- *
20
- * @param provider - The LLM provider to create a client for.
21
- * @param context - The request context for logging.
22
- * @param options - Optional provider-specific configuration options.
23
- * @returns A Promise resolving to an instance of OpenAI (for OpenRouter)
24
- * or GoogleGenAI (for Gemini).
25
- * @throws {McpError} If the provider is unsupported or API key/config is missing.
26
- */
27
- async getLlmClient(provider, context, options) {
28
- // Return type changed for Gemini
29
- const operation = `LlmFactory.getLlmClient.${provider}`;
30
- logger.info(`[${operation}] Requesting LLM client`, {
31
- ...context,
32
- provider,
33
- });
34
- switch (provider) {
35
- case "openrouter":
36
- return this.createOpenRouterClient(context, options);
37
- case "gemini":
38
- return this.createGeminiClient(context, options);
39
- default:
40
- logger.error(`[${operation}] Unsupported LLM provider requested: ${provider}`, context);
41
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Unsupported LLM provider: ${provider}`, { operation, provider });
42
- }
43
- }
44
- /**
45
- * Creates an OpenAI client configured for OpenRouter.
46
- * @private
47
- */
48
- createOpenRouterClient(context, options) {
49
- const operation = "LlmFactory.createOpenRouterClient";
50
- const apiKey = options?.apiKey || config.openrouterApiKey;
51
- const baseURL = options?.baseURL || "https://openrouter.ai/api/v1";
52
- const siteUrl = options?.siteUrl || config.openrouterAppUrl;
53
- const siteName = options?.siteName || config.openrouterAppName;
54
- if (!apiKey) {
55
- logger.error(`[${operation}] OPENROUTER_API_KEY is not set.`, context);
56
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "OpenRouter API key is not configured.", { operation });
57
- }
58
- try {
59
- const client = new OpenAI({
60
- baseURL,
61
- apiKey,
62
- defaultHeaders: {
63
- "HTTP-Referer": siteUrl,
64
- "X-Title": siteName,
65
- },
66
- });
67
- logger.info(`[${operation}] OpenRouter client created successfully.`, context);
68
- return client;
69
- }
70
- catch (error) {
71
- logger.error(`[${operation}] Failed to create OpenRouter client`, {
72
- ...context,
73
- error: error.message,
74
- });
75
- throw new McpError(BaseErrorCode.INITIALIZATION_FAILED, `Failed to initialize OpenRouter client: ${error.message}`, { operation, cause: error });
76
- }
77
- }
78
- /**
79
- * Creates a GoogleGenAI client for Gemini, supporting standard API key or Vertex AI.
80
- * @private
81
- */
82
- createGeminiClient(context, options) {
83
- const operation = "LlmFactory.createGeminiClient";
84
- if (options?.useVertexAi) {
85
- if (!options.project || !options.location) {
86
- logger.error(`[${operation}] Vertex AI project and location are required when useVertexAi is true.`, context);
87
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Vertex AI project and location must be configured if useVertexAi is true.", { operation });
88
- }
89
- try {
90
- // For Vertex AI, apiKey in GoogleGenAI constructor is optional if ADC are set up.
91
- // The SDK handles ADC automatically if apiKey is not provided.
92
- const clientConfig = {
93
- project: options.project,
94
- location: options.location,
95
- vertexai: true,
96
- };
97
- if (options.apiKey) {
98
- // Allow API key to be passed for Vertex if specific auth needed
99
- clientConfig.apiKey = options.apiKey;
100
- }
101
- const genAI = new GoogleGenAI(clientConfig);
102
- logger.info(`[${operation}] GoogleGenAI client for Vertex AI created successfully.`, context);
103
- return genAI;
104
- }
105
- catch (error) {
106
- logger.error(`[${operation}] Failed to create Gemini client for Vertex AI`, { ...context, error: error.message });
107
- throw new McpError(BaseErrorCode.INITIALIZATION_FAILED, `Failed to initialize Gemini client for Vertex AI: ${error.message}`, { operation, cause: error });
108
- }
109
- }
110
- else {
111
- // Standard Gemini API key authentication
112
- const apiKey = options?.apiKey || config.geminiApiKey;
113
- if (!apiKey) {
114
- logger.error(`[${operation}] GEMINI_API_KEY is not set for standard API usage.`, context);
115
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Gemini API key is not configured for standard API usage.", { operation });
116
- }
117
- try {
118
- const genAI = new GoogleGenAI({ apiKey });
119
- logger.info(`[${operation}] GoogleGenAI client (standard API key) created successfully.`, context);
120
- return genAI;
121
- }
122
- catch (error) {
123
- logger.error(`[${operation}] Failed to create Gemini client (standard API key)`, { ...context, error: error.message });
124
- throw new McpError(BaseErrorCode.INITIALIZATION_FAILED, `Failed to initialize Gemini client (standard API key): ${error.message}`, { operation, cause: error });
125
- }
126
- }
127
- }
128
- }
129
- /**
130
- * Singleton instance of the LlmFactory.
131
- */
132
- export const llmFactory = new LlmFactory();
@@ -1,6 +0,0 @@
1
- /**
2
- * @fileoverview Barrel file for the OpenRouter provider service.
3
- * Exports the OpenRouterProvider class and any related types.
4
- * @module services/llm-providers/openRouter/index
5
- */
6
- export * from "./openRouterProvider.js";
@@ -1,7 +0,0 @@
1
- /**
2
- * @fileoverview Barrel file for the OpenRouter provider service.
3
- * Exports the OpenRouterProvider class and any related types.
4
- * @module services/llm-providers/openRouter/index
5
- */
6
- export * from "./openRouterProvider.js";
7
- // Add other exports from this module if any in the future
@@ -1,99 +0,0 @@
1
- import { OpenRouterClientOptions } from "../llmFactory.js";
2
- import { ChatCompletion, ChatCompletionChunk, ChatCompletionCreateParamsNonStreaming, ChatCompletionCreateParamsStreaming } from "openai/resources/chat/completions";
3
- import { Stream } from "openai/streaming";
4
- import { OperationContext, RequestContext } from "../../../utils/internal/requestContext.js";
5
- /**
6
- * Defines the parameters for an OpenRouter chat completion request.
7
- * This type extends standard OpenAI chat completion parameters and includes
8
- * OpenRouter-specific fields.
9
- *
10
- * @property top_k - OpenRouter specific: Sample from the k most likely next tokens.
11
- * @property min_p - OpenRouter specific: Minimum probability for a token to be considered.
12
- * @property transforms - OpenRouter specific: Apply transformations to the request or response.
13
- * @property models - OpenRouter specific: A list of models to use, often for fallback or routing.
14
- * @property route - OpenRouter specific: Specifies routing strategy, e.g., 'fallback'.
15
- * @property provider - OpenRouter specific: Provider-specific parameters or routing preferences.
16
- * @property stream - If true, the response will be a stream of `ChatCompletionChunk` objects.
17
- * If false or undefined, a single `ChatCompletion` object is returned.
18
- */
19
- export type OpenRouterChatParams = (ChatCompletionCreateParamsNonStreaming | ChatCompletionCreateParamsStreaming) & {
20
- top_k?: number;
21
- min_p?: number;
22
- transforms?: string[];
23
- models?: string[];
24
- route?: "fallback";
25
- provider?: Record<string, any>;
26
- };
27
- /**
28
- * Service class for interacting with the OpenRouter API.
29
- * Uses the OpenAI SDK for chat completions, configured for OpenRouter.
30
- * Handles API key management, default headers, model-specific parameter adjustments,
31
- * and provides methods for chat completions and listing models.
32
- */
33
- declare class OpenRouterProvider {
34
- /**
35
- * The OpenAI SDK client instance configured for OpenRouter.
36
- * @private
37
- */
38
- private client?;
39
- /**
40
- * Current status of the OpenRouter service.
41
- * - `unconfigured`: API key is missing.
42
- * - `initializing`: Constructor is running.
43
- * - `ready`: Client initialized successfully and service is usable.
44
- * - `error`: An error occurred during initialization.
45
- */
46
- status: "unconfigured" | "initializing" | "ready" | "error";
47
- /**
48
- * Stores any error that occurred during client initialization.
49
- * @private
50
- */
51
- private initializationError;
52
- /**
53
- * Constructs an `OpenRouterProvider` instance.
54
- * Initializes the OpenAI client for OpenRouter if an API key is provided.
55
- * Sets default headers required by OpenRouter.
56
- * @param apiKey - The OpenRouter API key. If undefined, the service remains 'unconfigured'.
57
- * @param parentOpContext - Optional parent operation context for linked logging.
58
- */
59
- constructor(options?: OpenRouterClientOptions, parentOpContext?: OperationContext);
60
- /**
61
- * Checks if the service is ready to make API calls.
62
- * @param operation - The name of the operation attempting to use the service.
63
- * @param context - The request context for logging.
64
- * @throws {McpError} If the service is not ready.
65
- * @private
66
- */
67
- private checkReady;
68
- /**
69
- * Creates a chat completion using the OpenRouter API.
70
- * Can return either a single response or a stream of chunks.
71
- * Applies rate limiting and handles model-specific parameter adjustments.
72
- *
73
- * @param params - Parameters for the chat completion request.
74
- * @param context - Request context for logging, error handling, and rate limiting.
75
- * @returns A promise resolving with either a `ChatCompletion` or a `Stream<ChatCompletionChunk>`.
76
- * @throws {McpError} If service not ready, rate limit exceeded, or API call fails.
77
- */
78
- chatCompletion(params: OpenRouterChatParams, context: RequestContext): Promise<ChatCompletion | Stream<ChatCompletionChunk>>;
79
- /**
80
- * Lists available models from the OpenRouter API.
81
- * Makes a direct `fetch` call to the `/models` endpoint.
82
- *
83
- * @param context - Request context for logging and error handling.
84
- * @returns A promise resolving with the JSON response from the OpenRouter API.
85
- * @throws {McpError} If the service is not ready, or if the API call fails.
86
- */
87
- listModels(context: RequestContext): Promise<any>;
88
- }
89
- /**
90
- * Singleton instance of the `OpenRouterProvider`.
91
- * Initialized with the OpenRouter API key from application configuration.
92
- */
93
- declare const openRouterProviderInstance: OpenRouterProvider;
94
- export { openRouterProviderInstance as openRouterProvider };
95
- /**
96
- * Exporting the type of the OpenRouterProvider class for use in dependency injection
97
- * or for type hinting elsewhere in the application.
98
- */
99
- export type { OpenRouterProvider };