@mctx-ai/mcp-dev 0.5.8 → 1.0.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 CHANGED
@@ -23,8 +23,10 @@ Your server restarts automatically on file changes.
23
23
 
24
24
  ## What It Does
25
25
 
26
- - **Hot reload** — watches your files and restarts on save
26
+ - **Hot reload** — watches `.js`, `.mjs`, `.cjs`, and `.json` files and restarts on save
27
27
  - **Request logging** — logs every MCP request and response to the console
28
+ - **Handler log surfacing** — prints any `log.*()` calls made inside your handlers to the dev console after each request
29
+ - **Sampling stub** — when a tool calls `ask()`, the `/_mctx/sampling` endpoint returns a clear error explaining that sampling is not supported in dev mode
28
30
  - **Local testing** — serves your server over HTTP for use with any MCP client
29
31
 
30
32
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mctx-ai/mcp-dev",
3
- "version": "0.5.8",
3
+ "version": "1.0.0",
4
4
  "description": "Local development server for @mctx-ai/mcp-server with hot reload",
5
5
  "type": "module",
6
6
  "main": "./src/cli.js",
@@ -14,7 +14,7 @@
14
14
  "src"
15
15
  ],
16
16
  "scripts": {
17
- "test": "node --test",
17
+ "test": "node --test --test-force-exit",
18
18
  "lint": "echo 'Linting not configured yet'"
19
19
  },
20
20
  "keywords": [
@@ -35,11 +35,14 @@
35
35
  "url": "https://github.com/mctx-ai/mcp-server/issues"
36
36
  },
37
37
  "homepage": "https://docs.mctx.ai",
38
+ "engines": {
39
+ "node": ">=22.0.0"
40
+ },
38
41
  "publishConfig": {
39
42
  "access": "public",
40
43
  "provenance": true
41
44
  },
42
45
  "peerDependencies": {
43
- "@mctx-ai/mcp-server": "0.8.0"
46
+ "@mctx-ai/mcp-server": "^1.0.0"
44
47
  }
45
48
  }
package/src/server.js CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { createServer } from "http";
12
+ import { getLogBuffer, clearLogBuffer } from "@mctx-ai/mcp-server";
12
13
  import { watch } from "./watcher.js";
13
14
 
14
15
  // ANSI color codes for logging
@@ -67,6 +68,23 @@ function formatMethod(rpcRequest) {
67
68
  return `${method} (${params.name})`;
68
69
  }
69
70
 
71
+ // For completion/complete, show ref type and argument name
72
+ if (method === "completion/complete") {
73
+ const refType = params?.ref?.type;
74
+ const argName = params?.argument?.name;
75
+ if (refType && argName) {
76
+ return `${method} (${refType}, ${argName})`;
77
+ }
78
+ if (refType) {
79
+ return `${method} (${refType})`;
80
+ }
81
+ }
82
+
83
+ // For logging/setLevel, show the requested level
84
+ if (method === "logging/setLevel" && params?.level) {
85
+ return `${method} (${params.level})`;
86
+ }
87
+
70
88
  return method;
71
89
  }
72
90
 
@@ -194,6 +212,41 @@ export async function startDevServer(entryUrl, port) {
194
212
 
195
213
  // Create HTTP server
196
214
  const server = createServer(async (req, res) => {
215
+ // Handle /_mctx/sampling back-channel: sampling is not supported in dev mode.
216
+ // Return a JSON-RPC error response so ask() fails with a clear message rather
217
+ // than a network error.
218
+ if (req.url === "/_mctx/sampling") {
219
+ let samplingBody = "";
220
+ req.on("data", (chunk) => {
221
+ samplingBody += chunk.toString();
222
+ });
223
+ req.on("end", () => {
224
+ let samplingId = null;
225
+ try {
226
+ const parsed = JSON.parse(samplingBody);
227
+ samplingId = parsed.id || null;
228
+ } catch {
229
+ // ignore parse errors — we still send a valid error response
230
+ }
231
+ res.writeHead(200, { "Content-Type": "application/json" });
232
+ res.end(
233
+ JSON.stringify({
234
+ jsonrpc: "2.0",
235
+ error: {
236
+ code: -32601,
237
+ message: "Sampling is not supported in dev mode — ask() will return null",
238
+ },
239
+ id: samplingId,
240
+ }),
241
+ );
242
+ logFramework(
243
+ "Sampling request received — sampling is not supported in dev mode",
244
+ colors.yellow,
245
+ );
246
+ });
247
+ return;
248
+ }
249
+
197
250
  // Fix #2: If app failed to load initially, return error
198
251
  if (!app) {
199
252
  res.writeHead(503, { "Content-Type": "application/json" });
@@ -286,6 +339,23 @@ export async function startDevServer(entryUrl, port) {
286
339
  return;
287
340
  }
288
341
 
342
+ // Validate JSON-RPC method field before dispatching
343
+ if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
344
+ res.writeHead(400, { "Content-Type": "application/json" });
345
+ res.end(
346
+ JSON.stringify({
347
+ jsonrpc: "2.0",
348
+ error: {
349
+ code: -32600,
350
+ message: "Invalid Request - Missing or invalid method",
351
+ },
352
+ id: rpcRequest.id || null,
353
+ }),
354
+ );
355
+ log(`${colors.red}✗${colors.reset} Invalid Request - missing method`, colors.red);
356
+ return;
357
+ }
358
+
289
359
  // Log incoming request
290
360
  const methodDisplay = formatMethod(rpcRequest);
291
361
  log(`${colors.cyan}→${colors.reset} ${methodDisplay}`, colors.dim);
@@ -316,6 +386,28 @@ export async function startDevServer(entryUrl, port) {
316
386
  const statusColor = statusCode >= 200 && statusCode < 300 ? colors.green : colors.red;
317
387
  log(`${statusColor}←${colors.reset} ${statusCode} (${elapsed}ms)`, colors.dim);
318
388
 
389
+ // Surface buffered log entries from handler code to dev console
390
+ const bufferedLogs = getLogBuffer();
391
+ clearLogBuffer();
392
+ if (bufferedLogs.length > 0) {
393
+ for (const entry of bufferedLogs) {
394
+ const levelColor =
395
+ entry.level === "error" ||
396
+ entry.level === "critical" ||
397
+ entry.level === "alert" ||
398
+ entry.level === "emergency"
399
+ ? colors.red
400
+ : entry.level === "warning"
401
+ ? colors.yellow
402
+ : colors.dim;
403
+ const dataStr =
404
+ typeof entry.data === "string" ? entry.data : JSON.stringify(entry.data);
405
+ console.log(
406
+ `${colors.gray}[${timestamp()}]${colors.reset} ${levelColor}[log:${entry.level}]${colors.reset} ${dataStr}`,
407
+ );
408
+ }
409
+ }
410
+
319
411
  // Verbose logging: log full response body (skip initialize/initialized)
320
412
  if (
321
413
  isVerbose &&
package/src/watcher.js CHANGED
@@ -104,8 +104,9 @@ export function watch(filePath, onChange) {
104
104
  // Watch each directory
105
105
  for (const { path, recursive } of watchDirs) {
106
106
  const watcher = fsWatch(path, { recursive }, (eventType, changedFile) => {
107
- // Only watch .js files
108
- if (changedFile && !changedFile.endsWith(".js")) {
107
+ // Only watch .js, .mjs, .cjs, and .json files
108
+ const watchedExtensions = [".js", ".mjs", ".cjs", ".json"];
109
+ if (changedFile && !watchedExtensions.some((ext) => changedFile.endsWith(ext))) {
109
110
  return;
110
111
  }
111
112