@novorb/mcp-core 2.1.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/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/http-client.d.ts +69 -0
- package/dist/http-client.d.ts.map +1 -0
- package/dist/http-client.js +228 -0
- package/dist/http-client.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +80 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +236 -0
- package/dist/logger.js.map +1 -0
- package/dist/pagination.d.ts +45 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +46 -0
- package/dist/pagination.js.map +1 -0
- package/dist/retry.d.ts +51 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +94 -0
- package/dist/retry.js.map +1 -0
- package/dist/server.d.ts +80 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +73 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +102 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +213 -0
- package/dist/transport.js.map +1 -0
- package/package.json +86 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Logger
|
|
3
|
+
*
|
|
4
|
+
* Unified logger for MCP servers. Supports two output modes:
|
|
5
|
+
* - file: writes to rotating log files (for stdio-based MCP servers)
|
|
6
|
+
* - stderr: writes to stderr (for Docker / HTTP transport)
|
|
7
|
+
*
|
|
8
|
+
* Log format (text): 2026-03-22T13:27:58.513Z | INFO | Server | Starting...
|
|
9
|
+
* Log format (json): {"level":"INFO","timestamp":"...","component":"Server","message":"Starting..."}
|
|
10
|
+
*
|
|
11
|
+
* Environment variables (read once at first logger creation):
|
|
12
|
+
* - LOG_LEVEL: DEBUG, INFO, WARN, ERROR (default: INFO)
|
|
13
|
+
* - LOG_PATH: log directory (default: ./logs)
|
|
14
|
+
* - LOG_FILE: log file name (default: mcp.log)
|
|
15
|
+
*
|
|
16
|
+
* Security: `meta` is passed through `redactSecrets()` before serialisation — sensitive-keyed fields
|
|
17
|
+
* (authorization/token/api_key/secret/password/cookie/...) are replaced with `[REDACTED]` so a
|
|
18
|
+
* consumer that logs an error object or request params never writes a credential in cleartext. In
|
|
19
|
+
* text mode the message + component are also stripped of CR/LF so a logged value can't forge log lines.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
export var LogLevel;
|
|
24
|
+
(function (LogLevel) {
|
|
25
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
26
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
27
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
28
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
29
|
+
})(LogLevel || (LogLevel = {}));
|
|
30
|
+
/** Map string → LogLevel, case-insensitive */
|
|
31
|
+
function parseLogLevel(value) {
|
|
32
|
+
if (!value)
|
|
33
|
+
return undefined;
|
|
34
|
+
const upper = value.toUpperCase();
|
|
35
|
+
if (upper in LogLevel && typeof LogLevel[upper] === 'number') {
|
|
36
|
+
return LogLevel[upper];
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const DEFAULT_CONFIG = {
|
|
41
|
+
level: LogLevel.INFO,
|
|
42
|
+
output: 'stderr',
|
|
43
|
+
format: 'text',
|
|
44
|
+
logDir: 'logs',
|
|
45
|
+
logFile: 'mcp.log',
|
|
46
|
+
maxLogSize: 10 * 1024 * 1024,
|
|
47
|
+
maxLogFiles: 5,
|
|
48
|
+
};
|
|
49
|
+
/** Build config from defaults + env vars + explicit overrides */
|
|
50
|
+
function buildConfig(overrides) {
|
|
51
|
+
const envLevel = parseLogLevel(process.env.LOG_LEVEL);
|
|
52
|
+
const envDir = process.env.LOG_PATH;
|
|
53
|
+
const envFile = process.env.LOG_FILE;
|
|
54
|
+
return {
|
|
55
|
+
...DEFAULT_CONFIG,
|
|
56
|
+
...(envLevel !== undefined ? { level: envLevel } : {}),
|
|
57
|
+
...(envDir ? { logDir: envDir } : {}),
|
|
58
|
+
...(envFile ? { logFile: envFile } : {}),
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Keys whose values are redacted before a `meta` object is serialised. Defense-in-depth: consumers
|
|
63
|
+
// commonly pass error objects / request params carrying credentials, and a log sink must never write
|
|
64
|
+
// them in cleartext. Matched case-insensitively against each object key.
|
|
65
|
+
const SENSITIVE_KEY = /(authorization|cookie|password|secret|client[_-]?secret|access[_-]?token|refresh[_-]?token|token|api[_-]?key|apikey|bearer|private[_-]?key|signature|x-api-key)/i;
|
|
66
|
+
const REDACTED = '[REDACTED]';
|
|
67
|
+
/** Recursively copy a value, replacing any sensitive-keyed field with `[REDACTED]`. */
|
|
68
|
+
export function redactSecrets(value, depth = 0) {
|
|
69
|
+
if (depth > 6 || value === null || typeof value !== 'object')
|
|
70
|
+
return value;
|
|
71
|
+
if (Array.isArray(value))
|
|
72
|
+
return value.map(v => redactSecrets(v, depth + 1));
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const [k, v] of Object.entries(value)) {
|
|
75
|
+
out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redactSecrets(v, depth + 1);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
/** Collapse C0 control chars (incl. CR/LF) + DEL to spaces so a logged value can't forge log lines. */
|
|
80
|
+
function stripControlChars(s) {
|
|
81
|
+
let out = '';
|
|
82
|
+
for (let i = 0; i < s.length; i++) {
|
|
83
|
+
const code = s.charCodeAt(i);
|
|
84
|
+
out += (code < 0x20 || code === 0x7f) ? ' ' : s[i];
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
// Padded level names — fixed 5 chars for alignment
|
|
89
|
+
const LEVEL_NAMES = {
|
|
90
|
+
[LogLevel.DEBUG]: 'DEBUG',
|
|
91
|
+
[LogLevel.INFO]: 'INFO ',
|
|
92
|
+
[LogLevel.WARN]: 'WARN ',
|
|
93
|
+
[LogLevel.ERROR]: 'ERROR',
|
|
94
|
+
};
|
|
95
|
+
export class Logger {
|
|
96
|
+
config;
|
|
97
|
+
logPath;
|
|
98
|
+
constructor(config) {
|
|
99
|
+
this.config = buildConfig(config);
|
|
100
|
+
this.logPath = path.join(this.config.logDir, this.config.logFile);
|
|
101
|
+
if (this.config.output === 'file') {
|
|
102
|
+
this.ensureLogDirectory();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
ensureLogDirectory() {
|
|
106
|
+
if (!fs.existsSync(this.config.logDir)) {
|
|
107
|
+
fs.mkdirSync(this.config.logDir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
shouldLog(level) {
|
|
111
|
+
return level >= this.config.level;
|
|
112
|
+
}
|
|
113
|
+
formatText(level, message, context, meta) {
|
|
114
|
+
const timestamp = new Date().toISOString();
|
|
115
|
+
const levelStr = LEVEL_NAMES[level] ?? LogLevel[level].padEnd(5);
|
|
116
|
+
const contextStr = stripControlChars(context ?? 'General');
|
|
117
|
+
const safeMessage = stripControlChars(message);
|
|
118
|
+
const metaStr = meta !== undefined ? ` | ${JSON.stringify(redactSecrets(meta))}` : '';
|
|
119
|
+
return `${timestamp} | ${levelStr} | ${contextStr} | ${safeMessage}${metaStr}\n`;
|
|
120
|
+
}
|
|
121
|
+
formatJson(level, message, context, meta) {
|
|
122
|
+
const entry = {
|
|
123
|
+
level: LogLevel[level],
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
component: context ?? 'General',
|
|
126
|
+
message,
|
|
127
|
+
};
|
|
128
|
+
if (meta !== undefined) {
|
|
129
|
+
const safeMeta = redactSecrets(meta);
|
|
130
|
+
if (typeof safeMeta === 'object' && safeMeta !== null) {
|
|
131
|
+
Object.assign(entry, safeMeta);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
entry.meta = safeMeta;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return JSON.stringify(entry) + '\n';
|
|
138
|
+
}
|
|
139
|
+
format(level, message, context, meta) {
|
|
140
|
+
return this.config.format === 'json'
|
|
141
|
+
? this.formatJson(level, message, context, meta)
|
|
142
|
+
: this.formatText(level, message, context, meta);
|
|
143
|
+
}
|
|
144
|
+
write(level, message, context, meta) {
|
|
145
|
+
if (!this.shouldLog(level))
|
|
146
|
+
return;
|
|
147
|
+
const formatted = this.format(level, message, context, meta);
|
|
148
|
+
if (this.config.output === 'file') {
|
|
149
|
+
this.writeToFile(formatted);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
process.stderr.write(formatted);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
writeToFile(message) {
|
|
156
|
+
try {
|
|
157
|
+
if (fs.existsSync(this.logPath)) {
|
|
158
|
+
const stats = fs.statSync(this.logPath);
|
|
159
|
+
if (stats.size > this.config.maxLogSize) {
|
|
160
|
+
this.rotateLog();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
fs.appendFileSync(this.logPath, message, 'utf8');
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Silently fail — can't log to console in MCP stdio context
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
rotateLog() {
|
|
170
|
+
try {
|
|
171
|
+
for (let i = this.config.maxLogFiles - 1; i > 0; i--) {
|
|
172
|
+
const oldPath = `${this.logPath}.${i}`;
|
|
173
|
+
const newPath = `${this.logPath}.${i + 1}`;
|
|
174
|
+
if (fs.existsSync(oldPath)) {
|
|
175
|
+
if (i === this.config.maxLogFiles - 1) {
|
|
176
|
+
fs.unlinkSync(oldPath);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
fs.renameSync(oldPath, newPath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (fs.existsSync(this.logPath)) {
|
|
184
|
+
fs.renameSync(this.logPath, `${this.logPath}.1`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Silently fail
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
debug(message, context, meta) {
|
|
192
|
+
this.write(LogLevel.DEBUG, message, context, meta);
|
|
193
|
+
}
|
|
194
|
+
info(message, context, meta) {
|
|
195
|
+
this.write(LogLevel.INFO, message, context, meta);
|
|
196
|
+
}
|
|
197
|
+
warn(message, context, meta) {
|
|
198
|
+
this.write(LogLevel.WARN, message, context, meta);
|
|
199
|
+
}
|
|
200
|
+
error(message, context, meta) {
|
|
201
|
+
this.write(LogLevel.ERROR, message, context, meta);
|
|
202
|
+
}
|
|
203
|
+
setLevel(level) {
|
|
204
|
+
this.config.level = level;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Singleton management
|
|
208
|
+
let loggerInstance = null;
|
|
209
|
+
/**
|
|
210
|
+
* Initialize logger with custom config.
|
|
211
|
+
* Should be called once at application startup.
|
|
212
|
+
* Reads LOG_LEVEL, LOG_PATH, LOG_FILE from env (overrides take precedence).
|
|
213
|
+
*/
|
|
214
|
+
export function initLogger(config) {
|
|
215
|
+
loggerInstance = new Logger(config);
|
|
216
|
+
return loggerInstance;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get the logger instance.
|
|
220
|
+
* Creates a default stderr/text instance if initLogger was not called.
|
|
221
|
+
* Reads LOG_LEVEL, LOG_PATH, LOG_FILE from env on first creation.
|
|
222
|
+
*/
|
|
223
|
+
export function getLogger(config) {
|
|
224
|
+
if (!loggerInstance) {
|
|
225
|
+
loggerInstance = new Logger(config);
|
|
226
|
+
}
|
|
227
|
+
return loggerInstance;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Reset the singleton (for testing).
|
|
231
|
+
*/
|
|
232
|
+
export function resetLogger() {
|
|
233
|
+
loggerInstance = null;
|
|
234
|
+
}
|
|
235
|
+
export default getLogger;
|
|
236
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Pagination utilities
|
|
3
|
+
*
|
|
4
|
+
* Normalize and compute pagination metadata across MCP servers.
|
|
5
|
+
*/
|
|
6
|
+
export interface PaginationParams {
|
|
7
|
+
/** Page number (1-based, default: 1) */
|
|
8
|
+
page?: number;
|
|
9
|
+
/** Items per page (default: 20, max: 100) */
|
|
10
|
+
per_page?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface NormalizedPagination {
|
|
13
|
+
page: number;
|
|
14
|
+
per_page: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PaginationMetadata {
|
|
17
|
+
totalCount: number;
|
|
18
|
+
page: number;
|
|
19
|
+
perPage: number;
|
|
20
|
+
totalPages: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Normalize pagination parameters with sensible bounds.
|
|
24
|
+
*
|
|
25
|
+
* @param params - Raw pagination params
|
|
26
|
+
* @param maxPerPage - Maximum allowed per_page (default: 100)
|
|
27
|
+
* @param defaultPerPage - Default per_page (default: 20)
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizePagination(params?: PaginationParams, maxPerPage?: number, defaultPerPage?: number): NormalizedPagination;
|
|
30
|
+
/**
|
|
31
|
+
* Create pagination metadata from total count and normalized params.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createPaginationMetadata(totalCount: number, params: NormalizedPagination): PaginationMetadata;
|
|
34
|
+
/**
|
|
35
|
+
* Calculate offset for SQL/API pagination from 1-based page number.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pageToOffset(page: number, perPage: number): number;
|
|
38
|
+
/**
|
|
39
|
+
* Apply in-memory pagination to an array.
|
|
40
|
+
*/
|
|
41
|
+
export declare function paginateArray<T>(items: T[], params: NormalizedPagination): {
|
|
42
|
+
data: T[];
|
|
43
|
+
metadata: PaginationMetadata;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,CAAC,EAAE,gBAAgB,EACzB,UAAU,SAAM,EAChB,cAAc,SAAK,GAClB,oBAAoB,CAItB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,oBAAoB,GAC3B,kBAAkB,CAOpB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,EAAE,oBAAoB,GAC3B;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAA;CAAE,CAO7C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Pagination utilities
|
|
3
|
+
*
|
|
4
|
+
* Normalize and compute pagination metadata across MCP servers.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Normalize pagination parameters with sensible bounds.
|
|
8
|
+
*
|
|
9
|
+
* @param params - Raw pagination params
|
|
10
|
+
* @param maxPerPage - Maximum allowed per_page (default: 100)
|
|
11
|
+
* @param defaultPerPage - Default per_page (default: 20)
|
|
12
|
+
*/
|
|
13
|
+
export function normalizePagination(params, maxPerPage = 100, defaultPerPage = 20) {
|
|
14
|
+
const page = Math.max(1, params?.page ?? 1);
|
|
15
|
+
const per_page = Math.min(maxPerPage, Math.max(1, params?.per_page ?? defaultPerPage));
|
|
16
|
+
return { page, per_page };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create pagination metadata from total count and normalized params.
|
|
20
|
+
*/
|
|
21
|
+
export function createPaginationMetadata(totalCount, params) {
|
|
22
|
+
return {
|
|
23
|
+
totalCount,
|
|
24
|
+
page: params.page,
|
|
25
|
+
perPage: params.per_page,
|
|
26
|
+
totalPages: Math.ceil(totalCount / params.per_page),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculate offset for SQL/API pagination from 1-based page number.
|
|
31
|
+
*/
|
|
32
|
+
export function pageToOffset(page, perPage) {
|
|
33
|
+
return (Math.max(1, page) - 1) * perPage;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply in-memory pagination to an array.
|
|
37
|
+
*/
|
|
38
|
+
export function paginateArray(items, params) {
|
|
39
|
+
const offset = pageToOffset(params.page, params.per_page);
|
|
40
|
+
const data = items.slice(offset, offset + params.per_page);
|
|
41
|
+
return {
|
|
42
|
+
data,
|
|
43
|
+
metadata: createPaginationMetadata(items.length, params),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAyB,EACzB,UAAU,GAAG,GAAG,EAChB,cAAc,GAAG,EAAE;IAEnB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,IAAI,cAAc,CAAC,CAAC,CAAC;IACvF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CACtC,UAAkB,EAClB,MAA4B;IAE5B,OAAO;QACL,UAAU;QACV,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,QAAQ;QACxB,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC;KACpD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAe;IACxD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAU,EACV,MAA4B;IAE5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC3D,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE,wBAAwB,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Retry utilities
|
|
3
|
+
*
|
|
4
|
+
* Exponential backoff with jitter for retrying failed HTTP requests.
|
|
5
|
+
*/
|
|
6
|
+
export interface RetryOptions {
|
|
7
|
+
/** Maximum number of retries (default: 3) */
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
/** Initial delay in milliseconds (default: 1000) */
|
|
10
|
+
initialDelayMs?: number;
|
|
11
|
+
/** Maximum delay in milliseconds (default: 10000) */
|
|
12
|
+
maxDelayMs?: number;
|
|
13
|
+
/** Backoff multiplier (default: 2) */
|
|
14
|
+
backoffMultiplier?: number;
|
|
15
|
+
/** Jitter factor 0-1 (default: 0.1 = 10%) */
|
|
16
|
+
jitterFactor?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface RetryContext {
|
|
19
|
+
/** Current attempt number (0-based) */
|
|
20
|
+
attempt: number;
|
|
21
|
+
/** Maximum retries configured */
|
|
22
|
+
maxRetries: number;
|
|
23
|
+
/** Last error encountered */
|
|
24
|
+
lastError?: Error;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Calculate retry delay with exponential backoff and jitter.
|
|
28
|
+
*/
|
|
29
|
+
export declare function calculateRetryDelay(attempt: number, options?: RetryOptions): number;
|
|
30
|
+
/**
|
|
31
|
+
* Sleep for specified milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Determine if an error is retryable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Retry a function with exponential backoff.
|
|
40
|
+
*
|
|
41
|
+
* @param fn - Async function to retry (receives RetryContext)
|
|
42
|
+
* @param options - Retry options
|
|
43
|
+
* @returns Result of successful function call
|
|
44
|
+
* @throws Last error if all retries exhausted or error is not retryable
|
|
45
|
+
*/
|
|
46
|
+
export declare function retryWithBackoff<T>(fn: (context: RetryContext) => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Parse Retry-After header value to milliseconds.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseRetryAfter(retryAfter: string | undefined): number | undefined;
|
|
51
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAUD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,YAAiB,GACzB,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAQD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiBxD;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAsBZ;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAUlF"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Retry utilities
|
|
3
|
+
*
|
|
4
|
+
* Exponential backoff with jitter for retrying failed HTTP requests.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
maxRetries: 3,
|
|
8
|
+
initialDelayMs: 1000,
|
|
9
|
+
maxDelayMs: 10000,
|
|
10
|
+
backoffMultiplier: 2,
|
|
11
|
+
jitterFactor: 0.1,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Calculate retry delay with exponential backoff and jitter.
|
|
15
|
+
*/
|
|
16
|
+
export function calculateRetryDelay(attempt, options = {}) {
|
|
17
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
18
|
+
const exponentialDelay = opts.initialDelayMs * Math.pow(opts.backoffMultiplier, attempt);
|
|
19
|
+
const cappedDelay = Math.min(exponentialDelay, opts.maxDelayMs);
|
|
20
|
+
const jitterRange = cappedDelay * opts.jitterFactor;
|
|
21
|
+
const jitter = (Math.random() * 2 - 1) * jitterRange;
|
|
22
|
+
return Math.max(0, cappedDelay + jitter);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sleep for specified milliseconds.
|
|
26
|
+
*/
|
|
27
|
+
export function sleep(ms) {
|
|
28
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
/** HTTP status codes that should be retried */
|
|
31
|
+
const RETRYABLE_STATUS_CODES = [408, 429, 502, 503, 504];
|
|
32
|
+
/** Network error codes that should be retried */
|
|
33
|
+
const RETRYABLE_NETWORK_CODES = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED'];
|
|
34
|
+
/**
|
|
35
|
+
* Determine if an error is retryable.
|
|
36
|
+
*/
|
|
37
|
+
export function isRetryableError(error) {
|
|
38
|
+
if (error && typeof error === 'object') {
|
|
39
|
+
const err = error;
|
|
40
|
+
// Network errors
|
|
41
|
+
if (typeof err.code === 'string' && RETRYABLE_NETWORK_CODES.includes(err.code)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// HTTP status codes
|
|
45
|
+
const response = err.response;
|
|
46
|
+
const status = response?.status;
|
|
47
|
+
if (typeof status === 'number') {
|
|
48
|
+
return RETRYABLE_STATUS_CODES.includes(status);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Retry a function with exponential backoff.
|
|
55
|
+
*
|
|
56
|
+
* @param fn - Async function to retry (receives RetryContext)
|
|
57
|
+
* @param options - Retry options
|
|
58
|
+
* @returns Result of successful function call
|
|
59
|
+
* @throws Last error if all retries exhausted or error is not retryable
|
|
60
|
+
*/
|
|
61
|
+
export async function retryWithBackoff(fn, options = {}) {
|
|
62
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
63
|
+
let lastError;
|
|
64
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
65
|
+
const context = { attempt, maxRetries: opts.maxRetries, lastError };
|
|
66
|
+
try {
|
|
67
|
+
return await fn(context);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
lastError = error;
|
|
71
|
+
if (attempt === opts.maxRetries || !isRetryableError(error)) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const delay = calculateRetryDelay(attempt, options);
|
|
75
|
+
await sleep(delay);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw lastError || new Error('Retry exhausted');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse Retry-After header value to milliseconds.
|
|
82
|
+
*/
|
|
83
|
+
export function parseRetryAfter(retryAfter) {
|
|
84
|
+
if (!retryAfter)
|
|
85
|
+
return undefined;
|
|
86
|
+
const seconds = parseInt(retryAfter, 10);
|
|
87
|
+
if (!isNaN(seconds))
|
|
88
|
+
return seconds * 1000;
|
|
89
|
+
const date = new Date(retryAfter);
|
|
90
|
+
if (!isNaN(date.getTime()))
|
|
91
|
+
return Math.max(0, date.getTime() - Date.now());
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Server
|
|
3
|
+
*
|
|
4
|
+
* Re-exports and base patterns for building MCP servers with the high-level McpServer API.
|
|
5
|
+
* Import from here instead of the SDK directly — one centralised import point for all
|
|
6
|
+
* server-building primitives.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { createMcpServer, TOOL_ANNOTATIONS, completable } from '@novorb/mcp-core/server';
|
|
10
|
+
* import type { McpToolAnnotations, McpCallToolResult } from '@novorb/mcp-core/server';
|
|
11
|
+
*
|
|
12
|
+
* const server = createMcpServer('my-mcp', '1.0.0');
|
|
13
|
+
*
|
|
14
|
+
* server.registerTool('lookup', {
|
|
15
|
+
* description: 'Look up data by key',
|
|
16
|
+
* inputSchema: { key: z.string() },
|
|
17
|
+
* outputSchema: { value: z.string(), found: z.boolean() },
|
|
18
|
+
* annotations: TOOL_ANNOTATIONS.readOnly,
|
|
19
|
+
* }, async ({ key }) => {
|
|
20
|
+
* const value = await lookup(key);
|
|
21
|
+
* return {
|
|
22
|
+
* content: [{ type: 'text', text: value ?? 'not found' }],
|
|
23
|
+
* structuredContent: { value: value ?? '', found: value !== null },
|
|
24
|
+
* };
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* server.registerPrompt('find-and-summarise', {
|
|
28
|
+
* title: 'Find and summarise',
|
|
29
|
+
* description: 'Look up a key and summarise the value',
|
|
30
|
+
* argsSchema: { key: completable(z.string(), val => ['foo', 'bar'].filter(k => k.startsWith(val))) },
|
|
31
|
+
* }, ({ key }) => ({
|
|
32
|
+
* messages: [{ role: 'user', content: { type: 'text', text: `Look up "${key}" and summarise the result.` } }],
|
|
33
|
+
* }));
|
|
34
|
+
*/
|
|
35
|
+
export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
36
|
+
export { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
37
|
+
export type { ToolAnnotations as McpToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
|
|
38
|
+
export type { CallToolResult as McpCallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
39
|
+
/**
|
|
40
|
+
* Standard tool annotation presets.
|
|
41
|
+
*
|
|
42
|
+
* Pass one of these (or a spread of multiple) to `annotations` in `registerTool`.
|
|
43
|
+
* These are HINTS — clients may or may not act on them.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* annotations: TOOL_ANNOTATIONS.readOnly
|
|
47
|
+
* annotations: { ...TOOL_ANNOTATIONS.readOnly, title: 'Domain lookup' }
|
|
48
|
+
*/
|
|
49
|
+
export declare const TOOL_ANNOTATIONS: {
|
|
50
|
+
/** Read-only, safe-to-retry. Use for all lookup / search / status tools. */
|
|
51
|
+
readonly readOnly: {
|
|
52
|
+
readOnlyHint: true;
|
|
53
|
+
idempotentHint: true;
|
|
54
|
+
};
|
|
55
|
+
/** Write that is idempotent (calling N times = calling once). */
|
|
56
|
+
readonly idempotentWrite: {
|
|
57
|
+
readOnlyHint: false;
|
|
58
|
+
idempotentHint: true;
|
|
59
|
+
};
|
|
60
|
+
/** Mutating write that may behave differently on repeat calls. */
|
|
61
|
+
readonly mutableWrite: {
|
|
62
|
+
readOnlyHint: false;
|
|
63
|
+
idempotentHint: false;
|
|
64
|
+
};
|
|
65
|
+
/** Destructive / irreversible (delete, overwrite). */
|
|
66
|
+
readonly destructive: {
|
|
67
|
+
readOnlyHint: false;
|
|
68
|
+
destructiveHint: true;
|
|
69
|
+
idempotentHint: false;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
73
|
+
/**
|
|
74
|
+
* Create a pre-configured McpServer instance.
|
|
75
|
+
*
|
|
76
|
+
* Thin wrapper kept so individual servers import only from @novorb/mcp-core and
|
|
77
|
+
* don't carry a direct @modelcontextprotocol/sdk import.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createMcpServer(name: string, version: string): McpServer;
|
|
80
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,iDAAiD,CAAC;AAC9E,YAAY,EAAE,eAAe,IAAI,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAChG,YAAY,EAAE,cAAc,IAAI,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAQ9F;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB;IAC3B,4EAA4E;;;;;IAE5E,iEAAiE;;;;;IAEjE,kEAAkE;;;;;IAElE,sDAAsD;;;;;;CAE9C,CAAC;AAMX,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAExE"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core — Server
|
|
3
|
+
*
|
|
4
|
+
* Re-exports and base patterns for building MCP servers with the high-level McpServer API.
|
|
5
|
+
* Import from here instead of the SDK directly — one centralised import point for all
|
|
6
|
+
* server-building primitives.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { createMcpServer, TOOL_ANNOTATIONS, completable } from '@novorb/mcp-core/server';
|
|
10
|
+
* import type { McpToolAnnotations, McpCallToolResult } from '@novorb/mcp-core/server';
|
|
11
|
+
*
|
|
12
|
+
* const server = createMcpServer('my-mcp', '1.0.0');
|
|
13
|
+
*
|
|
14
|
+
* server.registerTool('lookup', {
|
|
15
|
+
* description: 'Look up data by key',
|
|
16
|
+
* inputSchema: { key: z.string() },
|
|
17
|
+
* outputSchema: { value: z.string(), found: z.boolean() },
|
|
18
|
+
* annotations: TOOL_ANNOTATIONS.readOnly,
|
|
19
|
+
* }, async ({ key }) => {
|
|
20
|
+
* const value = await lookup(key);
|
|
21
|
+
* return {
|
|
22
|
+
* content: [{ type: 'text', text: value ?? 'not found' }],
|
|
23
|
+
* structuredContent: { value: value ?? '', found: value !== null },
|
|
24
|
+
* };
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* server.registerPrompt('find-and-summarise', {
|
|
28
|
+
* title: 'Find and summarise',
|
|
29
|
+
* description: 'Look up a key and summarise the value',
|
|
30
|
+
* argsSchema: { key: completable(z.string(), val => ['foo', 'bar'].filter(k => k.startsWith(val))) },
|
|
31
|
+
* }, ({ key }) => ({
|
|
32
|
+
* messages: [{ role: 'user', content: { type: 'text', text: `Look up "${key}" and summarise the result.` } }],
|
|
33
|
+
* }));
|
|
34
|
+
*/
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Re-exports — individual servers import ONLY from @novorb/mcp-core
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
39
|
+
export { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
40
|
+
/**
|
|
41
|
+
* Standard tool annotation presets.
|
|
42
|
+
*
|
|
43
|
+
* Pass one of these (or a spread of multiple) to `annotations` in `registerTool`.
|
|
44
|
+
* These are HINTS — clients may or may not act on them.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* annotations: TOOL_ANNOTATIONS.readOnly
|
|
48
|
+
* annotations: { ...TOOL_ANNOTATIONS.readOnly, title: 'Domain lookup' }
|
|
49
|
+
*/
|
|
50
|
+
export const TOOL_ANNOTATIONS = {
|
|
51
|
+
/** Read-only, safe-to-retry. Use for all lookup / search / status tools. */
|
|
52
|
+
readOnly: { readOnlyHint: true, idempotentHint: true },
|
|
53
|
+
/** Write that is idempotent (calling N times = calling once). */
|
|
54
|
+
idempotentWrite: { readOnlyHint: false, idempotentHint: true },
|
|
55
|
+
/** Mutating write that may behave differently on repeat calls. */
|
|
56
|
+
mutableWrite: { readOnlyHint: false, idempotentHint: false },
|
|
57
|
+
/** Destructive / irreversible (delete, overwrite). */
|
|
58
|
+
destructive: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
|
|
59
|
+
};
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Factory
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
64
|
+
/**
|
|
65
|
+
* Create a pre-configured McpServer instance.
|
|
66
|
+
*
|
|
67
|
+
* Thin wrapper kept so individual servers import only from @novorb/mcp-core and
|
|
68
|
+
* don't carry a direct @modelcontextprotocol/sdk import.
|
|
69
|
+
*/
|
|
70
|
+
export function createMcpServer(name, version) {
|
|
71
|
+
return new McpServer({ name, version });
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=server.js.map
|