@cyanheads/pubmed-mcp-server 1.2.4 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -91
- package/dist/config/index.d.ts +13 -52
- package/dist/config/index.js +61 -204
- package/dist/index.js +30 -60
- package/dist/mcp-server/server.d.ts +0 -5
- package/dist/mcp-server/server.js +18 -34
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -2
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -2
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +3 -3
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -2
- package/dist/mcp-server/transports/auth/authFactory.d.ts +10 -0
- package/dist/mcp-server/transports/auth/authFactory.js +41 -0
- package/dist/mcp-server/transports/auth/authMiddleware.d.ts +19 -0
- package/dist/mcp-server/transports/auth/authMiddleware.js +57 -0
- package/dist/mcp-server/transports/auth/index.d.ts +8 -5
- package/dist/mcp-server/transports/auth/index.js +6 -4
- package/dist/mcp-server/transports/auth/{core → lib}/authTypes.d.ts +0 -5
- package/dist/mcp-server/transports/auth/lib/authTypes.js +8 -0
- package/dist/mcp-server/transports/auth/{core → lib}/authUtils.js +21 -14
- package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +17 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.js +1 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +7 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +112 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +7 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +101 -0
- package/dist/mcp-server/transports/core/baseTransportManager.d.ts +17 -0
- package/dist/mcp-server/transports/core/baseTransportManager.js +18 -0
- package/dist/mcp-server/transports/core/honoNodeBridge.d.ts +23 -0
- package/dist/mcp-server/transports/core/honoNodeBridge.js +51 -0
- package/dist/mcp-server/transports/core/statefulTransportManager.d.ts +31 -0
- package/dist/mcp-server/transports/core/statefulTransportManager.js +233 -0
- package/dist/mcp-server/transports/core/statelessTransportManager.d.ts +20 -0
- package/dist/mcp-server/transports/core/statelessTransportManager.js +92 -0
- package/dist/mcp-server/transports/core/transportTypes.d.ts +68 -0
- package/dist/mcp-server/transports/core/transportTypes.js +5 -0
- package/dist/mcp-server/transports/{httpErrorHandler.d.ts → http/httpErrorHandler.d.ts} +4 -9
- package/dist/mcp-server/transports/{httpErrorHandler.js → http/httpErrorHandler.js} +33 -8
- package/dist/mcp-server/transports/http/httpTransport.d.ts +22 -0
- package/dist/mcp-server/transports/http/httpTransport.js +251 -0
- package/dist/mcp-server/transports/http/httpTypes.d.ts +16 -0
- package/dist/mcp-server/transports/http/httpTypes.js +5 -0
- package/dist/mcp-server/transports/http/index.d.ts +7 -0
- package/dist/mcp-server/transports/http/index.js +6 -0
- package/dist/mcp-server/transports/http/mcpTransportMiddleware.d.ts +25 -0
- package/dist/mcp-server/transports/http/mcpTransportMiddleware.js +63 -0
- package/dist/mcp-server/transports/stdio/index.d.ts +5 -0
- package/dist/mcp-server/transports/stdio/index.js +5 -0
- package/dist/mcp-server/transports/{stdioTransport.d.ts → stdio/stdioTransport.d.ts} +2 -2
- package/dist/mcp-server/transports/{stdioTransport.js → stdio/stdioTransport.js} +10 -5
- package/dist/services/NCBI/{ncbiConstants.d.ts → core/ncbiConstants.d.ts} +1 -1
- package/dist/services/NCBI/{ncbiConstants.js → core/ncbiConstants.js} +1 -1
- package/dist/services/NCBI/{ncbiCoreApiClient.d.ts → core/ncbiCoreApiClient.d.ts} +2 -2
- package/dist/services/NCBI/{ncbiCoreApiClient.js → core/ncbiCoreApiClient.js} +4 -4
- package/dist/services/NCBI/{ncbiRequestQueueManager.d.ts → core/ncbiRequestQueueManager.d.ts} +2 -2
- package/dist/services/NCBI/{ncbiRequestQueueManager.js → core/ncbiRequestQueueManager.js} +3 -3
- package/dist/services/NCBI/{ncbiResponseHandler.d.ts → core/ncbiResponseHandler.d.ts} +2 -2
- package/dist/services/NCBI/{ncbiResponseHandler.js → core/ncbiResponseHandler.js} +3 -3
- package/dist/services/NCBI/{ncbiService.d.ts → core/ncbiService.d.ts} +3 -3
- package/dist/services/NCBI/{ncbiService.js → core/ncbiService.js} +2 -2
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.d.ts +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.js +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.d.ts +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.js +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.d.ts +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.js +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.d.ts +1 -1
- package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.js +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 +13 -1
- package/dist/utils/internal/logger.js +43 -9
- package/dist/utils/network/fetchWithTimeout.d.ts +21 -0
- package/dist/utils/network/fetchWithTimeout.js +59 -0
- package/dist/utils/network/index.d.ts +6 -0
- package/dist/utils/network/index.js +5 -0
- package/dist/utils/scheduling/index.d.ts +6 -0
- package/dist/utils/scheduling/index.js +6 -0
- package/dist/utils/scheduling/scheduler.d.ts +72 -0
- package/dist/utils/scheduling/scheduler.js +150 -0
- package/dist/utils/security/sanitization.js +35 -18
- package/package.json +9 -7
- package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
- package/dist/mcp-server/transports/httpTransport.js +0 -208
- /package/dist/mcp-server/transports/auth/{core → lib}/authContext.d.ts +0 -0
- /package/dist/mcp-server/transports/auth/{core → lib}/authContext.js +0 -0
- /package/dist/mcp-server/transports/auth/{core → lib}/authUtils.d.ts +0 -0
package/dist/config/index.js
CHANGED
|
@@ -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");
|
|
60
|
-
let pkg = { name: "mcp-
|
|
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,57 @@ 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
MCP_HTTP_ENDPOINT_PATH: z.string().default("/mcp"),
|
|
66
|
+
MCP_HTTP_MAX_PORT_RETRIES: z.coerce
|
|
67
|
+
.number()
|
|
68
|
+
.int()
|
|
69
|
+
.nonnegative()
|
|
70
|
+
.default(15),
|
|
71
|
+
MCP_HTTP_PORT_RETRY_DELAY_MS: z.coerce
|
|
72
|
+
.number()
|
|
73
|
+
.int()
|
|
74
|
+
.nonnegative()
|
|
75
|
+
.default(50),
|
|
76
|
+
MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS: z.coerce
|
|
77
|
+
.number()
|
|
78
|
+
.int()
|
|
79
|
+
.positive()
|
|
80
|
+
.default(1800000),
|
|
95
81
|
MCP_ALLOWED_ORIGINS: z.string().optional(),
|
|
96
|
-
|
|
82
|
+
// Authentication
|
|
83
|
+
MCP_AUTH_MODE: z.enum(["jwt", "oauth", "none"]).default("none"),
|
|
97
84
|
MCP_AUTH_SECRET_KEY: z
|
|
98
85
|
.string()
|
|
99
|
-
.min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long
|
|
86
|
+
.min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long.")
|
|
100
87
|
.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. */
|
|
104
88
|
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
89
|
OAUTH_JWKS_URI: z.string().url().optional(),
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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. */
|
|
90
|
+
OAUTH_AUDIENCE: z.string().optional(),
|
|
91
|
+
// Dev mode JWT
|
|
92
|
+
DEV_MCP_CLIENT_ID: z.string().optional(),
|
|
93
|
+
DEV_MCP_SCOPES: z.string().optional(),
|
|
94
|
+
// NCBI E-utilities
|
|
138
95
|
NCBI_API_KEY: z.string().optional(),
|
|
139
|
-
/** Tool identifier sent to NCBI. Defaults to MCP_SERVER_NAME/MCP_SERVER_VERSION. */
|
|
140
96
|
NCBI_TOOL_IDENTIFIER: z.string().optional(),
|
|
141
|
-
|
|
142
|
-
|
|
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. */
|
|
97
|
+
NCBI_ADMIN_EMAIL: z.string().email().optional(),
|
|
98
|
+
NCBI_REQUEST_DELAY_MS: z.coerce.number().int().positive().optional(),
|
|
149
99
|
NCBI_MAX_RETRIES: z.coerce.number().int().nonnegative().default(3),
|
|
150
100
|
})
|
|
151
101
|
.superRefine((data, ctx) => {
|
|
152
|
-
// Rule 1: MCP_AUTH_SECRET_KEY is required for http transport in production with jwt auth
|
|
153
102
|
if (data.NODE_ENV === "production" &&
|
|
154
103
|
data.MCP_TRANSPORT_TYPE === "http" &&
|
|
155
104
|
data.MCP_AUTH_MODE === "jwt" &&
|
|
@@ -157,30 +106,22 @@ const EnvSchema = z
|
|
|
157
106
|
ctx.addIssue({
|
|
158
107
|
code: z.ZodIssueCode.custom,
|
|
159
108
|
path: ["MCP_AUTH_SECRET_KEY"],
|
|
160
|
-
message: "MCP_AUTH_SECRET_KEY is required for 'jwt' auth with 'http' transport
|
|
109
|
+
message: "MCP_AUTH_SECRET_KEY is required for 'jwt' auth in production with 'http' transport.",
|
|
161
110
|
});
|
|
162
111
|
}
|
|
163
|
-
// Rule 2: Core OAuth variables are required when MCP_AUTH_MODE is 'oauth'
|
|
164
112
|
if (data.MCP_AUTH_MODE === "oauth") {
|
|
165
113
|
if (!data.OAUTH_ISSUER_URL) {
|
|
166
114
|
ctx.addIssue({
|
|
167
115
|
code: z.ZodIssueCode.custom,
|
|
168
116
|
path: ["OAUTH_ISSUER_URL"],
|
|
169
|
-
message: "OAUTH_ISSUER_URL is required
|
|
117
|
+
message: "OAUTH_ISSUER_URL is required for 'oauth' mode.",
|
|
170
118
|
});
|
|
171
119
|
}
|
|
172
120
|
if (!data.OAUTH_AUDIENCE) {
|
|
173
121
|
ctx.addIssue({
|
|
174
122
|
code: z.ZodIssueCode.custom,
|
|
175
123
|
path: ["OAUTH_AUDIENCE"],
|
|
176
|
-
message: "OAUTH_AUDIENCE is required
|
|
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'.",
|
|
124
|
+
message: "OAUTH_AUDIENCE is required for 'oauth' mode.",
|
|
184
125
|
});
|
|
185
126
|
}
|
|
186
127
|
}
|
|
@@ -188,153 +129,69 @@ const EnvSchema = z
|
|
|
188
129
|
const parsedEnv = EnvSchema.safeParse(process.env);
|
|
189
130
|
if (!parsedEnv.success) {
|
|
190
131
|
if (process.stdout.isTTY) {
|
|
191
|
-
console.error("❌ Invalid environment variables
|
|
132
|
+
console.error("❌ Invalid environment variables:", parsedEnv.error.flatten().fieldErrors);
|
|
192
133
|
}
|
|
193
|
-
// Consider throwing an error in production for critical misconfigurations.
|
|
194
134
|
}
|
|
195
135
|
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
136
|
const ensureDirectory = (dirPath, rootDir, dirName) => {
|
|
205
137
|
const resolvedDirPath = path.isAbsolute(dirPath)
|
|
206
138
|
? dirPath
|
|
207
139
|
: path.resolve(rootDir, dirPath);
|
|
208
|
-
|
|
209
|
-
if (!resolvedDirPath.startsWith(rootDir + path.sep) &&
|
|
210
|
-
resolvedDirPath !== rootDir) {
|
|
140
|
+
if (!resolvedDirPath.startsWith(rootDir)) {
|
|
211
141
|
if (process.stdout.isTTY) {
|
|
212
|
-
console.error(`Error: ${dirName} path "${dirPath}"
|
|
142
|
+
console.error(`Error: ${dirName} path "${dirPath}" is outside the project boundary "${rootDir}".`);
|
|
213
143
|
}
|
|
214
144
|
return null;
|
|
215
145
|
}
|
|
216
|
-
|
|
217
|
-
|
|
146
|
+
try {
|
|
147
|
+
if (!existsSync(resolvedDirPath)) {
|
|
218
148
|
mkdirSync(resolvedDirPath, { recursive: true });
|
|
219
|
-
if (process.stdout.isTTY) {
|
|
220
|
-
console.log(`Created ${dirName} directory: ${resolvedDirPath}`);
|
|
221
|
-
}
|
|
222
149
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
}
|
|
150
|
+
else {
|
|
151
|
+
if (!statSync(resolvedDirPath).isDirectory()) {
|
|
152
|
+
console.error(`Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`);
|
|
238
153
|
return null;
|
|
239
154
|
}
|
|
240
155
|
}
|
|
241
|
-
|
|
242
|
-
if (process.stdout.isTTY) {
|
|
243
|
-
console.error(`Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`);
|
|
244
|
-
}
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
156
|
+
return resolvedDirPath;
|
|
247
157
|
}
|
|
248
|
-
|
|
249
|
-
};
|
|
250
|
-
|
|
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
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error(`Error ensuring ${dirName} directory at ${resolvedDirPath}: ${error.message}`);
|
|
160
|
+
return null;
|
|
260
161
|
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Main application configuration object.
|
|
265
|
-
* Aggregates settings from validated environment variables and `package.json`.
|
|
266
|
-
*/
|
|
162
|
+
};
|
|
163
|
+
const validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
|
|
267
164
|
export const config = {
|
|
268
|
-
|
|
165
|
+
pkg,
|
|
269
166
|
mcpServerName: env.MCP_SERVER_NAME || pkg.name,
|
|
270
|
-
/** MCP server version. Env `MCP_SERVER_VERSION` > `package.json` version > "0.0.0". */
|
|
271
167
|
mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
|
|
272
|
-
/** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
|
|
273
168
|
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
169
|
logsPath: validatedLogsPath,
|
|
278
|
-
/** Runtime environment. From `NODE_ENV` env var. Default: "development". */
|
|
279
170
|
environment: env.NODE_ENV,
|
|
280
|
-
/** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
|
|
281
171
|
mcpTransportType: env.MCP_TRANSPORT_TYPE,
|
|
282
|
-
|
|
172
|
+
mcpSessionMode: env.MCP_SESSION_MODE,
|
|
283
173
|
mcpHttpPort: env.MCP_HTTP_PORT,
|
|
284
|
-
/** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
|
|
285
174
|
mcpHttpHost: env.MCP_HTTP_HOST,
|
|
286
|
-
|
|
175
|
+
mcpHttpEndpointPath: env.MCP_HTTP_ENDPOINT_PATH,
|
|
176
|
+
mcpHttpMaxPortRetries: env.MCP_HTTP_MAX_PORT_RETRIES,
|
|
177
|
+
mcpHttpPortRetryDelayMs: env.MCP_HTTP_PORT_RETRY_DELAY_MS,
|
|
178
|
+
mcpStatefulSessionStaleTimeoutMs: env.MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS,
|
|
287
179
|
mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",")
|
|
288
|
-
.map((
|
|
180
|
+
.map((o) => o.trim())
|
|
289
181
|
.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`. */
|
|
293
182
|
mcpAuthMode: env.MCP_AUTH_MODE,
|
|
294
|
-
|
|
183
|
+
mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
|
|
295
184
|
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
185
|
oauthJwksUri: env.OAUTH_JWKS_URI,
|
|
300
|
-
|
|
301
|
-
|
|
186
|
+
oauthAudience: env.OAUTH_AUDIENCE,
|
|
187
|
+
devMcpClientId: env.DEV_MCP_CLIENT_ID,
|
|
188
|
+
devMcpScopes: env.DEV_MCP_SCOPES?.split(",").map((s) => s.trim()),
|
|
302
189
|
ncbiApiKey: env.NCBI_API_KEY,
|
|
303
|
-
/** NCBI Tool Identifier. From `NCBI_TOOL_IDENTIFIER`. Defaults to server name/version. */
|
|
304
190
|
ncbiToolIdentifier: env.NCBI_TOOL_IDENTIFIER ||
|
|
305
191
|
`${env.MCP_SERVER_NAME || pkg.name}/${env.MCP_SERVER_VERSION || pkg.version}`,
|
|
306
|
-
/** NCBI Admin Email. From `NCBI_ADMIN_EMAIL`. */
|
|
307
192
|
ncbiAdminEmail: env.NCBI_ADMIN_EMAIL,
|
|
308
|
-
/** NCBI Request Delay in MS. From `NCBI_REQUEST_DELAY_MS`. Dynamically set based on API key presence. */
|
|
309
193
|
ncbiRequestDelayMs: env.NCBI_REQUEST_DELAY_MS ?? (env.NCBI_API_KEY ? 100 : 334),
|
|
310
|
-
/** NCBI Max Retries. From `NCBI_MAX_RETRIES`. */
|
|
311
194
|
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
195
|
};
|
|
331
|
-
/**
|
|
332
|
-
* Configured logging level for the application.
|
|
333
|
-
* Exported for convenience.
|
|
334
|
-
*/
|
|
335
196
|
export const logLevel = config.logLevel;
|
|
336
|
-
/**
|
|
337
|
-
* Configured runtime environment ("development", "production", etc.).
|
|
338
|
-
* Exported for convenience.
|
|
339
|
-
*/
|
|
340
197
|
export const environment = config.environment;
|
package/dist/index.js
CHANGED
|
@@ -51,70 +51,40 @@ const shutdown = async (signal) => {
|
|
|
51
51
|
triggerEvent: signal,
|
|
52
52
|
});
|
|
53
53
|
logger.info(`Received ${signal}. Initiating graceful shutdown...`, shutdownContext);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...shutdownContext,
|
|
61
|
-
errorMessage: closeError.message,
|
|
62
|
-
errorStack: closeError.stack,
|
|
63
|
-
});
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
else if (mcpClosed && httpClosed) {
|
|
67
|
-
logger.info("Graceful shutdown completed successfully. Exiting.", shutdownContext);
|
|
68
|
-
process.exit(0);
|
|
54
|
+
try {
|
|
55
|
+
let closePromise = Promise.resolve();
|
|
56
|
+
const transportType = config.mcpTransportType;
|
|
57
|
+
if (transportType === "stdio" && mcpStdioServer) {
|
|
58
|
+
logger.info("Attempting to close main MCP server (STDIO)...", shutdownContext);
|
|
59
|
+
closePromise = mcpStdioServer.close();
|
|
69
60
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
else if (transportType === "http" && actualHttpServer) {
|
|
62
|
+
logger.info("Attempting to close HTTP server...", shutdownContext);
|
|
63
|
+
closePromise = new Promise((resolve, reject) => {
|
|
64
|
+
actualHttpServer.close((err) => {
|
|
65
|
+
if (err) {
|
|
66
|
+
logger.error("Error closing HTTP server.", {
|
|
67
|
+
...shutdownContext,
|
|
68
|
+
error: err.message,
|
|
69
|
+
});
|
|
70
|
+
return reject(err);
|
|
71
|
+
}
|
|
72
|
+
logger.info("HTTP server closed successfully.", shutdownContext);
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
84
75
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
mcpClosed = true; // No STDIO McpServer to close
|
|
76
|
+
}
|
|
77
|
+
await closePromise;
|
|
78
|
+
logger.info("Graceful shutdown completed successfully. Exiting.", shutdownContext);
|
|
79
|
+
process.exit(0);
|
|
93
80
|
}
|
|
94
|
-
|
|
95
|
-
logger.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
...shutdownContext,
|
|
100
|
-
error: err,
|
|
101
|
-
});
|
|
102
|
-
if (!closeError)
|
|
103
|
-
closeError = err;
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
logger.info("HTTP server closed successfully.", shutdownContext);
|
|
107
|
-
}
|
|
108
|
-
httpClosed = true;
|
|
109
|
-
checkAndExit();
|
|
81
|
+
catch (error) {
|
|
82
|
+
logger.error("Critical error during shutdown process.", {
|
|
83
|
+
...shutdownContext,
|
|
84
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
85
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
110
86
|
});
|
|
111
|
-
|
|
112
|
-
else {
|
|
113
|
-
httpClosed = true; // No HTTP server to close
|
|
114
|
-
}
|
|
115
|
-
// Initial check in case no servers needed closing
|
|
116
|
-
if (mcpClosed && httpClosed) {
|
|
117
|
-
checkAndExit();
|
|
87
|
+
process.exit(1);
|
|
118
88
|
}
|
|
119
89
|
};
|
|
120
90
|
/**
|
|
@@ -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/
|
|
26
|
-
import {
|
|
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: {},
|
|
55
|
-
resources: { listChanged: true },
|
|
56
|
-
tools: { listChanged: true },
|
|
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.
|
|
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: "
|
|
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
|
-
|
|
75
|
+
const { server } = await startHttpTransport(serverFactory, context);
|
|
76
|
+
return server;
|
|
97
77
|
}
|
|
98
78
|
if (transportType === "stdio") {
|
|
99
|
-
const server = await
|
|
100
|
-
await
|
|
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,
|
|
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 "../../../
|
|
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
|