@mctx-ai/mcp-server 0.3.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/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @mctx-ai/mcp-server
3
+ *
4
+ * Build MCP servers with an Express-like API - no protocol knowledge required.
5
+ *
6
+ * Main package entry point that exports core functionality.
7
+ */
8
+
9
+ // Core server factory
10
+ export { createServer } from "./server.js";
11
+
12
+ // Type system for tool/prompt inputs
13
+ export { T, buildInputSchema } from "./types.js";
14
+
15
+ // Advanced features
16
+ export { conversation } from "./conversation.js";
17
+ export { createProgress, PROGRESS_DEFAULTS } from "./progress.js";
18
+ export { log } from "./log.js";
package/dist/log.js ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Logging Module
3
+ *
4
+ * Provides structured logging with RFC 5424 severity levels.
5
+ * Logs are buffered and sent as MCP notifications by the server.
6
+ *
7
+ * @module log
8
+ */
9
+
10
+ /**
11
+ * RFC 5424 severity levels (highest to lowest)
12
+ * Used for level comparison and filtering
13
+ */
14
+ const LEVELS = {
15
+ emergency: 0, // System is unusable
16
+ alert: 1, // Action must be taken immediately
17
+ critical: 2, // Critical conditions
18
+ error: 3, // Error conditions
19
+ warning: 4, // Warning conditions
20
+ notice: 5, // Normal but significant condition
21
+ info: 6, // Informational messages
22
+ debug: 7, // Debug-level messages
23
+ };
24
+
25
+ /**
26
+ * Maximum log buffer size (FIFO eviction when exceeded)
27
+ * @private
28
+ */
29
+ const MAX_LOG_BUFFER_SIZE = 10000;
30
+
31
+ /**
32
+ * Log buffer - stores notifications until server flushes them
33
+ * @private
34
+ */
35
+ let logBuffer = [];
36
+
37
+ /**
38
+ * Current minimum log level (messages below this are filtered)
39
+ * @private
40
+ */
41
+ let currentLogLevel = "debug"; // Default: log everything
42
+
43
+ /**
44
+ * Creates a log notification object
45
+ * @private
46
+ * @param {string} level - Log level
47
+ * @param {*} data - Log data (any JSON-serializable value)
48
+ * @returns {Object} Log notification object
49
+ */
50
+ function createLogNotification(level, data) {
51
+ // Check if we should log this level
52
+ if (!shouldLog(level, currentLogLevel)) {
53
+ return; // Don't add to buffer if below minimum level
54
+ }
55
+
56
+ const notification = {
57
+ type: "log",
58
+ level,
59
+ data,
60
+ };
61
+
62
+ logBuffer.push(notification);
63
+
64
+ // Enforce buffer size limit with FIFO eviction
65
+ if (logBuffer.length > MAX_LOG_BUFFER_SIZE) {
66
+ logBuffer.shift();
67
+ }
68
+
69
+ return notification;
70
+ }
71
+
72
+ /**
73
+ * Determines if a message should be logged based on severity levels
74
+ *
75
+ * @param {string} messageLevel - The level of the message being logged
76
+ * @param {string} clientLevel - The minimum level configured by client
77
+ * @returns {boolean} True if message should be logged
78
+ *
79
+ * @example
80
+ * shouldLog('error', 'warning') // true (error >= warning)
81
+ * shouldLog('debug', 'info') // false (debug < info)
82
+ * shouldLog('info', 'info') // true (info >= info)
83
+ */
84
+ export function shouldLog(messageLevel, clientLevel) {
85
+ const messageSeverity = LEVELS[messageLevel];
86
+ const clientSeverity = LEVELS[clientLevel];
87
+
88
+ // If either level is unknown, default to logging
89
+ if (messageSeverity === undefined || clientSeverity === undefined) {
90
+ return true;
91
+ }
92
+
93
+ // Lower numeric value = higher severity
94
+ // Message should be logged if its severity >= client's minimum severity
95
+ return messageSeverity <= clientSeverity;
96
+ }
97
+
98
+ /**
99
+ * Sets the minimum log level
100
+ * Messages below this level will be filtered out
101
+ *
102
+ * @param {string} level - Minimum log level (debug, info, notice, warning, error, critical, alert, emergency)
103
+ *
104
+ * @example
105
+ * setLogLevel('warning'); // Only log warning and above
106
+ * log.debug('test'); // Won't be logged
107
+ * log.error('problem'); // Will be logged
108
+ */
109
+ export function setLogLevel(level) {
110
+ if (LEVELS[level] === undefined) {
111
+ throw new Error(
112
+ `Invalid log level: "${level}". Must be one of: ${Object.keys(LEVELS).join(", ")}`,
113
+ );
114
+ }
115
+ currentLogLevel = level;
116
+ }
117
+
118
+ /**
119
+ * Gets the current log buffer
120
+ * Server uses this to retrieve buffered notifications
121
+ *
122
+ * @returns {Array<Object>} Array of log notification objects
123
+ */
124
+ export function getLogBuffer() {
125
+ return [...logBuffer]; // Return copy to prevent external modification
126
+ }
127
+
128
+ /**
129
+ * Clears the log buffer
130
+ * Server calls this after flushing notifications
131
+ */
132
+ export function clearLogBuffer() {
133
+ logBuffer = [];
134
+ }
135
+
136
+ /**
137
+ * Log object with methods for each severity level
138
+ *
139
+ * Each method creates a log notification and adds it to the buffer.
140
+ * The server will flush these as MCP notifications.
141
+ *
142
+ * @example
143
+ * log.debug('Variable value:', { x: 42 });
144
+ * log.info('Server started on port 3000');
145
+ * log.warning('Rate limit approaching');
146
+ * log.error('Database connection failed', error);
147
+ * log.critical('System out of memory');
148
+ */
149
+ export const log = {
150
+ /**
151
+ * Debug-level message (lowest severity)
152
+ * Used for detailed debugging information
153
+ */
154
+ debug(data) {
155
+ return createLogNotification("debug", data);
156
+ },
157
+
158
+ /**
159
+ * Informational message
160
+ * Used for general informational messages
161
+ */
162
+ info(data) {
163
+ return createLogNotification("info", data);
164
+ },
165
+
166
+ /**
167
+ * Notice - normal but significant condition
168
+ * Used for important events that are not errors
169
+ */
170
+ notice(data) {
171
+ return createLogNotification("notice", data);
172
+ },
173
+
174
+ /**
175
+ * Warning condition
176
+ * Used for warnings that don't prevent operation
177
+ */
178
+ warning(data) {
179
+ return createLogNotification("warning", data);
180
+ },
181
+
182
+ /**
183
+ * Error condition
184
+ * Used for errors that affect functionality
185
+ */
186
+ error(data) {
187
+ return createLogNotification("error", data);
188
+ },
189
+
190
+ /**
191
+ * Critical condition
192
+ * Used for critical conditions that require immediate attention
193
+ */
194
+ critical(data) {
195
+ return createLogNotification("critical", data);
196
+ },
197
+
198
+ /**
199
+ * Alert - action must be taken immediately
200
+ * Used for conditions requiring immediate operator intervention
201
+ */
202
+ alert(data) {
203
+ return createLogNotification("alert", data);
204
+ },
205
+
206
+ /**
207
+ * Emergency - system is unusable
208
+ * Used for system-wide failures
209
+ */
210
+ emergency(data) {
211
+ return createLogNotification("emergency", data);
212
+ },
213
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Progress Notifications Module
3
+ *
4
+ * Provides progress tracking for long-running generator tool handlers.
5
+ * Supports both determinate (with total) and indeterminate progress.
6
+ *
7
+ * @module progress
8
+ */
9
+
10
+ /**
11
+ * Default configuration for generator guardrails
12
+ * Server will use these to prevent runaway generators
13
+ */
14
+ export const PROGRESS_DEFAULTS = {
15
+ maxExecutionTime: 60000, // 60 seconds
16
+ maxYields: 10000,
17
+ };
18
+
19
+ /**
20
+ * Creates a progress step function for generator-based tools
21
+ *
22
+ * Returns a function that produces progress notification objects.
23
+ * Each call auto-increments the progress counter.
24
+ *
25
+ * @param {number} [total] - Total number of steps (optional, for determinate progress)
26
+ * @returns {Function} Step function that returns progress notification objects
27
+ *
28
+ * @example
29
+ * // Determinate progress (with known total)
30
+ * function* migrate({ sourceDb, targetDb }) {
31
+ * const tables = await getTables(sourceDb);
32
+ * const step = createProgress(tables.length);
33
+ *
34
+ * for (const table of tables) {
35
+ * yield step(); // { type: "progress", progress: 1, total: 5 }
36
+ * await copyTable(table, sourceDb, targetDb);
37
+ * }
38
+ *
39
+ * return "Migration complete";
40
+ * }
41
+ *
42
+ * @example
43
+ * // Indeterminate progress (no total)
44
+ * function* processQueue({ queueUrl }) {
45
+ * const step = createProgress();
46
+ *
47
+ * while (hasMessages(queueUrl)) {
48
+ * yield step(); // { type: "progress", progress: 1 }
49
+ * await processMessage(queueUrl);
50
+ * }
51
+ *
52
+ * return "Queue processed";
53
+ * }
54
+ */
55
+ export function createProgress(total) {
56
+ // Validate total if provided
57
+ if (total !== undefined && (typeof total !== "number" || total <= 0)) {
58
+ throw new Error(
59
+ "createProgress() total must be a positive number if provided",
60
+ );
61
+ }
62
+
63
+ let currentStep = 0;
64
+
65
+ /**
66
+ * Step function - call to generate next progress notification
67
+ * @returns {Object} Progress notification object
68
+ */
69
+ return function step() {
70
+ currentStep++;
71
+
72
+ const notification = {
73
+ type: "progress",
74
+ progress: currentStep,
75
+ };
76
+
77
+ // Include total only if provided (determinate progress)
78
+ if (total !== undefined) {
79
+ notification.total = total;
80
+ }
81
+
82
+ return notification;
83
+ };
84
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Sampling Support Module
3
+ *
4
+ * Enables MCP servers to request LLM completions from clients.
5
+ * Provides the `ask` function for tools that need AI assistance.
6
+ *
7
+ * @module sampling
8
+ */
9
+
10
+ /**
11
+ * Default timeout for sampling requests (30 seconds)
12
+ */
13
+ const DEFAULT_TIMEOUT = 30000;
14
+
15
+ /**
16
+ * Creates an ask function for a given request context
17
+ *
18
+ * The ask function allows tools to request LLM completions from the client.
19
+ * Returns null if the client doesn't support sampling.
20
+ *
21
+ * @param {Function} sendRequest - Callback to send MCP requests (async)
22
+ * @param {Object} clientCapabilities - Client capabilities from initialization
23
+ * @returns {Function|null} Ask function, or null if sampling not supported
24
+ *
25
+ * @example
26
+ * // In a tool handler
27
+ * function* summarize({ document, ask }) {
28
+ * if (!ask) {
29
+ * return "Client doesn't support sampling";
30
+ * }
31
+ *
32
+ * const summary = await ask("Summarize this document: " + document);
33
+ * return summary;
34
+ * }
35
+ *
36
+ * @example
37
+ * // Advanced usage with options
38
+ * const result = await ask({
39
+ * messages: [
40
+ * { role: "user", content: { type: "text", text: "What is the capital of France?" } }
41
+ * ],
42
+ * modelPreferences: {
43
+ * hints: [{ name: "claude-3-5-sonnet" }]
44
+ * },
45
+ * systemPrompt: "You are a helpful geography assistant",
46
+ * maxTokens: 1000,
47
+ * });
48
+ */
49
+ export function createAsk(sendRequest, clientCapabilities) {
50
+ // Validate inputs
51
+ if (typeof sendRequest !== "function") {
52
+ throw new Error("createAsk() requires sendRequest to be a function");
53
+ }
54
+
55
+ if (!clientCapabilities || typeof clientCapabilities !== "object") {
56
+ throw new Error("createAsk() requires clientCapabilities object");
57
+ }
58
+
59
+ // Check if client supports sampling
60
+ if (!clientCapabilities.sampling) {
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Ask function - request LLM completion from client
66
+ *
67
+ * @param {string|Object} promptOrOptions - Simple prompt string, or options object
68
+ * @param {number} [timeout=30000] - Request timeout in milliseconds
69
+ * @returns {Promise<string>} The LLM response content
70
+ * @throws {Error} If request fails or times out
71
+ */
72
+ return async function ask(promptOrOptions, timeout = DEFAULT_TIMEOUT) {
73
+ let requestParams;
74
+
75
+ // Simple string prompt - wrap as messages array
76
+ if (typeof promptOrOptions === "string") {
77
+ requestParams = {
78
+ messages: [
79
+ {
80
+ role: "user",
81
+ content: {
82
+ type: "text",
83
+ text: promptOrOptions,
84
+ },
85
+ },
86
+ ],
87
+ };
88
+ } else if (promptOrOptions && typeof promptOrOptions === "object") {
89
+ // Advanced options object
90
+ requestParams = { ...promptOrOptions };
91
+
92
+ // Validate required fields
93
+ if (!requestParams.messages || !Array.isArray(requestParams.messages)) {
94
+ throw new Error("ask() options must include messages array");
95
+ }
96
+ } else {
97
+ throw new Error("ask() requires a string prompt or options object");
98
+ }
99
+
100
+ // Create timeout promise
101
+ const timeoutPromise = new Promise((_, reject) => {
102
+ setTimeout(() => {
103
+ reject(new Error(`Sampling request timed out after ${timeout}ms`));
104
+ }, timeout);
105
+ });
106
+
107
+ // Send sampling request with timeout
108
+ try {
109
+ const responsePromise = sendRequest(
110
+ "sampling/createMessage",
111
+ requestParams,
112
+ );
113
+ const response = await Promise.race([responsePromise, timeoutPromise]);
114
+
115
+ // Extract content from response
116
+ if (!response || !response.content) {
117
+ throw new Error("Invalid sampling response: missing content");
118
+ }
119
+
120
+ return response.content;
121
+ } catch (error) {
122
+ // Re-throw with better context
123
+ if (error.message.includes("timed out")) {
124
+ throw error;
125
+ }
126
+
127
+ throw new Error(`Sampling request failed: ${error.message}`);
128
+ }
129
+ };
130
+ }