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.
- package/README.md +57 -47
- package/dist/index.js +332 -121
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
7
7
|
|
|
8
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-06-18/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE) [](https://github.com/cyanheads/mcp-ts-template/issues) [](https://www.typescriptlang.org/) [](https://bun.sh/) [](./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.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
179
|
-
|
|
|
180
|
-
| `MCP_TRANSPORT_TYPE`
|
|
181
|
-
| `MCP_HTTP_PORT`
|
|
182
|
-
| `MCP_AUTH_MODE`
|
|
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`
|
|
185
|
-
| `LOG_LEVEL`
|
|
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
|
|
227
|
+
bun test # Runs the test suite
|
|
223
228
|
```
|
|
224
229
|
|
|
225
230
|
### Cloudflare Workers
|
|
226
231
|
|
|
227
|
-
1.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
`
|
|
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
|
|
243
|
-
|
|
|
244
|
-
| `src/mcp-server/tools/definitions`
|
|
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`
|
|
247
|
-
| `src/storage`
|
|
248
|
-
| `src/services`
|
|
249
|
-
| `src/container`
|
|
250
|
-
| `src/utils`
|
|
251
|
-
| `src/config`
|
|
252
|
-
| `tests/`
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
125004
|
-
|
|
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
|
-
|
|
125166
|
-
const
|
|
125167
|
-
|
|
125168
|
-
|
|
125169
|
-
|
|
125170
|
-
|
|
125171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125186
|
-
|
|
125187
|
-
|
|
125188
|
-
|
|
125189
|
-
|
|
125190
|
-
|
|
125191
|
-
|
|
125192
|
-
|
|
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
|
-
|
|
125197
|
-
|
|
125198
|
-
|
|
125199
|
-
|
|
125200
|
-
|
|
125201
|
-
|
|
125202
|
-
|
|
125203
|
-
|
|
125204
|
-
|
|
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
|
-
|
|
125209
|
-
|
|
125210
|
-
prefix: r2Prefix
|
|
125211
|
-
|
|
125212
|
-
|
|
125213
|
-
|
|
125214
|
-
|
|
125215
|
-
|
|
125216
|
-
|
|
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
|
-
|
|
125232
|
-
|
|
125233
|
-
|
|
125234
|
-
|
|
125235
|
-
|
|
125236
|
-
|
|
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
|
-
|
|
125239
|
-
|
|
125240
|
-
|
|
125241
|
-
|
|
125242
|
-
|
|
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
|
-
|
|
125250
|
-
|
|
125251
|
-
|
|
125252
|
-
|
|
125253
|
-
|
|
125254
|
-
|
|
125255
|
-
|
|
125256
|
-
|
|
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
|
-
|
|
125261
|
-
|
|
125262
|
-
|
|
125263
|
-
|
|
125264
|
-
|
|
125265
|
-
|
|
125266
|
-
|
|
125267
|
-
|
|
125268
|
-
|
|
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
|
-
|
|
125273
|
-
|
|
125274
|
-
|
|
125275
|
-
|
|
125276
|
-
|
|
125277
|
-
|
|
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 = "
|
|
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,
|
|
128916
|
+
async function catFactToolLogic(input, appContext, _sdkContext) {
|
|
128794
128917
|
logger.debug("Processing template_cat_fact logic.", {
|
|
128795
|
-
...
|
|
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}`,
|
|
128800
|
-
const response = await fetchWithTimeout(url, CAT_FACT_API_TIMEOUT_MS,
|
|
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:
|
|
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
|
-
...
|
|
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:
|
|
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
|
-
...
|
|
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,
|
|
129007
|
+
async function echoToolLogic(input, appContext, _sdkContext) {
|
|
128885
129008
|
logger.debug("Processing echo message logic.", {
|
|
128886
|
-
...
|
|
129009
|
+
...appContext,
|
|
128887
129010
|
toolInput: input
|
|
128888
129011
|
});
|
|
128889
129012
|
if (input.message === TEST_ERROR_TRIGGER_MESSAGE) {
|
|
128890
129013
|
const errorData = {
|
|
128891
|
-
requestId:
|
|
129014
|
+
requestId: appContext.requestId
|
|
128892
129015
|
};
|
|
128893
|
-
if (typeof
|
|
128894
|
-
errorData.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 = "
|
|
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,
|
|
129091
|
+
async function imageTestToolLogic(input, appContext, _sdkContext) {
|
|
128969
129092
|
logger.debug("Processing template_image_test logic.", {
|
|
128970
|
-
...
|
|
129093
|
+
...appContext,
|
|
128971
129094
|
toolInput: input
|
|
128972
129095
|
});
|
|
128973
|
-
const response = await fetchWithTimeout(CAT_API_URL, API_TIMEOUT_MS,
|
|
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:
|
|
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:
|
|
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
|
-
...
|
|
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 = [
|
|
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:
|
|
129236
|
+
responseFormatter: responseFormatter5 = defaultResponseFormatter2
|
|
129031
129237
|
}) {
|
|
129032
129238
|
return async (input, callContext) => {
|
|
129033
|
-
const
|
|
129034
|
-
const
|
|
129035
|
-
|
|
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,
|
|
129250
|
+
const result = await measureToolExecution(() => logic(input, appContext, sdkContext), { ...appContext, toolName }, input);
|
|
129041
129251
|
return {
|
|
129042
129252
|
structuredContent: result,
|
|
129043
|
-
content:
|
|
129253
|
+
content: responseFormatter5(result)
|
|
129044
129254
|
};
|
|
129045
129255
|
} catch (error2) {
|
|
129046
129256
|
const mcpError = ErrorHandler.handleError(error2, {
|
|
129047
129257
|
operation: `tool:${toolName}`,
|
|
129048
|
-
context:
|
|
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.
|
|
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",
|