@cyanheads/pubmed-mcp-server 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +2 -2
  2. package/dist/config/index.d.ts +13 -52
  3. package/dist/config/index.js +51 -222
  4. package/dist/mcp-server/server.d.ts +0 -5
  5. package/dist/mcp-server/server.js +18 -34
  6. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -2
  7. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -2
  8. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +3 -3
  9. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -2
  10. package/dist/mcp-server/transports/auth/authFactory.d.ts +10 -0
  11. package/dist/mcp-server/transports/auth/authFactory.js +41 -0
  12. package/dist/mcp-server/transports/auth/authMiddleware.d.ts +19 -0
  13. package/dist/mcp-server/transports/auth/authMiddleware.js +57 -0
  14. package/dist/mcp-server/transports/auth/index.d.ts +8 -5
  15. package/dist/mcp-server/transports/auth/index.js +6 -4
  16. package/dist/mcp-server/transports/auth/{core → lib}/authTypes.d.ts +0 -5
  17. package/dist/mcp-server/transports/auth/lib/authTypes.js +8 -0
  18. package/dist/mcp-server/transports/auth/{core → lib}/authUtils.js +21 -14
  19. package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +17 -0
  20. package/dist/mcp-server/transports/auth/strategies/authStrategy.js +1 -0
  21. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +7 -0
  22. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +112 -0
  23. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +7 -0
  24. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +101 -0
  25. package/dist/mcp-server/transports/core/baseTransportManager.d.ts +17 -0
  26. package/dist/mcp-server/transports/core/baseTransportManager.js +18 -0
  27. package/dist/mcp-server/transports/core/honoNodeBridge.d.ts +23 -0
  28. package/dist/mcp-server/transports/core/honoNodeBridge.js +51 -0
  29. package/dist/mcp-server/transports/core/statefulTransportManager.d.ts +31 -0
  30. package/dist/mcp-server/transports/core/statefulTransportManager.js +233 -0
  31. package/dist/mcp-server/transports/core/statelessTransportManager.d.ts +20 -0
  32. package/dist/mcp-server/transports/core/statelessTransportManager.js +92 -0
  33. package/dist/mcp-server/transports/core/transportTypes.d.ts +68 -0
  34. package/dist/mcp-server/transports/core/transportTypes.js +5 -0
  35. package/dist/mcp-server/transports/{httpErrorHandler.d.ts → http/httpErrorHandler.d.ts} +4 -9
  36. package/dist/mcp-server/transports/{httpErrorHandler.js → http/httpErrorHandler.js} +33 -8
  37. package/dist/mcp-server/transports/http/httpTransport.d.ts +22 -0
  38. package/dist/mcp-server/transports/http/httpTransport.js +251 -0
  39. package/dist/mcp-server/transports/http/httpTypes.d.ts +16 -0
  40. package/dist/mcp-server/transports/http/httpTypes.js +5 -0
  41. package/dist/mcp-server/transports/http/index.d.ts +7 -0
  42. package/dist/mcp-server/transports/http/index.js +6 -0
  43. package/dist/mcp-server/transports/http/mcpTransportMiddleware.d.ts +25 -0
  44. package/dist/mcp-server/transports/http/mcpTransportMiddleware.js +63 -0
  45. package/dist/mcp-server/transports/stdio/index.d.ts +5 -0
  46. package/dist/mcp-server/transports/stdio/index.js +5 -0
  47. package/dist/mcp-server/transports/{stdioTransport.d.ts → stdio/stdioTransport.d.ts} +2 -2
  48. package/dist/mcp-server/transports/{stdioTransport.js → stdio/stdioTransport.js} +10 -5
  49. package/dist/services/NCBI/{ncbiConstants.d.ts → core/ncbiConstants.d.ts} +1 -1
  50. package/dist/services/NCBI/{ncbiConstants.js → core/ncbiConstants.js} +1 -1
  51. package/dist/services/NCBI/{ncbiCoreApiClient.d.ts → core/ncbiCoreApiClient.d.ts} +2 -2
  52. package/dist/services/NCBI/{ncbiCoreApiClient.js → core/ncbiCoreApiClient.js} +4 -4
  53. package/dist/services/NCBI/{ncbiRequestQueueManager.d.ts → core/ncbiRequestQueueManager.d.ts} +2 -2
  54. package/dist/services/NCBI/{ncbiRequestQueueManager.js → core/ncbiRequestQueueManager.js} +3 -3
  55. package/dist/services/NCBI/{ncbiResponseHandler.d.ts → core/ncbiResponseHandler.d.ts} +2 -2
  56. package/dist/services/NCBI/{ncbiResponseHandler.js → core/ncbiResponseHandler.js} +3 -3
  57. package/dist/services/NCBI/{ncbiService.d.ts → core/ncbiService.d.ts} +3 -3
  58. package/dist/services/NCBI/{ncbiService.js → core/ncbiService.js} +2 -2
  59. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.d.ts +1 -1
  60. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.js +1 -1
  61. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.d.ts +1 -1
  62. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.js +1 -1
  63. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.d.ts +1 -1
  64. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.js +1 -1
  65. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.d.ts +1 -1
  66. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.js +1 -1
  67. package/dist/types-global/errors.d.ts +2 -0
  68. package/dist/types-global/errors.js +2 -0
  69. package/dist/utils/internal/errorHandler.js +1 -1
  70. package/dist/utils/internal/logger.d.ts +13 -1
  71. package/dist/utils/internal/logger.js +43 -9
  72. package/dist/utils/network/fetchWithTimeout.d.ts +21 -0
  73. package/dist/utils/network/fetchWithTimeout.js +59 -0
  74. package/dist/utils/network/index.d.ts +6 -0
  75. package/dist/utils/network/index.js +5 -0
  76. package/dist/utils/scheduling/index.d.ts +6 -0
  77. package/dist/utils/scheduling/index.js +6 -0
  78. package/dist/utils/scheduling/scheduler.d.ts +72 -0
  79. package/dist/utils/scheduling/scheduler.js +150 -0
  80. package/dist/utils/security/sanitization.js +35 -18
  81. package/package.json +9 -7
  82. package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
  83. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
  84. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
  85. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
  86. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
  87. package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
  88. package/dist/mcp-server/transports/httpTransport.js +0 -208
  89. /package/dist/mcp-server/transports/auth/{core → lib}/authContext.d.ts +0 -0
  90. /package/dist/mcp-server/transports/auth/{core → lib}/authContext.js +0 -0
  91. /package/dist/mcp-server/transports/auth/{core → lib}/authUtils.d.ts +0 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # PubMed MCP Server
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
- [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.13.0-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-1.2.3-blue.svg)](./CHANGELOG.md)
4
+ [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.17.0-green.svg)](https://modelcontextprotocol.io/)
5
+ [![Version](https://img.shields.io/badge/Version-1.3.0-blue.svg)](./CHANGELOG.md)
6
6
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/cyanheads/pubmed-mcp-server/issues)
8
8
  [![GitHub](https://img.shields.io/github/stars/cyanheads/pubmed-mcp-server?style=social)](https://github.com/cyanheads/pubmed-mcp-server)
@@ -4,78 +4,39 @@
4
4
  * environment variables and `package.json`. It uses Zod for schema validation
5
5
  * to ensure type safety and correctness of configuration parameters.
6
6
  *
7
- * Key responsibilities:
8
- * - Load environment variables from a `.env` file.
9
- * - Read `package.json` for default server name and version.
10
- * - Define a Zod schema for all expected environment variables.
11
- * - Validate environment variables against the schema.
12
- * - Construct and export a comprehensive `config` object.
13
- * - Export individual configuration values like `logLevel` and `environment` for convenience.
14
- *
15
7
  * @module src/config/index
16
8
  */
17
- /**
18
- * Main application configuration object.
19
- * Aggregates settings from validated environment variables and `package.json`.
20
- */
21
9
  export declare const config: {
22
- /** MCP server name. Env `MCP_SERVER_NAME` > `package.json` name > "mcp-ts-template". */
10
+ pkg: {
11
+ name: string;
12
+ version: string;
13
+ };
23
14
  mcpServerName: string;
24
- /** MCP server version. Env `MCP_SERVER_VERSION` > `package.json` version > "0.0.0". */
25
15
  mcpServerVersion: string;
26
- /** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
27
16
  logLevel: string;
28
- /** Defines the logging output mode ('file' or 'stdout'). From `LOG_OUTPUT_MODE`. */
29
- logOutputMode: "file" | "stdout";
30
- /** Absolute path to the logs directory (if logOutputMode is 'file'). From `LOGS_DIR`. */
31
17
  logsPath: string | null;
32
- /** Runtime environment. From `NODE_ENV` env var. Default: "development". */
33
18
  environment: string;
34
- /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
35
19
  mcpTransportType: "stdio" | "http";
36
- /** HTTP server port (if http transport). From `MCP_HTTP_PORT` env var. Default: 3010. */
20
+ mcpSessionMode: "stateless" | "stateful" | "auto";
37
21
  mcpHttpPort: number;
38
- /** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
39
22
  mcpHttpHost: string;
40
- /** Array of allowed CORS origins (http transport). From `MCP_ALLOWED_ORIGINS` (comma-separated). */
23
+ mcpHttpEndpointPath: string;
24
+ mcpHttpMaxPortRetries: number;
25
+ mcpHttpPortRetryDelayMs: number;
26
+ mcpStatefulSessionStaleTimeoutMs: number;
41
27
  mcpAllowedOrigins: string[] | undefined;
42
- /** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
28
+ mcpAuthMode: "jwt" | "oauth" | "none";
43
29
  mcpAuthSecretKey: string | undefined;
44
- /** Auth mode ('jwt' or 'oauth'). From `MCP_AUTH_MODE`. */
45
- mcpAuthMode: "jwt" | "oauth";
46
- /** OAuth Issuer URL. From `OAUTH_ISSUER_URL`. */
47
30
  oauthIssuerUrl: string | undefined;
48
- /** OAuth Audience. From `OAUTH_AUDIENCE`. */
49
- oauthAudience: string | undefined;
50
- /** OAuth JWKS URI. From `OAUTH_JWKS_URI`. */
51
31
  oauthJwksUri: string | undefined;
52
- /** NCBI API Key. From `NCBI_API_KEY`. */
32
+ oauthAudience: string | undefined;
33
+ devMcpClientId: string | undefined;
34
+ devMcpScopes: string[] | undefined;
53
35
  ncbiApiKey: string | undefined;
54
- /** NCBI Tool Identifier. From `NCBI_TOOL_IDENTIFIER`. Defaults to server name/version. */
55
36
  ncbiToolIdentifier: string;
56
- /** NCBI Admin Email. From `NCBI_ADMIN_EMAIL`. */
57
37
  ncbiAdminEmail: string | undefined;
58
- /** NCBI Request Delay in MS. From `NCBI_REQUEST_DELAY_MS`. Dynamically set based on API key presence. */
59
38
  ncbiRequestDelayMs: number;
60
- /** NCBI Max Retries. From `NCBI_MAX_RETRIES`. */
61
39
  ncbiMaxRetries: number;
62
- /** OAuth Proxy configurations. Undefined if no related env vars are set. */
63
- oauthProxy: {
64
- authorizationUrl: string | undefined;
65
- tokenUrl: string | undefined;
66
- revocationUrl: string | undefined;
67
- issuerUrl: string | undefined;
68
- serviceDocumentationUrl: string | undefined;
69
- defaultClientRedirectUris: string[] | undefined;
70
- } | undefined;
71
40
  };
72
- /**
73
- * Configured logging level for the application.
74
- * Exported for convenience.
75
- */
76
41
  export declare const logLevel: string;
77
- /**
78
- * Configured runtime environment ("development", "production", etc.).
79
- * Exported for convenience.
80
- */
81
42
  export declare const environment: string;
@@ -4,14 +4,6 @@
4
4
  * environment variables and `package.json`. It uses Zod for schema validation
5
5
  * to ensure type safety and correctness of configuration parameters.
6
6
  *
7
- * Key responsibilities:
8
- * - Load environment variables from a `.env` file.
9
- * - Read `package.json` for default server name and version.
10
- * - Define a Zod schema for all expected environment variables.
11
- * - Validate environment variables against the schema.
12
- * - Construct and export a comprehensive `config` object.
13
- * - Export individual configuration values like `logLevel` and `environment` for convenience.
14
- *
15
7
  * @module src/config/index
16
8
  */
17
9
  import dotenv from "dotenv";
@@ -21,11 +13,6 @@ import { fileURLToPath } from "url";
21
13
  import { z } from "zod";
22
14
  dotenv.config();
23
15
  // --- Determine Project Root ---
24
- /**
25
- * Finds the project root directory by searching upwards for package.json.
26
- * @param startDir The directory to start searching from.
27
- * @returns The absolute path to the project root, or throws an error if not found.
28
- */
29
16
  const findProjectRoot = (startDir) => {
30
17
  let currentDir = startDir;
31
18
  while (true) {
@@ -35,7 +22,6 @@ const findProjectRoot = (startDir) => {
35
22
  }
36
23
  const parentDir = dirname(currentDir);
37
24
  if (parentDir === currentDir) {
38
- // Reached the root of the filesystem without finding package.json
39
25
  throw new Error(`Could not find project root (package.json) starting from ${startDir}`);
40
26
  }
41
27
  currentDir = parentDir;
@@ -43,21 +29,17 @@ const findProjectRoot = (startDir) => {
43
29
  };
44
30
  let projectRoot;
45
31
  try {
46
- // For ESM, __dirname is not available directly.
47
- // import.meta.url gives the URL of the current module.
48
32
  const currentModuleDir = dirname(fileURLToPath(import.meta.url));
49
33
  projectRoot = findProjectRoot(currentModuleDir);
50
34
  }
51
35
  catch (error) {
52
36
  console.error(`FATAL: Error determining project root: ${error.message}`);
53
- // Fallback to process.cwd() if project root cannot be determined.
54
- // This might happen in unusual execution environments.
55
37
  projectRoot = process.cwd();
56
38
  console.warn(`Warning: Using process.cwd() (${projectRoot}) as fallback project root.`);
57
39
  }
58
40
  // --- End Determine Project Root ---
59
- const pkgPath = join(projectRoot, "package.json"); // Use determined projectRoot
60
- let pkg = { name: "mcp-ts-template", version: "0.0.0" };
41
+ const pkgPath = join(projectRoot, "package.json");
42
+ let pkg = { name: "pubmed-mcp-server", version: "0.0.0" };
61
43
  try {
62
44
  pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
63
45
  }
@@ -66,90 +48,42 @@ catch (error) {
66
48
  console.error("Warning: Could not read package.json for default config values. Using hardcoded defaults.", error);
67
49
  }
68
50
  }
69
- /**
70
- * Zod schema for validating environment variables.
71
- * Provides type safety, validation, defaults, and clear error messages.
72
- * @private
73
- */
74
51
  const EnvSchema = z
75
52
  .object({
76
- /** Optional. The desired name for the MCP server. Defaults to `package.json` name. */
53
+ // Core Server Config
77
54
  MCP_SERVER_NAME: z.string().optional(),
78
- /** Optional. The version of the MCP server. Defaults to `package.json` version. */
79
55
  MCP_SERVER_VERSION: z.string().optional(),
80
- /** Minimum logging level. See `McpLogLevel` in logger utility. Default: "debug". */
56
+ NODE_ENV: z.string().default("development"),
57
+ // Logging
81
58
  MCP_LOG_LEVEL: z.string().default("debug"),
82
- /** Directory for log files. Defaults to "logs" in project root. */
83
59
  LOGS_DIR: z.string().default(path.join(projectRoot, "logs")),
84
- /** Defines the logging output mode. "file" for logs in LOGS_DIR, "stdout" for console logging. */
85
- LOG_OUTPUT_MODE: z.enum(["file", "stdout"]).default("file"),
86
- /** Runtime environment (e.g., "development", "production"). Default: "development". */
87
- NODE_ENV: z.string().default("development"),
88
- /** MCP communication transport ("stdio" or "http"). Default: "stdio". */
60
+ // Transport
89
61
  MCP_TRANSPORT_TYPE: z.enum(["stdio", "http"]).default("stdio"),
90
- /** HTTP server port (if MCP_TRANSPORT_TYPE is "http"). Default: 3010. */
62
+ MCP_SESSION_MODE: z.enum(["stateless", "stateful", "auto"]).default("auto"),
91
63
  MCP_HTTP_PORT: z.coerce.number().int().positive().default(3010),
92
- /** HTTP server host (if MCP_TRANSPORT_TYPE is "http"). Default: "127.0.0.1". */
93
64
  MCP_HTTP_HOST: z.string().default("127.0.0.1"),
94
- /** Optional. Comma-separated allowed origins for CORS (HTTP transport). */
65
+ MCP_HTTP_ENDPOINT_PATH: z.string().default("/mcp"),
66
+ MCP_HTTP_MAX_PORT_RETRIES: z.coerce.number().int().nonnegative().default(15),
67
+ MCP_HTTP_PORT_RETRY_DELAY_MS: z.coerce.number().int().nonnegative().default(50),
68
+ MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS: z.coerce.number().int().positive().default(1800000),
95
69
  MCP_ALLOWED_ORIGINS: z.string().optional(),
96
- /** Optional. Secret key (min 32 chars) for auth tokens (HTTP transport). CRITICAL for production. */
97
- MCP_AUTH_SECRET_KEY: z
98
- .string()
99
- .min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security reasons.")
100
- .optional(),
101
- /** Authentication mode ('jwt' or 'oauth'). Default: 'jwt'. */
102
- MCP_AUTH_MODE: z.enum(["jwt", "oauth"]).default("jwt"),
103
- /** OAuth: The expected issuer of the JWT. */
70
+ // Authentication
71
+ MCP_AUTH_MODE: z.enum(["jwt", "oauth", "none"]).default("none"),
72
+ MCP_AUTH_SECRET_KEY: z.string().min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long.").optional(),
104
73
  OAUTH_ISSUER_URL: z.string().url().optional(),
105
- /** OAuth: The expected audience of the JWT. */
106
- OAUTH_AUDIENCE: z.string().optional(),
107
- /** OAuth: The URI of the JWKS endpoint. */
108
74
  OAUTH_JWKS_URI: z.string().url().optional(),
109
- /** Optional. OAuth provider authorization endpoint URL. */
110
- OAUTH_PROXY_AUTHORIZATION_URL: z
111
- .string()
112
- .url("OAUTH_PROXY_AUTHORIZATION_URL must be a valid URL.")
113
- .optional(),
114
- /** Optional. OAuth provider token endpoint URL. */
115
- OAUTH_PROXY_TOKEN_URL: z
116
- .string()
117
- .url("OAUTH_PROXY_TOKEN_URL must be a valid URL.")
118
- .optional(),
119
- /** Optional. OAuth provider revocation endpoint URL. */
120
- OAUTH_PROXY_REVOCATION_URL: z
121
- .string()
122
- .url("OAUTH_PROXY_REVOCATION_URL must be a valid URL.")
123
- .optional(),
124
- /** Optional. OAuth provider issuer URL. */
125
- OAUTH_PROXY_ISSUER_URL: z
126
- .string()
127
- .url("OAUTH_PROXY_ISSUER_URL must be a valid URL.")
128
- .optional(),
129
- /** Optional. OAuth service documentation URL. */
130
- OAUTH_PROXY_SERVICE_DOCUMENTATION_URL: z
131
- .string()
132
- .url("OAUTH_PROXY_SERVICE_DOCUMENTATION_URL must be a valid URL.")
133
- .optional(),
134
- /** Optional. Comma-separated default OAuth client redirect URIs. */
135
- OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS: z.string().optional(),
136
- // NCBI E-utilities Configuration
137
- /** NCBI API Key. Optional, but highly recommended for higher rate limits. */
75
+ OAUTH_AUDIENCE: z.string().optional(),
76
+ // Dev mode JWT
77
+ DEV_MCP_CLIENT_ID: z.string().optional(),
78
+ DEV_MCP_SCOPES: z.string().optional(),
79
+ // NCBI E-utilities
138
80
  NCBI_API_KEY: z.string().optional(),
139
- /** Tool identifier sent to NCBI. Defaults to MCP_SERVER_NAME/MCP_SERVER_VERSION. */
140
81
  NCBI_TOOL_IDENTIFIER: z.string().optional(),
141
- /** Administrator's email for NCBI contact. Optional, but recommended if using an API key. */
142
- NCBI_ADMIN_EMAIL: z
143
- .string()
144
- .email("NCBI_ADMIN_EMAIL must be a valid email address.")
145
- .optional(),
146
- /** Milliseconds to wait between NCBI requests. Default: 100 (for API key), 334 (without API key). */
147
- NCBI_REQUEST_DELAY_MS: z.coerce.number().int().positive().optional(), // Default will be set conditionally
148
- /** Maximum number of retries for failed NCBI requests. Default: 3. */
82
+ NCBI_ADMIN_EMAIL: z.string().email().optional(),
83
+ NCBI_REQUEST_DELAY_MS: z.coerce.number().int().positive().optional(),
149
84
  NCBI_MAX_RETRIES: z.coerce.number().int().nonnegative().default(3),
150
85
  })
151
86
  .superRefine((data, ctx) => {
152
- // Rule 1: MCP_AUTH_SECRET_KEY is required for http transport in production with jwt auth
153
87
  if (data.NODE_ENV === "production" &&
154
88
  data.MCP_TRANSPORT_TYPE === "http" &&
155
89
  data.MCP_AUTH_MODE === "jwt" &&
@@ -157,184 +91,79 @@ const EnvSchema = z
157
91
  ctx.addIssue({
158
92
  code: z.ZodIssueCode.custom,
159
93
  path: ["MCP_AUTH_SECRET_KEY"],
160
- message: "MCP_AUTH_SECRET_KEY is required for 'jwt' auth with 'http' transport in a 'production' environment.",
94
+ message: "MCP_AUTH_SECRET_KEY is required for 'jwt' auth in production with 'http' transport.",
161
95
  });
162
96
  }
163
- // Rule 2: Core OAuth variables are required when MCP_AUTH_MODE is 'oauth'
164
97
  if (data.MCP_AUTH_MODE === "oauth") {
165
98
  if (!data.OAUTH_ISSUER_URL) {
166
- ctx.addIssue({
167
- code: z.ZodIssueCode.custom,
168
- path: ["OAUTH_ISSUER_URL"],
169
- message: "OAUTH_ISSUER_URL is required when MCP_AUTH_MODE is 'oauth'.",
170
- });
99
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["OAUTH_ISSUER_URL"], message: "OAUTH_ISSUER_URL is required for 'oauth' mode." });
171
100
  }
172
101
  if (!data.OAUTH_AUDIENCE) {
173
- ctx.addIssue({
174
- code: z.ZodIssueCode.custom,
175
- path: ["OAUTH_AUDIENCE"],
176
- message: "OAUTH_AUDIENCE is required when MCP_AUTH_MODE is 'oauth'.",
177
- });
178
- }
179
- if (!data.OAUTH_JWKS_URI) {
180
- ctx.addIssue({
181
- code: z.ZodIssueCode.custom,
182
- path: ["OAUTH_JWKS_URI"],
183
- message: "OAUTH_JWKS_URI is required when MCP_AUTH_MODE is 'oauth'.",
184
- });
102
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["OAUTH_AUDIENCE"], message: "OAUTH_AUDIENCE is required for 'oauth' mode." });
185
103
  }
186
104
  }
187
105
  });
188
106
  const parsedEnv = EnvSchema.safeParse(process.env);
189
107
  if (!parsedEnv.success) {
190
108
  if (process.stdout.isTTY) {
191
- console.error("❌ Invalid environment variables found:", parsedEnv.error.flatten().fieldErrors);
109
+ console.error("❌ Invalid environment variables:", parsedEnv.error.flatten().fieldErrors);
192
110
  }
193
- // Consider throwing an error in production for critical misconfigurations.
194
111
  }
195
112
  const env = parsedEnv.success ? parsedEnv.data : EnvSchema.parse({});
196
- // --- Directory Ensurance Function ---
197
- /**
198
- * Ensures a directory exists and is within the project root.
199
- * @param dirPath The desired path for the directory (can be relative or absolute).
200
- * @param rootDir The root directory of the project to contain the directory.
201
- * @param dirName The name of the directory type for logging (e.g., "logs").
202
- * @returns The validated, absolute path to the directory, or null if invalid.
203
- */
204
113
  const ensureDirectory = (dirPath, rootDir, dirName) => {
205
- const resolvedDirPath = path.isAbsolute(dirPath)
206
- ? dirPath
207
- : path.resolve(rootDir, dirPath);
208
- // Ensure the resolved path is within the project root boundary
209
- if (!resolvedDirPath.startsWith(rootDir + path.sep) &&
210
- resolvedDirPath !== rootDir) {
114
+ const resolvedDirPath = path.isAbsolute(dirPath) ? dirPath : path.resolve(rootDir, dirPath);
115
+ if (!resolvedDirPath.startsWith(rootDir)) {
211
116
  if (process.stdout.isTTY) {
212
- console.error(`Error: ${dirName} path "${dirPath}" resolves to "${resolvedDirPath}", which is outside the project boundary "${rootDir}".`);
117
+ console.error(`Error: ${dirName} path "${dirPath}" is outside the project boundary "${rootDir}".`);
213
118
  }
214
119
  return null;
215
120
  }
216
- if (!existsSync(resolvedDirPath)) {
217
- try {
121
+ try {
122
+ if (!existsSync(resolvedDirPath)) {
218
123
  mkdirSync(resolvedDirPath, { recursive: true });
219
- if (process.stdout.isTTY) {
220
- console.log(`Created ${dirName} directory: ${resolvedDirPath}`);
221
- }
222
124
  }
223
- catch (err) {
224
- const errorMessage = err instanceof Error ? err.message : String(err);
225
- if (process.stdout.isTTY) {
226
- console.error(`Error creating ${dirName} directory at ${resolvedDirPath}: ${errorMessage}`);
227
- }
228
- return null;
229
- }
230
- }
231
- else {
232
- try {
233
- const stats = statSync(resolvedDirPath);
234
- if (!stats.isDirectory()) {
235
- if (process.stdout.isTTY) {
236
- console.error(`Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`);
237
- }
125
+ else {
126
+ if (!statSync(resolvedDirPath).isDirectory()) {
127
+ console.error(`Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`);
238
128
  return null;
239
129
  }
240
130
  }
241
- catch (statError) {
242
- if (process.stdout.isTTY) {
243
- console.error(`Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`);
244
- }
245
- return null;
246
- }
131
+ return resolvedDirPath;
247
132
  }
248
- return resolvedDirPath;
249
- };
250
- // --- End Directory Ensurance Function ---
251
- // --- Logs Directory Handling ---
252
- let validatedLogsPath = null;
253
- if (env.LOG_OUTPUT_MODE === "file") {
254
- validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
255
- if (!validatedLogsPath) {
256
- if (process.stdout.isTTY) {
257
- console.error("FATAL: Log mode is 'file' but logs directory is invalid or could not be created. Please check LOGS_DIR, permissions, and path. Exiting.");
258
- }
259
- process.exit(1); // Exit if file logging is configured but directory is not usable
133
+ catch (error) {
134
+ console.error(`Error ensuring ${dirName} directory at ${resolvedDirPath}: ${error.message}`);
135
+ return null;
260
136
  }
261
- }
262
- // --- End Logs Directory Handling ---
263
- /**
264
- * Main application configuration object.
265
- * Aggregates settings from validated environment variables and `package.json`.
266
- */
137
+ };
138
+ const validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
267
139
  export const config = {
268
- /** MCP server name. Env `MCP_SERVER_NAME` > `package.json` name > "mcp-ts-template". */
140
+ pkg,
269
141
  mcpServerName: env.MCP_SERVER_NAME || pkg.name,
270
- /** MCP server version. Env `MCP_SERVER_VERSION` > `package.json` version > "0.0.0". */
271
142
  mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
272
- /** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
273
143
  logLevel: env.MCP_LOG_LEVEL,
274
- /** Defines the logging output mode ('file' or 'stdout'). From `LOG_OUTPUT_MODE`. */
275
- logOutputMode: env.LOG_OUTPUT_MODE,
276
- /** Absolute path to the logs directory (if logOutputMode is 'file'). From `LOGS_DIR`. */
277
144
  logsPath: validatedLogsPath,
278
- /** Runtime environment. From `NODE_ENV` env var. Default: "development". */
279
145
  environment: env.NODE_ENV,
280
- /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
281
146
  mcpTransportType: env.MCP_TRANSPORT_TYPE,
282
- /** HTTP server port (if http transport). From `MCP_HTTP_PORT` env var. Default: 3010. */
147
+ mcpSessionMode: env.MCP_SESSION_MODE,
283
148
  mcpHttpPort: env.MCP_HTTP_PORT,
284
- /** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
285
149
  mcpHttpHost: env.MCP_HTTP_HOST,
286
- /** Array of allowed CORS origins (http transport). From `MCP_ALLOWED_ORIGINS` (comma-separated). */
287
- mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",")
288
- .map((origin) => origin.trim())
289
- .filter(Boolean),
290
- /** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
291
- mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
292
- /** Auth mode ('jwt' or 'oauth'). From `MCP_AUTH_MODE`. */
150
+ mcpHttpEndpointPath: env.MCP_HTTP_ENDPOINT_PATH,
151
+ mcpHttpMaxPortRetries: env.MCP_HTTP_MAX_PORT_RETRIES,
152
+ mcpHttpPortRetryDelayMs: env.MCP_HTTP_PORT_RETRY_DELAY_MS,
153
+ mcpStatefulSessionStaleTimeoutMs: env.MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS,
154
+ mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",").map((o) => o.trim()).filter(Boolean),
293
155
  mcpAuthMode: env.MCP_AUTH_MODE,
294
- /** OAuth Issuer URL. From `OAUTH_ISSUER_URL`. */
156
+ mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
295
157
  oauthIssuerUrl: env.OAUTH_ISSUER_URL,
296
- /** OAuth Audience. From `OAUTH_AUDIENCE`. */
297
- oauthAudience: env.OAUTH_AUDIENCE,
298
- /** OAuth JWKS URI. From `OAUTH_JWKS_URI`. */
299
158
  oauthJwksUri: env.OAUTH_JWKS_URI,
300
- // NCBI Configuration
301
- /** NCBI API Key. From `NCBI_API_KEY`. */
159
+ oauthAudience: env.OAUTH_AUDIENCE,
160
+ devMcpClientId: env.DEV_MCP_CLIENT_ID,
161
+ devMcpScopes: env.DEV_MCP_SCOPES?.split(",").map((s) => s.trim()),
302
162
  ncbiApiKey: env.NCBI_API_KEY,
303
- /** NCBI Tool Identifier. From `NCBI_TOOL_IDENTIFIER`. Defaults to server name/version. */
304
- ncbiToolIdentifier: env.NCBI_TOOL_IDENTIFIER ||
305
- `${env.MCP_SERVER_NAME || pkg.name}/${env.MCP_SERVER_VERSION || pkg.version}`,
306
- /** NCBI Admin Email. From `NCBI_ADMIN_EMAIL`. */
163
+ ncbiToolIdentifier: env.NCBI_TOOL_IDENTIFIER || `${env.MCP_SERVER_NAME || pkg.name}/${env.MCP_SERVER_VERSION || pkg.version}`,
307
164
  ncbiAdminEmail: env.NCBI_ADMIN_EMAIL,
308
- /** NCBI Request Delay in MS. From `NCBI_REQUEST_DELAY_MS`. Dynamically set based on API key presence. */
309
165
  ncbiRequestDelayMs: env.NCBI_REQUEST_DELAY_MS ?? (env.NCBI_API_KEY ? 100 : 334),
310
- /** NCBI Max Retries. From `NCBI_MAX_RETRIES`. */
311
166
  ncbiMaxRetries: env.NCBI_MAX_RETRIES,
312
- /** OAuth Proxy configurations. Undefined if no related env vars are set. */
313
- oauthProxy: env.OAUTH_PROXY_AUTHORIZATION_URL ||
314
- env.OAUTH_PROXY_TOKEN_URL ||
315
- env.OAUTH_PROXY_REVOCATION_URL ||
316
- env.OAUTH_PROXY_ISSUER_URL ||
317
- env.OAUTH_PROXY_SERVICE_DOCUMENTATION_URL ||
318
- env.OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS
319
- ? {
320
- authorizationUrl: env.OAUTH_PROXY_AUTHORIZATION_URL,
321
- tokenUrl: env.OAUTH_PROXY_TOKEN_URL,
322
- revocationUrl: env.OAUTH_PROXY_REVOCATION_URL,
323
- issuerUrl: env.OAUTH_PROXY_ISSUER_URL,
324
- serviceDocumentationUrl: env.OAUTH_PROXY_SERVICE_DOCUMENTATION_URL,
325
- defaultClientRedirectUris: env.OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS?.split(",")
326
- .map((uri) => uri.trim())
327
- .filter(Boolean),
328
- }
329
- : undefined,
330
167
  };
331
- /**
332
- * Configured logging level for the application.
333
- * Exported for convenience.
334
- */
335
168
  export const logLevel = config.logLevel;
336
- /**
337
- * Configured runtime environment ("development", "production", etc.).
338
- * Exported for convenience.
339
- */
340
169
  export const environment = config.environment;
@@ -7,16 +7,11 @@
7
7
  * based on configuration.
8
8
  * 4. Handles top-level error management during startup.
9
9
  *
10
- * MCP Specification References:
11
- * - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx
12
- * - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx
13
- * - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
14
10
  * @module src/mcp-server/server
15
11
  */
16
12
  import { ServerType } from "@hono/node-server";
17
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
14
  /**
19
15
  * Main application entry point. Initializes and starts the MCP server.
20
- * Orchestrates server startup, transport selection, and top-level error handling.
21
16
  */
22
17
  export declare function initializeAndStartServer(): Promise<void | McpServer | ServerType>;
@@ -7,10 +7,6 @@
7
7
  * based on configuration.
8
8
  * 4. Handles top-level error management during startup.
9
9
  *
10
- * MCP Specification References:
11
- * - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx
12
- * - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx
13
- * - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
14
10
  * @module src/mcp-server/server
15
11
  */
16
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -22,19 +18,11 @@ import { registerGeneratePubMedChartTool } from "./tools/generatePubMedChart/ind
22
18
  import { registerGetPubMedArticleConnectionsTool } from "./tools/getPubMedArticleConnections/index.js";
23
19
  import { registerPubMedResearchAgentTool } from "./tools/pubmedResearchAgent/index.js";
24
20
  import { registerSearchPubMedArticlesTool } from "./tools/searchPubMedArticles/index.js";
25
- import { startHttpTransport } from "./transports/httpTransport.js";
26
- import { connectStdioTransport } from "./transports/stdioTransport.js";
21
+ import { startHttpTransport } from "./transports/http/index.js";
22
+ import { startStdioTransport } from "./transports/stdio/index.js";
27
23
  /**
28
24
  * Creates and configures a new instance of the `McpServer`.
29
25
  *
30
- * This function defines the server's identity and capabilities as presented
31
- * to clients during MCP initialization.
32
- *
33
- * MCP Spec Relevance:
34
- * - Server Identity (`serverInfo`): `name` and `version` are part of `ServerInformation`.
35
- * - Capabilities Declaration: Declares supported features (logging, dynamic resources/tools).
36
- * - Resource/Tool Registration: Makes capabilities discoverable and invocable.
37
- *
38
26
  * @returns A promise resolving with the configured `McpServer` instance.
39
27
  * @throws {McpError} If any resource or tool registration fails.
40
28
  * @private
@@ -44,21 +32,16 @@ async function createMcpServerInstance() {
44
32
  operation: "createMcpServerInstance",
45
33
  });
46
34
  logger.info("Initializing MCP server instance", context);
47
- requestContextService.configure({
48
- appName: config.mcpServerName,
49
- appVersion: config.mcpServerVersion,
50
- environment,
51
- });
52
35
  const server = new McpServer({ name: config.mcpServerName, version: config.mcpServerVersion }, {
53
36
  capabilities: {
54
- logging: {}, // Server can receive logging/setLevel and send notifications/message
55
- resources: { listChanged: true }, // Server supports dynamic resource lists
56
- tools: { listChanged: true }, // Server supports dynamic tool lists
37
+ logging: {},
38
+ resources: { listChanged: true },
39
+ tools: { listChanged: true },
57
40
  },
58
41
  });
59
42
  await ErrorHandler.tryCatch(async () => {
60
43
  logger.debug("Registering resources and tools...", context);
61
- // IMPORTANT: Keep tool registrations in alphabetical order. Do not remove this comment.
44
+ // IMPORTANT: Keep tool registrations in alphabetical order.
62
45
  await registerFetchPubMedContentTool(server);
63
46
  await registerGeneratePubMedChartTool(server);
64
47
  await registerGetPubMedArticleConnectionsTool(server);
@@ -66,7 +49,7 @@ async function createMcpServerInstance() {
66
49
  await registerSearchPubMedArticlesTool(server);
67
50
  logger.info("Resources and tools registered successfully", context);
68
51
  }, {
69
- operation: "registerAllTools",
52
+ operation: "registerAllCapabilities",
70
53
  context,
71
54
  errorCode: BaseErrorCode.INITIALIZATION_FAILED,
72
55
  critical: true,
@@ -76,11 +59,6 @@ async function createMcpServerInstance() {
76
59
  /**
77
60
  * Selects, sets up, and starts the appropriate MCP transport layer based on configuration.
78
61
  *
79
- * MCP Spec Relevance:
80
- * - Transport Selection: Uses `config.mcpTransportType` ('stdio' or 'http').
81
- * - Transport Connection: Calls dedicated functions for chosen transport.
82
- * - Server Instance Lifecycle: Single instance for 'stdio', per-session for 'http'.
83
- *
84
62
  * @returns Resolves with `McpServer` for 'stdio', `http.Server` for 'http', or `void`.
85
63
  * @throws {Error} If transport type is unsupported or setup fails.
86
64
  * @private
@@ -92,25 +70,31 @@ async function startTransport() {
92
70
  transport: transportType,
93
71
  });
94
72
  logger.info(`Starting transport: ${transportType}`, context);
73
+ const serverFactory = createMcpServerInstance;
95
74
  if (transportType === "http") {
96
- return startHttpTransport(createMcpServerInstance, context);
75
+ const { server } = await startHttpTransport(serverFactory, context);
76
+ return server;
97
77
  }
98
78
  if (transportType === "stdio") {
99
- const server = await createMcpServerInstance();
100
- await connectStdioTransport(server, context);
79
+ const server = await serverFactory();
80
+ await startStdioTransport(server, context);
101
81
  return server;
102
82
  }
103
83
  throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
104
84
  }
105
85
  /**
106
86
  * Main application entry point. Initializes and starts the MCP server.
107
- * Orchestrates server startup, transport selection, and top-level error handling.
108
87
  */
109
88
  export async function initializeAndStartServer() {
110
89
  const context = requestContextService.createRequestContext({
111
90
  operation: "initializeAndStartServer",
112
91
  });
113
92
  logger.info("MCP Server initialization sequence started.", context);
93
+ requestContextService.configure({
94
+ appName: config.mcpServerName,
95
+ appVersion: config.mcpServerVersion,
96
+ environment,
97
+ });
114
98
  try {
115
99
  const result = await startTransport();
116
100
  logger.info("MCP Server initialization sequence completed successfully.", context);
@@ -121,7 +105,7 @@ export async function initializeAndStartServer() {
121
105
  operation: "initializeAndStartServer",
122
106
  context: context,
123
107
  critical: true,
124
- rethrow: false, // Ensure we don't rethrow, so we can exit gracefully.
108
+ rethrow: false,
125
109
  });
126
110
  logger.info("Exiting process due to critical initialization error.", context);
127
111
  process.exit(1);
@@ -6,10 +6,10 @@
6
6
  * @module src/mcp-server/tools/fetchPubMedContent/logic
7
7
  */
8
8
  import { z } from "zod";
9
- import { getNcbiService } from "../../../services/NCBI/ncbiService.js";
9
+ import { getNcbiService } from "../../../services/NCBI/core/ncbiService.js";
10
10
  import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
11
11
  import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
12
- import { ensureArray, extractAbstractText, extractArticleDates, extractAuthors, extractDoi, extractGrants, extractJournalInfo, extractKeywords, extractMeshTerms, extractPmid, extractPublicationTypes, getText, } from "../../../utils/parsing/ncbi-parsing/index.js";
12
+ import { ensureArray, extractAbstractText, extractArticleDates, extractAuthors, extractDoi, extractGrants, extractJournalInfo, extractKeywords, extractMeshTerms, extractPmid, extractPublicationTypes, getText, } from "../../../services/NCBI/parsing/index.js";
13
13
  export const FetchPubMedContentInputSchema = z
14
14
  .object({
15
15
  pmids: z