@cyanheads/mcp-ts-core 0.1.29 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -1
- package/README.md +18 -1
- package/biome.json +10 -0
- package/dist/testing/fuzz.d.ts +109 -0
- package/dist/testing/fuzz.d.ts.map +1 -0
- package/dist/testing/fuzz.js +558 -0
- package/dist/testing/fuzz.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/network/retry.d.ts +83 -0
- package/dist/utils/network/retry.d.ts.map +1 -0
- package/dist/utils/network/retry.js +152 -0
- package/dist/utils/network/retry.js.map +1 -0
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
- package/dist/utils/parsing/xmlParser.js +3 -1
- package/dist/utils/parsing/xmlParser.js.map +1 -1
- package/package.json +5 -1
- package/skills/add-service/SKILL.md +53 -0
- package/skills/api-utils/SKILL.md +1 -0
- package/skills/design-mcp-server/SKILL.md +11 -0
- package/skills/report-issue-framework/SKILL.md +223 -0
- package/skills/report-issue-local/SKILL.md +217 -0
- package/skills/setup/SKILL.md +1 -0
- package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +106 -0
- package/templates/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +36 -0
- package/templates/AGENTS.md +2 -0
- package/templates/CLAUDE.md +2 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { RequestContext } from '../../utils/internal/requestContext.js';
|
|
2
|
+
/** Configuration for {@link withRetry}. */
|
|
3
|
+
export interface RetryOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Base delay in milliseconds before the first retry.
|
|
6
|
+
* Subsequent delays are `baseDelayMs * 2^attempt`. Default: `1000`.
|
|
7
|
+
*
|
|
8
|
+
* Calibrate to the upstream's recovery time:
|
|
9
|
+
* - 200–500ms for ephemeral failures (connection pool)
|
|
10
|
+
* - 1–2s for rate-limited APIs
|
|
11
|
+
* - 2–5s for service degradation / outages
|
|
12
|
+
*/
|
|
13
|
+
baseDelayMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Request context for correlated logging. When provided, log entries
|
|
16
|
+
* include `requestId`, `traceId`, etc.
|
|
17
|
+
*/
|
|
18
|
+
context?: RequestContext;
|
|
19
|
+
/**
|
|
20
|
+
* Custom predicate to determine if an error is transient and should be
|
|
21
|
+
* retried. When provided, this replaces the default `McpError` code check.
|
|
22
|
+
* Return `true` to retry, `false` to fail immediately.
|
|
23
|
+
*/
|
|
24
|
+
isTransient?: (error: unknown) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Jitter factor applied to each delay. `0` = no jitter, `1` = full jitter
|
|
27
|
+
* (delay randomized between 0 and calculated delay). Default: `0.25`.
|
|
28
|
+
*/
|
|
29
|
+
jitter?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum delay cap in milliseconds. Prevents unbounded growth on high
|
|
32
|
+
* retry counts. Default: `30000` (30s).
|
|
33
|
+
*/
|
|
34
|
+
maxDelayMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of retry attempts after the initial call.
|
|
37
|
+
* Total attempts = `maxRetries + 1`. Default: `3`.
|
|
38
|
+
*/
|
|
39
|
+
maxRetries?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Operation name for structured log messages. Used in log context and
|
|
42
|
+
* enriched error messages on exhaustion.
|
|
43
|
+
*/
|
|
44
|
+
operation?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional AbortSignal. When aborted, the retry loop exits immediately
|
|
47
|
+
* without further attempts.
|
|
48
|
+
*/
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Executes `fn` with retry logic and exponential backoff.
|
|
53
|
+
*
|
|
54
|
+
* The retry boundary should wrap the **full pipeline** — HTTP fetch, response
|
|
55
|
+
* parsing, and validation — not just the network call. This ensures that
|
|
56
|
+
* transient upstream errors (e.g., HTTP 200 with an error body) are retried.
|
|
57
|
+
*
|
|
58
|
+
* When retries exhaust, the final error is enriched with attempt count in both
|
|
59
|
+
* the message and structured data, so callers know retries were already attempted.
|
|
60
|
+
*
|
|
61
|
+
* @typeParam T - Return type of the operation.
|
|
62
|
+
* @param fn - The async operation to execute with retries.
|
|
63
|
+
* @param options - Retry configuration. All fields optional with sensible defaults.
|
|
64
|
+
* @returns The result of `fn` on success.
|
|
65
|
+
* @throws The enriched final error when all attempts are exhausted, or the original
|
|
66
|
+
* error immediately if it is not classified as transient.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* // Service method — retry covers fetch + parse
|
|
71
|
+
* async function fetchStudy(id: string, ctx: Context): Promise<Study> {
|
|
72
|
+
* return withRetry(
|
|
73
|
+
* async () => {
|
|
74
|
+
* const text = await apiClient.get(`/studies/${id}`);
|
|
75
|
+
* return responseHandler.parse<Study>(text);
|
|
76
|
+
* },
|
|
77
|
+
* { operation: 'fetchStudy', context: ctx, baseDelayMs: 1000 },
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
83
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAYzE,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE1C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA6DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,CAiD/F"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Retry utility with exponential backoff for wrapping operations that
|
|
3
|
+
* may fail transiently. Designed so the retry boundary covers the full pipeline
|
|
4
|
+
* (HTTP fetch + response parsing/validation), not just the network call.
|
|
5
|
+
* @module src/utils/network/retry
|
|
6
|
+
* @see docs/service-resilience.md
|
|
7
|
+
*/
|
|
8
|
+
import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
|
|
9
|
+
import { logger } from '../../utils/internal/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Error codes considered transient — eligible for retry.
|
|
12
|
+
* Matches the framework's error classification in `mappings.ts`.
|
|
13
|
+
*/
|
|
14
|
+
const TRANSIENT_CODES = new Set([
|
|
15
|
+
JsonRpcErrorCode.ServiceUnavailable,
|
|
16
|
+
JsonRpcErrorCode.Timeout,
|
|
17
|
+
JsonRpcErrorCode.RateLimited,
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Computes the backoff delay for a given attempt with optional jitter.
|
|
21
|
+
*
|
|
22
|
+
* @param attempt - Zero-based attempt index (0 = first retry).
|
|
23
|
+
* @param baseDelayMs - Base delay in milliseconds.
|
|
24
|
+
* @param maxDelayMs - Maximum delay cap.
|
|
25
|
+
* @param jitter - Jitter factor (0–1).
|
|
26
|
+
* @returns Delay in milliseconds.
|
|
27
|
+
*/
|
|
28
|
+
function computeDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
|
|
29
|
+
const exponential = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
30
|
+
if (jitter <= 0)
|
|
31
|
+
return exponential;
|
|
32
|
+
const jitterRange = exponential * jitter;
|
|
33
|
+
return exponential - jitterRange + Math.random() * jitterRange * 2;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default transient check: `McpError` with a transient code, or any non-McpError
|
|
37
|
+
* (network failures, unexpected throws) which are assumed transient.
|
|
38
|
+
*/
|
|
39
|
+
function defaultIsTransient(error) {
|
|
40
|
+
if (error instanceof McpError) {
|
|
41
|
+
return TRANSIENT_CODES.has(error.code);
|
|
42
|
+
}
|
|
43
|
+
// Non-McpError (raw network errors, unexpected throws) — assume transient
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Enriches an error with retry exhaustion context.
|
|
48
|
+
* Appends attempt count to the message and to `data` for programmatic access.
|
|
49
|
+
*/
|
|
50
|
+
function enrichExhaustedError(error, totalAttempts, operation) {
|
|
51
|
+
if (error instanceof McpError) {
|
|
52
|
+
const suffix = `(failed after ${totalAttempts} attempt${totalAttempts > 1 ? 's' : ''})`;
|
|
53
|
+
const enrichedMessage = error.message ? `${error.message} ${suffix}` : suffix;
|
|
54
|
+
const enrichedData = {
|
|
55
|
+
...error.data,
|
|
56
|
+
retryAttempts: totalAttempts,
|
|
57
|
+
...(operation ? { operation } : {}),
|
|
58
|
+
};
|
|
59
|
+
return new McpError(error.code, enrichedMessage, enrichedData, { cause: error });
|
|
60
|
+
}
|
|
61
|
+
if (error instanceof Error) {
|
|
62
|
+
const suffix = `(failed after ${totalAttempts} attempt${totalAttempts > 1 ? 's' : ''})`;
|
|
63
|
+
const wrapped = new Error(`${error.message} ${suffix}`, { cause: error });
|
|
64
|
+
wrapped.name = error.name;
|
|
65
|
+
return wrapped;
|
|
66
|
+
}
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Executes `fn` with retry logic and exponential backoff.
|
|
71
|
+
*
|
|
72
|
+
* The retry boundary should wrap the **full pipeline** — HTTP fetch, response
|
|
73
|
+
* parsing, and validation — not just the network call. This ensures that
|
|
74
|
+
* transient upstream errors (e.g., HTTP 200 with an error body) are retried.
|
|
75
|
+
*
|
|
76
|
+
* When retries exhaust, the final error is enriched with attempt count in both
|
|
77
|
+
* the message and structured data, so callers know retries were already attempted.
|
|
78
|
+
*
|
|
79
|
+
* @typeParam T - Return type of the operation.
|
|
80
|
+
* @param fn - The async operation to execute with retries.
|
|
81
|
+
* @param options - Retry configuration. All fields optional with sensible defaults.
|
|
82
|
+
* @returns The result of `fn` on success.
|
|
83
|
+
* @throws The enriched final error when all attempts are exhausted, or the original
|
|
84
|
+
* error immediately if it is not classified as transient.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // Service method — retry covers fetch + parse
|
|
89
|
+
* async function fetchStudy(id: string, ctx: Context): Promise<Study> {
|
|
90
|
+
* return withRetry(
|
|
91
|
+
* async () => {
|
|
92
|
+
* const text = await apiClient.get(`/studies/${id}`);
|
|
93
|
+
* return responseHandler.parse<Study>(text);
|
|
94
|
+
* },
|
|
95
|
+
* { operation: 'fetchStudy', context: ctx, baseDelayMs: 1000 },
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export async function withRetry(fn, options = {}) {
|
|
101
|
+
const { maxRetries = 3, baseDelayMs = 1000, maxDelayMs = 30_000, jitter = 0.25, operation, context, signal, isTransient = defaultIsTransient, } = options;
|
|
102
|
+
const totalAttempts = maxRetries + 1;
|
|
103
|
+
for (let attempt = 0; attempt < totalAttempts; attempt++) {
|
|
104
|
+
try {
|
|
105
|
+
return await fn();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Abort signal — exit immediately, no more retries
|
|
109
|
+
if (signal?.aborted) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
const isLastAttempt = attempt >= maxRetries;
|
|
113
|
+
// Non-transient errors fail immediately
|
|
114
|
+
if (!isTransient(error)) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
if (isLastAttempt) {
|
|
118
|
+
throw enrichExhaustedError(error, totalAttempts, operation);
|
|
119
|
+
}
|
|
120
|
+
// Log and backoff
|
|
121
|
+
const delay = computeDelay(attempt, baseDelayMs, maxDelayMs, jitter);
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
+
logger.debug(`Retry ${attempt + 1}/${maxRetries} for ${operation ?? 'operation'}: ${errorMessage} — waiting ${Math.round(delay)}ms`, context);
|
|
124
|
+
await sleep(delay, signal);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Unreachable — the loop always returns or throws
|
|
128
|
+
throw new McpError(JsonRpcErrorCode.InternalError, 'withRetry: unexpected loop exit');
|
|
129
|
+
}
|
|
130
|
+
/** Sleeps for the given duration, aborting early if the signal fires. */
|
|
131
|
+
function sleep(ms, signal) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
if (signal?.aborted) {
|
|
134
|
+
reject(signal.reason);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let onAbort;
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
if (onAbort)
|
|
140
|
+
signal?.removeEventListener('abort', onAbort);
|
|
141
|
+
resolve();
|
|
142
|
+
}, ms);
|
|
143
|
+
if (signal) {
|
|
144
|
+
onAbort = () => {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
reject(signal.reason);
|
|
147
|
+
};
|
|
148
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xmlParser.d.ts","sourceRoot":"","sources":["../../../src/utils/parsing/xmlParser.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"xmlParser.d.ts","sourceRoot":"","sources":["../../../src/utils/parsing/xmlParser.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;AAoBhG;;;;;;;;;GASG;AACH,qBAAa,SAAS;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CA6DlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
|
@@ -15,7 +15,9 @@ import { thinkBlockRegex } from './thinkBlock.js';
|
|
|
15
15
|
let _fxp;
|
|
16
16
|
let _xmlParserInstance;
|
|
17
17
|
async function getFxp() {
|
|
18
|
-
|
|
18
|
+
if (_fxp)
|
|
19
|
+
return _fxp;
|
|
20
|
+
_fxp = await import('fast-xml-parser').catch(() => {
|
|
19
21
|
throw configurationError('Install "fast-xml-parser" to use XML parsing: bun add fast-xml-parser');
|
|
20
22
|
});
|
|
21
23
|
return _fxp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xmlParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/xmlParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAuB,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"xmlParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/xmlParser.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for building MCP servers. Build tools, not infrastructure. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Node.js and Cloudflare Workers.",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
"types": "./dist/testing/index.d.ts",
|
|
84
84
|
"import": "./dist/testing/index.js"
|
|
85
85
|
},
|
|
86
|
+
"./testing/fuzz": {
|
|
87
|
+
"types": "./dist/testing/fuzz.d.ts",
|
|
88
|
+
"import": "./dist/testing/fuzz.js"
|
|
89
|
+
},
|
|
86
90
|
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
87
91
|
"./vitest.config": "./vitest.config.base.ts",
|
|
88
92
|
"./biome": "./biome.json",
|
|
@@ -98,6 +98,58 @@ handler: async (input, ctx) => {
|
|
|
98
98
|
},
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## Resilience (External API Services)
|
|
102
|
+
|
|
103
|
+
When a service wraps an external API, apply these patterns. See `docs/service-resilience.md` for full rationale.
|
|
104
|
+
|
|
105
|
+
### Retry wraps the full pipeline
|
|
106
|
+
|
|
107
|
+
Place retry at the service method level — covering both HTTP fetch and response parsing/validation. The HTTP client should be single-attempt; the service owns retry. Use `withRetry` from `@cyanheads/mcp-ts-core/utils`:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { withRetry, fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
|
|
111
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
112
|
+
|
|
113
|
+
async fetchItem(id: string, ctx: Context): Promise<Item> {
|
|
114
|
+
return withRetry(
|
|
115
|
+
async () => {
|
|
116
|
+
const response = await fetchWithTimeout(
|
|
117
|
+
`${this.baseUrl}/items/${id}`,
|
|
118
|
+
10_000,
|
|
119
|
+
ctx,
|
|
120
|
+
);
|
|
121
|
+
const text = await response.text();
|
|
122
|
+
return this.parseResponse<Item>(text);
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
operation: 'fetchItem',
|
|
126
|
+
context: ctx,
|
|
127
|
+
baseDelayMs: 1000, // calibrate to upstream recovery time
|
|
128
|
+
signal: ctx.signal,
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Key principles
|
|
135
|
+
|
|
136
|
+
1. **Calibrate backoff to the upstream.** 200–500ms for ephemeral failures, 1–2s for rate-limited APIs, 2–5s for service degradation. The default `baseDelayMs: 1000` suits most APIs.
|
|
137
|
+
2. **Check HTTP status before parsing.** `fetchWithTimeout` already throws `ServiceUnavailable` on non-OK responses — this prevents feeding HTML error pages into XML/JSON parsers.
|
|
138
|
+
3. **Classify parse failures by content.** If the upstream returns HTTP 200 with an HTML error page, detect it and throw `ServiceUnavailable` (transient) instead of `SerializationError` (non-transient).
|
|
139
|
+
4. **Exhausted retries say so.** `withRetry` automatically enriches the final error with attempt count — callers know retries were already attempted.
|
|
140
|
+
|
|
141
|
+
### Response handler pattern
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
parseResponse<T>(text: string): T {
|
|
145
|
+
// Detect HTML error pages masquerading as successful responses
|
|
146
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
147
|
+
throw serviceUnavailable('API returned HTML instead of expected format — likely rate-limited.');
|
|
148
|
+
}
|
|
149
|
+
// Parse and validate...
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
101
153
|
## Checklist
|
|
102
154
|
|
|
103
155
|
- [ ] Directory created at `src/services/{{domain}}/`
|
|
@@ -106,4 +158,5 @@ handler: async (input, ctx) => {
|
|
|
106
158
|
- [ ] Service methods accept `Context` for logging and storage
|
|
107
159
|
- [ ] `init` function registered in `setup()` callback in `src/index.ts`
|
|
108
160
|
- [ ] Accessor throws `Error` if not initialized
|
|
161
|
+
- [ ] If wrapping external API: retry covers full pipeline (fetch + parse), backoff calibrated
|
|
109
162
|
- [ ] `bun run devcheck` passes
|
|
@@ -30,6 +30,7 @@ Utility exports from `@cyanheads/mcp-ts-core/utils`. Utilities with complex APIs
|
|
|
30
30
|
| Export | API | Notes |
|
|
31
31
|
|:-------|:----|:------|
|
|
32
32
|
| `fetchWithTimeout` | `(url, timeoutMs, context: RequestContext, options?: FetchWithTimeoutOptions) -> Promise<Response>` | Wraps `fetch` with `AbortController` timeout. `FetchWithTimeoutOptions` extends `RequestInit` (minus `signal`) and adds `rejectPrivateIPs?: boolean` and `signal?: AbortSignal` (external cancellation). SSRF protection: blocks RFC 1918, loopback, link-local, CGNAT, cloud metadata. DNS validation on Node; hostname-only on Workers. Manual redirect following (max 5) with per-hop SSRF check. |
|
|
33
|
+
| `withRetry` | `<T>(fn: () => Promise<T>, options?: RetryOptions) -> Promise<T>` | Executes `fn` with exponential backoff. Retries on transient errors (`ServiceUnavailable`, `Timeout`, `RateLimited`); non-transient errors fail immediately. On exhaustion, enriches the final error with attempt count in message and `data.retryAttempts`. **Place the retry boundary around the full pipeline** (fetch + parse), not just the network call. See `docs/service-resilience.md`. `RetryOptions`: `maxRetries` (default `3`), `baseDelayMs` (default `1000`), `maxDelayMs` (default `30000`), `jitter` (default `0.25`), `operation` (log label), `context` (RequestContext), `signal` (AbortSignal), `isTransient` (custom predicate). |
|
|
33
34
|
|
|
34
35
|
---
|
|
35
36
|
|
|
@@ -269,6 +269,16 @@ Skip for purely data/action-oriented servers.
|
|
|
269
269
|
|
|
270
270
|
**Services** — one per external dependency. Init/accessor pattern. Skip if all tools are thin wrappers with no shared state.
|
|
271
271
|
|
|
272
|
+
For services wrapping external APIs, plan the resilience layer. See `docs/service-resilience.md` for full rationale.
|
|
273
|
+
|
|
274
|
+
| Concern | Decision |
|
|
275
|
+
|:--------|:---------|
|
|
276
|
+
| **Retry boundary** | Service method wraps full pipeline (fetch + parse), not just the network call. Use `withRetry` from `/utils`. |
|
|
277
|
+
| **Backoff calibration** | Match base delay to upstream recovery time: 200–500ms (ephemeral), 1–2s (rate-limited), 2–5s (degraded). |
|
|
278
|
+
| **HTTP status check** | `fetchWithTimeout` already handles this — non-OK → `ServiceUnavailable`. |
|
|
279
|
+
| **Parse failure classification** | Response handler detects HTML error pages and throws transient errors, not `SerializationError`. |
|
|
280
|
+
| **Exhausted retry messaging** | `withRetry` enriches the final error with attempt count automatically. |
|
|
281
|
+
|
|
272
282
|
**Config** — list env vars (API keys, base URLs). Goes in `src/config/server-config.ts` as a separate Zod schema.
|
|
273
283
|
|
|
274
284
|
### 8. Write the Design Doc
|
|
@@ -358,6 +368,7 @@ Execute the plan using the scaffolding skills:
|
|
|
358
368
|
- [ ] Annotations set correctly (`readOnlyHint`, `destructiveHint`, etc.)
|
|
359
369
|
- [ ] Resource URIs use `{param}` templates, pagination planned for large lists
|
|
360
370
|
- [ ] Service layer planned (or explicitly skipped with reasoning)
|
|
371
|
+
- [ ] Resilience planned for external API services (retry boundary, backoff, parse classification)
|
|
361
372
|
- [ ] Server config env vars identified
|
|
362
373
|
- [ ] Design doc written to `docs/design.md`
|
|
363
374
|
- [ ] Design confirmed with user (or user pre-authorized implementation)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: report-issue-framework
|
|
3
|
+
description: >
|
|
4
|
+
File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.
|
|
5
|
+
metadata:
|
|
6
|
+
author: cyanheads
|
|
7
|
+
version: "1.1"
|
|
8
|
+
audience: external
|
|
9
|
+
type: workflow
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
You've isolated a problem to `@cyanheads/mcp-ts-core` itself — not your server code, not a misconfiguration, not a missing peer dependency. Typical triggers:
|
|
15
|
+
|
|
16
|
+
- Framework builder (`tool()`, `resource()`, `prompt()`) rejects valid input or produces incorrect output
|
|
17
|
+
- `createApp()` or `createWorkerHandler()` fails on a valid config
|
|
18
|
+
- `Context` properties (`ctx.log`, `ctx.state`, `ctx.elicit`, etc.) behave contrary to docs
|
|
19
|
+
- A utility from `/utils`, `/errors`, `/auth`, `/storage`, `/services` returns wrong results or throws unexpectedly
|
|
20
|
+
- Type exports are incorrect or missing (compile error on documented usage)
|
|
21
|
+
- The definition linter (`bun run lint:mcp`) produces false positives or misses real violations
|
|
22
|
+
|
|
23
|
+
## Before Filing
|
|
24
|
+
|
|
25
|
+
1. **Confirm framework version** — `bun pm ls @cyanheads/mcp-ts-core` or check `node_modules/@cyanheads/mcp-ts-core/package.json`
|
|
26
|
+
2. **Check you're on latest** — `bun outdated @cyanheads/mcp-ts-core`. If behind, update and retest before filing.
|
|
27
|
+
3. **Isolate the issue** — reproduce with a minimal handler or standalone script. Strip server-specific services, config, and dependencies. If the bug disappears when isolated, it's likely in your server code.
|
|
28
|
+
4. **Search existing issues** — don't file duplicates:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gh issue list -R cyanheads/mcp-ts-core --search "your error message or keyword"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Redact Before Posting
|
|
35
|
+
|
|
36
|
+
GitHub issues are **public**. Do not include secrets, credentials, API keys, or tokens. Redact sensitive values from env vars, headers, and logs before submitting. Replace with obvious placeholders: `REDACTED`, `sk-...REDACTED`. Do not rely on partial masking — partial keys can still be exploited.
|
|
37
|
+
|
|
38
|
+
## Filing a Bug
|
|
39
|
+
|
|
40
|
+
The repo has YAML form issue templates. Use `--web` to open the form in the browser (preferred when available), or pass `--title` + `--body` for non-interactive use.
|
|
41
|
+
|
|
42
|
+
### Browser (interactive)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gh issue create -R cyanheads/mcp-ts-core --template "Bug Report" --web
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### CLI (non-interactive)
|
|
49
|
+
|
|
50
|
+
Structure the `--body` to match the template's form fields:
|
|
51
|
+
|
|
52
|
+
````bash
|
|
53
|
+
gh issue create -R cyanheads/mcp-ts-core \
|
|
54
|
+
--title "bug(scope): concise description" \
|
|
55
|
+
--label "bug" \
|
|
56
|
+
--body "$(cat <<'ISSUE'
|
|
57
|
+
### mcp-ts-core version
|
|
58
|
+
|
|
59
|
+
0.1.29
|
|
60
|
+
|
|
61
|
+
### Runtime
|
|
62
|
+
|
|
63
|
+
Bun
|
|
64
|
+
|
|
65
|
+
### Runtime version
|
|
66
|
+
|
|
67
|
+
Bun 1.2.x
|
|
68
|
+
|
|
69
|
+
### Transport
|
|
70
|
+
|
|
71
|
+
stdio
|
|
72
|
+
|
|
73
|
+
### OS
|
|
74
|
+
|
|
75
|
+
macOS 15.x
|
|
76
|
+
|
|
77
|
+
### Description
|
|
78
|
+
|
|
79
|
+
Brief explanation of the bug — what you expected vs what happened.
|
|
80
|
+
|
|
81
|
+
### Reproduction
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
85
|
+
|
|
86
|
+
export const broken = tool('broken_example', {
|
|
87
|
+
description: 'Minimal repro.',
|
|
88
|
+
input: z.object({ id: z.string().describe('ID') }),
|
|
89
|
+
output: z.object({
|
|
90
|
+
name: z.string().describe('Name'),
|
|
91
|
+
extra: z.string().optional().describe('Optional field'),
|
|
92
|
+
}),
|
|
93
|
+
async handler(input, ctx) {
|
|
94
|
+
return { name: 'test' }; // omitting optional field causes validation error
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Actual behavior
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Error: Output validation failed: ...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Expected behavior
|
|
106
|
+
|
|
107
|
+
Omitting an optional output field should pass validation.
|
|
108
|
+
|
|
109
|
+
### Additional context
|
|
110
|
+
|
|
111
|
+
Any workarounds, related issues, or observations.
|
|
112
|
+
ISSUE
|
|
113
|
+
)"
|
|
114
|
+
````
|
|
115
|
+
|
|
116
|
+
### Title conventions
|
|
117
|
+
|
|
118
|
+
Format: `bug(<scope>): concise description`
|
|
119
|
+
|
|
120
|
+
| Scope | When |
|
|
121
|
+
|:------|:-----|
|
|
122
|
+
| `tool` | Tool builder, handler, format, annotations |
|
|
123
|
+
| `resource` | Resource builder, handler, list, params |
|
|
124
|
+
| `prompt` | Prompt builder, generate, args |
|
|
125
|
+
| `context` | Context, logger, state, progress, elicit, sample |
|
|
126
|
+
| `config` | AppConfig, parseConfig, env parsing |
|
|
127
|
+
| `errors` | McpError, error factories, auto-classification |
|
|
128
|
+
| `auth` | Auth modes, scope checking, JWT/OAuth |
|
|
129
|
+
| `storage` | StorageService, providers |
|
|
130
|
+
| `transport` | stdio/http transport, SSE, session handling |
|
|
131
|
+
| `worker` | createWorkerHandler, Worker runtime |
|
|
132
|
+
| `utils` | Utilities (formatting, parsing, pagination, etc.) |
|
|
133
|
+
| `linter` | Definition linter false positives/negatives |
|
|
134
|
+
| `types` | Type exports, type inference |
|
|
135
|
+
| `services` | LLM, Speech, Graph services |
|
|
136
|
+
| `deps` | Dependency issues, peer dep conflicts |
|
|
137
|
+
|
|
138
|
+
### Labels
|
|
139
|
+
|
|
140
|
+
| Label | When |
|
|
141
|
+
|:------|:-----|
|
|
142
|
+
| `bug` | Something broken |
|
|
143
|
+
| `regression` | Worked before, broken after update |
|
|
144
|
+
| `types` | TypeScript type issue |
|
|
145
|
+
| `docs` | Documentation is wrong or misleading |
|
|
146
|
+
| `enhancement` | Feature request or improvement (not a bug) |
|
|
147
|
+
|
|
148
|
+
Combine labels: `--label "bug" --label "types"`.
|
|
149
|
+
|
|
150
|
+
### Attaching logs or stack traces
|
|
151
|
+
|
|
152
|
+
For long output, write to a file and attach:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bun run dev:stdio 2>&1 | head -100 > /tmp/mcp-error.log
|
|
156
|
+
|
|
157
|
+
# As part of a new issue
|
|
158
|
+
gh issue create -R cyanheads/mcp-ts-core \
|
|
159
|
+
--title "bug(transport): stdio crashes on large payload" \
|
|
160
|
+
--label "bug" \
|
|
161
|
+
--body-file /tmp/mcp-error.log
|
|
162
|
+
|
|
163
|
+
# Or as a comment on an existing issue
|
|
164
|
+
gh issue comment <number> -R cyanheads/mcp-ts-core --body-file /tmp/mcp-error.log
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Filing a Feature Request
|
|
168
|
+
|
|
169
|
+
### Browser (interactive)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
gh issue create -R cyanheads/mcp-ts-core --template "Feature Request" --web
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### CLI (non-interactive)
|
|
176
|
+
|
|
177
|
+
````bash
|
|
178
|
+
gh issue create -R cyanheads/mcp-ts-core \
|
|
179
|
+
--title "feat(scope): concise description" \
|
|
180
|
+
--label "enhancement" \
|
|
181
|
+
--body "$(cat <<'ISSUE'
|
|
182
|
+
### Use case
|
|
183
|
+
|
|
184
|
+
Describe the problem you're solving and why the framework should handle it.
|
|
185
|
+
|
|
186
|
+
### Proposed API
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
190
|
+
|
|
191
|
+
const result = await withRetry(() => fetchExternal(url), {
|
|
192
|
+
maxAttempts: 3,
|
|
193
|
+
backoff: 'exponential',
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Alternatives considered
|
|
198
|
+
|
|
199
|
+
What you tried or considered instead.
|
|
200
|
+
ISSUE
|
|
201
|
+
)"
|
|
202
|
+
````
|
|
203
|
+
|
|
204
|
+
## Following Up
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Check issue status
|
|
208
|
+
gh issue view <number> -R cyanheads/mcp-ts-core
|
|
209
|
+
|
|
210
|
+
# Add context or respond to maintainer questions
|
|
211
|
+
gh issue comment <number> -R cyanheads/mcp-ts-core --body "Additional context..."
|
|
212
|
+
|
|
213
|
+
# List your open issues
|
|
214
|
+
gh issue list -R cyanheads/mcp-ts-core --author @me
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Checklist
|
|
218
|
+
|
|
219
|
+
- [ ] Confirmed bug is in `@cyanheads/mcp-ts-core`, not server code
|
|
220
|
+
- [ ] Running latest (or documented) framework version
|
|
221
|
+
- [ ] Searched existing issues — no duplicate found
|
|
222
|
+
- [ ] All secrets, credentials, and tokens redacted
|
|
223
|
+
- [ ] Issue filed with: version, runtime, repro code, actual vs expected behavior
|