mcp-ts-template 2.1.8 → 2.2.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 +57 -47
  2. package/dist/index.js +332 -121
  3. package/package.json +4 -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.1.8-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-77.29%25-brightgreen.svg?style=flat-square)](./coverage/lcov-report/)
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/)
9
9
 
10
10
  </div>
11
11
 
@@ -14,6 +14,7 @@
14
14
  ## ✨ Features
15
15
 
16
16
  - **Declarative Tools & Resources**: Define capabilities in single, self-contained files. The framework handles registration and execution.
17
+ - **Elicitation Support**: Tools can interactively prompt the user for missing parameters during execution, streamlining user workflows.
17
18
  - **Robust Error Handling**: A unified `McpError` system ensures consistent, structured error responses across the server.
18
19
  - **Pluggable Authentication**: Secure your server with zero-fuss support for `none`, `jwt`, or `oauth` modes.
19
20
  - **Abstracted Storage**: Swap storage backends (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2`) without changing business logic.
@@ -29,22 +30,25 @@
29
30
 
30
31
  ### Installation
31
32
 
32
- 1. **Clone the repository:**
33
- ```sh
34
- git clone https://github.com/cyanheads/mcp-ts-template.git
35
- ```
36
- 2. **Navigate into the directory:**
37
- ```sh
38
- cd mcp-ts-template
39
- ```
40
- 3. **Install dependencies:**
41
- ```sh
42
- bun install
43
- ```
33
+ 1.
34
+ **Clone the repository:**
35
+ ```sh
36
+ git clone https://github.com/cyanheads/mcp-ts-template.git
37
+ ```
38
+ 2.
39
+ **Navigate into the directory:**
40
+ ```sh
41
+ cd mcp-ts-template
42
+ ```
43
+ 3.
44
+ **Install dependencies:**
45
+ ```sh
46
+ bun install
47
+ ```
44
48
 
45
49
  ## 🛠️ Understanding the Template: Tools & Resources
46
50
 
47
- This template includes working examples of a tool (`template_echo_message`) and a resource (`echo-resource`). Here’s how they are structured.
51
+ This template includes working examples of tools and resources.
48
52
 
49
53
  ### 1. Example Tool: `template_echo_message`
50
54
 
@@ -56,7 +60,7 @@ This tool echoes back a message with optional formatting. You can find the full
56
60
  ```ts
57
61
  // Located at: src/mcp-server/tools/definitions/template-echo-message.tool.ts
58
62
  import { z } from 'zod';
59
- import type { ToolDefinition } from '@/mcp-server/tools/utils/toolDefinition.js';
63
+ import type { SdkContext, ToolDefinition } from '@/mcp-server/tools/utils/toolDefinition.js';
60
64
  import { withToolAuth } from '@/mcp-server/transports/auth/lib/withAuth.js';
61
65
  import { type RequestContext, logger } from '@/utils/index.js';
62
66
 
@@ -86,7 +90,8 @@ const OutputSchema = z.object({
86
90
  // 2. Implement the pure business logic for the tool.
87
91
  async function echoToolLogic(
88
92
  input: z.infer<typeof InputSchema>,
89
- context: RequestContext,
93
+ appContext: RequestContext,
94
+ sdkContext: SdkContext,
90
95
  ): Promise<z.infer<typeof OutputSchema>> {
91
96
  // ... logic to format and repeat the message
92
97
  const formattedMessage = input.message.toUpperCase(); // simplified for example
@@ -98,7 +103,7 @@ async function echoToolLogic(
98
103
  export const echoTool: ToolDefinition<typeof InputSchema, typeof OutputSchema> =
99
104
  {
100
105
  name: 'template_echo_message', // The official tool name
101
- title: 'Echo Message',
106
+ title: 'Template Echo Message',
102
107
  description:
103
108
  'Echoes a message back with optional formatting and repetition.',
104
109
  inputSchema: InputSchema,
@@ -107,7 +112,7 @@ export const echoTool: ToolDefinition<typeof InputSchema, typeof OutputSchema> =
107
112
  };
108
113
  ```
109
114
 
110
- The `echoTool` is registered in `src/mcp-server/tools/definitions/index.ts`, making it available to the server on startup.
115
+ The `echoTool` is registered in `src/mcp-server/tools/definitions/index.ts`, making it available to the server on startup. For an example of how to use the new elicitation feature, see `template_madlibs_elicitation.tool.ts`.
111
116
 
112
117
  </details>
113
118
 
@@ -175,14 +180,14 @@ Like the tool, `echoResourceDefinition` is registered in `src/mcp-server/resourc
175
180
 
176
181
  All configuration is centralized and validated at startup in `src/config/index.ts`. Key environment variables in your `.env` file include:
177
182
 
178
- | Variable | Description | Default |
179
- | :---------------------- | :----------------------------------------------------------------------------- | :---------- |
180
- | `MCP_TRANSPORT_TYPE` | The transport to use: `stdio` or `http`. | `http` |
181
- | `MCP_HTTP_PORT` | The port for the HTTP server. | `3010` |
182
- | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
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` |
183
188
  | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`. | `in-memory` |
184
- | `OTEL_ENABLED` | Set to `true` to enable OpenTelemetry. | `false` |
185
- | `LOG_LEVEL` | The minimum level for logging. | `info` |
189
+ | `OTEL_ENABLED` | Set to `true` to enable OpenTelemetry. | `false` |
190
+ | `LOG_LEVEL` | The minimum level for logging. | `info` |
186
191
 
187
192
  ### Authentication & Authorization
188
193
 
@@ -219,43 +224,48 @@ All configuration is centralized and validated at startup in `src/config/index.t
219
224
  - **Run checks and tests**:
220
225
  ```sh
221
226
  bun devcheck # Lints, formats, type-checks, and more
222
- bun test # Runs the test suite
227
+ bun test # Runs the test suite
223
228
  ```
224
229
 
225
230
  ### Cloudflare Workers
226
231
 
227
- 1. **Build the Worker bundle**:
228
- ```sh
229
- bun build:worker
230
- ```
231
- 2. **Run locally with Wrangler**:
232
- ```sh
233
- bun deploy:dev
234
- ```
235
- 3. **Deploy to Cloudflare**:
236
- `sh
232
+ 1.
233
+ **Build the Worker bundle**:
234
+ ```sh
235
+ bun build:worker
236
+ ```
237
+ 2.
238
+ **Run locally with Wrangler**:
239
+ ```sh
240
+ bun deploy:dev
241
+ ```
242
+ 3.
243
+ **Deploy to Cloudflare**:
244
+ `sh
237
245
  bun deploy:prod
238
- ` > **Note**: The `wrangler.toml` file is pre-configured to enable `nodejs_compat` for best results.
246
+ `
247
+ > **Note**: The `wrangler.toml` file is pre-configured to enable `nodejs_compat` for best results.
239
248
 
240
249
  ## 📂 Project Structure
241
250
 
242
- | Directory | Purpose & Contents |
243
- | :------------------------------------- | :----------------------------------------------------------------------------------- |
244
- | `src/mcp-server/tools/definitions` | Your tool definitions (`*.tool.ts`). This is where you add new capabilities. |
251
+ | Directory | Purpose & Contents |
252
+ | :--- | :--- |
253
+ | `src/mcp-server/tools/definitions` | Your tool definitions (`*.tool.ts`). This is where you add new capabilities. |
245
254
  | `src/mcp-server/resources/definitions` | Your resource definitions (`*.resource.ts`). This is where you add new data sources. |
246
- | `src/mcp-server/transports` | Implementations for HTTP and STDIO transports, including auth middleware. |
247
- | `src/storage` | The `StorageService` abstraction and all storage provider implementations. |
248
- | `src/services` | Integrations with external services (e.g., the default OpenRouter LLM provider). |
249
- | `src/container` | Dependency injection container registrations and tokens. |
250
- | `src/utils` | Core utilities for logging, error handling, performance, security, and telemetry. |
251
- | `src/config` | Environment variable parsing and validation with Zod. |
252
- | `tests/` | Unit and integration tests, mirroring the `src/` directory structure. |
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. |
253
262
 
254
263
  ## 🧑‍💻 Agent Development Guide
255
264
 
256
265
  For a strict set of rules when using this template with an AI agent, please refer to **`AGENTS.md`**. Key principles include:
257
266
 
258
267
  - **Logic Throws, Handlers Catch**: Never use `try/catch` in your tool/resource `logic`. Throw an `McpError` instead.
268
+ - **Use Elicitation for Missing Input**: If a tool requires user input that wasn't provided, use the `elicitInput` function from the `SdkContext` to ask the user for it.
259
269
  - **Pass the Context**: Always pass the `RequestContext` object through your call stack.
260
270
  - **Use the Barrel Exports**: Register new tools and resources only in the `index.ts` barrel files.
261
271
 
package/dist/index.js CHANGED
@@ -117229,7 +117229,7 @@ var z = /* @__PURE__ */ Object.freeze({
117229
117229
  // package.json
117230
117230
  var package_default = {
117231
117231
  name: "mcp-ts-template",
117232
- version: "2.1.7",
117232
+ version: "2.1.8",
117233
117233
  mcpName: "io.github.cyanheads/mcp-ts-template",
117234
117234
  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
117235
  main: "dist/index.js",
@@ -117284,8 +117284,8 @@ var package_default = {
117284
117284
  prepare: "[ -d .husky ] && husky || true",
117285
117285
  inspector: "bunx mcp-inspector --config mcp.json --server mcp-ts-template",
117286
117286
  "db:duckdb-example": "MCP_LOG_LEVEL=debug tsc && node dist/storage/duckdbExample.js",
117287
- test: "bun test",
117288
- "test:coverage": "bun test --coverage",
117287
+ test: "bun test --config vitest.config.ts",
117288
+ "test:coverage": "bun test --coverage --config vitest.config.ts",
117289
117289
  audit: "bun audit",
117290
117290
  "audit:fix": "bun audit --fix",
117291
117291
  "publish-mcp": "bun scripts/validate-mcp-publish-schema.ts"
@@ -117750,6 +117750,7 @@ var JsonRpcErrorCode;
117750
117750
  JsonRpcErrorCode2[JsonRpcErrorCode2["ConfigurationError"] = -32008] = "ConfigurationError";
117751
117751
  JsonRpcErrorCode2[JsonRpcErrorCode2["InitializationFailed"] = -32009] = "InitializationFailed";
117752
117752
  JsonRpcErrorCode2[JsonRpcErrorCode2["DatabaseError"] = -32010] = "DatabaseError";
117753
+ JsonRpcErrorCode2[JsonRpcErrorCode2["SerializationError"] = -32070] = "SerializationError";
117753
117754
  JsonRpcErrorCode2[JsonRpcErrorCode2["UnknownError"] = -32099] = "UnknownError";
117754
117755
  })(JsonRpcErrorCode ||= {});
117755
117756
 
@@ -124913,6 +124914,8 @@ var import_tsyringe5 = __toESM(require_cjs3(), 1);
124913
124914
  import { existsSync, mkdirSync } from "fs";
124914
124915
  import { readFile, readdir, rm, writeFile } from "fs/promises";
124915
124916
  import path2 from "path";
124917
+ var FILE_ENVELOPE_VERSION = 1;
124918
+
124916
124919
  class FileSystemProvider {
124917
124920
  storagePath;
124918
124921
  constructor(storagePath) {
@@ -124949,12 +124952,38 @@ class FileSystemProvider {
124949
124952
  }
124950
124953
  return filePath;
124951
124954
  }
124955
+ buildEnvelope(value, options) {
124956
+ const expiresAt = options?.ttl ? Date.now() + options.ttl * 1000 : undefined;
124957
+ return {
124958
+ __mcp: { v: FILE_ENVELOPE_VERSION, ...expiresAt ? { expiresAt } : {} },
124959
+ value
124960
+ };
124961
+ }
124962
+ async parseAndValidate(raw, tenantId, key, filePath, context) {
124963
+ try {
124964
+ const parsed = JSON.parse(raw);
124965
+ if (parsed && typeof parsed === "object" && "__mcp" in parsed) {
124966
+ const env2 = parsed;
124967
+ const expiresAt = env2.__mcp?.expiresAt;
124968
+ if (expiresAt && Date.now() > expiresAt) {
124969
+ try {
124970
+ await rm(filePath);
124971
+ } catch {}
124972
+ return null;
124973
+ }
124974
+ return env2.value;
124975
+ }
124976
+ return parsed;
124977
+ } catch (error2) {
124978
+ throw new McpError(-32070 /* SerializationError */, `Failed to parse stored JSON for key "${key}" (tenant "${tenantId}").`, { ...context, error: error2 });
124979
+ }
124980
+ }
124952
124981
  async get(tenantId, key, context) {
124953
124982
  const filePath = this.getFilePath(tenantId, key);
124954
124983
  return ErrorHandler.tryCatch(async () => {
124955
124984
  try {
124956
124985
  const data = await readFile(filePath, "utf-8");
124957
- return JSON.parse(data);
124986
+ return this.parseAndValidate(data, tenantId, key, filePath, context);
124958
124987
  } catch (error2) {
124959
124988
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
124960
124989
  return null;
@@ -124967,10 +124996,11 @@ class FileSystemProvider {
124967
124996
  input: { tenantId, key }
124968
124997
  });
124969
124998
  }
124970
- async set(tenantId, key, value, context) {
124999
+ async set(tenantId, key, value, context, options) {
124971
125000
  const filePath = this.getFilePath(tenantId, key);
124972
125001
  return ErrorHandler.tryCatch(async () => {
124973
- const content = typeof value === "string" ? value : JSON.stringify(value, null, 2);
125002
+ const envelope = this.buildEnvelope(value, options);
125003
+ const content = JSON.stringify(envelope, null, 2);
124974
125004
  mkdirSync(path2.dirname(filePath), { recursive: true });
124975
125005
  await writeFile(filePath, content, "utf-8");
124976
125006
  }, {
@@ -124997,11 +125027,39 @@ class FileSystemProvider {
124997
125027
  input: { tenantId, key }
124998
125028
  });
124999
125029
  }
125030
+ async listFilesRecursively(dir, baseDir) {
125031
+ const entries = await readdir(dir, { withFileTypes: true });
125032
+ const results = [];
125033
+ for (const entry of entries) {
125034
+ const fullPath = path2.join(dir, entry.name);
125035
+ if (entry.isDirectory()) {
125036
+ results.push(...await this.listFilesRecursively(fullPath, baseDir));
125037
+ } else if (entry.isFile()) {
125038
+ const rel = path2.relative(baseDir, fullPath);
125039
+ results.push(rel.split(path2.sep).join("/"));
125040
+ }
125041
+ }
125042
+ return results;
125043
+ }
125000
125044
  async list(tenantId, prefix, context) {
125001
125045
  return ErrorHandler.tryCatch(async () => {
125002
125046
  const tenantPath = this.getTenantPath(tenantId);
125003
- const files = await readdir(tenantPath);
125004
- return files.filter((file) => file.startsWith(prefix));
125047
+ const allKeys = await this.listFilesRecursively(tenantPath, tenantPath);
125048
+ const candidateKeys = allKeys.filter((k) => k.startsWith(prefix));
125049
+ const validKeys = [];
125050
+ for (const k of candidateKeys) {
125051
+ const filePath = this.getFilePath(tenantId, k);
125052
+ try {
125053
+ const raw = await readFile(filePath, "utf-8");
125054
+ const value = await this.parseAndValidate(raw, tenantId, k, filePath, context);
125055
+ if (value !== null) {
125056
+ validKeys.push(k);
125057
+ }
125058
+ } catch (_e) {
125059
+ continue;
125060
+ }
125061
+ }
125062
+ return validKeys;
125005
125063
  }, {
125006
125064
  operation: "FileSystemProvider.list",
125007
125065
  context,
@@ -125154,6 +125212,8 @@ SupabaseProvider = __legacyDecorateClassTS([
125154
125212
  ], SupabaseProvider);
125155
125213
 
125156
125214
  // src/storage/providers/cloudflare/r2Provider.ts
125215
+ var R2_ENVELOPE_VERSION = 1;
125216
+
125157
125217
  class R2Provider {
125158
125218
  bucket;
125159
125219
  constructor(bucket) {
@@ -125162,58 +125222,99 @@ class R2Provider {
125162
125222
  getR2Key(tenantId, key) {
125163
125223
  return `${tenantId}:${key}`;
125164
125224
  }
125165
- async get(tenantId, key, context) {
125166
- const r2Key = this.getR2Key(tenantId, key);
125167
- logger.debug(`[R2Provider] Getting key: ${r2Key}`, context);
125168
- const object = await this.bucket.get(r2Key);
125169
- if (object === null) {
125170
- logger.debug(`[R2Provider] Key not found: ${r2Key}`, context);
125171
- return null;
125172
- }
125225
+ buildEnvelope(value, options) {
125226
+ const expiresAt = options?.ttl ? Date.now() + options.ttl * 1000 : undefined;
125227
+ return {
125228
+ __mcp: { v: R2_ENVELOPE_VERSION, ...expiresAt ? { expiresAt } : {} },
125229
+ value
125230
+ };
125231
+ }
125232
+ parseAndValidate(raw, tenantId, key, context) {
125173
125233
  try {
125174
- return await object.json();
125234
+ const parsed = JSON.parse(raw);
125235
+ if (parsed && typeof parsed === "object" && "__mcp" in parsed) {
125236
+ const env2 = parsed;
125237
+ const expiresAt = env2.__mcp?.expiresAt;
125238
+ if (expiresAt && Date.now() > expiresAt) {
125239
+ return null;
125240
+ }
125241
+ return env2.value;
125242
+ }
125243
+ return parsed;
125175
125244
  } catch (error2) {
125176
- logger.error(`[R2Provider] Failed to parse JSON for key: ${r2Key}`, {
125177
- ...context,
125178
- error: error2
125179
- });
125180
- return null;
125245
+ throw new McpError(-32070 /* SerializationError */, `[R2Provider] Failed to parse JSON for key: ${this.getR2Key(tenantId, key)}`, { ...context, error: error2 });
125181
125246
  }
125182
125247
  }
125248
+ async get(tenantId, key, context) {
125249
+ const r2Key = this.getR2Key(tenantId, key);
125250
+ return ErrorHandler.tryCatch(async () => {
125251
+ logger.debug(`[R2Provider] Getting key: ${r2Key}`, context);
125252
+ const object = await this.bucket.get(r2Key);
125253
+ if (object === null) {
125254
+ return null;
125255
+ }
125256
+ const text = await object.text();
125257
+ const value = this.parseAndValidate(text, tenantId, key, context);
125258
+ if (value === null) {
125259
+ await this.bucket.delete(r2Key).catch(() => {});
125260
+ logger.debug(`[R2Provider] Key expired and removed: ${r2Key}`, context);
125261
+ }
125262
+ return value;
125263
+ }, {
125264
+ operation: "R2Provider.get",
125265
+ context,
125266
+ input: { tenantId, key }
125267
+ });
125268
+ }
125183
125269
  async set(tenantId, key, value, context, options) {
125184
125270
  const r2Key = this.getR2Key(tenantId, key);
125185
- logger.debug(`[R2Provider] Setting key: ${r2Key}`, { ...context, options });
125186
- const valueToStore = JSON.stringify(value);
125187
- const putOptions = {};
125188
- if (options?.ttl) {
125189
- logger.warning(`[R2Provider] TTL is not natively supported by R2. The 'ttl' option for key '${r2Key}' will be ignored.`, context);
125190
- }
125191
- await this.bucket.put(r2Key, valueToStore, putOptions);
125192
- logger.debug(`[R2Provider] Successfully set key: ${r2Key}`, context);
125271
+ return ErrorHandler.tryCatch(async () => {
125272
+ logger.debug(`[R2Provider] Setting key: ${r2Key}`, {
125273
+ ...context,
125274
+ options
125275
+ });
125276
+ const envelope = this.buildEnvelope(value, options);
125277
+ const body = JSON.stringify(envelope);
125278
+ await this.bucket.put(r2Key, body);
125279
+ logger.debug(`[R2Provider] Successfully set key: ${r2Key}`, context);
125280
+ }, {
125281
+ operation: "R2Provider.set",
125282
+ context,
125283
+ input: { tenantId, key }
125284
+ });
125193
125285
  }
125194
125286
  async delete(tenantId, key, context) {
125195
125287
  const r2Key = this.getR2Key(tenantId, key);
125196
- logger.debug(`[R2Provider] Deleting key: ${r2Key}`, context);
125197
- const head = await this.bucket.head(r2Key);
125198
- if (head === null) {
125199
- logger.debug(`[R2Provider] Key to delete not found: ${r2Key}`, context);
125200
- return false;
125201
- }
125202
- await this.bucket.delete(r2Key);
125203
- logger.debug(`[R2Provider] Successfully deleted key: ${r2Key}`, context);
125204
- return true;
125288
+ return ErrorHandler.tryCatch(async () => {
125289
+ logger.debug(`[R2Provider] Deleting key: ${r2Key}`, context);
125290
+ const head = await this.bucket.head(r2Key);
125291
+ if (head === null) {
125292
+ logger.debug(`[R2Provider] Key to delete not found: ${r2Key}`, context);
125293
+ return false;
125294
+ }
125295
+ await this.bucket.delete(r2Key);
125296
+ logger.debug(`[R2Provider] Successfully deleted key: ${r2Key}`, context);
125297
+ return true;
125298
+ }, {
125299
+ operation: "R2Provider.delete",
125300
+ context,
125301
+ input: { tenantId, key }
125302
+ });
125205
125303
  }
125206
125304
  async list(tenantId, prefix, context) {
125207
125305
  const r2Prefix = this.getR2Key(tenantId, prefix);
125208
- logger.debug(`[R2Provider] Listing keys with prefix: ${r2Prefix}`, context);
125209
- const listOptions = {
125210
- prefix: r2Prefix
125211
- };
125212
- const listed = await this.bucket.list(listOptions);
125213
- const tenantPrefix = `${tenantId}:`;
125214
- const keys = listed.objects.map((obj) => obj.key.startsWith(tenantPrefix) ? obj.key.substring(tenantPrefix.length) : obj.key);
125215
- logger.debug(`[R2Provider] Found ${keys.length} keys with prefix: ${r2Prefix}`, context);
125216
- return keys;
125306
+ return ErrorHandler.tryCatch(async () => {
125307
+ logger.debug(`[R2Provider] Listing keys with prefix: ${r2Prefix}`, context);
125308
+ const listed = await this.bucket.list({ prefix: r2Prefix });
125309
+ const tenantPrefix = `${tenantId}:`;
125310
+ const keys = listed.objects.map((obj) => obj.key.startsWith(tenantPrefix) ? obj.key.substring(tenantPrefix.length) : obj.key);
125311
+ logger.debug(`[R2Provider] Found ${keys.length} keys with prefix: ${r2Prefix}`, context);
125312
+ return keys;
125313
+ }, {
125314
+ operation: "R2Provider.list",
125315
+ context,
125316
+ input: { tenantId, prefix }
125317
+ });
125217
125318
  }
125218
125319
  }
125219
125320
 
@@ -125228,59 +125329,78 @@ class KvProvider {
125228
125329
  }
125229
125330
  async get(tenantId, key, context) {
125230
125331
  const kvKey = this.getKvKey(tenantId, key);
125231
- logger.debug(`[KvProvider] Getting key: ${kvKey}`, context);
125232
- try {
125233
- const result = await this.kv.get(kvKey, "json");
125234
- if (result === null) {
125235
- logger.debug(`[KvProvider] Key not found: ${kvKey}`, context);
125236
- return null;
125332
+ return ErrorHandler.tryCatch(async () => {
125333
+ logger.debug(`[KvProvider] Getting key: ${kvKey}`, context);
125334
+ try {
125335
+ const result = await this.kv.get(kvKey, "json");
125336
+ return result;
125337
+ } catch (error2) {
125338
+ throw new McpError(-32070 /* SerializationError */, `[KvProvider] Failed to parse JSON for key: ${kvKey}`, { ...context, error: error2 });
125237
125339
  }
125238
- return result;
125239
- } catch (error2) {
125240
- logger.error(`[KvProvider] Failed to get or parse key: ${kvKey}`, {
125241
- ...context,
125242
- error: error2
125243
- });
125244
- return null;
125245
- }
125340
+ }, {
125341
+ operation: "KvProvider.get",
125342
+ context,
125343
+ input: { tenantId, key }
125344
+ });
125246
125345
  }
125247
125346
  async set(tenantId, key, value, context, options) {
125248
125347
  const kvKey = this.getKvKey(tenantId, key);
125249
- logger.debug(`[KvProvider] Setting key: ${kvKey}`, { ...context, options });
125250
- const valueToStore = JSON.stringify(value);
125251
- const putOptions = {};
125252
- if (options?.ttl) {
125253
- putOptions.expirationTtl = options.ttl;
125254
- }
125255
- await this.kv.put(kvKey, valueToStore, putOptions);
125256
- logger.debug(`[KvProvider] Successfully set key: ${kvKey}`, context);
125348
+ return ErrorHandler.tryCatch(async () => {
125349
+ logger.debug(`[KvProvider] Setting key: ${kvKey}`, {
125350
+ ...context,
125351
+ options
125352
+ });
125353
+ const valueToStore = JSON.stringify(value);
125354
+ const putOptions = {};
125355
+ if (options?.ttl) {
125356
+ putOptions.expirationTtl = options.ttl;
125357
+ }
125358
+ await this.kv.put(kvKey, valueToStore, putOptions);
125359
+ logger.debug(`[KvProvider] Successfully set key: ${kvKey}`, context);
125360
+ }, {
125361
+ operation: "KvProvider.set",
125362
+ context,
125363
+ input: { tenantId, key }
125364
+ });
125257
125365
  }
125258
125366
  async delete(tenantId, key, context) {
125259
125367
  const kvKey = this.getKvKey(tenantId, key);
125260
- logger.debug(`[KvProvider] Deleting key: ${kvKey}`, context);
125261
- const value = await this.kv.get(kvKey);
125262
- if (value === null) {
125263
- logger.debug(`[KvProvider] Key to delete not found: ${kvKey}`, context);
125264
- return false;
125265
- }
125266
- await this.kv.delete(kvKey);
125267
- logger.debug(`[KvProvider] Successfully deleted key: ${kvKey}`, context);
125268
- return true;
125368
+ return ErrorHandler.tryCatch(async () => {
125369
+ logger.debug(`[KvProvider] Deleting key: ${kvKey}`, context);
125370
+ const value = await this.kv.get(kvKey);
125371
+ if (value === null) {
125372
+ logger.debug(`[KvProvider] Key to delete not found: ${kvKey}`, context);
125373
+ return false;
125374
+ }
125375
+ await this.kv.delete(kvKey);
125376
+ logger.debug(`[KvProvider] Successfully deleted key: ${kvKey}`, context);
125377
+ return true;
125378
+ }, {
125379
+ operation: "KvProvider.delete",
125380
+ context,
125381
+ input: { tenantId, key }
125382
+ });
125269
125383
  }
125270
125384
  async list(tenantId, prefix, context) {
125271
125385
  const kvPrefix = this.getKvKey(tenantId, prefix);
125272
- logger.debug(`[KvProvider] Listing keys with prefix: ${kvPrefix}`, context);
125273
- const listed = await this.kv.list({ prefix: kvPrefix });
125274
- const tenantPrefix = `${tenantId}:`;
125275
- const keys = listed.keys.map((keyInfo) => keyInfo.name.startsWith(tenantPrefix) ? keyInfo.name.substring(tenantPrefix.length) : keyInfo.name);
125276
- logger.debug(`[KvProvider] Found ${keys.length} keys with prefix: ${kvPrefix}`, context);
125277
- return keys;
125386
+ return ErrorHandler.tryCatch(async () => {
125387
+ logger.debug(`[KvProvider] Listing keys with prefix: ${kvPrefix}`, context);
125388
+ const listed = await this.kv.list({ prefix: kvPrefix });
125389
+ const tenantPrefix = `${tenantId}:`;
125390
+ const keys = listed.keys.map((keyInfo) => keyInfo.name.startsWith(tenantPrefix) ? keyInfo.name.substring(tenantPrefix.length) : keyInfo.name);
125391
+ logger.debug(`[KvProvider] Found ${keys.length} keys with prefix: ${kvPrefix}`, context);
125392
+ return keys;
125393
+ }, {
125394
+ operation: "KvProvider.list",
125395
+ context,
125396
+ input: { tenantId, prefix }
125397
+ });
125278
125398
  }
125279
125399
  }
125280
125400
 
125281
125401
  // src/storage/core/storageFactory.ts
125282
125402
  var isServerless3 = typeof process === "undefined" || process.env.IS_SERVERLESS === "true";
125283
- function createStorageProvider(config2) {
125403
+ function createStorageProvider(config2, deps = {}) {
125284
125404
  const context = requestContextService.createRequestContext({
125285
125405
  operation: "createStorageProvider"
125286
125406
  });
@@ -125302,16 +125422,19 @@ function createStorageProvider(config2) {
125302
125422
  if (!config2.supabase?.url || !config2.supabase?.serviceRoleKey) {
125303
125423
  throw new McpError(-32008 /* ConfigurationError */, "SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY must be set for the supabase storage provider.", context);
125304
125424
  }
125425
+ if (deps.supabaseClient) {
125426
+ return new SupabaseProvider(deps.supabaseClient);
125427
+ }
125305
125428
  return import_tsyringe5.container.resolve(SupabaseProvider);
125306
125429
  case "cloudflare-r2":
125307
125430
  if (isServerless3) {
125308
- const bucket = globalThis.R2_BUCKET;
125431
+ const bucket = deps.r2Bucket ?? globalThis.R2_BUCKET;
125309
125432
  return new R2Provider(bucket);
125310
125433
  }
125311
125434
  throw new McpError(-32008 /* ConfigurationError */, "Cloudflare R2 storage is only available in a Cloudflare Worker environment.", context);
125312
125435
  case "cloudflare-kv":
125313
125436
  if (isServerless3) {
125314
- const kv = globalThis.KV_NAMESPACE;
125437
+ const kv = deps.kvNamespace ?? globalThis.KV_NAMESPACE;
125315
125438
  return new KvProvider(kv);
125316
125439
  }
125317
125440
  throw new McpError(-32008 /* ConfigurationError */, "Cloudflare KV storage is only available in a Cloudflare Worker environment.", context);
@@ -125387,9 +125510,9 @@ function withRequiredScopes(requiredScopes) {
125387
125510
 
125388
125511
  // src/mcp-server/transports/auth/lib/withAuth.ts
125389
125512
  function withToolAuth(requiredScopes, logicFn) {
125390
- return async (input, context) => {
125513
+ return async (input, context, sdkContext) => {
125391
125514
  withRequiredScopes(requiredScopes);
125392
- return Promise.resolve(logicFn(input, context));
125515
+ return Promise.resolve(logicFn(input, context, sdkContext));
125393
125516
  };
125394
125517
  }
125395
125518
  function withResourceAuth(requiredScopes, logicFn) {
@@ -128768,7 +128891,7 @@ var import_tsyringe8 = __toESM(require_cjs3(), 1);
128768
128891
 
128769
128892
  // src/mcp-server/tools/definitions/template-cat-fact.tool.ts
128770
128893
  var TOOL_NAME = "template_cat_fact";
128771
- var TOOL_TITLE = "template_cat_fact";
128894
+ var TOOL_TITLE = "Template Cat Fact";
128772
128895
  var TOOL_DESCRIPTION = "Fetches a random cat fact from a public API with an optional maximum length.";
128773
128896
  var TOOL_ANNOTATIONS = {
128774
128897
  readOnlyHint: true,
@@ -128790,20 +128913,20 @@ var OutputSchema2 = z.object({
128790
128913
  requestedMaxLength: z.number().int().optional().describe("The maximum length that was requested for the fact."),
128791
128914
  timestamp: z.string().datetime().describe("ISO 8601 timestamp of when the response was generated.")
128792
128915
  }).describe("Cat fact tool response payload.");
128793
- async function catFactToolLogic(input, context) {
128916
+ async function catFactToolLogic(input, appContext, _sdkContext) {
128794
128917
  logger.debug("Processing template_cat_fact logic.", {
128795
- ...context,
128918
+ ...appContext,
128796
128919
  toolInput: input
128797
128920
  });
128798
128921
  const url = input.maxLength !== undefined ? `${CAT_FACT_API_URL}?max_length=${input.maxLength}` : CAT_FACT_API_URL;
128799
- logger.info(`Fetching random cat fact from: ${url}`, context);
128800
- const response = await fetchWithTimeout(url, CAT_FACT_API_TIMEOUT_MS, context);
128922
+ logger.info(`Fetching random cat fact from: ${url}`, appContext);
128923
+ const response = await fetchWithTimeout(url, CAT_FACT_API_TIMEOUT_MS, appContext);
128801
128924
  if (!response.ok) {
128802
128925
  const errorText = await response.text().catch(() => {
128803
128926
  return;
128804
128927
  });
128805
128928
  throw new McpError(-32000 /* ServiceUnavailable */, `Cat Fact API request failed: ${response.status} ${response.statusText}`, {
128806
- requestId: context.requestId,
128929
+ requestId: appContext.requestId,
128807
128930
  httpStatusCode: response.status,
128808
128931
  responseBody: errorText
128809
128932
  });
@@ -128812,12 +128935,12 @@ async function catFactToolLogic(input, context) {
128812
128935
  const parsed = CatFactApiSchema.safeParse(rawData);
128813
128936
  if (!parsed.success) {
128814
128937
  logger.error("Cat Fact API response validation failed", {
128815
- ...context,
128938
+ ...appContext,
128816
128939
  receivedData: rawData,
128817
128940
  issues: parsed.error.issues
128818
128941
  });
128819
128942
  throw new McpError(-32000 /* ServiceUnavailable */, "Cat Fact API returned unexpected data format.", {
128820
- requestId: context.requestId,
128943
+ requestId: appContext.requestId,
128821
128944
  issues: parsed.error.issues
128822
128945
  });
128823
128946
  }
@@ -128829,7 +128952,7 @@ async function catFactToolLogic(input, context) {
128829
128952
  timestamp: new Date().toISOString()
128830
128953
  };
128831
128954
  logger.notice("Random cat fact fetched and processed successfully.", {
128832
- ...context,
128955
+ ...appContext,
128833
128956
  factLength: toolResponse.length
128834
128957
  });
128835
128958
  return toolResponse;
@@ -128855,7 +128978,7 @@ var catFactTool = {
128855
128978
 
128856
128979
  // src/mcp-server/tools/definitions/template-echo-message.tool.ts
128857
128980
  var TOOL_NAME2 = "template_echo_message";
128858
- var TOOL_TITLE2 = "Echo Message";
128981
+ var TOOL_TITLE2 = "Template Echo Message";
128859
128982
  var TOOL_DESCRIPTION2 = "Echoes a message back with optional formatting and repetition.";
128860
128983
  var TOOL_ANNOTATIONS2 = {
128861
128984
  readOnlyHint: true,
@@ -128881,17 +129004,17 @@ var OutputSchema3 = z.object({
128881
129004
  repeatCount: z.number().int().min(1).describe("The number of times the message was repeated."),
128882
129005
  timestamp: z.string().datetime().optional().describe("Optional ISO 8601 timestamp of when the response was generated.")
128883
129006
  }).describe("Echo tool response payload.");
128884
- async function echoToolLogic(input, context) {
129007
+ async function echoToolLogic(input, appContext, _sdkContext) {
128885
129008
  logger.debug("Processing echo message logic.", {
128886
- ...context,
129009
+ ...appContext,
128887
129010
  toolInput: input
128888
129011
  });
128889
129012
  if (input.message === TEST_ERROR_TRIGGER_MESSAGE) {
128890
129013
  const errorData = {
128891
- requestId: context.requestId
129014
+ requestId: appContext.requestId
128892
129015
  };
128893
- if (typeof context.traceId === "string") {
128894
- errorData.traceId = context.traceId;
129016
+ if (typeof appContext.traceId === "string") {
129017
+ errorData.traceId = appContext.traceId;
128895
129018
  }
128896
129019
  throw new McpError(-32007 /* ValidationError */, "Deliberate failure triggered.", errorData);
128897
129020
  }
@@ -128950,7 +129073,7 @@ function arrayBufferToBase64(buffer) {
128950
129073
 
128951
129074
  // src/mcp-server/tools/definitions/template-image-test.tool.ts
128952
129075
  var TOOL_NAME3 = "template_image_test";
128953
- var TOOL_TITLE3 = "Fetch Image Test";
129076
+ var TOOL_TITLE3 = "Template Image Test";
128954
129077
  var TOOL_DESCRIPTION3 = "Fetches a random cat image and returns it base64-encoded with the MIME type. Useful for testing image handling.";
128955
129078
  var TOOL_ANNOTATIONS3 = {
128956
129079
  readOnlyHint: true,
@@ -128965,25 +129088,25 @@ var OutputSchema4 = z.object({
128965
129088
  data: z.string().describe("Base64 encoded image data."),
128966
129089
  mimeType: z.string().describe("The MIME type of the image (e.g., 'image/jpeg').")
128967
129090
  }).describe("Image tool response payload.");
128968
- async function imageTestToolLogic(input, context) {
129091
+ async function imageTestToolLogic(input, appContext, _sdkContext) {
128969
129092
  logger.debug("Processing template_image_test logic.", {
128970
- ...context,
129093
+ ...appContext,
128971
129094
  toolInput: input
128972
129095
  });
128973
- const response = await fetchWithTimeout(CAT_API_URL, API_TIMEOUT_MS, context);
129096
+ const response = await fetchWithTimeout(CAT_API_URL, API_TIMEOUT_MS, appContext);
128974
129097
  if (!response.ok) {
128975
129098
  const errorText = await response.text().catch(() => {
128976
129099
  return;
128977
129100
  });
128978
129101
  throw new McpError(-32000 /* ServiceUnavailable */, `Image API request failed: ${response.status} ${response.statusText}`, {
128979
- requestId: context.requestId,
129102
+ requestId: appContext.requestId,
128980
129103
  httpStatusCode: response.status,
128981
129104
  responseBody: errorText
128982
129105
  });
128983
129106
  }
128984
129107
  const arrayBuf = await response.arrayBuffer();
128985
129108
  if (arrayBuf.byteLength === 0) {
128986
- throw new McpError(-32000 /* ServiceUnavailable */, "Image API returned an empty payload.", { requestId: context.requestId });
129109
+ throw new McpError(-32000 /* ServiceUnavailable */, "Image API returned an empty payload.", { requestId: appContext.requestId });
128987
129110
  }
128988
129111
  const mimeType = response.headers.get("content-type") || "image/jpeg";
128989
129112
  const result = {
@@ -128991,7 +129114,7 @@ async function imageTestToolLogic(input, context) {
128991
129114
  mimeType
128992
129115
  };
128993
129116
  logger.notice("Image fetched and encoded successfully.", {
128994
- ...context,
129117
+ ...appContext,
128995
129118
  mimeType,
128996
129119
  byteLength: arrayBuf.byteLength
128997
129120
  });
@@ -129017,8 +129140,91 @@ var imageTestTool = {
129017
129140
  responseFormatter: responseFormatter3
129018
129141
  };
129019
129142
 
129143
+ // src/mcp-server/tools/definitions/template_madlibs_elicitation.tool.ts
129144
+ var TOOL_NAME4 = "template_madlibs_elicitation";
129145
+ var TOOL_TITLE4 = "Mad Libs Elicitation Game";
129146
+ 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.";
129147
+ var TOOL_ANNOTATIONS4 = {
129148
+ readOnlyHint: true,
129149
+ idempotentHint: false,
129150
+ openWorldHint: false
129151
+ };
129152
+ var InputSchema4 = z.object({
129153
+ noun: z.string().optional().describe("A noun for the story."),
129154
+ verb: z.string().optional().describe("A verb (past tense) for the story."),
129155
+ adjective: z.string().optional().describe("An adjective for the story.")
129156
+ }).describe("Inputs for the Mad Libs game. Any missing fields will be elicited.");
129157
+ var OutputSchema5 = z.object({
129158
+ story: z.string().describe("The final, generated Mad Libs story."),
129159
+ noun: z.string().describe("The noun used in the story."),
129160
+ verb: z.string().describe("The verb used in the story."),
129161
+ adjective: z.string().describe("The adjective used in the story.")
129162
+ }).describe("The completed Mad Libs story and the words used.");
129163
+ function hasElicitInput(ctx) {
129164
+ return typeof ctx?.elicitInput === "function";
129165
+ }
129166
+ async function elicitAndValidate(partOfSpeech, sdkContext) {
129167
+ if (!hasElicitInput(sdkContext)) {
129168
+ throw new McpError(-32600 /* InvalidRequest */, "Elicitation is not available in the current context.", { requestId: sdkContext.requestId, operation: "madlibs.elicit" });
129169
+ }
129170
+ const elicitedUnknown = await sdkContext.elicitInput({
129171
+ message: `I need a ${partOfSpeech}.`,
129172
+ schema: { type: "string" }
129173
+ });
129174
+ const validation = z.string().min(1).safeParse(elicitedUnknown);
129175
+ if (!validation.success) {
129176
+ throw new McpError(-32602 /* InvalidParams */, `Invalid ${partOfSpeech} received from user.`, { provided: elicitedUnknown });
129177
+ }
129178
+ return validation.data;
129179
+ }
129180
+ async function madlibsToolLogic(input, appContext, sdkContext) {
129181
+ logger.debug("Processing Mad Libs logic.", { ...appContext, toolInput: input });
129182
+ const noun = input.noun ?? await elicitAndValidate("noun", sdkContext);
129183
+ const verb = input.verb ?? await elicitAndValidate("verb", sdkContext);
129184
+ const adjective = input.adjective ?? await elicitAndValidate("adjective", sdkContext);
129185
+ const story = `The ${adjective} ${noun} ${verb} over the lazy dog.`;
129186
+ const response = {
129187
+ story,
129188
+ noun,
129189
+ verb,
129190
+ adjective
129191
+ };
129192
+ return Promise.resolve(response);
129193
+ }
129194
+ function responseFormatter4(result) {
129195
+ return [
129196
+ {
129197
+ type: "text",
129198
+ text: result.story
129199
+ },
129200
+ {
129201
+ type: "text",
129202
+ text: JSON.stringify({
129203
+ noun: result.noun,
129204
+ verb: result.verb,
129205
+ adjective: result.adjective
129206
+ }, null, 2)
129207
+ }
129208
+ ];
129209
+ }
129210
+ var madlibsElicitationTool = {
129211
+ name: TOOL_NAME4,
129212
+ title: TOOL_TITLE4,
129213
+ description: TOOL_DESCRIPTION4,
129214
+ inputSchema: InputSchema4,
129215
+ outputSchema: OutputSchema5,
129216
+ annotations: TOOL_ANNOTATIONS4,
129217
+ logic: withToolAuth(["tool:madlibs:play"], madlibsToolLogic),
129218
+ responseFormatter: responseFormatter4
129219
+ };
129220
+
129020
129221
  // src/mcp-server/tools/definitions/index.ts
129021
- var allToolDefinitions = [catFactTool, echoTool, imageTestTool];
129222
+ var allToolDefinitions = [
129223
+ catFactTool,
129224
+ echoTool,
129225
+ imageTestTool,
129226
+ madlibsElicitationTool
129227
+ ];
129022
129228
 
129023
129229
  // src/mcp-server/tools/utils/toolHandlerFactory.ts
129024
129230
  var defaultResponseFormatter2 = (result) => [
@@ -129027,25 +129233,29 @@ var defaultResponseFormatter2 = (result) => [
129027
129233
  function createMcpToolHandler({
129028
129234
  toolName,
129029
129235
  logic,
129030
- responseFormatter: responseFormatter4 = defaultResponseFormatter2
129236
+ responseFormatter: responseFormatter5 = defaultResponseFormatter2
129031
129237
  }) {
129032
129238
  return async (input, callContext) => {
129033
- const sessionId = typeof callContext?.sessionId === "string" ? callContext.sessionId : undefined;
129034
- const handlerContext = requestContextService.createRequestContext({
129035
- parentContext: callContext,
129239
+ const sdkContext = callContext;
129240
+ const sessionId = typeof sdkContext?.sessionId === "string" ? sdkContext.sessionId : undefined;
129241
+ const appContext = requestContextService.createRequestContext({
129242
+ parentContext: sdkContext,
129036
129243
  operation: "HandleToolRequest",
129037
129244
  additionalContext: { toolName, sessionId, input }
129038
129245
  });
129246
+ if ("elicitInput" in sdkContext && typeof sdkContext.elicitInput === "function") {
129247
+ appContext.elicitInput = sdkContext.elicitInput;
129248
+ }
129039
129249
  try {
129040
- const result = await measureToolExecution(() => logic(input, handlerContext), { ...handlerContext, toolName }, input);
129250
+ const result = await measureToolExecution(() => logic(input, appContext, sdkContext), { ...appContext, toolName }, input);
129041
129251
  return {
129042
129252
  structuredContent: result,
129043
- content: responseFormatter4(result)
129253
+ content: responseFormatter5(result)
129044
129254
  };
129045
129255
  } catch (error2) {
129046
129256
  const mcpError = ErrorHandler.handleError(error2, {
129047
129257
  operation: `tool:${toolName}`,
129048
- context: handlerContext,
129258
+ context: appContext,
129049
129259
  input
129050
129260
  });
129051
129261
  return {
@@ -133920,7 +134130,8 @@ async function createMcpServerInstance() {
133920
134130
  capabilities: {
133921
134131
  logging: {},
133922
134132
  resources: { listChanged: true },
133923
- tools: { listChanged: true }
134133
+ tools: { listChanged: true },
134134
+ elicitation: {}
133924
134135
  }
133925
134136
  });
133926
134137
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ts-template",
3
- "version": "2.1.8",
3
+ "version": "2.2.0",
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",
@@ -55,8 +55,8 @@
55
55
  "prepare": "[ -d .husky ] && husky || true",
56
56
  "inspector": "bunx mcp-inspector --config mcp.json --server mcp-ts-template",
57
57
  "db:duckdb-example": "MCP_LOG_LEVEL=debug tsc && node dist/storage/duckdbExample.js",
58
- "test": "bun test",
59
- "test:coverage": "bun test --coverage",
58
+ "test": "bun test --config vitest.config.ts",
59
+ "test:coverage": "bun test --coverage --config vitest.config.ts",
60
60
  "audit": "bun audit",
61
61
  "audit:fix": "bun audit --fix",
62
62
  "publish-mcp": "bun scripts/validate-mcp-publish-schema.ts"
@@ -154,6 +154,7 @@
154
154
  "mcp",
155
155
  "model-context-protocol",
156
156
  "mcp-server",
157
+ "elicitation",
157
158
  "observability",
158
159
  "opentelemetry",
159
160
  "otel",