clinicaltrialsgov-mcp-server 1.2.1 → 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 (3) hide show
  1. package/README.md +31 -8
  2. package/dist/index.js +633 -124
  3. package/package.json +7 -3
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-2.3.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--06--18-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-06-18/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.18.2-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/clinicaltrialsgov-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.23-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-92.46%25-brightgreen.svg?style=flat-square)](./vitest.config.ts)
8
+ [![Version](https://img.shields.io/badge/Version-1.3.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--06--18-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-06-18/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.18.2-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/clinicaltrialsgov-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.23-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-92.46%25-brightgreen.svg?style=flat-square)](./vitest.config.ts)
9
9
 
10
10
  </div>
11
11
 
@@ -13,13 +13,14 @@
13
13
 
14
14
  ## 🛠️ Tools Overview
15
15
 
16
- This server provides three powerful tools for accessing and analyzing clinical trial data from ClinicalTrials.gov:
16
+ This server provides four powerful tools for accessing and analyzing clinical trial data from ClinicalTrials.gov:
17
17
 
18
- | Tool Name | Description |
19
- | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------- |
20
- | `clinicaltrials_search_studies` | Searches for clinical studies using a combination of query terms and filters. Supports pagination, sorting, and geographic filtering. |
21
- | `clinicaltrials_get_study` | Fetches one or more clinical studies by their NCT IDs. Returns either complete study data or concise summaries for each. |
22
- | `clinicaltrials_analyze_trends` | Performs statistical analysis on studies, aggregating data by status, country, sponsor, or phase. Handles up to 5000 studies per analysis. |
18
+ | Tool Name | Description |
19
+ | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | `clinicaltrials_search_studies` | Searches for clinical studies using query terms, filters, pagination, and sorting. Now includes geographic filtering. |
21
+ | `clinicaltrials_get_study` | Fetches one or more clinical studies by their NCT IDs, returning either full data or concise summaries. |
22
+ | `clinicaltrials_analyze_trends` | Performs statistical analysis on up to 5000 studies, with new time-series analysis by year and month. |
23
+ | `clinicaltrials_compare_studies`| Performs a detailed side-by-side comparison of 2-5 clinical studies, highlighting commonalities and differences. |
23
24
 
24
25
  ### `clinicaltrials_search_studies`
25
26
 
@@ -85,6 +86,28 @@ This server provides three powerful tools for accessing and analyzing clinical t
85
86
 
86
87
  📖 **[View detailed examples →](./examples/clinicaltrials_analyze_trends.md)**
87
88
 
89
+ ---
90
+
91
+ ### `clinicaltrials_compare_studies`
92
+
93
+ **Compare and contrast multiple studies** to identify key similarities and differences.
94
+
95
+ **Key Features:**
96
+
97
+ - Side-by-side comparison of 2-5 studies by NCT ID
98
+ - Extracts and contrasts eligibility, design, interventions, outcomes, sponsors, and more
99
+ - Generates a summary of commonalities and differences
100
+ - Handles partial failures gracefully if some studies cannot be fetched
101
+ - Highly configurable to focus on specific fields of interest
102
+
103
+ **Example Use Cases:**
104
+
105
+ - "Compare the study design and eligibility criteria for NCT04516746 and NCT04516759"
106
+ - "What are the main differences in interventions and outcomes between these three leading Alzheimer's trials?"
107
+ - "Show me a side-by-side of sponsor and location data for these competitor studies"
108
+
109
+ 📖 **[View detailed examples →](./examples/clinicaltrials_compare_studies.md)**
110
+
88
111
  ## ✨ Features
89
112
 
90
113
  This server is built on the [`mcp-ts-template`](https://github.com/cyanheads/mcp-ts-template) and inherits its rich feature set:
@@ -158,7 +181,7 @@ All configuration is centralized and validated at startup in `src/config/index.t
158
181
  | Variable | Description | Default |
159
182
  | :---------------------- | :----------------------------------------------------------------------------- | :---------- |
160
183
  | `MCP_TRANSPORT_TYPE` | The transport to use: `stdio` or `http`. | `http` |
161
- | `MCP_HTTP_PORT` | The port for the HTTP server. | `3010` |
184
+ | `MCP_HTTP_PORT` | The port for the HTTP server. | `3017` |
162
185
  | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
163
186
  | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`. | `in-memory` |
164
187
  | `OTEL_ENABLED` | Set to `true` to enable OpenTelemetry. | `false` |
package/dist/index.js CHANGED
@@ -117491,55 +117491,10 @@ var z = /* @__PURE__ */ Object.freeze({
117491
117491
  quotelessJson,
117492
117492
  ZodError
117493
117493
  });
117494
-
117495
- // src/types-global/errors.ts
117496
- var JsonRpcErrorCode;
117497
- ((JsonRpcErrorCode2) => {
117498
- JsonRpcErrorCode2[JsonRpcErrorCode2["ParseError"] = -32700] = "ParseError";
117499
- JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidRequest"] = -32600] = "InvalidRequest";
117500
- JsonRpcErrorCode2[JsonRpcErrorCode2["MethodNotFound"] = -32601] = "MethodNotFound";
117501
- JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidParams"] = -32602] = "InvalidParams";
117502
- JsonRpcErrorCode2[JsonRpcErrorCode2["InternalError"] = -32603] = "InternalError";
117503
- JsonRpcErrorCode2[JsonRpcErrorCode2["ServiceUnavailable"] = -32000] = "ServiceUnavailable";
117504
- JsonRpcErrorCode2[JsonRpcErrorCode2["NotFound"] = -32001] = "NotFound";
117505
- JsonRpcErrorCode2[JsonRpcErrorCode2["Conflict"] = -32002] = "Conflict";
117506
- JsonRpcErrorCode2[JsonRpcErrorCode2["RateLimited"] = -32003] = "RateLimited";
117507
- JsonRpcErrorCode2[JsonRpcErrorCode2["Timeout"] = -32004] = "Timeout";
117508
- JsonRpcErrorCode2[JsonRpcErrorCode2["Forbidden"] = -32005] = "Forbidden";
117509
- JsonRpcErrorCode2[JsonRpcErrorCode2["Unauthorized"] = -32006] = "Unauthorized";
117510
- JsonRpcErrorCode2[JsonRpcErrorCode2["ValidationError"] = -32007] = "ValidationError";
117511
- JsonRpcErrorCode2[JsonRpcErrorCode2["ConfigurationError"] = -32008] = "ConfigurationError";
117512
- JsonRpcErrorCode2[JsonRpcErrorCode2["InitializationFailed"] = -32009] = "InitializationFailed";
117513
- JsonRpcErrorCode2[JsonRpcErrorCode2["DatabaseError"] = -32010] = "DatabaseError";
117514
- JsonRpcErrorCode2[JsonRpcErrorCode2["SerializationError"] = -32070] = "SerializationError";
117515
- JsonRpcErrorCode2[JsonRpcErrorCode2["UnknownError"] = -32099] = "UnknownError";
117516
- })(JsonRpcErrorCode ||= {});
117517
-
117518
- class McpError extends Error {
117519
- code;
117520
- data;
117521
- constructor(code, message, data, options) {
117522
- super(message, options);
117523
- this.code = code;
117524
- if (data) {
117525
- this.data = data;
117526
- }
117527
- this.name = "McpError";
117528
- Object.setPrototypeOf(this, McpError.prototype);
117529
- if (Error.captureStackTrace) {
117530
- Error.captureStackTrace(this, McpError);
117531
- }
117532
- }
117533
- }
117534
- var ErrorSchema = z.object({
117535
- code: z.nativeEnum(JsonRpcErrorCode).describe("Standardized error code from JsonRpcErrorCode enum"),
117536
- message: z.string().min(1, "Error message cannot be empty.").describe("Detailed human-readable error message"),
117537
- data: z.record(z.string(), z.unknown()).optional().describe("Optional structured data providing more context about the error")
117538
- }).describe("Schema for validating structured error objects, ensuring consistency in error reporting.");
117539
117494
  // package.json
117540
117495
  var package_default = {
117541
117496
  name: "clinicaltrialsgov-mcp-server",
117542
- version: "1.2.1",
117497
+ version: "1.3.0",
117543
117498
  mcpName: "io.github.cyanheads/clinicaltrialsgov-mcp-server",
117544
117499
  description: "ClinicalTrials.gov Model Context Protocol (MCP) Server that provides a suite of tools for interacting with the official ClinicalTrials.gov v2 API. Enables AI agents and LLMs to programmatically search, retrieve, and analyze clinical trial data.",
117545
117500
  main: "dist/index.js",
@@ -117569,9 +117524,12 @@ var package_default = {
117569
117524
  homepage: "https://github.com/cyanheads/clinicaltrialsgov-mcp-server#readme",
117570
117525
  scripts: {
117571
117526
  build: "bun build ./src/index.ts --outdir ./dist --target node",
117527
+ "build:worker": "bun build ./src/worker.ts --outdir ./dist --target bun --no-external",
117528
+ "deploy:dev": "MCP_TRANSPORT_TYPE=http bunx wrangler dev",
117529
+ "deploy:prod": "MCP_TRANSPORT_TYPE=http bunx wrangler deploy",
117572
117530
  start: "bun ./dist/index.js",
117573
- "start:stdio": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=stdio bun ./dist/index.js",
117574
- "start:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http bun ./dist/index.js",
117531
+ "start:stdio": "MCP_TRANSPORT_TYPE=stdio bun ./dist/index.js",
117532
+ "start:http": "MCP_TRANSPORT_TYPE=http bun ./dist/index.js",
117575
117533
  dev: "bun --watch src/index.ts",
117576
117534
  "dev:stdio": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=stdio bun --watch src/index.ts",
117577
117535
  "dev:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http bun --watch src/index.ts",
@@ -117586,6 +117544,7 @@ var package_default = {
117586
117544
  tree: "bun run scripts/tree.ts",
117587
117545
  "fetch-spec": "bun run scripts/fetch-openapi-spec.ts",
117588
117546
  format: 'bun run prettier --write "**/*.{ts,js,json,md,html,css}"',
117547
+ prepare: "bunx husky",
117589
117548
  inspector: "bunx mcp-inspector --config mcp.json --server clinicaltrialsgov-mcp-server",
117590
117549
  "db:duckdb-example": "MCP_LOG_LEVEL=debug tsc && node dist/storage/duckdbExample.js",
117591
117550
  test: "bun test --config vitest.config.ts",
@@ -117711,6 +117670,51 @@ var package_default = {
117711
117670
  }
117712
117671
  };
117713
117672
 
117673
+ // src/types-global/errors.ts
117674
+ var JsonRpcErrorCode;
117675
+ ((JsonRpcErrorCode2) => {
117676
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ParseError"] = -32700] = "ParseError";
117677
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidRequest"] = -32600] = "InvalidRequest";
117678
+ JsonRpcErrorCode2[JsonRpcErrorCode2["MethodNotFound"] = -32601] = "MethodNotFound";
117679
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidParams"] = -32602] = "InvalidParams";
117680
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InternalError"] = -32603] = "InternalError";
117681
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ServiceUnavailable"] = -32000] = "ServiceUnavailable";
117682
+ JsonRpcErrorCode2[JsonRpcErrorCode2["NotFound"] = -32001] = "NotFound";
117683
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Conflict"] = -32002] = "Conflict";
117684
+ JsonRpcErrorCode2[JsonRpcErrorCode2["RateLimited"] = -32003] = "RateLimited";
117685
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Timeout"] = -32004] = "Timeout";
117686
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Forbidden"] = -32005] = "Forbidden";
117687
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Unauthorized"] = -32006] = "Unauthorized";
117688
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ValidationError"] = -32007] = "ValidationError";
117689
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ConfigurationError"] = -32008] = "ConfigurationError";
117690
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InitializationFailed"] = -32009] = "InitializationFailed";
117691
+ JsonRpcErrorCode2[JsonRpcErrorCode2["DatabaseError"] = -32010] = "DatabaseError";
117692
+ JsonRpcErrorCode2[JsonRpcErrorCode2["SerializationError"] = -32070] = "SerializationError";
117693
+ JsonRpcErrorCode2[JsonRpcErrorCode2["UnknownError"] = -32099] = "UnknownError";
117694
+ })(JsonRpcErrorCode ||= {});
117695
+
117696
+ class McpError extends Error {
117697
+ code;
117698
+ data;
117699
+ constructor(code, message, data, options) {
117700
+ super(message, options);
117701
+ this.code = code;
117702
+ if (data) {
117703
+ this.data = data;
117704
+ }
117705
+ this.name = "McpError";
117706
+ Object.setPrototypeOf(this, McpError.prototype);
117707
+ if (Error.captureStackTrace) {
117708
+ Error.captureStackTrace(this, McpError);
117709
+ }
117710
+ }
117711
+ }
117712
+ var ErrorSchema = z.object({
117713
+ code: z.nativeEnum(JsonRpcErrorCode).describe("Standardized error code from JsonRpcErrorCode enum"),
117714
+ message: z.string().min(1, "Error message cannot be empty.").describe("Detailed human-readable error message"),
117715
+ data: z.record(z.string(), z.unknown()).optional().describe("Optional structured data providing more context about the error")
117716
+ }).describe("Schema for validating structured error objects, ensuring consistency in error reporting.");
117717
+
117714
117718
  // src/config/index.ts
117715
117719
  var packageManifest = package_default;
117716
117720
  var hasFileSystemAccess = typeof process !== "undefined" && typeof process.versions === "object" && process.versions !== null && typeof process.versions.node === "string";
@@ -117759,7 +117763,7 @@ var ConfigSchema = z.object({
117759
117763
  }, z.enum(["development", "production", "testing"])).default("development"),
117760
117764
  mcpTransportType: z.preprocess(emptyStringAsUndefined, z.enum(["stdio", "http"]).default("stdio")),
117761
117765
  mcpSessionMode: z.preprocess(emptyStringAsUndefined, z.enum(["stateless", "stateful", "auto"]).default("auto")),
117762
- mcpHttpPort: z.coerce.number().default(3010),
117766
+ mcpHttpPort: z.coerce.number().default(3017),
117763
117767
  mcpHttpHost: z.string().default("127.0.0.1"),
117764
117768
  mcpHttpEndpointPath: z.string().default("/mcp"),
117765
117769
  mcpHttpMaxPortRetries: z.coerce.number().default(15),
@@ -119520,7 +119524,7 @@ async function fetchWithTimeout(url, timeoutMs, context, options) {
119520
119524
 
119521
119525
  // src/container/index.ts
119522
119526
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
119523
- var import_tsyringe21 = __toESM(require_cjs3(), 1);
119527
+ var import_tsyringe22 = __toESM(require_cjs3(), 1);
119524
119528
 
119525
119529
  // src/container/registrations/core.ts
119526
119530
  var import_tsyringe9 = __toESM(require_cjs3(), 1);
@@ -119742,8 +119746,23 @@ class ClinicalTrialsGovProvider {
119742
119746
  if (params.query) {
119743
119747
  queryParams.set("query.term", params.query);
119744
119748
  }
119745
- if (params.filter) {
119746
- queryParams.set("filter.advanced", params.filter);
119749
+ const geoFilters = [];
119750
+ if (params.country) {
119751
+ geoFilters.push(`AREA[LocationCountry]${params.country}`);
119752
+ }
119753
+ if (params.state) {
119754
+ geoFilters.push(`AREA[LocationState]${params.state}`);
119755
+ }
119756
+ if (params.city) {
119757
+ geoFilters.push(`AREA[LocationCity]${params.city}`);
119758
+ }
119759
+ let combinedFilter = params.filter || "";
119760
+ if (geoFilters.length > 0) {
119761
+ const geoFilterExpression = geoFilters.join(" AND ");
119762
+ combinedFilter = combinedFilter ? `(${combinedFilter}) AND (${geoFilterExpression})` : geoFilterExpression;
119763
+ }
119764
+ if (combinedFilter) {
119765
+ queryParams.set("filter.advanced", combinedFilter);
119747
119766
  }
119748
119767
  if (params.pageSize) {
119749
119768
  queryParams.set("pageSize", String(params.pageSize));
@@ -119754,6 +119773,9 @@ class ClinicalTrialsGovProvider {
119754
119773
  if (params.sort) {
119755
119774
  queryParams.set("sort", params.sort);
119756
119775
  }
119776
+ if (params.fields && params.fields.length > 0) {
119777
+ queryParams.set("fields", params.fields.join(","));
119778
+ }
119757
119779
  queryParams.set("countTotal", "true");
119758
119780
  const url = `${BASE_URL}/studies?${queryParams.toString()}`;
119759
119781
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
@@ -126883,7 +126905,7 @@ var registerCoreServices = () => {
126883
126905
  };
126884
126906
 
126885
126907
  // src/container/registrations/mcp.ts
126886
- var import_tsyringe20 = __toESM(require_cjs3(), 1);
126908
+ var import_tsyringe21 = __toESM(require_cjs3(), 1);
126887
126909
 
126888
126910
  // src/mcp-server/resources/resource-registration.ts
126889
126911
  var import_tsyringe10 = __toESM(require_cjs3(), 1);
@@ -130198,10 +130220,10 @@ var registerResources = (container3) => {
130198
130220
  };
130199
130221
 
130200
130222
  // src/mcp-server/server.ts
130201
- var import_tsyringe15 = __toESM(require_cjs3(), 1);
130223
+ var import_tsyringe16 = __toESM(require_cjs3(), 1);
130202
130224
 
130203
130225
  // src/mcp-server/tools/tool-registration.ts
130204
- var import_tsyringe14 = __toESM(require_cjs3(), 1);
130226
+ var import_tsyringe15 = __toESM(require_cjs3(), 1);
130205
130227
 
130206
130228
  // src/mcp-server/tools/definitions/clinicaltrials-analyze-trends.tool.ts
130207
130229
  var import_tsyringe11 = __toESM(require_cjs3(), 1);
@@ -130247,7 +130269,7 @@ function withToolAuth(requiredScopes, logicFn) {
130247
130269
  // src/mcp-server/tools/definitions/clinicaltrials-analyze-trends.tool.ts
130248
130270
  var TOOL_NAME = "clinicaltrials_analyze_trends";
130249
130271
  var TOOL_TITLE = "Analyze Clinical Trial Trends";
130250
- var TOOL_DESCRIPTION = "Performs statistical analysis on clinical trial studies matching search criteria. Aggregates data by status, country, sponsor type, or phase. May fetch up to 5000 studies.";
130272
+ var TOOL_DESCRIPTION = "Performs statistical analysis on clinical trial studies matching search criteria. Aggregates data by status, country, sponsor type, phase, year, or month. May fetch up to 5000 studies.";
130251
130273
  var TOOL_ANNOTATIONS = {
130252
130274
  readOnlyHint: true,
130253
130275
  idempotentHint: true,
@@ -130259,7 +130281,9 @@ var AnalysisTypeSchema = z.enum([
130259
130281
  "countByStatus",
130260
130282
  "countByCountry",
130261
130283
  "countBySponsorType",
130262
- "countByPhase"
130284
+ "countByPhase",
130285
+ "countByYear",
130286
+ "countByMonth"
130263
130287
  ]);
130264
130288
  var InputSchema = z.object({
130265
130289
  query: z.string().optional().describe("General search query for conditions, interventions, sponsors, or other terms."),
@@ -130267,7 +130291,7 @@ var InputSchema = z.object({
130267
130291
  analysisType: z.union([
130268
130292
  AnalysisTypeSchema.describe("A single analysis type to perform."),
130269
130293
  z.array(AnalysisTypeSchema).min(1).describe("An array of analysis types to perform.")
130270
- ]).describe("Specify one or more analysis types: countByStatus, countByCountry, countBySponsorType, or countByPhase.")
130294
+ ]).describe("Specify one or more analysis types: countByStatus, countByCountry, countBySponsorType, countByPhase, countByYear, or countByMonth.")
130271
130295
  }).describe("Input parameters for analyzing clinical trial trends.");
130272
130296
  var AnalysisResultSchema = z.object({
130273
130297
  analysisType: AnalysisTypeSchema.describe("The type of analysis performed."),
@@ -130353,6 +130377,26 @@ async function analyzeTrendsLogic(input, appContext, _sdkContext) {
130353
130377
  });
130354
130378
  continue;
130355
130379
  }
130380
+ case "countByYear": {
130381
+ const startDate = study.protocolSection?.statusModule?.startDateStruct?.date;
130382
+ if (startDate) {
130383
+ const year = startDate.substring(0, 4);
130384
+ results[year] = (results[year] ?? 0) + 1;
130385
+ } else {
130386
+ results["Unknown"] = (results["Unknown"] ?? 0) + 1;
130387
+ }
130388
+ continue;
130389
+ }
130390
+ case "countByMonth": {
130391
+ const startDate = study.protocolSection?.statusModule?.startDateStruct?.date;
130392
+ if (startDate && startDate.length >= 7) {
130393
+ const yearMonth = startDate.substring(0, 7);
130394
+ results[yearMonth] = (results[yearMonth] ?? 0) + 1;
130395
+ } else {
130396
+ results["Unknown"] = (results["Unknown"] ?? 0) + 1;
130397
+ }
130398
+ continue;
130399
+ }
130356
130400
  }
130357
130401
  if (key) {
130358
130402
  results[key] = (results[key] ?? 0) + 1;
@@ -130413,16 +130457,472 @@ var analyzeTrendsTool = {
130413
130457
  responseFormatter
130414
130458
  };
130415
130459
 
130416
- // src/mcp-server/tools/definitions/clinicaltrials-get-study.tool.ts
130460
+ // src/mcp-server/tools/definitions/clinicaltrials-compare-studies.tool.ts
130417
130461
  var import_tsyringe12 = __toESM(require_cjs3(), 1);
130418
- var TOOL_NAME2 = "clinicaltrials_get_study";
130419
- var TOOL_TITLE2 = "Get Clinical Study";
130420
- var TOOL_DESCRIPTION2 = "Fetches one or more clinical trial studies from ClinicalTrials.gov by their NCT ID(s). Returns full study data or concise summaries.";
130462
+ var TOOL_NAME2 = "clinicaltrials_compare_studies";
130463
+ var TOOL_TITLE2 = "Compare Clinical Studies";
130464
+ var TOOL_DESCRIPTION2 = "Performs side-by-side comparison of 2-5 clinical trial studies. Compares eligibility criteria, design, interventions, outcomes, sponsors, and other key aspects.";
130421
130465
  var TOOL_ANNOTATIONS2 = {
130422
130466
  readOnlyHint: true,
130423
130467
  idempotentHint: true,
130424
130468
  openWorldHint: true
130425
130469
  };
130470
+ var ComparisonCategorySchema = z.enum([
130471
+ "eligibility",
130472
+ "design",
130473
+ "interventions",
130474
+ "outcomes",
130475
+ "sponsors",
130476
+ "locations",
130477
+ "status",
130478
+ "all"
130479
+ ]);
130480
+ var InputSchema2 = z.object({
130481
+ nctIds: z.array(z.string().regex(/^[Nn][Cc][Tt]\d{8}$/, "NCT ID must be 8 digits")).min(2, "At least 2 NCT IDs are required for comparison.").max(5, "Maximum 5 NCT IDs allowed for comparison.").describe("An array of 2-5 NCT IDs to compare."),
130482
+ compareFields: z.union([
130483
+ ComparisonCategorySchema.describe("A single comparison category to analyze."),
130484
+ z.array(ComparisonCategorySchema).min(1).describe("An array of comparison categories to analyze.")
130485
+ ]).default("all").describe("Specify which aspects to compare: eligibility, design, interventions, outcomes, sponsors, locations, status, or all.")
130486
+ }).describe("Input parameters for comparing clinical trial studies.");
130487
+ var StudyComparisonSchema = z.object({
130488
+ nctId: z.string().describe("The NCT identifier."),
130489
+ title: z.string().optional().describe("The study title."),
130490
+ eligibility: z.object({
130491
+ criteria: z.string().optional().describe("Eligibility criteria text."),
130492
+ sex: z.string().optional().describe("Sex requirement."),
130493
+ minimumAge: z.string().optional().describe("Minimum age."),
130494
+ healthyVolunteers: z.boolean().optional().describe("Accepts healthy volunteers."),
130495
+ stdAges: z.array(z.string()).optional().describe("Standard age groups.")
130496
+ }).optional().describe("Eligibility criteria details."),
130497
+ design: z.object({
130498
+ studyType: z.string().optional().describe("Type of study."),
130499
+ phases: z.array(z.string()).optional().describe("Trial phases."),
130500
+ allocation: z.string().optional().describe("Allocation method."),
130501
+ interventionModel: z.string().optional().describe("Intervention model."),
130502
+ primaryPurpose: z.string().optional().describe("Primary purpose."),
130503
+ masking: z.string().optional().describe("Masking/blinding approach.")
130504
+ }).optional().describe("Study design details."),
130505
+ interventions: z.array(z.object({
130506
+ type: z.string().optional().describe("Intervention type."),
130507
+ name: z.string().optional().describe("Intervention name."),
130508
+ description: z.string().optional().describe("Description.")
130509
+ })).optional().describe("List of interventions."),
130510
+ outcomes: z.object({
130511
+ primary: z.array(z.object({
130512
+ measure: z.string().optional().describe("Outcome measure."),
130513
+ timeFrame: z.string().optional().describe("Time frame.")
130514
+ })).optional().describe("Primary outcomes."),
130515
+ secondary: z.array(z.object({
130516
+ measure: z.string().optional().describe("Outcome measure."),
130517
+ timeFrame: z.string().optional().describe("Time frame.")
130518
+ })).optional().describe("Secondary outcomes.")
130519
+ }).optional().describe("Outcome measures."),
130520
+ sponsors: z.object({
130521
+ leadSponsor: z.object({
130522
+ name: z.string().optional().describe("Sponsor name."),
130523
+ class: z.string().optional().describe("Sponsor class.")
130524
+ }).optional().describe("Lead sponsor."),
130525
+ collaborators: z.array(z.object({
130526
+ name: z.string().optional().describe("Collaborator name."),
130527
+ class: z.string().optional().describe("Collaborator class.")
130528
+ })).optional().describe("Collaborators.")
130529
+ }).optional().describe("Sponsor information."),
130530
+ locations: z.object({
130531
+ totalCount: z.number().optional().describe("Total number of locations."),
130532
+ countries: z.array(z.string()).optional().describe("List of countries."),
130533
+ topCities: z.array(z.string()).optional().describe("Top cities by location count.")
130534
+ }).optional().describe("Location summary."),
130535
+ status: z.object({
130536
+ overallStatus: z.string().optional().describe("Overall status."),
130537
+ startDate: z.string().optional().describe("Start date."),
130538
+ completionDate: z.string().optional().describe("Completion date."),
130539
+ lastUpdateDate: z.string().optional().describe("Last update date.")
130540
+ }).optional().describe("Status and timeline information.")
130541
+ }).describe("Structured comparison data for a single study.");
130542
+ var OutputSchema2 = z.object({
130543
+ comparisons: z.array(StudyComparisonSchema).describe("Array of study comparisons."),
130544
+ summary: z.object({
130545
+ totalStudies: z.number().describe("Number of studies compared."),
130546
+ comparedFields: z.array(z.string()).describe("Fields that were compared."),
130547
+ commonalities: z.array(z.string()).optional().describe("Key commonalities found across studies."),
130548
+ differences: z.array(z.string()).optional().describe("Key differences found across studies.")
130549
+ }).describe("Summary of the comparison."),
130550
+ errors: z.array(z.object({
130551
+ nctId: z.string().describe("The NCT ID that failed."),
130552
+ error: z.string().describe("Error message.")
130553
+ })).optional().describe("Any errors encountered during comparison.")
130554
+ }).describe("Comparison results for clinical trial studies.");
130555
+ function extractEligibility(study) {
130556
+ const eligibility = study.protocolSection?.eligibilityModule;
130557
+ if (!eligibility)
130558
+ return;
130559
+ return {
130560
+ criteria: eligibility.eligibilityCriteria,
130561
+ sex: eligibility.sex,
130562
+ minimumAge: eligibility.minimumAge,
130563
+ healthyVolunteers: eligibility.healthyVolunteers,
130564
+ stdAges: eligibility.stdAges
130565
+ };
130566
+ }
130567
+ function extractDesign(study) {
130568
+ const design = study.protocolSection?.designModule;
130569
+ if (!design)
130570
+ return;
130571
+ return {
130572
+ studyType: design.studyType,
130573
+ phases: design.phases,
130574
+ allocation: design.designInfo?.allocation,
130575
+ interventionModel: design.designInfo?.interventionModel,
130576
+ primaryPurpose: design.designInfo?.primaryPurpose,
130577
+ masking: design.designInfo?.maskingInfo?.masking
130578
+ };
130579
+ }
130580
+ function extractInterventions(study) {
130581
+ const interventions = study.protocolSection?.armsInterventionsModule?.interventions;
130582
+ if (!interventions)
130583
+ return;
130584
+ return interventions.map((i) => ({
130585
+ type: i.type,
130586
+ name: i.name,
130587
+ description: i.description
130588
+ }));
130589
+ }
130590
+ function extractOutcomes(study) {
130591
+ const outcomes = study.protocolSection?.outcomesModule;
130592
+ if (!outcomes)
130593
+ return;
130594
+ return {
130595
+ primary: outcomes.primaryOutcomes?.map((o) => ({
130596
+ measure: o.measure,
130597
+ timeFrame: o.timeFrame
130598
+ })),
130599
+ secondary: outcomes.secondaryOutcomes?.map((o) => ({
130600
+ measure: o.measure,
130601
+ timeFrame: o.timeFrame
130602
+ }))
130603
+ };
130604
+ }
130605
+ function extractSponsors(study) {
130606
+ const sponsors = study.protocolSection?.sponsorCollaboratorsModule;
130607
+ if (!sponsors)
130608
+ return;
130609
+ return {
130610
+ leadSponsor: sponsors.leadSponsor ? {
130611
+ name: sponsors.leadSponsor.name,
130612
+ class: sponsors.leadSponsor.class
130613
+ } : undefined,
130614
+ collaborators: sponsors.collaborators?.map((c) => ({
130615
+ name: c.name,
130616
+ class: c.class
130617
+ }))
130618
+ };
130619
+ }
130620
+ function extractLocations(study) {
130621
+ const locations = study.protocolSection?.contactsLocationsModule?.locations;
130622
+ if (!locations || locations.length === 0)
130623
+ return;
130624
+ const countries = [
130625
+ ...new Set(locations.map((l) => l.country).filter(Boolean))
130626
+ ];
130627
+ const cityCount = {};
130628
+ locations.forEach((loc) => {
130629
+ if (loc.city) {
130630
+ cityCount[loc.city] = (cityCount[loc.city] ?? 0) + 1;
130631
+ }
130632
+ });
130633
+ const topCities = Object.entries(cityCount).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([city]) => city);
130634
+ return {
130635
+ totalCount: locations.length,
130636
+ countries,
130637
+ topCities
130638
+ };
130639
+ }
130640
+ function extractStatus(study) {
130641
+ const status = study.protocolSection?.statusModule;
130642
+ if (!status)
130643
+ return;
130644
+ return {
130645
+ overallStatus: status.overallStatus,
130646
+ startDate: status.startDateStruct?.date,
130647
+ completionDate: status.completionDateStruct?.date,
130648
+ lastUpdateDate: status.lastUpdatePostDateStruct?.date
130649
+ };
130650
+ }
130651
+ function createStudyComparison(study, compareFields) {
130652
+ const nctId = study.protocolSection?.identificationModule?.nctId ?? "Unknown";
130653
+ const title = study.protocolSection?.identificationModule?.officialTitle ?? study.protocolSection?.identificationModule?.briefTitle;
130654
+ const comparison = { nctId, title };
130655
+ const shouldInclude = (field) => compareFields.includes("all") || compareFields.includes(field);
130656
+ if (shouldInclude("eligibility")) {
130657
+ comparison.eligibility = extractEligibility(study);
130658
+ }
130659
+ if (shouldInclude("design")) {
130660
+ comparison.design = extractDesign(study);
130661
+ }
130662
+ if (shouldInclude("interventions")) {
130663
+ comparison.interventions = extractInterventions(study);
130664
+ }
130665
+ if (shouldInclude("outcomes")) {
130666
+ comparison.outcomes = extractOutcomes(study);
130667
+ }
130668
+ if (shouldInclude("sponsors")) {
130669
+ comparison.sponsors = extractSponsors(study);
130670
+ }
130671
+ if (shouldInclude("locations")) {
130672
+ comparison.locations = extractLocations(study);
130673
+ }
130674
+ if (shouldInclude("status")) {
130675
+ comparison.status = extractStatus(study);
130676
+ }
130677
+ return comparison;
130678
+ }
130679
+ function analyzeSummary(comparisons, compareFields) {
130680
+ const commonalities = [];
130681
+ const differences = [];
130682
+ if (compareFields.includes("all") || compareFields.includes("design")) {
130683
+ const allPhases = comparisons.map((c) => c.design?.phases).filter(Boolean);
130684
+ if (allPhases.length > 0) {
130685
+ const firstPhases = allPhases[0]?.join(",");
130686
+ const allSame = allPhases.every((p) => p?.join(",") === firstPhases);
130687
+ if (allSame && firstPhases) {
130688
+ commonalities.push(`All studies are in phase: ${firstPhases}`);
130689
+ } else {
130690
+ differences.push("Studies are in different trial phases");
130691
+ }
130692
+ }
130693
+ }
130694
+ if (compareFields.includes("all") || compareFields.includes("sponsors")) {
130695
+ const sponsors = comparisons.map((c) => c.sponsors?.leadSponsor?.name).filter(Boolean);
130696
+ const uniqueSponsors = [...new Set(sponsors)];
130697
+ if (uniqueSponsors.length === 1 && sponsors.length === comparisons.length) {
130698
+ commonalities.push(`All studies sponsored by: ${uniqueSponsors[0]}`);
130699
+ } else if (uniqueSponsors.length > 1) {
130700
+ differences.push(`Different lead sponsors: ${uniqueSponsors.join(", ")}`);
130701
+ }
130702
+ }
130703
+ if (compareFields.includes("all") || compareFields.includes("status")) {
130704
+ const statuses = comparisons.map((c) => c.status?.overallStatus).filter(Boolean);
130705
+ const uniqueStatuses = [...new Set(statuses)];
130706
+ if (uniqueStatuses.length === 1 && statuses.length === comparisons.length) {
130707
+ commonalities.push(`All studies have status: ${uniqueStatuses[0]}`);
130708
+ } else if (uniqueStatuses.length > 1) {
130709
+ differences.push(`Different statuses: ${uniqueStatuses.join(", ")}`);
130710
+ }
130711
+ }
130712
+ if (compareFields.includes("all") || compareFields.includes("locations")) {
130713
+ const allCountries = comparisons.map((c) => c.locations?.countries ?? []).filter((countries) => countries.length > 0);
130714
+ if (allCountries.length > 1) {
130715
+ const commonCountries = allCountries.reduce((acc, countries) => acc.filter((c) => countries.includes(c)));
130716
+ if (commonCountries.length > 0) {
130717
+ commonalities.push(`Common countries: ${commonCountries.slice(0, 5).join(", ")}`);
130718
+ }
130719
+ }
130720
+ }
130721
+ return {
130722
+ totalStudies: comparisons.length,
130723
+ comparedFields: compareFields,
130724
+ commonalities: commonalities.length > 0 ? commonalities : undefined,
130725
+ differences: differences.length > 0 ? differences : undefined
130726
+ };
130727
+ }
130728
+ async function compareStudiesLogic(input, appContext, _sdkContext) {
130729
+ logger.debug(`Executing compareStudiesLogic for NCT IDs: ${input.nctIds.join(", ")}`, {
130730
+ ...appContext,
130731
+ toolInput: input
130732
+ });
130733
+ const provider = import_tsyringe12.container.resolve(ClinicalTrialsProvider);
130734
+ const compareFields = Array.isArray(input.compareFields) ? input.compareFields : [input.compareFields];
130735
+ const comparisons = [];
130736
+ const errors = [];
130737
+ const studyPromises = input.nctIds.map(async (nctId) => {
130738
+ try {
130739
+ const study = await provider.fetchStudy(nctId, appContext);
130740
+ logger.info(`Successfully fetched study ${nctId} for comparison`, {
130741
+ ...appContext
130742
+ });
130743
+ return { nctId, study };
130744
+ } catch (error2) {
130745
+ const errorMessage = error2 instanceof McpError ? error2.message : "An unexpected error occurred";
130746
+ logger.warning(`Failed to fetch study ${nctId}: ${errorMessage}`, {
130747
+ ...appContext,
130748
+ nctId,
130749
+ error: error2
130750
+ });
130751
+ errors.push({ nctId, error: errorMessage });
130752
+ return null;
130753
+ }
130754
+ });
130755
+ const results = await Promise.all(studyPromises);
130756
+ results.forEach((result2) => {
130757
+ if (result2) {
130758
+ const comparison = createStudyComparison(result2.study, compareFields);
130759
+ comparisons.push(comparison);
130760
+ }
130761
+ });
130762
+ if (comparisons.length < 2) {
130763
+ throw new McpError(-32007 /* ValidationError */, `Insufficient studies for comparison. Need at least 2, got ${comparisons.length}. ${errors.length > 0 ? `Errors: ${errors.map((e) => `${e.nctId}: ${e.error}`).join("; ")}` : ""}`, { errors, successfulFetches: comparisons.length });
130764
+ }
130765
+ const summary = analyzeSummary(comparisons, compareFields);
130766
+ logger.info(`Successfully compared ${comparisons.length} studies`, {
130767
+ ...appContext,
130768
+ comparedFields: compareFields
130769
+ });
130770
+ const result = { comparisons, summary };
130771
+ if (errors.length > 0) {
130772
+ result.errors = errors;
130773
+ }
130774
+ return result;
130775
+ }
130776
+ function responseFormatter2(result) {
130777
+ const { comparisons, summary, errors } = result;
130778
+ const summaryParts = [
130779
+ `# Comparison of ${summary.totalStudies} Clinical Trials`,
130780
+ "",
130781
+ "## Studies",
130782
+ ...comparisons.map((c) => `- **${c.nctId}**: ${c.title ?? "No title"}`),
130783
+ ""
130784
+ ];
130785
+ if (summary.commonalities && summary.commonalities.length > 0) {
130786
+ summaryParts.push("## Commonalities");
130787
+ summaryParts.push(...summary.commonalities.map((c) => `- ${c}`));
130788
+ summaryParts.push("");
130789
+ }
130790
+ if (summary.differences && summary.differences.length > 0) {
130791
+ summaryParts.push("## Key Differences");
130792
+ summaryParts.push(...summary.differences.map((d) => `- ${d}`));
130793
+ summaryParts.push("");
130794
+ }
130795
+ if (errors && errors.length > 0) {
130796
+ summaryParts.push("## Errors");
130797
+ summaryParts.push(...errors.map((e) => `- **${e.nctId}**: ${e.error}`));
130798
+ summaryParts.push("");
130799
+ }
130800
+ summaryParts.push("---");
130801
+ summaryParts.push("");
130802
+ const detailParts = ["## Detailed Comparison", ""];
130803
+ comparisons.forEach((comp, idx) => {
130804
+ if (idx > 0)
130805
+ detailParts.push("---", "");
130806
+ detailParts.push(`### ${comp.nctId}: ${comp.title ?? "No title"}`, "");
130807
+ if (comp.status) {
130808
+ detailParts.push("**Status:**");
130809
+ detailParts.push(`- Overall Status: ${comp.status.overallStatus ?? "N/A"}`);
130810
+ detailParts.push(`- Start Date: ${comp.status.startDate ?? "N/A"}`);
130811
+ detailParts.push(`- Completion Date: ${comp.status.completionDate ?? "N/A"}`);
130812
+ detailParts.push("");
130813
+ }
130814
+ if (comp.design) {
130815
+ detailParts.push("**Design:**");
130816
+ detailParts.push(`- Study Type: ${comp.design.studyType ?? "N/A"}`);
130817
+ detailParts.push(`- Phases: ${comp.design.phases?.join(", ") ?? "N/A"}`);
130818
+ detailParts.push(`- Allocation: ${comp.design.allocation ?? "N/A"}`);
130819
+ detailParts.push(`- Intervention Model: ${comp.design.interventionModel ?? "N/A"}`);
130820
+ detailParts.push(`- Primary Purpose: ${comp.design.primaryPurpose ?? "N/A"}`);
130821
+ detailParts.push(`- Masking: ${comp.design.masking ?? "N/A"}`);
130822
+ detailParts.push("");
130823
+ }
130824
+ if (comp.eligibility) {
130825
+ detailParts.push("**Eligibility:**");
130826
+ detailParts.push(`- Sex: ${comp.eligibility.sex ?? "N/A"}`);
130827
+ detailParts.push(`- Minimum Age: ${comp.eligibility.minimumAge ?? "N/A"}`);
130828
+ detailParts.push(`- Healthy Volunteers: ${comp.eligibility.healthyVolunteers ?? "N/A"}`);
130829
+ if (comp.eligibility.stdAges?.length) {
130830
+ detailParts.push(`- Age Groups: ${comp.eligibility.stdAges.join(", ")}`);
130831
+ }
130832
+ if (comp.eligibility.criteria) {
130833
+ detailParts.push(`- Criteria: ${comp.eligibility.criteria.substring(0, 200)}${comp.eligibility.criteria.length > 200 ? "..." : ""}`);
130834
+ }
130835
+ detailParts.push("");
130836
+ }
130837
+ if (comp.interventions && comp.interventions.length > 0) {
130838
+ detailParts.push("**Interventions:**");
130839
+ comp.interventions.forEach((int) => {
130840
+ detailParts.push(`- ${int.type ?? "Unknown"}: ${int.name ?? "N/A"}`);
130841
+ if (int.description) {
130842
+ detailParts.push(` ${int.description.substring(0, 150)}${int.description.length > 150 ? "..." : ""}`);
130843
+ }
130844
+ });
130845
+ detailParts.push("");
130846
+ }
130847
+ if (comp.outcomes) {
130848
+ if (comp.outcomes.primary && comp.outcomes.primary.length > 0) {
130849
+ detailParts.push("**Primary Outcomes:**");
130850
+ comp.outcomes.primary.forEach((out) => {
130851
+ detailParts.push(`- ${out.measure ?? "N/A"}`);
130852
+ if (out.timeFrame) {
130853
+ detailParts.push(` Time Frame: ${out.timeFrame}`);
130854
+ }
130855
+ });
130856
+ detailParts.push("");
130857
+ }
130858
+ if (comp.outcomes.secondary && comp.outcomes.secondary.length > 0) {
130859
+ detailParts.push("**Secondary Outcomes:**");
130860
+ comp.outcomes.secondary.slice(0, 3).forEach((out) => {
130861
+ detailParts.push(`- ${out.measure ?? "N/A"}`);
130862
+ });
130863
+ if (comp.outcomes.secondary.length > 3) {
130864
+ detailParts.push(` ...and ${comp.outcomes.secondary.length - 3} more`);
130865
+ }
130866
+ detailParts.push("");
130867
+ }
130868
+ }
130869
+ if (comp.sponsors) {
130870
+ detailParts.push("**Sponsors:**");
130871
+ if (comp.sponsors.leadSponsor) {
130872
+ detailParts.push(`- Lead: ${comp.sponsors.leadSponsor.name ?? "N/A"} (${comp.sponsors.leadSponsor.class ?? "N/A"})`);
130873
+ }
130874
+ if (comp.sponsors.collaborators && comp.sponsors.collaborators.length > 0) {
130875
+ detailParts.push(`- Collaborators: ${comp.sponsors.collaborators.length}`);
130876
+ comp.sponsors.collaborators.slice(0, 3).forEach((collab) => {
130877
+ detailParts.push(` - ${collab.name ?? "N/A"}`);
130878
+ });
130879
+ if (comp.sponsors.collaborators.length > 3) {
130880
+ detailParts.push(` ...and ${comp.sponsors.collaborators.length - 3} more`);
130881
+ }
130882
+ }
130883
+ detailParts.push("");
130884
+ }
130885
+ if (comp.locations) {
130886
+ detailParts.push("**Locations:**");
130887
+ detailParts.push(`- Total: ${comp.locations.totalCount ?? 0}`);
130888
+ if (comp.locations.countries?.length) {
130889
+ detailParts.push(`- Countries: ${comp.locations.countries.join(", ")}`);
130890
+ }
130891
+ if (comp.locations.topCities?.length) {
130892
+ detailParts.push(`- Top Cities: ${comp.locations.topCities.join(", ")}`);
130893
+ }
130894
+ detailParts.push("");
130895
+ }
130896
+ });
130897
+ return [
130898
+ {
130899
+ type: "text",
130900
+ text: [...summaryParts, ...detailParts].join(`
130901
+ `)
130902
+ }
130903
+ ];
130904
+ }
130905
+ var compareStudiesTool = {
130906
+ name: TOOL_NAME2,
130907
+ title: TOOL_TITLE2,
130908
+ description: TOOL_DESCRIPTION2,
130909
+ inputSchema: InputSchema2,
130910
+ outputSchema: OutputSchema2,
130911
+ annotations: TOOL_ANNOTATIONS2,
130912
+ logic: withToolAuth(["tool:clinicaltrials:read"], compareStudiesLogic),
130913
+ responseFormatter: responseFormatter2
130914
+ };
130915
+
130916
+ // src/mcp-server/tools/definitions/clinicaltrials-get-study.tool.ts
130917
+ var import_tsyringe13 = __toESM(require_cjs3(), 1);
130918
+ var TOOL_NAME3 = "clinicaltrials_get_study";
130919
+ var TOOL_TITLE3 = "Get Clinical Study";
130920
+ var TOOL_DESCRIPTION3 = "Fetches one or more clinical trial studies from ClinicalTrials.gov by their NCT ID(s). Returns full study data or concise summaries.";
130921
+ var TOOL_ANNOTATIONS3 = {
130922
+ readOnlyHint: true,
130923
+ idempotentHint: true,
130924
+ openWorldHint: true
130925
+ };
130426
130926
  var StudySummarySchema = z.object({
130427
130927
  nctId: z.string().optional().describe("The NCT identifier of the study."),
130428
130928
  title: z.string().optional().describe("The official title of the study."),
@@ -130435,14 +130935,14 @@ var StudySummarySchema = z.object({
130435
130935
  })).optional().describe("List of interventions being tested."),
130436
130936
  leadSponsor: z.string().optional().describe("The lead sponsor organization.")
130437
130937
  }).passthrough().describe("Concise summary of a clinical trial study.");
130438
- var InputSchema2 = z.object({
130938
+ var InputSchema3 = z.object({
130439
130939
  nctIds: z.union([
130440
130940
  z.string().regex(/^[Nn][Cc][Tt]\d{8}$/, "NCT ID must be 8 digits").describe('A single NCT ID (e.g., "NCT12345678").'),
130441
130941
  z.array(z.string().regex(/^[Nn][Cc][Tt]\d{8}$/, "NCT ID must be 8 digits")).min(1, "At least one NCT ID is required.").max(5, "Maximum 5 NCT IDs allowed per request.").describe("An array of up to 5 NCT IDs.")
130442
130942
  ]).describe('A single NCT ID (e.g., "NCT12345678") or an array of up to 5 NCT IDs to fetch.'),
130443
130943
  summaryOnly: z.boolean().default(false).describe("If true, returns concise summaries. If false (default), returns full study data.")
130444
130944
  }).describe("Input parameters for fetching clinical trial studies.");
130445
- var OutputSchema2 = z.object({
130945
+ var OutputSchema3 = z.object({
130446
130946
  studies: z.array(z.union([StudySchema, StudySummarySchema])).describe("Array of full study data or summaries."),
130447
130947
  errors: z.array(z.object({
130448
130948
  nctId: z.string().describe("The NCT ID that failed."),
@@ -130469,7 +130969,7 @@ async function getStudyLogic(input, appContext, _sdkContext) {
130469
130969
  ...appContext,
130470
130970
  toolInput: input
130471
130971
  });
130472
- const provider = import_tsyringe12.container.resolve(ClinicalTrialsProvider);
130972
+ const provider = import_tsyringe13.container.resolve(ClinicalTrialsProvider);
130473
130973
  const studies = [];
130474
130974
  const errors = [];
130475
130975
  const studyPromises = nctIds.map(async (nctId) => {
@@ -130502,7 +131002,7 @@ async function getStudyLogic(input, appContext, _sdkContext) {
130502
131002
  }
130503
131003
  return result;
130504
131004
  }
130505
- function responseFormatter2(result) {
131005
+ function responseFormatter3(result) {
130506
131006
  return [
130507
131007
  {
130508
131008
  type: "text",
@@ -130511,34 +131011,38 @@ function responseFormatter2(result) {
130511
131011
  ];
130512
131012
  }
130513
131013
  var getStudyTool = {
130514
- name: TOOL_NAME2,
130515
- title: TOOL_TITLE2,
130516
- description: TOOL_DESCRIPTION2,
130517
- inputSchema: InputSchema2,
130518
- outputSchema: OutputSchema2,
130519
- annotations: TOOL_ANNOTATIONS2,
131014
+ name: TOOL_NAME3,
131015
+ title: TOOL_TITLE3,
131016
+ description: TOOL_DESCRIPTION3,
131017
+ inputSchema: InputSchema3,
131018
+ outputSchema: OutputSchema3,
131019
+ annotations: TOOL_ANNOTATIONS3,
130520
131020
  logic: withToolAuth(["tool:clinicaltrials:read"], getStudyLogic),
130521
- responseFormatter: responseFormatter2
131021
+ responseFormatter: responseFormatter3
130522
131022
  };
130523
131023
 
130524
131024
  // src/mcp-server/tools/definitions/clinicaltrials-search-studies.tool.ts
130525
- var import_tsyringe13 = __toESM(require_cjs3(), 1);
130526
- var TOOL_NAME3 = "clinicaltrials_search_studies";
130527
- var TOOL_TITLE3 = "Search Clinical Trials";
130528
- var TOOL_DESCRIPTION3 = "Searches for clinical trial studies from ClinicalTrials.gov using queries and filters. Supports pagination, sorting, and advanced filtering.";
130529
- var TOOL_ANNOTATIONS3 = {
131025
+ var import_tsyringe14 = __toESM(require_cjs3(), 1);
131026
+ var TOOL_NAME4 = "clinicaltrials_search_studies";
131027
+ var TOOL_TITLE4 = "Search Clinical Trials";
131028
+ var TOOL_DESCRIPTION4 = "Searches for clinical trial studies from ClinicalTrials.gov using queries and filters. Supports pagination, sorting, and advanced filtering.";
131029
+ var TOOL_ANNOTATIONS4 = {
130530
131030
  readOnlyHint: true,
130531
131031
  idempotentHint: true,
130532
131032
  openWorldHint: true
130533
131033
  };
130534
- var InputSchema3 = z.object({
131034
+ var InputSchema4 = z.object({
130535
131035
  query: z.string().optional().describe("General search query for conditions, interventions, sponsors, or other terms."),
130536
131036
  filter: z.string().optional().describe("Advanced filter expression using the ClinicalTrials.gov filter syntax."),
130537
131037
  pageSize: z.number().int().min(1).max(200).default(10).describe("Number of studies to return per page (1-200). Defaults to 10."),
130538
131038
  pageToken: z.string().optional().describe("Token for retrieving the next page of results."),
130539
- sort: z.string().optional().describe('Sort order specification (e.g., "LastUpdateDate:desc", "EnrollmentCount").')
131039
+ sort: z.string().optional().describe('Sort order specification (e.g., "LastUpdateDate:desc", "EnrollmentCount").'),
131040
+ fields: z.array(z.string()).optional().describe('Specific fields to return (reduces payload size). Example: ["NCTId", "BriefTitle", "OverallStatus"].'),
131041
+ country: z.string().optional().describe('Filter studies by country (e.g., "United States", "Canada").'),
131042
+ state: z.string().optional().describe('Filter studies by state or province (e.g., "California", "Ontario").'),
131043
+ city: z.string().optional().describe('Filter studies by city (e.g., "New York", "Toronto").')
130540
131044
  }).describe("Input parameters for searching clinical trial studies.");
130541
- var OutputSchema3 = z.object({
131045
+ var OutputSchema4 = z.object({
130542
131046
  pagedStudies: PagedStudiesSchema
130543
131047
  });
130544
131048
  async function searchStudiesLogic(input, appContext, _sdkContext) {
@@ -130546,13 +131050,17 @@ async function searchStudiesLogic(input, appContext, _sdkContext) {
130546
131050
  ...appContext,
130547
131051
  toolInput: input
130548
131052
  });
130549
- const provider = import_tsyringe13.container.resolve(ClinicalTrialsProvider);
131053
+ const provider = import_tsyringe14.container.resolve(ClinicalTrialsProvider);
130550
131054
  const pagedStudies = await provider.listStudies({
130551
131055
  ...input.query && { query: input.query },
130552
131056
  ...input.filter && { filter: input.filter },
130553
131057
  pageSize: input.pageSize,
130554
131058
  ...input.pageToken && { pageToken: input.pageToken },
130555
- ...input.sort && { sort: input.sort }
131059
+ ...input.sort && { sort: input.sort },
131060
+ ...input.fields && { fields: input.fields },
131061
+ ...input.country && { country: input.country },
131062
+ ...input.state && { state: input.state },
131063
+ ...input.city && { city: input.city }
130556
131064
  }, appContext);
130557
131065
  logger.info(`Successfully searched studies: ${pagedStudies.studies?.length ?? 0} results`, {
130558
131066
  ...appContext,
@@ -130560,7 +131068,7 @@ async function searchStudiesLogic(input, appContext, _sdkContext) {
130560
131068
  });
130561
131069
  return { pagedStudies };
130562
131070
  }
130563
- function responseFormatter3(result) {
131071
+ function responseFormatter4(result) {
130564
131072
  const { pagedStudies } = result;
130565
131073
  const studyCount = pagedStudies.studies?.length ?? 0;
130566
131074
  const totalCount = pagedStudies.totalCount;
@@ -130593,19 +131101,20 @@ ${studyList}${moreStudies}${pagination}`
130593
131101
  ];
130594
131102
  }
130595
131103
  var searchStudiesTool = {
130596
- name: TOOL_NAME3,
130597
- title: TOOL_TITLE3,
130598
- description: TOOL_DESCRIPTION3,
130599
- inputSchema: InputSchema3,
130600
- outputSchema: OutputSchema3,
130601
- annotations: TOOL_ANNOTATIONS3,
131104
+ name: TOOL_NAME4,
131105
+ title: TOOL_TITLE4,
131106
+ description: TOOL_DESCRIPTION4,
131107
+ inputSchema: InputSchema4,
131108
+ outputSchema: OutputSchema4,
131109
+ annotations: TOOL_ANNOTATIONS4,
130602
131110
  logic: withToolAuth(["tool:clinicaltrials:read"], searchStudiesLogic),
130603
- responseFormatter: responseFormatter3
131111
+ responseFormatter: responseFormatter4
130604
131112
  };
130605
131113
 
130606
131114
  // src/mcp-server/tools/definitions/index.ts
130607
131115
  var allToolDefinitions = [
130608
131116
  analyzeTrendsTool,
131117
+ compareStudiesTool,
130609
131118
  getStudyTool,
130610
131119
  searchStudiesTool
130611
131120
  ];
@@ -130617,7 +131126,7 @@ var defaultResponseFormatter2 = (result) => [
130617
131126
  function createMcpToolHandler({
130618
131127
  toolName,
130619
131128
  logic,
130620
- responseFormatter: responseFormatter4 = defaultResponseFormatter2
131129
+ responseFormatter: responseFormatter5 = defaultResponseFormatter2
130621
131130
  }) {
130622
131131
  return async (input, callContext) => {
130623
131132
  const sdkContext = callContext;
@@ -130634,7 +131143,7 @@ function createMcpToolHandler({
130634
131143
  const result = await measureToolExecution(() => logic(input, appContext, sdkContext), { ...appContext, toolName }, input);
130635
131144
  return {
130636
131145
  structuredContent: result,
130637
- content: responseFormatter4(result)
131146
+ content: responseFormatter5(result)
130638
131147
  };
130639
131148
  } catch (error2) {
130640
131149
  const mcpError = ErrorHandler.handleError(error2, {
@@ -130705,15 +131214,15 @@ class ToolRegistry {
130705
131214
  }
130706
131215
  }
130707
131216
  ToolRegistry = __legacyDecorateClassTS([
130708
- import_tsyringe14.injectable(),
130709
- __legacyDecorateParamTS(0, import_tsyringe14.injectAll(ToolDefinitions)),
131217
+ import_tsyringe15.injectable(),
131218
+ __legacyDecorateParamTS(0, import_tsyringe15.injectAll(ToolDefinitions)),
130710
131219
  __legacyMetadataTS("design:paramtypes", [
130711
131220
  Array
130712
131221
  ])
130713
131222
  ], ToolRegistry);
130714
- var registerTools = (container6) => {
131223
+ var registerTools = (container7) => {
130715
131224
  for (const tool of allToolDefinitions) {
130716
- container6.register(ToolDefinitions, { useValue: tool });
131225
+ container7.register(ToolDefinitions, { useValue: tool });
130717
131226
  }
130718
131227
  };
130719
131228
 
@@ -130745,9 +131254,9 @@ async function createMcpServerInstance() {
130745
131254
  });
130746
131255
  try {
130747
131256
  logger.debug("Registering all MCP capabilities via registries...", context);
130748
- const toolRegistry = import_tsyringe15.container.resolve(ToolRegistry);
131257
+ const toolRegistry = import_tsyringe16.container.resolve(ToolRegistry);
130749
131258
  await toolRegistry.registerAll(server);
130750
- const resourceRegistry = import_tsyringe15.container.resolve(ResourceRegistry);
131259
+ const resourceRegistry = import_tsyringe16.container.resolve(ResourceRegistry);
130751
131260
  await resourceRegistry.registerAll(server);
130752
131261
  logger.info("All MCP capabilities registered successfully", context);
130753
131262
  } catch (err) {
@@ -130762,7 +131271,7 @@ async function createMcpServerInstance() {
130762
131271
  }
130763
131272
 
130764
131273
  // src/mcp-server/transports/manager.ts
130765
- var import_tsyringe19 = __toESM(require_cjs3(), 1);
131274
+ var import_tsyringe20 = __toESM(require_cjs3(), 1);
130766
131275
 
130767
131276
  // node_modules/hono/dist/http-exception.js
130768
131277
  var HTTPException = class extends Error {
@@ -133430,7 +133939,7 @@ var cors = (options) => {
133430
133939
  import http from "http";
133431
133940
  import { randomUUID } from "node:crypto";
133432
133941
  // src/mcp-server/transports/auth/authFactory.ts
133433
- var import_tsyringe18 = __toESM(require_cjs3(), 1);
133942
+ var import_tsyringe19 = __toESM(require_cjs3(), 1);
133434
133943
 
133435
133944
  // node_modules/jose/dist/webapi/lib/buffer_utils.js
133436
133945
  var encoder = new TextEncoder;
@@ -134948,7 +135457,7 @@ function createRemoteJWKSet(url, options) {
134948
135457
  return remoteJWKSet;
134949
135458
  }
134950
135459
  // src/mcp-server/transports/auth/strategies/jwtStrategy.ts
134951
- var import_tsyringe16 = __toESM(require_cjs3(), 1);
135460
+ var import_tsyringe17 = __toESM(require_cjs3(), 1);
134952
135461
  class JwtStrategy {
134953
135462
  config;
134954
135463
  logger;
@@ -135051,9 +135560,9 @@ class JwtStrategy {
135051
135560
  }
135052
135561
  }
135053
135562
  JwtStrategy = __legacyDecorateClassTS([
135054
- import_tsyringe16.injectable(),
135055
- __legacyDecorateParamTS(0, import_tsyringe16.inject(AppConfig)),
135056
- __legacyDecorateParamTS(1, import_tsyringe16.inject(Logger2)),
135563
+ import_tsyringe17.injectable(),
135564
+ __legacyDecorateParamTS(0, import_tsyringe17.inject(AppConfig)),
135565
+ __legacyDecorateParamTS(1, import_tsyringe17.inject(Logger2)),
135057
135566
  __legacyMetadataTS("design:paramtypes", [
135058
135567
  Object,
135059
135568
  Object
@@ -135061,7 +135570,7 @@ JwtStrategy = __legacyDecorateClassTS([
135061
135570
  ], JwtStrategy);
135062
135571
 
135063
135572
  // src/mcp-server/transports/auth/strategies/oauthStrategy.ts
135064
- var import_tsyringe17 = __toESM(require_cjs3(), 1);
135573
+ var import_tsyringe18 = __toESM(require_cjs3(), 1);
135065
135574
  class OauthStrategy {
135066
135575
  config;
135067
135576
  logger;
@@ -135178,9 +135687,9 @@ class OauthStrategy {
135178
135687
  }
135179
135688
  }
135180
135689
  OauthStrategy = __legacyDecorateClassTS([
135181
- import_tsyringe17.injectable(),
135182
- __legacyDecorateParamTS(0, import_tsyringe17.inject(AppConfig)),
135183
- __legacyDecorateParamTS(1, import_tsyringe17.inject(Logger2)),
135690
+ import_tsyringe18.injectable(),
135691
+ __legacyDecorateParamTS(0, import_tsyringe18.inject(AppConfig)),
135692
+ __legacyDecorateParamTS(1, import_tsyringe18.inject(Logger2)),
135184
135693
  __legacyMetadataTS("design:paramtypes", [
135185
135694
  Object,
135186
135695
  Object
@@ -135188,8 +135697,8 @@ OauthStrategy = __legacyDecorateClassTS([
135188
135697
  ], OauthStrategy);
135189
135698
 
135190
135699
  // src/mcp-server/transports/auth/authFactory.ts
135191
- import_tsyringe18.container.register(JwtStrategy, { useClass: JwtStrategy });
135192
- import_tsyringe18.container.register(OauthStrategy, { useClass: OauthStrategy });
135700
+ import_tsyringe19.container.register(JwtStrategy, { useClass: JwtStrategy });
135701
+ import_tsyringe19.container.register(OauthStrategy, { useClass: OauthStrategy });
135193
135702
  function createAuthStrategy() {
135194
135703
  const context = requestContextService.createRequestContext({
135195
135704
  operation: "createAuthStrategy",
@@ -135199,10 +135708,10 @@ function createAuthStrategy() {
135199
135708
  switch (config.mcpAuthMode) {
135200
135709
  case "jwt":
135201
135710
  logger.debug("Resolving JWT strategy from container.", context);
135202
- return import_tsyringe18.container.resolve(JwtStrategy);
135711
+ return import_tsyringe19.container.resolve(JwtStrategy);
135203
135712
  case "oauth":
135204
135713
  logger.debug("Resolving OAuth strategy from container.", context);
135205
- return import_tsyringe18.container.resolve(OauthStrategy);
135714
+ return import_tsyringe19.container.resolve(OauthStrategy);
135206
135715
  case "none":
135207
135716
  logger.info("Authentication is disabled ('none' mode).", context);
135208
135717
  return null;
@@ -135716,10 +136225,10 @@ class TransportManager {
135716
136225
  }
135717
136226
  }
135718
136227
  TransportManager = __legacyDecorateClassTS([
135719
- import_tsyringe19.injectable(),
135720
- __legacyDecorateParamTS(0, import_tsyringe19.inject(AppConfig)),
135721
- __legacyDecorateParamTS(1, import_tsyringe19.inject(Logger2)),
135722
- __legacyDecorateParamTS(2, import_tsyringe19.inject(CreateMcpServerInstance)),
136228
+ import_tsyringe20.injectable(),
136229
+ __legacyDecorateParamTS(0, import_tsyringe20.inject(AppConfig)),
136230
+ __legacyDecorateParamTS(1, import_tsyringe20.inject(Logger2)),
136231
+ __legacyDecorateParamTS(2, import_tsyringe20.inject(CreateMcpServerInstance)),
135723
136232
  __legacyMetadataTS("design:paramtypes", [
135724
136233
  typeof AppConfigType === "undefined" ? Object : AppConfigType,
135725
136234
  Object,
@@ -135729,14 +136238,14 @@ TransportManager = __legacyDecorateClassTS([
135729
136238
 
135730
136239
  // src/container/registrations/mcp.ts
135731
136240
  var registerMcpServices = () => {
135732
- import_tsyringe20.container.registerSingleton(ToolRegistry);
135733
- import_tsyringe20.container.registerSingleton(ResourceRegistry);
135734
- registerTools(import_tsyringe20.container);
135735
- registerResources(import_tsyringe20.container);
135736
- import_tsyringe20.container.register(CreateMcpServerInstance, {
136241
+ import_tsyringe21.container.registerSingleton(ToolRegistry);
136242
+ import_tsyringe21.container.registerSingleton(ResourceRegistry);
136243
+ registerTools(import_tsyringe21.container);
136244
+ registerResources(import_tsyringe21.container);
136245
+ import_tsyringe21.container.register(CreateMcpServerInstance, {
135737
136246
  useValue: createMcpServerInstance
135738
136247
  });
135739
- import_tsyringe20.container.registerSingleton(TransportManagerToken, TransportManager);
136248
+ import_tsyringe21.container.registerSingleton(TransportManagerToken, TransportManager);
135740
136249
  logger.info("MCP services and factories registered with the DI container.");
135741
136250
  };
135742
136251
 
@@ -135750,7 +136259,7 @@ function composeContainer() {
135750
136259
  registerMcpServices();
135751
136260
  isContainerComposed = true;
135752
136261
  }
135753
- var container_default = import_tsyringe21.container;
136262
+ var container_default = import_tsyringe22.container;
135754
136263
 
135755
136264
  // src/index.ts
135756
136265
  var config2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clinicaltrialsgov-mcp-server",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "mcpName": "io.github.cyanheads/clinicaltrialsgov-mcp-server",
5
5
  "description": "ClinicalTrials.gov Model Context Protocol (MCP) Server that provides a suite of tools for interacting with the official ClinicalTrials.gov v2 API. Enables AI agents and LLMs to programmatically search, retrieve, and analyze clinical trial data.",
6
6
  "main": "dist/index.js",
@@ -30,9 +30,12 @@
30
30
  "homepage": "https://github.com/cyanheads/clinicaltrialsgov-mcp-server#readme",
31
31
  "scripts": {
32
32
  "build": "bun build ./src/index.ts --outdir ./dist --target node",
33
+ "build:worker": "bun build ./src/worker.ts --outdir ./dist --target bun --no-external",
34
+ "deploy:dev": "MCP_TRANSPORT_TYPE=http bunx wrangler dev",
35
+ "deploy:prod": "MCP_TRANSPORT_TYPE=http bunx wrangler deploy",
33
36
  "start": "bun ./dist/index.js",
34
- "start:stdio": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=stdio bun ./dist/index.js",
35
- "start:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http bun ./dist/index.js",
37
+ "start:stdio": "MCP_TRANSPORT_TYPE=stdio bun ./dist/index.js",
38
+ "start:http": "MCP_TRANSPORT_TYPE=http bun ./dist/index.js",
36
39
  "dev": "bun --watch src/index.ts",
37
40
  "dev:stdio": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=stdio bun --watch src/index.ts",
38
41
  "dev:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http bun --watch src/index.ts",
@@ -47,6 +50,7 @@
47
50
  "tree": "bun run scripts/tree.ts",
48
51
  "fetch-spec": "bun run scripts/fetch-openapi-spec.ts",
49
52
  "format": "bun run prettier --write \"**/*.{ts,js,json,md,html,css}\"",
53
+ "prepare": "bunx husky",
50
54
  "inspector": "bunx mcp-inspector --config mcp.json --server clinicaltrialsgov-mcp-server",
51
55
  "db:duckdb-example": "MCP_LOG_LEVEL=debug tsc && node dist/storage/duckdbExample.js",
52
56
  "test": "bun test --config vitest.config.ts",