mcp-ts-template 2.2.0 → 2.2.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.
Files changed (3) hide show
  1. package/README.md +39 -34
  2. package/dist/index.js +298 -207
  3. package/package.json +1 -1
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.2.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/mcp-ts-template/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.22-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-83.55%25-brightgreen.svg?style=flat-square)](./coverage/lcov-report/)
8
+ [![Version](https://img.shields.io/badge/Version-2.2.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/mcp-ts-template/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.22-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-83.55%25-brightgreen.svg?style=flat-square)](./coverage/lcov-report/)
9
9
 
10
10
  </div>
11
11
 
@@ -30,18 +30,20 @@
30
30
 
31
31
  ### Installation
32
32
 
33
- 1.
34
- **Clone the repository:**
33
+ 1. **Clone the repository:**
34
+
35
35
  ```sh
36
36
  git clone https://github.com/cyanheads/mcp-ts-template.git
37
37
  ```
38
- 2.
39
- **Navigate into the directory:**
38
+
39
+ 2. **Navigate into the directory:**
40
+
40
41
  ```sh
41
42
  cd mcp-ts-template
42
43
  ```
43
- 3.
44
- **Install dependencies:**
44
+
45
+ 3. **Install dependencies:**
46
+
45
47
  ```sh
46
48
  bun install
47
49
  ```
@@ -60,7 +62,10 @@ This tool echoes back a message with optional formatting. You can find the full
60
62
  ```ts
61
63
  // Located at: src/mcp-server/tools/definitions/template-echo-message.tool.ts
62
64
  import { z } from 'zod';
63
- import type { SdkContext, ToolDefinition } from '@/mcp-server/tools/utils/toolDefinition.js';
65
+ import type {
66
+ SdkContext,
67
+ ToolDefinition,
68
+ } from '@/mcp-server/tools/utils/toolDefinition.js';
64
69
  import { withToolAuth } from '@/mcp-server/transports/auth/lib/withAuth.js';
65
70
  import { type RequestContext, logger } from '@/utils/index.js';
66
71
 
@@ -180,14 +185,14 @@ Like the tool, `echoResourceDefinition` is registered in `src/mcp-server/resourc
180
185
 
181
186
  All configuration is centralized and validated at startup in `src/config/index.ts`. Key environment variables in your `.env` file include:
182
187
 
183
- | Variable | Description | Default |
184
- | :--- | :--- | :--- |
185
- | `MCP_TRANSPORT_TYPE` | The transport to use: `stdio` or `http`. | `http` |
186
- | `MCP_HTTP_PORT` | The port for the HTTP server. | `3010` |
187
- | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
188
+ | Variable | Description | Default |
189
+ | :---------------------- | :----------------------------------------------------------------------------- | :---------- |
190
+ | `MCP_TRANSPORT_TYPE` | The transport to use: `stdio` or `http`. | `http` |
191
+ | `MCP_HTTP_PORT` | The port for the HTTP server. | `3010` |
192
+ | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
188
193
  | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`. | `in-memory` |
189
- | `OTEL_ENABLED` | Set to `true` to enable OpenTelemetry. | `false` |
190
- | `LOG_LEVEL` | The minimum level for logging. | `info` |
194
+ | `OTEL_ENABLED` | Set to `true` to enable OpenTelemetry. | `false` |
195
+ | `LOG_LEVEL` | The minimum level for logging. | `info` |
191
196
 
192
197
  ### Authentication & Authorization
193
198
 
@@ -229,36 +234,36 @@ All configuration is centralized and validated at startup in `src/config/index.t
229
234
 
230
235
  ### Cloudflare Workers
231
236
 
232
- 1.
233
- **Build the Worker bundle**:
237
+ 1. **Build the Worker bundle**:
238
+
234
239
  ```sh
235
240
  bun build:worker
236
241
  ```
237
- 2.
238
- **Run locally with Wrangler**:
242
+
243
+ 2. **Run locally with Wrangler**:
244
+
239
245
  ```sh
240
246
  bun deploy:dev
241
247
  ```
242
- 3.
243
- **Deploy to Cloudflare**:
244
- `sh
248
+
249
+ 3. **Deploy to Cloudflare**:
250
+ `sh
245
251
  bun deploy:prod
246
- `
247
- > **Note**: The `wrangler.toml` file is pre-configured to enable `nodejs_compat` for best results.
252
+ ` > **Note**: The `wrangler.toml` file is pre-configured to enable `nodejs_compat` for best results.
248
253
 
249
254
  ## 📂 Project Structure
250
255
 
251
- | Directory | Purpose & Contents |
252
- | :--- | :--- |
253
- | `src/mcp-server/tools/definitions` | Your tool definitions (`*.tool.ts`). This is where you add new capabilities. |
256
+ | Directory | Purpose & Contents |
257
+ | :------------------------------------- | :----------------------------------------------------------------------------------- |
258
+ | `src/mcp-server/tools/definitions` | Your tool definitions (`*.tool.ts`). This is where you add new capabilities. |
254
259
  | `src/mcp-server/resources/definitions` | Your resource definitions (`*.resource.ts`). This is where you add new data sources. |
255
- | `src/mcp-server/transports` | Implementations for HTTP and STDIO transports, including auth middleware. |
256
- | `src/storage` | The `StorageService` abstraction and all storage provider implementations. |
257
- | `src/services` | Integrations with external services (e.g., the default OpenRouter LLM provider). |
258
- | `src/container` | Dependency injection container registrations and tokens. |
259
- | `src/utils` | Core utilities for logging, error handling, performance, security, and telemetry. |
260
- | `src/config` | Environment variable parsing and validation with Zod. |
261
- | `tests/` | Unit and integration tests, mirroring the `src/` directory structure. |
260
+ | `src/mcp-server/transports` | Implementations for HTTP and STDIO transports, including auth middleware. |
261
+ | `src/storage` | The `StorageService` abstraction and all storage provider implementations. |
262
+ | `src/services` | Integrations with external services (e.g., the default OpenRouter LLM provider). |
263
+ | `src/container` | Dependency injection container registrations and tokens. |
264
+ | `src/utils` | Core utilities for logging, error handling, performance, security, and telemetry. |
265
+ | `src/config` | Environment variable parsing and validation with Zod. |
266
+ | `tests/` | Unit and integration tests, mirroring the `src/` directory structure. |
262
267
 
263
268
  ## 🧑‍💻 Agent Development Guide
264
269
 
package/dist/index.js CHANGED
@@ -117226,10 +117226,55 @@ var z = /* @__PURE__ */ Object.freeze({
117226
117226
  quotelessJson,
117227
117227
  ZodError
117228
117228
  });
117229
+
117230
+ // src/types-global/errors.ts
117231
+ var JsonRpcErrorCode;
117232
+ ((JsonRpcErrorCode2) => {
117233
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ParseError"] = -32700] = "ParseError";
117234
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidRequest"] = -32600] = "InvalidRequest";
117235
+ JsonRpcErrorCode2[JsonRpcErrorCode2["MethodNotFound"] = -32601] = "MethodNotFound";
117236
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidParams"] = -32602] = "InvalidParams";
117237
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InternalError"] = -32603] = "InternalError";
117238
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ServiceUnavailable"] = -32000] = "ServiceUnavailable";
117239
+ JsonRpcErrorCode2[JsonRpcErrorCode2["NotFound"] = -32001] = "NotFound";
117240
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Conflict"] = -32002] = "Conflict";
117241
+ JsonRpcErrorCode2[JsonRpcErrorCode2["RateLimited"] = -32003] = "RateLimited";
117242
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Timeout"] = -32004] = "Timeout";
117243
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Forbidden"] = -32005] = "Forbidden";
117244
+ JsonRpcErrorCode2[JsonRpcErrorCode2["Unauthorized"] = -32006] = "Unauthorized";
117245
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ValidationError"] = -32007] = "ValidationError";
117246
+ JsonRpcErrorCode2[JsonRpcErrorCode2["ConfigurationError"] = -32008] = "ConfigurationError";
117247
+ JsonRpcErrorCode2[JsonRpcErrorCode2["InitializationFailed"] = -32009] = "InitializationFailed";
117248
+ JsonRpcErrorCode2[JsonRpcErrorCode2["DatabaseError"] = -32010] = "DatabaseError";
117249
+ JsonRpcErrorCode2[JsonRpcErrorCode2["SerializationError"] = -32070] = "SerializationError";
117250
+ JsonRpcErrorCode2[JsonRpcErrorCode2["UnknownError"] = -32099] = "UnknownError";
117251
+ })(JsonRpcErrorCode ||= {});
117252
+
117253
+ class McpError extends Error {
117254
+ code;
117255
+ data;
117256
+ constructor(code, message, data, options) {
117257
+ super(message, options);
117258
+ this.code = code;
117259
+ if (data) {
117260
+ this.data = data;
117261
+ }
117262
+ this.name = "McpError";
117263
+ Object.setPrototypeOf(this, McpError.prototype);
117264
+ if (Error.captureStackTrace) {
117265
+ Error.captureStackTrace(this, McpError);
117266
+ }
117267
+ }
117268
+ }
117269
+ var ErrorSchema = z.object({
117270
+ code: z.nativeEnum(JsonRpcErrorCode).describe("Standardized error code from JsonRpcErrorCode enum"),
117271
+ message: z.string().min(1, "Error message cannot be empty.").describe("Detailed human-readable error message"),
117272
+ data: z.record(z.string(), z.unknown()).optional().describe("Optional structured data providing more context about the error")
117273
+ }).describe("Schema for validating structured error objects, ensuring consistency in error reporting.");
117229
117274
  // package.json
117230
117275
  var package_default = {
117231
117276
  name: "mcp-ts-template",
117232
- version: "2.1.8",
117277
+ version: "2.2.1",
117233
117278
  mcpName: "io.github.cyanheads/mcp-ts-template",
117234
117279
  description: "The definitive, production-grade template for building powerful and scalable Model Context Protocol (MCP) servers with TypeScript, featuring built-in observability (OpenTelemetry), declarative tooling, robust error handling, and a modular, DI-driven architecture.",
117235
117280
  main: "dist/index.js",
@@ -117383,6 +117428,7 @@ var package_default = {
117383
117428
  "mcp",
117384
117429
  "model-context-protocol",
117385
117430
  "mcp-server",
117431
+ "elicitation",
117386
117432
  "observability",
117387
117433
  "opentelemetry",
117388
117434
  "otel",
@@ -117420,7 +117466,7 @@ var package_default = {
117420
117466
  // src/config/index.ts
117421
117467
  var packageManifest = package_default;
117422
117468
  var hasFileSystemAccess = typeof process !== "undefined" && typeof process.versions === "object" && process.versions !== null && typeof process.versions.node === "string";
117423
- import_dotenv.default.config();
117469
+ import_dotenv.default.config({ quiet: true });
117424
117470
  var emptyStringAsUndefined = (val) => {
117425
117471
  if (typeof val === "string" && val.trim() === "") {
117426
117472
  return;
@@ -117639,7 +117685,9 @@ var parseConfig = () => {
117639
117685
  if (process.stdout.isTTY) {
117640
117686
  console.error("❌ Invalid configuration found. Please check your environment variables.", parsedConfig.error.flatten().fieldErrors);
117641
117687
  }
117642
- process.exit(1);
117688
+ throw new McpError(-32008 /* ConfigurationError */, "Invalid application configuration.", {
117689
+ validationErrors: parsedConfig.error.flatten().fieldErrors
117690
+ });
117643
117691
  }
117644
117692
  return parsedConfig.data;
117645
117693
  };
@@ -117731,51 +117779,6 @@ var import_reflect_metadata2 = __toESM(require_Reflect(), 1);
117731
117779
  // src/utils/internal/errorHandler.ts
117732
117780
  var import_api3 = __toESM(require_src(), 1);
117733
117781
 
117734
- // src/types-global/errors.ts
117735
- var JsonRpcErrorCode;
117736
- ((JsonRpcErrorCode2) => {
117737
- JsonRpcErrorCode2[JsonRpcErrorCode2["ParseError"] = -32700] = "ParseError";
117738
- JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidRequest"] = -32600] = "InvalidRequest";
117739
- JsonRpcErrorCode2[JsonRpcErrorCode2["MethodNotFound"] = -32601] = "MethodNotFound";
117740
- JsonRpcErrorCode2[JsonRpcErrorCode2["InvalidParams"] = -32602] = "InvalidParams";
117741
- JsonRpcErrorCode2[JsonRpcErrorCode2["InternalError"] = -32603] = "InternalError";
117742
- JsonRpcErrorCode2[JsonRpcErrorCode2["ServiceUnavailable"] = -32000] = "ServiceUnavailable";
117743
- JsonRpcErrorCode2[JsonRpcErrorCode2["NotFound"] = -32001] = "NotFound";
117744
- JsonRpcErrorCode2[JsonRpcErrorCode2["Conflict"] = -32002] = "Conflict";
117745
- JsonRpcErrorCode2[JsonRpcErrorCode2["RateLimited"] = -32003] = "RateLimited";
117746
- JsonRpcErrorCode2[JsonRpcErrorCode2["Timeout"] = -32004] = "Timeout";
117747
- JsonRpcErrorCode2[JsonRpcErrorCode2["Forbidden"] = -32005] = "Forbidden";
117748
- JsonRpcErrorCode2[JsonRpcErrorCode2["Unauthorized"] = -32006] = "Unauthorized";
117749
- JsonRpcErrorCode2[JsonRpcErrorCode2["ValidationError"] = -32007] = "ValidationError";
117750
- JsonRpcErrorCode2[JsonRpcErrorCode2["ConfigurationError"] = -32008] = "ConfigurationError";
117751
- JsonRpcErrorCode2[JsonRpcErrorCode2["InitializationFailed"] = -32009] = "InitializationFailed";
117752
- JsonRpcErrorCode2[JsonRpcErrorCode2["DatabaseError"] = -32010] = "DatabaseError";
117753
- JsonRpcErrorCode2[JsonRpcErrorCode2["SerializationError"] = -32070] = "SerializationError";
117754
- JsonRpcErrorCode2[JsonRpcErrorCode2["UnknownError"] = -32099] = "UnknownError";
117755
- })(JsonRpcErrorCode ||= {});
117756
-
117757
- class McpError extends Error {
117758
- code;
117759
- data;
117760
- constructor(code, message, data, options) {
117761
- super(message, options);
117762
- this.code = code;
117763
- if (data) {
117764
- this.data = data;
117765
- }
117766
- this.name = "McpError";
117767
- Object.setPrototypeOf(this, McpError.prototype);
117768
- if (Error.captureStackTrace) {
117769
- Error.captureStackTrace(this, McpError);
117770
- }
117771
- }
117772
- }
117773
- var ErrorSchema = z.object({
117774
- code: z.nativeEnum(JsonRpcErrorCode).describe("Standardized error code from JsonRpcErrorCode enum"),
117775
- message: z.string().min(1, "Error message cannot be empty.").describe("Detailed human-readable error message"),
117776
- data: z.record(z.string(), z.unknown()).optional().describe("Optional structured data providing more context about the error")
117777
- }).describe("Schema for validating structured error objects, ensuring consistency in error reporting.");
117778
-
117779
117782
  // src/utils/internal/logger.ts
117780
117783
  var import_pino = __toESM(require_pino(), 1);
117781
117784
 
@@ -119228,9 +119231,12 @@ async function fetchWithTimeout(url, timeoutMs, context, options) {
119228
119231
  }
119229
119232
  }
119230
119233
 
119234
+ // src/index.ts
119235
+ var import_reflect_metadata3 = __toESM(require_Reflect(), 1);
119236
+
119231
119237
  // src/container/index.ts
119232
119238
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
119233
- var import_tsyringe14 = __toESM(require_cjs3(), 1);
119239
+ var import_tsyringe15 = __toESM(require_cjs3(), 1);
119234
119240
 
119235
119241
  // src/container/registrations/core.ts
119236
119242
  var import_tsyringe6 = __toESM(require_cjs3(), 1);
@@ -125473,7 +125479,7 @@ var registerCoreServices = () => {
125473
125479
  };
125474
125480
 
125475
125481
  // src/container/registrations/mcp.ts
125476
- var import_tsyringe13 = __toESM(require_cjs3(), 1);
125482
+ var import_tsyringe14 = __toESM(require_cjs3(), 1);
125477
125483
 
125478
125484
  // src/mcp-server/resources/resource-registration.ts
125479
125485
  var import_tsyringe7 = __toESM(require_cjs3(), 1);
@@ -128884,7 +128890,7 @@ var registerResources = (container3) => {
128884
128890
  };
128885
128891
 
128886
128892
  // src/mcp-server/server.ts
128887
- var import_tsyringe12 = __toESM(require_cjs3(), 1);
128893
+ var import_tsyringe9 = __toESM(require_cjs3(), 1);
128888
128894
 
128889
128895
  // src/mcp-server/tools/tool-registration.ts
128890
128896
  var import_tsyringe8 = __toESM(require_cjs3(), 1);
@@ -129140,7 +129146,7 @@ var imageTestTool = {
129140
129146
  responseFormatter: responseFormatter3
129141
129147
  };
129142
129148
 
129143
- // src/mcp-server/tools/definitions/template_madlibs_elicitation.tool.ts
129149
+ // src/mcp-server/tools/definitions/template-madlibs-elicitation.tool.ts
129144
129150
  var TOOL_NAME4 = "template_madlibs_elicitation";
129145
129151
  var TOOL_TITLE4 = "Mad Libs Elicitation Game";
129146
129152
  var TOOL_DESCRIPTION4 = "Plays a game of Mad Libs. If any parts of speech (noun, verb, adjective) are missing, it will use elicitation to ask the user for them.";
@@ -129178,7 +129184,10 @@ async function elicitAndValidate(partOfSpeech, sdkContext) {
129178
129184
  return validation.data;
129179
129185
  }
129180
129186
  async function madlibsToolLogic(input, appContext, sdkContext) {
129181
- logger.debug("Processing Mad Libs logic.", { ...appContext, toolInput: input });
129187
+ logger.debug("Processing Mad Libs logic.", {
129188
+ ...appContext,
129189
+ toolInput: input
129190
+ });
129182
129191
  const noun = input.noun ?? await elicitAndValidate("noun", sdkContext);
129183
129192
  const verb = input.verb ?? await elicitAndValidate("verb", sdkContext);
129184
129193
  const adjective = input.adjective ?? await elicitAndValidate("adjective", sdkContext);
@@ -129333,6 +129342,50 @@ var registerTools = (container3) => {
129333
129342
  }
129334
129343
  };
129335
129344
 
129345
+ // src/mcp-server/server.ts
129346
+ async function createMcpServerInstance() {
129347
+ const context = requestContextService.createRequestContext({
129348
+ operation: "createMcpServerInstance"
129349
+ });
129350
+ logger.info("Initializing MCP server instance", context);
129351
+ requestContextService.configure({
129352
+ appName: config.mcpServerName,
129353
+ appVersion: config.mcpServerVersion,
129354
+ environment: config.environment
129355
+ });
129356
+ const server = new McpServer({
129357
+ name: config.mcpServerName,
129358
+ version: config.mcpServerVersion,
129359
+ description: config.mcpServerDescription
129360
+ }, {
129361
+ capabilities: {
129362
+ logging: {},
129363
+ resources: { listChanged: true },
129364
+ tools: { listChanged: true },
129365
+ elicitation: {}
129366
+ }
129367
+ });
129368
+ try {
129369
+ logger.debug("Registering resources and tools via registries...", context);
129370
+ const toolRegistry = import_tsyringe9.container.resolve(ToolRegistry);
129371
+ await toolRegistry.registerAll(server);
129372
+ const resourceRegistry = import_tsyringe9.container.resolve(ResourceRegistry);
129373
+ await resourceRegistry.registerAll(server);
129374
+ logger.info("Resources and tools registered successfully", context);
129375
+ } catch (err) {
129376
+ logger.error("Failed to register resources/tools", {
129377
+ ...context,
129378
+ error: err instanceof Error ? err.message : String(err),
129379
+ stack: err instanceof Error ? err.stack : undefined
129380
+ });
129381
+ throw err;
129382
+ }
129383
+ return server;
129384
+ }
129385
+
129386
+ // src/mcp-server/transports/manager.ts
129387
+ var import_tsyringe13 = __toESM(require_cjs3(), 1);
129388
+
129336
129389
  // node_modules/hono/dist/http-exception.js
129337
129390
  var HTTPException = class extends Error {
129338
129391
  res;
@@ -131995,7 +132048,7 @@ var cors = (options) => {
131995
132048
  import http from "http";
131996
132049
  import { randomUUID } from "node:crypto";
131997
132050
  // src/mcp-server/transports/auth/authFactory.ts
131998
- var import_tsyringe11 = __toESM(require_cjs3(), 1);
132051
+ var import_tsyringe12 = __toESM(require_cjs3(), 1);
131999
132052
 
132000
132053
  // node_modules/jose/dist/webapi/lib/buffer_utils.js
132001
132054
  var encoder = new TextEncoder;
@@ -133513,7 +133566,7 @@ function createRemoteJWKSet(url, options) {
133513
133566
  return remoteJWKSet;
133514
133567
  }
133515
133568
  // src/mcp-server/transports/auth/strategies/jwtStrategy.ts
133516
- var import_tsyringe9 = __toESM(require_cjs3(), 1);
133569
+ var import_tsyringe10 = __toESM(require_cjs3(), 1);
133517
133570
  class JwtStrategy {
133518
133571
  config;
133519
133572
  logger;
@@ -133616,9 +133669,9 @@ class JwtStrategy {
133616
133669
  }
133617
133670
  }
133618
133671
  JwtStrategy = __legacyDecorateClassTS([
133619
- import_tsyringe9.injectable(),
133620
- __legacyDecorateParamTS(0, import_tsyringe9.inject(AppConfig)),
133621
- __legacyDecorateParamTS(1, import_tsyringe9.inject(Logger2)),
133672
+ import_tsyringe10.injectable(),
133673
+ __legacyDecorateParamTS(0, import_tsyringe10.inject(AppConfig)),
133674
+ __legacyDecorateParamTS(1, import_tsyringe10.inject(Logger2)),
133622
133675
  __legacyMetadataTS("design:paramtypes", [
133623
133676
  Object,
133624
133677
  Object
@@ -133626,7 +133679,7 @@ JwtStrategy = __legacyDecorateClassTS([
133626
133679
  ], JwtStrategy);
133627
133680
 
133628
133681
  // src/mcp-server/transports/auth/strategies/oauthStrategy.ts
133629
- var import_tsyringe10 = __toESM(require_cjs3(), 1);
133682
+ var import_tsyringe11 = __toESM(require_cjs3(), 1);
133630
133683
  class OauthStrategy {
133631
133684
  config;
133632
133685
  logger;
@@ -133723,9 +133776,9 @@ class OauthStrategy {
133723
133776
  }
133724
133777
  }
133725
133778
  OauthStrategy = __legacyDecorateClassTS([
133726
- import_tsyringe10.injectable(),
133727
- __legacyDecorateParamTS(0, import_tsyringe10.inject(AppConfig)),
133728
- __legacyDecorateParamTS(1, import_tsyringe10.inject(Logger2)),
133779
+ import_tsyringe11.injectable(),
133780
+ __legacyDecorateParamTS(0, import_tsyringe11.inject(AppConfig)),
133781
+ __legacyDecorateParamTS(1, import_tsyringe11.inject(Logger2)),
133729
133782
  __legacyMetadataTS("design:paramtypes", [
133730
133783
  Object,
133731
133784
  Object
@@ -133733,8 +133786,8 @@ OauthStrategy = __legacyDecorateClassTS([
133733
133786
  ], OauthStrategy);
133734
133787
 
133735
133788
  // src/mcp-server/transports/auth/authFactory.ts
133736
- import_tsyringe11.container.register(JwtStrategy, { useClass: JwtStrategy });
133737
- import_tsyringe11.container.register(OauthStrategy, { useClass: OauthStrategy });
133789
+ import_tsyringe12.container.register(JwtStrategy, { useClass: JwtStrategy });
133790
+ import_tsyringe12.container.register(OauthStrategy, { useClass: OauthStrategy });
133738
133791
  function createAuthStrategy() {
133739
133792
  const context = requestContextService.createRequestContext({
133740
133793
  operation: "createAuthStrategy",
@@ -133744,10 +133797,10 @@ function createAuthStrategy() {
133744
133797
  switch (config.mcpAuthMode) {
133745
133798
  case "jwt":
133746
133799
  logger.debug("Resolving JWT strategy from container.", context);
133747
- return import_tsyringe11.container.resolve(JwtStrategy);
133800
+ return import_tsyringe12.container.resolve(JwtStrategy);
133748
133801
  case "oauth":
133749
133802
  logger.debug("Resolving OAuth strategy from container.", context);
133750
- return import_tsyringe11.container.resolve(OauthStrategy);
133803
+ return import_tsyringe12.container.resolve(OauthStrategy);
133751
133804
  case "none":
133752
133805
  logger.info("Authentication is disabled ('none' mode).", context);
133753
133806
  return null;
@@ -133756,6 +133809,53 @@ function createAuthStrategy() {
133756
133809
  throw new Error(`Unknown authentication mode: ${String(config.mcpAuthMode)}`);
133757
133810
  }
133758
133811
  }
133812
+ // src/mcp-server/transports/auth/authMiddleware.ts
133813
+ function createAuthMiddleware(strategy) {
133814
+ return async function authMiddleware(c, next) {
133815
+ const context = requestContextService.createRequestContext({
133816
+ operation: "authMiddleware",
133817
+ additionalContext: {
133818
+ method: c.req.method,
133819
+ path: c.req.path
133820
+ }
133821
+ });
133822
+ logger.debug("Initiating authentication check.", context);
133823
+ const authHeader = c.req.header("Authorization");
133824
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
133825
+ logger.warning("Authorization header missing or invalid.", context);
133826
+ throw new McpError(-32006 /* Unauthorized */, "Missing or invalid Authorization header. Bearer scheme required.", context);
133827
+ }
133828
+ const token = authHeader.substring(7);
133829
+ if (!token) {
133830
+ logger.warning("Bearer token is missing from Authorization header.", context);
133831
+ throw new McpError(-32006 /* Unauthorized */, "Authentication token is missing.", context);
133832
+ }
133833
+ logger.debug("Extracted Bearer token, proceeding to verification.", context);
133834
+ try {
133835
+ const authInfo = await strategy.verify(token);
133836
+ const authLogContext = {
133837
+ ...context,
133838
+ ...authInfo.tenantId ? { tenantId: authInfo.tenantId } : {},
133839
+ clientId: authInfo.clientId,
133840
+ subject: authInfo.subject,
133841
+ scopes: authInfo.scopes
133842
+ };
133843
+ logger.info("Authentication successful. Auth context populated.", authLogContext);
133844
+ await authContext.run({ authInfo }, next);
133845
+ } catch (error2) {
133846
+ logger.warning("Authentication verification failed.", {
133847
+ ...context,
133848
+ error: error2 instanceof Error ? error2.message : String(error2)
133849
+ });
133850
+ throw ErrorHandler.handleError(error2, {
133851
+ operation: "authMiddlewareVerification",
133852
+ context,
133853
+ rethrow: true,
133854
+ errorCode: -32006 /* Unauthorized */
133855
+ });
133856
+ }
133857
+ };
133858
+ }
133759
133859
  // src/mcp-server/transports/http/httpErrorHandler.ts
133760
133860
  var httpErrorHandler = async (err, c) => {
133761
133861
  const context = requestContextService.createRequestContext({
@@ -133841,6 +133941,13 @@ var httpErrorHandler = async (err, c) => {
133841
133941
  };
133842
133942
 
133843
133943
  // src/mcp-server/transports/http/httpTransport.ts
133944
+ class McpSessionTransport extends StreamableHTTPTransport {
133945
+ sessionId;
133946
+ constructor(sessionId) {
133947
+ super();
133948
+ this.sessionId = sessionId;
133949
+ }
133950
+ }
133844
133951
  function createHttpApp(mcpServer, parentContext) {
133845
133952
  const app = new Hono2;
133846
133953
  const transportContext = {
@@ -133875,14 +133982,22 @@ function createHttpApp(mcpServer, parentContext) {
133875
133982
  }
133876
133983
  });
133877
133984
  });
133985
+ const authStrategy = createAuthStrategy();
133986
+ if (authStrategy) {
133987
+ const authMiddleware = createAuthMiddleware(authStrategy);
133988
+ app.use(config.mcpHttpEndpointPath, authMiddleware);
133989
+ logger.info("Authentication middleware enabled for MCP endpoint.", transportContext);
133990
+ } else {
133991
+ logger.info("Authentication is disabled; MCP endpoint is unprotected.", transportContext);
133992
+ }
133878
133993
  app.all(config.mcpHttpEndpointPath, async (c) => {
133879
133994
  logger.debug("Handling MCP request.", {
133880
133995
  ...transportContext,
133881
133996
  path: c.req.path,
133882
133997
  method: c.req.method
133883
133998
  });
133884
- const transport = new StreamableHTTPTransport;
133885
- transport.sessionId = c.req.header("mcp-session-id") ?? randomUUID();
133999
+ const sessionId = c.req.header("mcp-session-id") ?? randomUUID();
134000
+ const transport = new McpSessionTransport(sessionId);
133886
134001
  const handleRpc = async () => {
133887
134002
  await mcpServer.connect(transport);
133888
134003
  const response = await transport.handleRequest(c);
@@ -133891,30 +134006,12 @@ function createHttpApp(mcpServer, parentContext) {
133891
134006
  }
133892
134007
  return c.body(null, 204);
133893
134008
  };
133894
- const protectRpc = config.mcpAuthMode !== "none";
133895
134009
  try {
133896
- if (!protectRpc) {
133897
- return await handleRpc();
133898
- }
133899
- const authHeader = c.req.header("Authorization");
133900
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
133901
- logger.warning("Authorization header missing or invalid (HTTP JSON-RPC).", {
133902
- ...transportContext,
133903
- method: c.req.method,
133904
- path: c.req.path
133905
- });
133906
- throw new McpError(-32006 /* Unauthorized */, "Missing or invalid Authorization header. Bearer scheme required.", transportContext);
134010
+ const store = authContext.getStore();
134011
+ if (store) {
134012
+ return await authContext.run(store, handleRpc);
133907
134013
  }
133908
- const strategy = createAuthStrategy();
133909
- if (!strategy) {
133910
- logger.warning("Auth mode indicates protection, but no strategy was created. Proceeding unauthenticated.", transportContext);
133911
- return await handleRpc();
133912
- }
133913
- const token = authHeader.substring(7);
133914
- const authInfo = await strategy.verify(token);
133915
- return await authContext.run({ authInfo }, async () => {
133916
- return await handleRpc();
133917
- });
134014
+ return await handleRpc();
133918
134015
  } catch (err) {
133919
134016
  await transport.close?.();
133920
134017
  throw err instanceof Error ? err : new Error(String(err));
@@ -133985,7 +134082,25 @@ async function startHttpTransport(mcpServer, parentContext) {
133985
134082
  const app = createHttpApp(mcpServer, transportContext);
133986
134083
  const server = await startHttpServerWithRetry(app, config.mcpHttpPort, config.mcpHttpHost, config.mcpHttpMaxPortRetries, transportContext);
133987
134084
  logger.info("HTTP transport started successfully.", transportContext);
133988
- return { app, server };
134085
+ return server;
134086
+ }
134087
+ async function stopHttpTransport(server, parentContext) {
134088
+ const operationContext = {
134089
+ ...parentContext,
134090
+ operation: "stopHttpTransport",
134091
+ transportType: "Http"
134092
+ };
134093
+ logger.info("Attempting to stop http transport...", operationContext);
134094
+ return new Promise((resolve, reject) => {
134095
+ server.close((err) => {
134096
+ if (err) {
134097
+ logger.error("Error closing HTTP server.", err, operationContext);
134098
+ return reject(err);
134099
+ }
134100
+ logger.info("HTTP server closed successfully.", operationContext);
134101
+ resolve();
134102
+ });
134103
+ });
133989
134104
  }
133990
134105
 
133991
134106
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
@@ -134102,6 +134217,7 @@ async function startStdioTransport(server, parentContext) {
134102
134217
  (MCP Spec: 2025-03-26 Stdio Transport)
134103
134218
  `);
134104
134219
  }
134220
+ return server;
134105
134221
  } catch (err) {
134106
134222
  throw ErrorHandler.handleError(err, {
134107
134223
  operation: "connectStdioTransport",
@@ -134111,100 +134227,89 @@ async function startStdioTransport(server, parentContext) {
134111
134227
  });
134112
134228
  }
134113
134229
  }
134114
- // src/mcp-server/server.ts
134115
- async function createMcpServerInstance() {
134116
- const context = requestContextService.createRequestContext({
134117
- operation: "createMcpServerInstance"
134118
- });
134119
- logger.info("Initializing MCP server instance", context);
134120
- requestContextService.configure({
134121
- appName: config.mcpServerName,
134122
- appVersion: config.mcpServerVersion,
134123
- environment: config.environment
134124
- });
134125
- const server = new McpServer({
134126
- name: config.mcpServerName,
134127
- version: config.mcpServerVersion,
134128
- description: config.mcpServerDescription
134129
- }, {
134130
- capabilities: {
134131
- logging: {},
134132
- resources: { listChanged: true },
134133
- tools: { listChanged: true },
134134
- elicitation: {}
134135
- }
134136
- });
134137
- try {
134138
- logger.debug("Registering resources and tools via registries...", context);
134139
- const toolRegistry = import_tsyringe12.container.resolve(ToolRegistry);
134140
- await toolRegistry.registerAll(server);
134141
- const resourceRegistry = import_tsyringe12.container.resolve(ResourceRegistry);
134142
- await resourceRegistry.registerAll(server);
134143
- logger.info("Resources and tools registered successfully", context);
134144
- } catch (err) {
134145
- logger.error("Failed to register resources/tools", {
134146
- ...context,
134147
- error: err instanceof Error ? err.message : String(err),
134148
- stack: err instanceof Error ? err.stack : undefined
134149
- });
134150
- throw err;
134230
+ async function stopStdioTransport(server, parentContext) {
134231
+ const operationContext = {
134232
+ ...parentContext,
134233
+ operation: "stopStdioTransport",
134234
+ transportType: "Stdio"
134235
+ };
134236
+ logger.info("Attempting to stop stdio transport...", operationContext);
134237
+ if (server) {
134238
+ await server.close();
134239
+ logger.info("Stdio transport stopped successfully.", operationContext);
134151
134240
  }
134152
- return server;
134153
134241
  }
134154
- async function startTransport() {
134155
- const transportType = config.mcpTransportType;
134156
- const context = requestContextService.createRequestContext({
134157
- operation: "startTransport",
134158
- transport: transportType
134159
- });
134160
- logger.info(`Starting transport: ${transportType}`, context);
134161
- if (transportType === "http") {
134162
- const mcpServer = await createMcpServerInstance();
134163
- const { server } = await startHttpTransport(mcpServer, context);
134164
- return { server };
134165
- }
134166
- if (transportType === "stdio") {
134167
- const server = await createMcpServerInstance();
134168
- await startStdioTransport(server, context);
134169
- return { server };
134170
- }
134171
- logger.crit(`Unsupported transport type configured: ${transportType}`, context);
134172
- throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
134173
- }
134174
- async function initializeAndStartServer() {
134175
- const context = requestContextService.createRequestContext({
134176
- operation: "initializeAndStartServer"
134177
- });
134178
- logger.info("MCP Server initialization sequence started.", context);
134179
- try {
134180
- const result = await startTransport();
134181
- logger.info("MCP Server initialization sequence completed successfully.", context);
134182
- return result;
134183
- } catch (err) {
134184
- logger.crit("Critical error during MCP server initialization.", {
134185
- ...context,
134186
- error: err instanceof Error ? err.message : String(err),
134187
- stack: err instanceof Error ? err.stack : undefined
134188
- });
134189
- ErrorHandler.handleError(err, {
134190
- ...context,
134191
- operation: "initializeAndStartServer_Catch",
134192
- critical: true
134242
+
134243
+ // src/mcp-server/transports/manager.ts
134244
+ class TransportManager {
134245
+ config;
134246
+ logger;
134247
+ createMcpServer;
134248
+ serverInstance = null;
134249
+ constructor(config2, logger2, createMcpServer) {
134250
+ this.config = config2;
134251
+ this.logger = logger2;
134252
+ this.createMcpServer = createMcpServer;
134253
+ }
134254
+ async start() {
134255
+ const context = requestContextService.createRequestContext({
134256
+ operation: "TransportManager.start",
134257
+ transport: this.config.mcpTransportType
134258
+ });
134259
+ this.logger.info(`Starting transport: ${this.config.mcpTransportType}`, context);
134260
+ const mcpServer = await this.createMcpServer();
134261
+ if (this.config.mcpTransportType === "http") {
134262
+ this.serverInstance = await startHttpTransport(mcpServer, context);
134263
+ } else if (this.config.mcpTransportType === "stdio") {
134264
+ this.serverInstance = await startStdioTransport(mcpServer, context);
134265
+ } else {
134266
+ const transportType = String(this.config.mcpTransportType);
134267
+ const error2 = new Error(`Unsupported transport type: ${transportType}`);
134268
+ this.logger.crit(error2.message, context);
134269
+ throw error2;
134270
+ }
134271
+ }
134272
+ async stop(signal) {
134273
+ const context = requestContextService.createRequestContext({
134274
+ operation: "TransportManager.stop",
134275
+ signal
134193
134276
  });
134194
- logger.info("Exiting process due to critical initialization error.", context);
134195
- process.exit(1);
134277
+ if (!this.serverInstance) {
134278
+ this.logger.warning("Stop called but no active server instance found.", context);
134279
+ return;
134280
+ }
134281
+ if (this.config.mcpTransportType === "http") {
134282
+ await stopHttpTransport(this.serverInstance, context);
134283
+ } else if (this.config.mcpTransportType === "stdio") {
134284
+ await stopStdioTransport(this.serverInstance, context);
134285
+ }
134286
+ }
134287
+ getServer() {
134288
+ return this.serverInstance;
134196
134289
  }
134197
134290
  }
134291
+ TransportManager = __legacyDecorateClassTS([
134292
+ import_tsyringe13.injectable(),
134293
+ __legacyDecorateParamTS(0, import_tsyringe13.inject(AppConfig)),
134294
+ __legacyDecorateParamTS(1, import_tsyringe13.inject(Logger2)),
134295
+ __legacyDecorateParamTS(2, import_tsyringe13.inject(CreateMcpServerInstance)),
134296
+ __legacyMetadataTS("design:paramtypes", [
134297
+ typeof AppConfigType === "undefined" ? Object : AppConfigType,
134298
+ Object,
134299
+ Function
134300
+ ])
134301
+ ], TransportManager);
134198
134302
 
134199
134303
  // src/container/registrations/mcp.ts
134200
134304
  var registerMcpServices = () => {
134201
- import_tsyringe13.container.registerSingleton(ToolRegistry);
134202
- import_tsyringe13.container.registerSingleton(ResourceRegistry);
134203
- registerTools(import_tsyringe13.container);
134204
- registerResources(import_tsyringe13.container);
134205
- import_tsyringe13.container.register(CreateMcpServerInstance, {
134305
+ import_tsyringe14.container.registerSingleton(ToolRegistry);
134306
+ import_tsyringe14.container.registerSingleton(ResourceRegistry);
134307
+ registerTools(import_tsyringe14.container);
134308
+ registerResources(import_tsyringe14.container);
134309
+ import_tsyringe14.container.register(CreateMcpServerInstance, {
134206
134310
  useValue: createMcpServerInstance
134207
134311
  });
134312
+ import_tsyringe14.container.registerSingleton(TransportManagerToken, TransportManager);
134208
134313
  logger.info("MCP services and factories registered with the DI container.");
134209
134314
  };
134210
134315
 
@@ -134218,12 +134323,11 @@ function composeContainer() {
134218
134323
  registerMcpServices();
134219
134324
  isContainerComposed = true;
134220
134325
  }
134221
- var container_default = import_tsyringe14.container;
134326
+ var container_default = import_tsyringe15.container;
134222
134327
 
134223
134328
  // src/index.ts
134224
134329
  var config2;
134225
- var mcpStdioServer;
134226
- var actualHttpServer;
134330
+ var transportManager;
134227
134331
  var isShuttingDown = false;
134228
134332
  var shutdown = async (signal) => {
134229
134333
  if (isShuttingDown) {
@@ -134236,25 +134340,9 @@ var shutdown = async (signal) => {
134236
134340
  });
134237
134341
  logger.info(`Received ${signal}. Initiating graceful shutdown...`, shutdownContext);
134238
134342
  try {
134239
- let closePromise = Promise.resolve();
134240
- const transportType = config2.mcpTransportType;
134241
- if (transportType === "stdio" && mcpStdioServer) {
134242
- logger.info("Attempting to close main MCP server (STDIO)...", shutdownContext);
134243
- closePromise = mcpStdioServer.close();
134244
- } else if (transportType === "http" && actualHttpServer) {
134245
- logger.info("Attempting to close HTTP server...", shutdownContext);
134246
- closePromise = new Promise((resolve, reject) => {
134247
- actualHttpServer.close((err) => {
134248
- if (err) {
134249
- logger.error("Error closing HTTP server.", err, shutdownContext);
134250
- return reject(err);
134251
- }
134252
- logger.info("HTTP server closed successfully.", shutdownContext);
134253
- resolve();
134254
- });
134255
- });
134343
+ if (transportManager) {
134344
+ await transportManager.stop(signal);
134256
134345
  }
134257
- await closePromise;
134258
134346
  logger.info("Graceful shutdown completed successfully. Exiting.", shutdownContext);
134259
134347
  await shutdownOpenTelemetry();
134260
134348
  await logger.close();
@@ -134268,8 +134356,16 @@ var shutdown = async (signal) => {
134268
134356
  }
134269
134357
  };
134270
134358
  var start = async () => {
134271
- composeContainer();
134272
- config2 = container_default.resolve(AppConfig);
134359
+ try {
134360
+ composeContainer();
134361
+ config2 = container_default.resolve(AppConfig);
134362
+ } catch (_error) {
134363
+ if (process.stdout.isTTY) {
134364
+ console.error("Halting due to critical configuration error.");
134365
+ }
134366
+ await shutdownOpenTelemetry();
134367
+ process.exit(1);
134368
+ }
134273
134369
  await initializePerformance_Hrt();
134274
134370
  const validMcpLogLevels = [
134275
134371
  "debug",
@@ -134293,21 +134389,16 @@ var start = async () => {
134293
134389
  await logger.initialize(validatedMcpLogLevel);
134294
134390
  logger.info(`Logger initialized. Effective MCP logging level: ${validatedMcpLogLevel}.`, requestContextService.createRequestContext({ operation: "LoggerInit" }));
134295
134391
  logger.info(`Storage service initialized with provider: ${config2.storage.providerType}`, requestContextService.createRequestContext({ operation: "StorageInit" }));
134296
- const transportType = config2.mcpTransportType;
134392
+ transportManager = container_default.resolve(TransportManagerToken);
134297
134393
  const startupContext = requestContextService.createRequestContext({
134298
- operation: `ServerStartupSequence_${transportType}`,
134394
+ operation: "ServerStartup",
134299
134395
  applicationName: config2.mcpServerName,
134300
134396
  applicationVersion: config2.mcpServerVersion,
134301
134397
  nodeEnvironment: config2.environment
134302
134398
  });
134303
- logger.info(`Starting ${config2.mcpServerName} (Version: ${config2.mcpServerVersion}, Transport: ${transportType}, Env: ${config2.environment})...`, startupContext);
134399
+ logger.info(`Starting ${config2.mcpServerName} (v${config2.mcpServerVersion})...`, startupContext);
134304
134400
  try {
134305
- const { server } = await initializeAndStartServer();
134306
- if (transportType === "http") {
134307
- actualHttpServer = server;
134308
- } else if (transportType === "stdio") {
134309
- mcpStdioServer = server;
134310
- }
134401
+ await transportManager.start();
134311
134402
  logger.info(`${config2.mcpServerName} is now running and ready.`, startupContext);
134312
134403
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
134313
134404
  process.on("SIGINT", () => void shutdown("SIGINT"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ts-template",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-template",
5
5
  "description": "The definitive, production-grade template for building powerful and scalable Model Context Protocol (MCP) servers with TypeScript, featuring built-in observability (OpenTelemetry), declarative tooling, robust error handling, and a modular, DI-driven architecture.",
6
6
  "main": "dist/index.js",