mcp-ts-template 2.2.5 → 2.2.7

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 +2 -1
  2. package/dist/index.js +508 -64
  3. package/package.json +2 -2
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.3-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.23-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-87.86%25-brightgreen.svg?style=flat-square)](./coverage/lcov-report/)
8
+ [![Version](https://img.shields.io/badge/Version-2.2.7-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.23-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Code Coverage](https://img.shields.io/badge/Coverage-87.86%25-brightgreen.svg?style=flat-square)](./coverage/lcov-report/)
9
9
 
10
10
  </div>
11
11
 
@@ -15,6 +15,7 @@
15
15
 
16
16
  - **Declarative Tools & Resources**: Define capabilities in single, self-contained files. The framework handles registration and execution.
17
17
  - **Elicitation Support**: Tools can interactively prompt the user for missing parameters during execution, streamlining user workflows.
18
+ - **Speech-to-Text & Text-to-Speech**: Integrated speech service with providers for ElevenLabs and OpenAI Whisper.
18
19
  - **Robust Error Handling**: A unified `McpError` system ensures consistent, structured error responses across the server.
19
20
  - **Pluggable Authentication**: Secure your server with zero-fuss support for `none`, `jwt`, or `oauth` modes.
20
21
  - **Abstracted Storage**: Swap storage backends (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2`) without changing business logic.
package/dist/index.js CHANGED
@@ -117536,7 +117536,7 @@ var ErrorSchema = z.object({
117536
117536
  // package.json
117537
117537
  var package_default = {
117538
117538
  name: "mcp-ts-template",
117539
- version: "2.2.4",
117539
+ version: "2.2.5",
117540
117540
  mcpName: "io.github.cyanheads/mcp-ts-template",
117541
117541
  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.",
117542
117542
  main: "dist/index.js",
@@ -117619,7 +117619,7 @@ var package_default = {
117619
117619
  jose: "^6.1.0",
117620
117620
  "js-yaml": "^4.1.0",
117621
117621
  "node-cron": "^4.2.1",
117622
- openai: "^5.23.1",
117622
+ openai: "^5.23.2",
117623
117623
  papaparse: "^5.5.3",
117624
117624
  "partial-json": "^0.1.7",
117625
117625
  pino: "^9.12.0",
@@ -117848,7 +117848,26 @@ var ConfigSchema = z.object({
117848
117848
  }
117849
117849
  return str;
117850
117850
  }, z.enum(["NONE", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE", "ALL"])).default("INFO")
117851
- })
117851
+ }),
117852
+ speech: z.object({
117853
+ tts: z.object({
117854
+ enabled: z.coerce.boolean().default(false),
117855
+ provider: z.enum(["elevenlabs"]).default("elevenlabs"),
117856
+ apiKey: z.string().optional(),
117857
+ baseUrl: z.string().url().optional(),
117858
+ defaultVoiceId: z.string().optional(),
117859
+ defaultModelId: z.string().optional(),
117860
+ timeout: z.coerce.number().optional()
117861
+ }).optional(),
117862
+ stt: z.object({
117863
+ enabled: z.coerce.boolean().default(false),
117864
+ provider: z.enum(["openai-whisper"]).default("openai-whisper"),
117865
+ apiKey: z.string().optional(),
117866
+ baseUrl: z.string().url().optional(),
117867
+ defaultModelId: z.string().optional(),
117868
+ timeout: z.coerce.number().optional()
117869
+ }).optional()
117870
+ }).optional()
117852
117871
  });
117853
117872
  var parseConfig = () => {
117854
117873
  const env = process.env;
@@ -117915,6 +117934,25 @@ var parseConfig = () => {
117915
117934
  samplingRatio: env.OTEL_TRACES_SAMPLER_ARG,
117916
117935
  logLevel: env.OTEL_LOG_LEVEL
117917
117936
  },
117937
+ speech: env.SPEECH_TTS_ENABLED || env.SPEECH_STT_ENABLED ? {
117938
+ tts: env.SPEECH_TTS_ENABLED ? {
117939
+ enabled: env.SPEECH_TTS_ENABLED,
117940
+ provider: env.SPEECH_TTS_PROVIDER,
117941
+ apiKey: env.SPEECH_TTS_API_KEY,
117942
+ baseUrl: env.SPEECH_TTS_BASE_URL,
117943
+ defaultVoiceId: env.SPEECH_TTS_DEFAULT_VOICE_ID,
117944
+ defaultModelId: env.SPEECH_TTS_DEFAULT_MODEL_ID,
117945
+ timeout: env.SPEECH_TTS_TIMEOUT
117946
+ } : undefined,
117947
+ stt: env.SPEECH_STT_ENABLED ? {
117948
+ enabled: env.SPEECH_STT_ENABLED,
117949
+ provider: env.SPEECH_STT_PROVIDER,
117950
+ apiKey: env.SPEECH_STT_API_KEY,
117951
+ baseUrl: env.SPEECH_STT_BASE_URL,
117952
+ defaultModelId: env.SPEECH_STT_DEFAULT_MODEL_ID,
117953
+ timeout: env.SPEECH_STT_TIMEOUT
117954
+ } : undefined
117955
+ } : undefined,
117918
117956
  mcpServerName: env.MCP_SERVER_NAME,
117919
117957
  mcpServerVersion: env.MCP_SERVER_VERSION,
117920
117958
  mcpServerDescription: env.MCP_SERVER_DESCRIPTION
@@ -119310,6 +119348,7 @@ var CreateMcpServerInstance = Symbol("CreateMcpServerInstance");
119310
119348
  var RateLimiterService = Symbol("RateLimiterService");
119311
119349
  var TransportManagerToken = Symbol("TransportManager");
119312
119350
  var SupabaseAdminClient = Symbol("SupabaseAdminClient");
119351
+ var SpeechService = Symbol("SpeechService");
119313
119352
 
119314
119353
  // src/utils/security/rateLimiter.ts
119315
119354
  class RateLimiter {
@@ -119510,10 +119549,10 @@ async function fetchWithTimeout(url, timeoutMs, context, options) {
119510
119549
 
119511
119550
  // src/container/index.ts
119512
119551
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
119513
- var import_tsyringe17 = __toESM(require_cjs3(), 1);
119552
+ var import_tsyringe19 = __toESM(require_cjs3(), 1);
119514
119553
 
119515
119554
  // src/container/registrations/core.ts
119516
- var import_tsyringe6 = __toESM(require_cjs3(), 1);
119555
+ var import_tsyringe8 = __toESM(require_cjs3(), 1);
119517
119556
  var import_supabase_js2 = __toESM(require_main6(), 1);
119518
119557
 
119519
119558
  // node_modules/openai/internal/tslib.mjs
@@ -119744,7 +119783,7 @@ var safeJSON = (text) => {
119744
119783
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
119745
119784
 
119746
119785
  // node_modules/openai/version.mjs
119747
- var VERSION = "5.23.1";
119786
+ var VERSION = "5.23.2";
119748
119787
 
119749
119788
  // node_modules/openai/internal/detect-platform.mjs
119750
119789
  var isRunningInBrowser = () => {
@@ -125145,8 +125184,378 @@ OpenRouterProvider = __legacyDecorateClassTS([
125145
125184
  ])
125146
125185
  ], OpenRouterProvider);
125147
125186
 
125148
- // src/storage/core/StorageService.ts
125187
+ // src/services/speech/providers/elevenlabs.provider.ts
125149
125188
  var import_tsyringe3 = __toESM(require_cjs3(), 1);
125189
+ class ElevenLabsProvider {
125190
+ name = "elevenlabs";
125191
+ supportsTTS = true;
125192
+ supportsSTT = false;
125193
+ apiKey;
125194
+ baseUrl;
125195
+ defaultVoiceId;
125196
+ defaultModelId;
125197
+ timeout;
125198
+ constructor(config2) {
125199
+ if (!config2.apiKey) {
125200
+ throw new McpError(-32602 /* InvalidParams */, "ElevenLabs API key is required");
125201
+ }
125202
+ this.apiKey = config2.apiKey;
125203
+ this.baseUrl = config2.baseUrl || "https://api.elevenlabs.io/v1";
125204
+ this.defaultVoiceId = config2.defaultVoiceId || "EXAVITQu4vr4xnSDxMaL";
125205
+ this.defaultModelId = config2.defaultModelId || "eleven_monolingual_v1";
125206
+ this.timeout = config2.timeout || 30000;
125207
+ logger.info(`ElevenLabs TTS provider initialized: ${this.baseUrl}, voice=${this.defaultVoiceId}`);
125208
+ }
125209
+ async textToSpeech(options) {
125210
+ const context = requestContextService.createRequestContext({
125211
+ operation: "elevenlabs-tts",
125212
+ ...options.context || {}
125213
+ });
125214
+ const voiceId = options.voice?.voiceId || this.defaultVoiceId;
125215
+ const modelId = options.modelId || this.defaultModelId;
125216
+ logger.debug("Converting text to speech with ElevenLabs", context);
125217
+ if (!options.text || options.text.trim().length === 0) {
125218
+ throw new McpError(-32602 /* InvalidParams */, "Text cannot be empty", context);
125219
+ }
125220
+ if (options.text.length > 5000) {
125221
+ throw new McpError(-32602 /* InvalidParams */, "Text exceeds maximum length of 5000 characters", context);
125222
+ }
125223
+ const url = `${this.baseUrl}/text-to-speech/${voiceId}`;
125224
+ const voiceSettings = {
125225
+ stability: options.voice?.stability ?? 0.5,
125226
+ similarity_boost: options.voice?.similarityBoost ?? 0.75,
125227
+ style: options.voice?.style ?? 0,
125228
+ use_speaker_boost: true
125229
+ };
125230
+ const requestBody = {
125231
+ text: options.text,
125232
+ model_id: modelId,
125233
+ voice_settings: voiceSettings
125234
+ };
125235
+ try {
125236
+ const response = await fetchWithTimeout(url, this.timeout, context, {
125237
+ method: "POST",
125238
+ headers: {
125239
+ "Content-Type": "application/json",
125240
+ "xi-api-key": this.apiKey
125241
+ },
125242
+ body: JSON.stringify(requestBody)
125243
+ });
125244
+ if (!response.ok) {
125245
+ const errorText = await response.text();
125246
+ logger.error(`ElevenLabs API error: ${response.status}`, context);
125247
+ throw new McpError(-32603 /* InternalError */, `ElevenLabs API error: ${response.status} - ${errorText}`, context);
125248
+ }
125249
+ const audioBuffer = Buffer.from(await response.arrayBuffer());
125250
+ logger.info(`Text-to-speech conversion successful (voice=${voiceId}, ${audioBuffer.length} bytes)`, context);
125251
+ return {
125252
+ audio: audioBuffer,
125253
+ format: "mp3",
125254
+ characterCount: options.text.length,
125255
+ metadata: {
125256
+ voiceId,
125257
+ modelId,
125258
+ provider: this.name
125259
+ }
125260
+ };
125261
+ } catch (error2) {
125262
+ if (error2 instanceof McpError) {
125263
+ throw error2;
125264
+ }
125265
+ logger.error("Failed to convert text to speech", error2 instanceof Error ? error2 : new Error(String(error2)), context);
125266
+ throw new McpError(-32603 /* InternalError */, `Failed to convert text to speech: ${error2 instanceof Error ? error2.message : "Unknown error"}`, context);
125267
+ }
125268
+ }
125269
+ speechToText(_options) {
125270
+ throw new McpError(-32601 /* MethodNotFound */, "Speech-to-text is not supported by ElevenLabs provider");
125271
+ }
125272
+ async getVoices() {
125273
+ const context = requestContextService.createRequestContext({
125274
+ operation: "elevenlabs-getVoices"
125275
+ });
125276
+ logger.debug("Fetching available voices from ElevenLabs", context);
125277
+ const url = `${this.baseUrl}/voices`;
125278
+ try {
125279
+ const response = await fetchWithTimeout(url, this.timeout, context, {
125280
+ method: "GET",
125281
+ headers: {
125282
+ "xi-api-key": this.apiKey
125283
+ }
125284
+ });
125285
+ if (!response.ok) {
125286
+ const errorText = await response.text();
125287
+ logger.error(`Failed to fetch voices: ${response.status}`, context);
125288
+ throw new McpError(-32603 /* InternalError */, `Failed to fetch voices: ${response.status} - ${errorText}`);
125289
+ }
125290
+ const data = await response.json();
125291
+ const voices = data.voices.map((v) => ({
125292
+ id: v.voice_id,
125293
+ name: v.name,
125294
+ ...v.description !== undefined && { description: v.description },
125295
+ ...v.category !== undefined && { category: v.category },
125296
+ ...v.preview_url !== undefined && { previewUrl: v.preview_url },
125297
+ ...v.labels?.gender !== undefined && {
125298
+ gender: v.labels.gender
125299
+ },
125300
+ metadata: {
125301
+ labels: v.labels
125302
+ }
125303
+ }));
125304
+ logger.info(`Successfully fetched ${voices.length} voices`, context);
125305
+ return voices;
125306
+ } catch (error2) {
125307
+ if (error2 instanceof McpError) {
125308
+ throw error2;
125309
+ }
125310
+ logger.error("Failed to fetch voices", error2 instanceof Error ? error2 : new Error(String(error2)), context);
125311
+ throw new McpError(-32603 /* InternalError */, `Failed to fetch voices: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
125312
+ }
125313
+ }
125314
+ async healthCheck() {
125315
+ try {
125316
+ await this.getVoices();
125317
+ return true;
125318
+ } catch (error2) {
125319
+ const context = requestContextService.createRequestContext({
125320
+ operation: "elevenlabs-healthCheck"
125321
+ });
125322
+ logger.error("ElevenLabs health check failed", error2 instanceof Error ? error2 : new Error(String(error2)), context);
125323
+ return false;
125324
+ }
125325
+ }
125326
+ }
125327
+ ElevenLabsProvider = __legacyDecorateClassTS([
125328
+ import_tsyringe3.injectable(),
125329
+ __legacyMetadataTS("design:paramtypes", [
125330
+ typeof SpeechProviderConfig === "undefined" ? Object : SpeechProviderConfig
125331
+ ])
125332
+ ], ElevenLabsProvider);
125333
+
125334
+ // src/services/speech/providers/whisper.provider.ts
125335
+ var import_tsyringe4 = __toESM(require_cjs3(), 1);
125336
+ class WhisperProvider {
125337
+ name = "openai-whisper";
125338
+ supportsTTS = false;
125339
+ supportsSTT = true;
125340
+ apiKey;
125341
+ baseUrl;
125342
+ defaultModelId;
125343
+ timeout;
125344
+ constructor(config2) {
125345
+ if (!config2.apiKey) {
125346
+ throw new McpError(-32602 /* InvalidParams */, "OpenAI API key is required");
125347
+ }
125348
+ this.apiKey = config2.apiKey;
125349
+ this.baseUrl = config2.baseUrl || "https://api.openai.com/v1";
125350
+ this.defaultModelId = config2.defaultModelId || "whisper-1";
125351
+ this.timeout = config2.timeout || 60000;
125352
+ logger.info(`OpenAI Whisper STT provider initialized: ${this.baseUrl}, model=${this.defaultModelId}`);
125353
+ }
125354
+ textToSpeech(_options) {
125355
+ throw new McpError(-32601 /* MethodNotFound */, "Text-to-speech is not supported by Whisper provider");
125356
+ }
125357
+ async speechToText(options) {
125358
+ const context = requestContextService.createRequestContext({
125359
+ operation: "whisper-stt",
125360
+ ...options.context || {}
125361
+ });
125362
+ const modelId = options.modelId || this.defaultModelId;
125363
+ logger.debug("Converting speech to text with Whisper", context);
125364
+ if (!options.audio) {
125365
+ throw new McpError(-32602 /* InvalidParams */, "Audio data is required", context);
125366
+ }
125367
+ let audioBuffer;
125368
+ if (typeof options.audio === "string") {
125369
+ try {
125370
+ audioBuffer = Buffer.from(options.audio, "base64");
125371
+ } catch (_error) {
125372
+ throw new McpError(-32602 /* InvalidParams */, "Invalid base64 audio data", context);
125373
+ }
125374
+ } else {
125375
+ audioBuffer = options.audio;
125376
+ }
125377
+ const maxSize = 25 * 1024 * 1024;
125378
+ if (audioBuffer.length > maxSize) {
125379
+ throw new McpError(-32602 /* InvalidParams */, `Audio file exceeds maximum size of 25MB (got ${Math.round(audioBuffer.length / 1024 / 1024)}MB)`, context);
125380
+ }
125381
+ const url = `${this.baseUrl}/audio/transcriptions`;
125382
+ const formData = new FormData;
125383
+ const extension = this.getFileExtension(options.format);
125384
+ const blob = new Blob([audioBuffer], {
125385
+ type: this.getMimeType(options.format)
125386
+ });
125387
+ formData.append("file", blob, `audio.${extension}`);
125388
+ formData.append("model", modelId);
125389
+ if (options.language) {
125390
+ formData.append("language", options.language);
125391
+ }
125392
+ if (options.temperature !== undefined) {
125393
+ formData.append("temperature", options.temperature.toString());
125394
+ }
125395
+ if (options.prompt) {
125396
+ formData.append("prompt", options.prompt);
125397
+ }
125398
+ formData.append("response_format", options.timestamps ? "verbose_json" : "json");
125399
+ if (options.timestamps) {
125400
+ formData.append("timestamp_granularities[]", "word");
125401
+ }
125402
+ try {
125403
+ const response = await fetchWithTimeout(url, this.timeout, context, {
125404
+ method: "POST",
125405
+ headers: {
125406
+ Authorization: `Bearer ${this.apiKey}`
125407
+ },
125408
+ body: formData
125409
+ });
125410
+ if (!response.ok) {
125411
+ const errorText = await response.text();
125412
+ logger.error(`Whisper API error: ${response.status}`, context);
125413
+ throw new McpError(-32603 /* InternalError */, `Whisper API error: ${response.status} - ${errorText}`, context);
125414
+ }
125415
+ const data = await response.json();
125416
+ const words = data.words?.map((w) => ({
125417
+ word: w.word,
125418
+ start: w.start,
125419
+ end: w.end
125420
+ }));
125421
+ logger.info(`Speech-to-text transcription successful (${data.text.length} chars)`, context);
125422
+ return {
125423
+ text: data.text,
125424
+ ...data.language !== undefined && { language: data.language },
125425
+ ...data.duration !== undefined && { duration: data.duration },
125426
+ ...words !== undefined && { words },
125427
+ metadata: {
125428
+ modelId,
125429
+ provider: this.name,
125430
+ ...data.task !== undefined && { task: data.task }
125431
+ }
125432
+ };
125433
+ } catch (error2) {
125434
+ if (error2 instanceof McpError) {
125435
+ throw error2;
125436
+ }
125437
+ logger.error("Failed to transcribe audio", error2 instanceof Error ? error2 : new Error(String(error2)), context);
125438
+ throw new McpError(-32603 /* InternalError */, `Failed to transcribe audio: ${error2 instanceof Error ? error2.message : "Unknown error"}`, context);
125439
+ }
125440
+ }
125441
+ getVoices() {
125442
+ throw new McpError(-32601 /* MethodNotFound */, "Voice listing is not supported by Whisper provider (STT only)");
125443
+ }
125444
+ async healthCheck() {
125445
+ try {
125446
+ const context = requestContextService.createRequestContext({
125447
+ operation: "whisper-healthCheck"
125448
+ });
125449
+ const response = await fetchWithTimeout(`${this.baseUrl}/models`, 5000, context, {
125450
+ method: "GET",
125451
+ headers: {
125452
+ Authorization: `Bearer ${this.apiKey}`
125453
+ }
125454
+ });
125455
+ return response.ok;
125456
+ } catch (error2) {
125457
+ const context = requestContextService.createRequestContext({
125458
+ operation: "whisper-healthCheck"
125459
+ });
125460
+ logger.error("Whisper health check failed", error2 instanceof Error ? error2 : new Error(String(error2)), context);
125461
+ return false;
125462
+ }
125463
+ }
125464
+ getFileExtension(format) {
125465
+ const formatMap = {
125466
+ mp3: "mp3",
125467
+ wav: "wav",
125468
+ ogg: "ogg",
125469
+ flac: "flac",
125470
+ webm: "webm",
125471
+ m4a: "m4a"
125472
+ };
125473
+ return format && formatMap[format] ? formatMap[format] : "mp3";
125474
+ }
125475
+ getMimeType(format) {
125476
+ const mimeMap = {
125477
+ mp3: "audio/mpeg",
125478
+ wav: "audio/wav",
125479
+ ogg: "audio/ogg",
125480
+ flac: "audio/flac",
125481
+ webm: "audio/webm",
125482
+ m4a: "audio/mp4"
125483
+ };
125484
+ return format && mimeMap[format] ? mimeMap[format] : "audio/mpeg";
125485
+ }
125486
+ }
125487
+ WhisperProvider = __legacyDecorateClassTS([
125488
+ import_tsyringe4.injectable(),
125489
+ __legacyMetadataTS("design:paramtypes", [
125490
+ typeof SpeechProviderConfig === "undefined" ? Object : SpeechProviderConfig
125491
+ ])
125492
+ ], WhisperProvider);
125493
+
125494
+ // src/services/speech/index.ts
125495
+ function createSpeechProvider(config2) {
125496
+ logger.debug(`Creating speech provider: ${config2.provider}`);
125497
+ switch (config2.provider) {
125498
+ case "elevenlabs":
125499
+ return new ElevenLabsProvider(config2);
125500
+ case "openai-whisper":
125501
+ return new WhisperProvider(config2);
125502
+ case "mock":
125503
+ throw new McpError(-32602 /* InvalidParams */, "Mock provider not yet implemented");
125504
+ default: {
125505
+ const _exhaustive = config2.provider;
125506
+ throw new McpError(-32602 /* InvalidParams */, `Unknown speech provider: ${String(_exhaustive)}`);
125507
+ }
125508
+ }
125509
+ }
125510
+
125511
+ class SpeechService2 {
125512
+ ttsProvider;
125513
+ sttProvider;
125514
+ constructor(ttsConfig, sttConfig) {
125515
+ if (ttsConfig) {
125516
+ this.ttsProvider = createSpeechProvider(ttsConfig);
125517
+ if (!this.ttsProvider.supportsTTS) {
125518
+ logger.warning(`TTS provider ${ttsConfig.provider} does not support text-to-speech`);
125519
+ }
125520
+ }
125521
+ if (sttConfig) {
125522
+ this.sttProvider = createSpeechProvider(sttConfig);
125523
+ if (!this.sttProvider.supportsSTT) {
125524
+ logger.warning(`STT provider ${sttConfig.provider} does not support speech-to-text`);
125525
+ }
125526
+ }
125527
+ logger.info(`Speech service initialized: TTS=${this.ttsProvider?.name ?? "none"}, STT=${this.sttProvider?.name ?? "none"}`);
125528
+ }
125529
+ getTTSProvider() {
125530
+ if (!this.ttsProvider) {
125531
+ throw new McpError(-32600 /* InvalidRequest */, "No TTS provider configured");
125532
+ }
125533
+ return this.ttsProvider;
125534
+ }
125535
+ getSTTProvider() {
125536
+ if (!this.sttProvider) {
125537
+ throw new McpError(-32600 /* InvalidRequest */, "No STT provider configured");
125538
+ }
125539
+ return this.sttProvider;
125540
+ }
125541
+ hasTTS() {
125542
+ return this.ttsProvider?.supportsTTS ?? false;
125543
+ }
125544
+ hasSTT() {
125545
+ return this.sttProvider?.supportsSTT ?? false;
125546
+ }
125547
+ async healthCheck() {
125548
+ const ttsHealth = this.ttsProvider ? await this.ttsProvider.healthCheck() : false;
125549
+ const sttHealth = this.sttProvider ? await this.sttProvider.healthCheck() : false;
125550
+ return {
125551
+ tts: ttsHealth,
125552
+ stt: sttHealth
125553
+ };
125554
+ }
125555
+ }
125556
+
125557
+ // src/storage/core/StorageService.ts
125558
+ var import_tsyringe5 = __toESM(require_cjs3(), 1);
125150
125559
  function requireTenantId(context) {
125151
125560
  if (!context.tenantId) {
125152
125561
  throw new McpError(-32603 /* InternalError */, "Tenant ID is required for storage operations but was not found in the request context.", {
@@ -125196,15 +125605,15 @@ class StorageService2 {
125196
125605
  }
125197
125606
  }
125198
125607
  StorageService2 = __legacyDecorateClassTS([
125199
- import_tsyringe3.injectable(),
125200
- __legacyDecorateParamTS(0, import_tsyringe3.inject(StorageProvider)),
125608
+ import_tsyringe5.injectable(),
125609
+ __legacyDecorateParamTS(0, import_tsyringe5.inject(StorageProvider)),
125201
125610
  __legacyMetadataTS("design:paramtypes", [
125202
125611
  typeof IStorageProvider === "undefined" ? Object : IStorageProvider
125203
125612
  ])
125204
125613
  ], StorageService2);
125205
125614
 
125206
125615
  // src/storage/core/storageFactory.ts
125207
- var import_tsyringe5 = __toESM(require_cjs3(), 1);
125616
+ var import_tsyringe7 = __toESM(require_cjs3(), 1);
125208
125617
 
125209
125618
  // src/storage/providers/fileSystem/fileSystemProvider.ts
125210
125619
  import { existsSync, mkdirSync } from "fs";
@@ -125551,7 +125960,7 @@ class InMemoryProvider {
125551
125960
  }
125552
125961
 
125553
125962
  // src/storage/providers/supabase/supabaseProvider.ts
125554
- var import_tsyringe4 = __toESM(require_cjs3(), 1);
125963
+ var import_tsyringe6 = __toESM(require_cjs3(), 1);
125555
125964
  var import_supabase_js = __toESM(require_main6(), 1);
125556
125965
  var TABLE_NAME = "kv_store";
125557
125966
  var DEFAULT_LIST_LIMIT3 = 1000;
@@ -125713,8 +126122,8 @@ class SupabaseProvider {
125713
126122
  }
125714
126123
  }
125715
126124
  SupabaseProvider = __legacyDecorateClassTS([
125716
- import_tsyringe4.injectable(),
125717
- __legacyDecorateParamTS(0, import_tsyringe4.inject(SupabaseAdminClient)),
126125
+ import_tsyringe6.injectable(),
126126
+ __legacyDecorateParamTS(0, import_tsyringe6.inject(SupabaseAdminClient)),
125718
126127
  __legacyMetadataTS("design:paramtypes", [
125719
126128
  typeof import_supabase_js.SupabaseClient === "undefined" ? Object : import_supabase_js.SupabaseClient
125720
126129
  ])
@@ -126107,7 +126516,7 @@ function createStorageProvider(config2, deps = {}) {
126107
126516
  if (deps.supabaseClient) {
126108
126517
  return new SupabaseProvider(deps.supabaseClient);
126109
126518
  }
126110
- return import_tsyringe5.container.resolve(SupabaseProvider);
126519
+ return import_tsyringe7.container.resolve(SupabaseProvider);
126111
126520
  case "cloudflare-r2":
126112
126521
  if (isServerless3) {
126113
126522
  const bucket = deps.r2Bucket ?? globalThis.R2_BUCKET;
@@ -126130,9 +126539,9 @@ function createStorageProvider(config2, deps = {}) {
126130
126539
  // src/container/registrations/core.ts
126131
126540
  var registerCoreServices = () => {
126132
126541
  const config2 = parseConfig();
126133
- import_tsyringe6.container.register(AppConfig, { useValue: config2 });
126134
- import_tsyringe6.container.register(Logger2, { useValue: logger });
126135
- import_tsyringe6.container.register(SupabaseAdminClient, {
126542
+ import_tsyringe8.container.register(AppConfig, { useValue: config2 });
126543
+ import_tsyringe8.container.register(Logger2, { useValue: logger });
126544
+ import_tsyringe8.container.register(SupabaseAdminClient, {
126136
126545
  useFactory: (c) => {
126137
126546
  const cfg = c.resolve(AppConfig);
126138
126547
  if (!cfg.supabase?.url || !cfg.supabase?.serviceRoleKey) {
@@ -126143,22 +126552,57 @@ var registerCoreServices = () => {
126143
126552
  });
126144
126553
  }
126145
126554
  });
126146
- import_tsyringe6.container.register(StorageProvider, {
126555
+ import_tsyringe8.container.register(StorageProvider, {
126147
126556
  useFactory: (c) => createStorageProvider(c.resolve(AppConfig))
126148
126557
  });
126149
- import_tsyringe6.container.register(StorageService, { useClass: StorageService2 }, { lifecycle: import_tsyringe6.Lifecycle.Singleton });
126150
- import_tsyringe6.container.register(LlmProvider, {
126558
+ import_tsyringe8.container.register(StorageService, { useClass: StorageService2 }, { lifecycle: import_tsyringe8.Lifecycle.Singleton });
126559
+ import_tsyringe8.container.register(LlmProvider, {
126151
126560
  useClass: OpenRouterProvider
126152
126561
  });
126153
- import_tsyringe6.container.register(RateLimiterService, { useClass: RateLimiter }, { lifecycle: import_tsyringe6.Lifecycle.Singleton });
126562
+ import_tsyringe8.container.register(RateLimiterService, { useClass: RateLimiter }, { lifecycle: import_tsyringe8.Lifecycle.Singleton });
126563
+ import_tsyringe8.container.register(SpeechService, {
126564
+ useFactory: (c) => {
126565
+ const cfg = c.resolve(AppConfig);
126566
+ const ttsConfig = cfg.speech?.tts?.enabled && cfg.speech.tts.apiKey ? {
126567
+ provider: "elevenlabs",
126568
+ apiKey: cfg.speech.tts.apiKey,
126569
+ ...cfg.speech.tts.baseUrl && {
126570
+ baseUrl: cfg.speech.tts.baseUrl
126571
+ },
126572
+ ...cfg.speech.tts.defaultVoiceId && {
126573
+ defaultVoiceId: cfg.speech.tts.defaultVoiceId
126574
+ },
126575
+ ...cfg.speech.tts.defaultModelId && {
126576
+ defaultModelId: cfg.speech.tts.defaultModelId
126577
+ },
126578
+ ...cfg.speech.tts.timeout && {
126579
+ timeout: cfg.speech.tts.timeout
126580
+ }
126581
+ } : undefined;
126582
+ const sttConfig = cfg.speech?.stt?.enabled && cfg.speech.stt.apiKey ? {
126583
+ provider: "openai-whisper",
126584
+ apiKey: cfg.speech.stt.apiKey,
126585
+ ...cfg.speech.stt.baseUrl && {
126586
+ baseUrl: cfg.speech.stt.baseUrl
126587
+ },
126588
+ ...cfg.speech.stt.defaultModelId && {
126589
+ defaultModelId: cfg.speech.stt.defaultModelId
126590
+ },
126591
+ ...cfg.speech.stt.timeout && {
126592
+ timeout: cfg.speech.stt.timeout
126593
+ }
126594
+ } : undefined;
126595
+ return new SpeechService2(ttsConfig, sttConfig);
126596
+ }
126597
+ });
126154
126598
  logger.info("Core services registered with the DI container.");
126155
126599
  };
126156
126600
 
126157
126601
  // src/container/registrations/mcp.ts
126158
- var import_tsyringe16 = __toESM(require_cjs3(), 1);
126602
+ var import_tsyringe18 = __toESM(require_cjs3(), 1);
126159
126603
 
126160
126604
  // src/mcp-server/resources/resource-registration.ts
126161
- var import_tsyringe7 = __toESM(require_cjs3(), 1);
126605
+ var import_tsyringe9 = __toESM(require_cjs3(), 1);
126162
126606
 
126163
126607
  // src/mcp-server/transports/auth/lib/authUtils.ts
126164
126608
  function withRequiredScopes(requiredScopes) {
@@ -129553,8 +129997,8 @@ class ResourceRegistry {
129553
129997
  }
129554
129998
  }
129555
129999
  ResourceRegistry = __legacyDecorateClassTS([
129556
- import_tsyringe7.injectable(),
129557
- __legacyDecorateParamTS(0, import_tsyringe7.injectAll(ResourceDefinitions)),
130000
+ import_tsyringe9.injectable(),
130001
+ __legacyDecorateParamTS(0, import_tsyringe9.injectAll(ResourceDefinitions)),
129558
130002
  __legacyMetadataTS("design:paramtypes", [
129559
130003
  Array
129560
130004
  ])
@@ -129566,10 +130010,10 @@ var registerResources = (container3) => {
129566
130010
  };
129567
130011
 
129568
130012
  // src/mcp-server/server.ts
129569
- var import_tsyringe11 = __toESM(require_cjs3(), 1);
130013
+ var import_tsyringe13 = __toESM(require_cjs3(), 1);
129570
130014
 
129571
130015
  // src/mcp-server/prompts/prompt-registration.ts
129572
- var import_tsyringe8 = __toESM(require_cjs3(), 1);
130016
+ var import_tsyringe10 = __toESM(require_cjs3(), 1);
129573
130017
 
129574
130018
  // src/mcp-server/prompts/definitions/code-review.prompt.ts
129575
130019
  var PROMPT_NAME = "code_review";
@@ -129661,15 +130105,15 @@ class PromptRegistry {
129661
130105
  }
129662
130106
  }
129663
130107
  PromptRegistry = __legacyDecorateClassTS([
129664
- import_tsyringe8.injectable(),
129665
- __legacyDecorateParamTS(0, import_tsyringe8.inject(Logger2)),
130108
+ import_tsyringe10.injectable(),
130109
+ __legacyDecorateParamTS(0, import_tsyringe10.inject(Logger2)),
129666
130110
  __legacyMetadataTS("design:paramtypes", [
129667
130111
  Object
129668
130112
  ])
129669
130113
  ], PromptRegistry);
129670
130114
 
129671
130115
  // src/mcp-server/roots/roots-registration.ts
129672
- var import_tsyringe9 = __toESM(require_cjs3(), 1);
130116
+ var import_tsyringe11 = __toESM(require_cjs3(), 1);
129673
130117
  class RootsRegistry {
129674
130118
  logger;
129675
130119
  constructor(logger2) {
@@ -129684,15 +130128,15 @@ class RootsRegistry {
129684
130128
  }
129685
130129
  }
129686
130130
  RootsRegistry = __legacyDecorateClassTS([
129687
- import_tsyringe9.injectable(),
129688
- __legacyDecorateParamTS(0, import_tsyringe9.inject(Logger2)),
130131
+ import_tsyringe11.injectable(),
130132
+ __legacyDecorateParamTS(0, import_tsyringe11.inject(Logger2)),
129689
130133
  __legacyMetadataTS("design:paramtypes", [
129690
130134
  Object
129691
130135
  ])
129692
130136
  ], RootsRegistry);
129693
130137
 
129694
130138
  // src/mcp-server/tools/tool-registration.ts
129695
- var import_tsyringe10 = __toESM(require_cjs3(), 1);
130139
+ var import_tsyringe12 = __toESM(require_cjs3(), 1);
129696
130140
 
129697
130141
  // src/mcp-server/tools/definitions/template-cat-fact.tool.ts
129698
130142
  var TOOL_NAME = "template_cat_fact";
@@ -130255,8 +130699,8 @@ class ToolRegistry {
130255
130699
  }
130256
130700
  }
130257
130701
  ToolRegistry = __legacyDecorateClassTS([
130258
- import_tsyringe10.injectable(),
130259
- __legacyDecorateParamTS(0, import_tsyringe10.injectAll(ToolDefinitions)),
130702
+ import_tsyringe12.injectable(),
130703
+ __legacyDecorateParamTS(0, import_tsyringe12.injectAll(ToolDefinitions)),
130260
130704
  __legacyMetadataTS("design:paramtypes", [
130261
130705
  Array
130262
130706
  ])
@@ -130295,13 +130739,13 @@ async function createMcpServerInstance() {
130295
130739
  });
130296
130740
  try {
130297
130741
  logger.debug("Registering all MCP capabilities via registries...", context);
130298
- const toolRegistry = import_tsyringe11.container.resolve(ToolRegistry);
130742
+ const toolRegistry = import_tsyringe13.container.resolve(ToolRegistry);
130299
130743
  await toolRegistry.registerAll(server);
130300
- const resourceRegistry = import_tsyringe11.container.resolve(ResourceRegistry);
130744
+ const resourceRegistry = import_tsyringe13.container.resolve(ResourceRegistry);
130301
130745
  await resourceRegistry.registerAll(server);
130302
- const promptRegistry = import_tsyringe11.container.resolve(PromptRegistry);
130746
+ const promptRegistry = import_tsyringe13.container.resolve(PromptRegistry);
130303
130747
  promptRegistry.registerAll(server);
130304
- const rootsRegistry = import_tsyringe11.container.resolve(RootsRegistry);
130748
+ const rootsRegistry = import_tsyringe13.container.resolve(RootsRegistry);
130305
130749
  rootsRegistry.registerAll(server);
130306
130750
  logger.info("All MCP capabilities registered successfully", context);
130307
130751
  } catch (err) {
@@ -130316,7 +130760,7 @@ async function createMcpServerInstance() {
130316
130760
  }
130317
130761
 
130318
130762
  // src/mcp-server/transports/manager.ts
130319
- var import_tsyringe15 = __toESM(require_cjs3(), 1);
130763
+ var import_tsyringe17 = __toESM(require_cjs3(), 1);
130320
130764
 
130321
130765
  // node_modules/hono/dist/http-exception.js
130322
130766
  var HTTPException = class extends Error {
@@ -132984,7 +133428,7 @@ var cors = (options) => {
132984
133428
  import http from "http";
132985
133429
  import { randomUUID } from "node:crypto";
132986
133430
  // src/mcp-server/transports/auth/authFactory.ts
132987
- var import_tsyringe14 = __toESM(require_cjs3(), 1);
133431
+ var import_tsyringe16 = __toESM(require_cjs3(), 1);
132988
133432
 
132989
133433
  // node_modules/jose/dist/webapi/lib/buffer_utils.js
132990
133434
  var encoder = new TextEncoder;
@@ -134502,7 +134946,7 @@ function createRemoteJWKSet(url, options) {
134502
134946
  return remoteJWKSet;
134503
134947
  }
134504
134948
  // src/mcp-server/transports/auth/strategies/jwtStrategy.ts
134505
- var import_tsyringe12 = __toESM(require_cjs3(), 1);
134949
+ var import_tsyringe14 = __toESM(require_cjs3(), 1);
134506
134950
  class JwtStrategy {
134507
134951
  config;
134508
134952
  logger;
@@ -134605,9 +135049,9 @@ class JwtStrategy {
134605
135049
  }
134606
135050
  }
134607
135051
  JwtStrategy = __legacyDecorateClassTS([
134608
- import_tsyringe12.injectable(),
134609
- __legacyDecorateParamTS(0, import_tsyringe12.inject(AppConfig)),
134610
- __legacyDecorateParamTS(1, import_tsyringe12.inject(Logger2)),
135052
+ import_tsyringe14.injectable(),
135053
+ __legacyDecorateParamTS(0, import_tsyringe14.inject(AppConfig)),
135054
+ __legacyDecorateParamTS(1, import_tsyringe14.inject(Logger2)),
134611
135055
  __legacyMetadataTS("design:paramtypes", [
134612
135056
  Object,
134613
135057
  Object
@@ -134615,7 +135059,7 @@ JwtStrategy = __legacyDecorateClassTS([
134615
135059
  ], JwtStrategy);
134616
135060
 
134617
135061
  // src/mcp-server/transports/auth/strategies/oauthStrategy.ts
134618
- var import_tsyringe13 = __toESM(require_cjs3(), 1);
135062
+ var import_tsyringe15 = __toESM(require_cjs3(), 1);
134619
135063
  class OauthStrategy {
134620
135064
  config;
134621
135065
  logger;
@@ -134732,9 +135176,9 @@ class OauthStrategy {
134732
135176
  }
134733
135177
  }
134734
135178
  OauthStrategy = __legacyDecorateClassTS([
134735
- import_tsyringe13.injectable(),
134736
- __legacyDecorateParamTS(0, import_tsyringe13.inject(AppConfig)),
134737
- __legacyDecorateParamTS(1, import_tsyringe13.inject(Logger2)),
135179
+ import_tsyringe15.injectable(),
135180
+ __legacyDecorateParamTS(0, import_tsyringe15.inject(AppConfig)),
135181
+ __legacyDecorateParamTS(1, import_tsyringe15.inject(Logger2)),
134738
135182
  __legacyMetadataTS("design:paramtypes", [
134739
135183
  Object,
134740
135184
  Object
@@ -134742,8 +135186,8 @@ OauthStrategy = __legacyDecorateClassTS([
134742
135186
  ], OauthStrategy);
134743
135187
 
134744
135188
  // src/mcp-server/transports/auth/authFactory.ts
134745
- import_tsyringe14.container.register(JwtStrategy, { useClass: JwtStrategy });
134746
- import_tsyringe14.container.register(OauthStrategy, { useClass: OauthStrategy });
135189
+ import_tsyringe16.container.register(JwtStrategy, { useClass: JwtStrategy });
135190
+ import_tsyringe16.container.register(OauthStrategy, { useClass: OauthStrategy });
134747
135191
  function createAuthStrategy() {
134748
135192
  const context = requestContextService.createRequestContext({
134749
135193
  operation: "createAuthStrategy",
@@ -134753,10 +135197,10 @@ function createAuthStrategy() {
134753
135197
  switch (config.mcpAuthMode) {
134754
135198
  case "jwt":
134755
135199
  logger.debug("Resolving JWT strategy from container.", context);
134756
- return import_tsyringe14.container.resolve(JwtStrategy);
135200
+ return import_tsyringe16.container.resolve(JwtStrategy);
134757
135201
  case "oauth":
134758
135202
  logger.debug("Resolving OAuth strategy from container.", context);
134759
- return import_tsyringe14.container.resolve(OauthStrategy);
135203
+ return import_tsyringe16.container.resolve(OauthStrategy);
134760
135204
  case "none":
134761
135205
  logger.info("Authentication is disabled ('none' mode).", context);
134762
135206
  return null;
@@ -135270,10 +135714,10 @@ class TransportManager {
135270
135714
  }
135271
135715
  }
135272
135716
  TransportManager = __legacyDecorateClassTS([
135273
- import_tsyringe15.injectable(),
135274
- __legacyDecorateParamTS(0, import_tsyringe15.inject(AppConfig)),
135275
- __legacyDecorateParamTS(1, import_tsyringe15.inject(Logger2)),
135276
- __legacyDecorateParamTS(2, import_tsyringe15.inject(CreateMcpServerInstance)),
135717
+ import_tsyringe17.injectable(),
135718
+ __legacyDecorateParamTS(0, import_tsyringe17.inject(AppConfig)),
135719
+ __legacyDecorateParamTS(1, import_tsyringe17.inject(Logger2)),
135720
+ __legacyDecorateParamTS(2, import_tsyringe17.inject(CreateMcpServerInstance)),
135277
135721
  __legacyMetadataTS("design:paramtypes", [
135278
135722
  typeof AppConfigType === "undefined" ? Object : AppConfigType,
135279
135723
  Object,
@@ -135283,14 +135727,14 @@ TransportManager = __legacyDecorateClassTS([
135283
135727
 
135284
135728
  // src/container/registrations/mcp.ts
135285
135729
  var registerMcpServices = () => {
135286
- import_tsyringe16.container.registerSingleton(ToolRegistry);
135287
- import_tsyringe16.container.registerSingleton(ResourceRegistry);
135288
- registerTools(import_tsyringe16.container);
135289
- registerResources(import_tsyringe16.container);
135290
- import_tsyringe16.container.register(CreateMcpServerInstance, {
135730
+ import_tsyringe18.container.registerSingleton(ToolRegistry);
135731
+ import_tsyringe18.container.registerSingleton(ResourceRegistry);
135732
+ registerTools(import_tsyringe18.container);
135733
+ registerResources(import_tsyringe18.container);
135734
+ import_tsyringe18.container.register(CreateMcpServerInstance, {
135291
135735
  useValue: createMcpServerInstance
135292
135736
  });
135293
- import_tsyringe16.container.registerSingleton(TransportManagerToken, TransportManager);
135737
+ import_tsyringe18.container.registerSingleton(TransportManagerToken, TransportManager);
135294
135738
  logger.info("MCP services and factories registered with the DI container.");
135295
135739
  };
135296
135740
 
@@ -135304,7 +135748,7 @@ function composeContainer() {
135304
135748
  registerMcpServices();
135305
135749
  isContainerComposed = true;
135306
135750
  }
135307
- var container_default = import_tsyringe17.container;
135751
+ var container_default = import_tsyringe19.container;
135308
135752
 
135309
135753
  // src/index.ts
135310
135754
  var config2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ts-template",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
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",
@@ -83,7 +83,7 @@
83
83
  "jose": "^6.1.0",
84
84
  "js-yaml": "^4.1.0",
85
85
  "node-cron": "^4.2.1",
86
- "openai": "^5.23.1",
86
+ "openai": "^5.23.2",
87
87
  "papaparse": "^5.5.3",
88
88
  "partial-json": "^0.1.7",
89
89
  "pino": "^9.12.0",