@leo000001/claude-code-mcp 2.0.1 → 2.0.3

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 CHANGED
@@ -7,6 +7,47 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { z } from "zod";
9
9
 
10
+ // src/utils/permission-updated-input.ts
11
+ function normalizePermissionUpdatedInput(input) {
12
+ if (input && typeof input === "object" && !Array.isArray(input)) {
13
+ return input;
14
+ }
15
+ return { input };
16
+ }
17
+
18
+ // src/utils/normalize-tool-input.ts
19
+ function normalizeMsysToWindowsPath(path2) {
20
+ if (/^[a-zA-Z]:[\\/]/.test(path2) || path2.startsWith("\\\\")) return void 0;
21
+ if (path2.startsWith("//")) {
22
+ const m2 = path2.match(/^\/\/([^/]{2,})\/(.*)$/);
23
+ if (m2) {
24
+ const host = m2[1];
25
+ const rest2 = m2[2] ?? "";
26
+ return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
27
+ }
28
+ }
29
+ const cyg = path2.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
30
+ if (cyg) {
31
+ const drive2 = cyg[1].toUpperCase();
32
+ const rest2 = cyg[2] ?? "";
33
+ return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
34
+ }
35
+ const m = path2.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
36
+ if (!m) return void 0;
37
+ const drive = m[1].toUpperCase();
38
+ const rest = m[2] ?? "";
39
+ return `${drive}:\\${rest.replace(/\//g, "\\")}`;
40
+ }
41
+ function normalizeToolInput(toolName, input, platform = process.platform) {
42
+ if (platform !== "win32") return input;
43
+ if (toolName !== "NotebookEdit") return input;
44
+ const filePath = input.file_path;
45
+ if (typeof filePath !== "string") return input;
46
+ const normalized = normalizeMsysToWindowsPath(filePath);
47
+ if (!normalized) return input;
48
+ return { ...input, file_path: normalized };
49
+ }
50
+
10
51
  // src/session/manager.ts
11
52
  var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
12
53
  var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
@@ -19,7 +60,9 @@ var SessionManager = class _SessionManager {
19
60
  cleanupTimer;
20
61
  sessionTtlMs = DEFAULT_SESSION_TTL_MS;
21
62
  runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
22
- constructor() {
63
+ platform;
64
+ constructor(opts) {
65
+ this.platform = opts?.platform ?? process.platform;
23
66
  this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
24
67
  if (this.cleanupTimer.unref) {
25
68
  this.cleanupTimer.unref();
@@ -193,10 +236,16 @@ var SessionManager = class _SessionManager {
193
236
  const info = this.sessions.get(sessionId);
194
237
  if (!state || !info) return false;
195
238
  if (!state.pendingPermissions.has(req.requestId)) {
239
+ const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
240
+ const record = {
241
+ ...req,
242
+ timeoutMs,
243
+ expiresAt: inferredExpiresAt
244
+ };
196
245
  const timeoutId = setTimeout(() => {
197
246
  this.finishRequest(
198
247
  sessionId,
199
- req.requestId,
248
+ record.requestId,
200
249
  {
201
250
  behavior: "deny",
202
251
  message: `Permission request timed out after ${timeoutMs}ms.`,
@@ -205,12 +254,12 @@ var SessionManager = class _SessionManager {
205
254
  "timeout"
206
255
  );
207
256
  }, timeoutMs);
208
- state.pendingPermissions.set(req.requestId, { record: req, finish, timeoutId });
257
+ state.pendingPermissions.set(record.requestId, { record, finish, timeoutId });
209
258
  info.status = "waiting_permission";
210
259
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
211
260
  this.pushEvent(sessionId, {
212
261
  type: "permission_request",
213
- data: req,
262
+ data: record,
214
263
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
215
264
  });
216
265
  return true;
@@ -242,11 +291,40 @@ var SessionManager = class _SessionManager {
242
291
  };
243
292
  }
244
293
  }
294
+ if (finalResult.behavior === "allow") {
295
+ const updatedInput = finalResult.updatedInput;
296
+ const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
297
+ if (!validRecord) {
298
+ finalResult = {
299
+ ...finalResult,
300
+ updatedInput: normalizePermissionUpdatedInput(pending.record.input)
301
+ };
302
+ } else {
303
+ finalResult = {
304
+ ...finalResult,
305
+ updatedInput: normalizeToolInput(
306
+ pending.record.toolName,
307
+ updatedInput,
308
+ this.platform
309
+ )
310
+ };
311
+ }
312
+ }
245
313
  if (pending.timeoutId) clearTimeout(pending.timeoutId);
246
314
  state.pendingPermissions.delete(requestId);
315
+ const eventData = {
316
+ requestId,
317
+ toolName: pending.record.toolName,
318
+ behavior: finalResult.behavior,
319
+ source
320
+ };
321
+ if (finalResult.behavior === "deny") {
322
+ eventData.message = finalResult.message;
323
+ eventData.interrupt = finalResult.interrupt;
324
+ }
247
325
  this.pushEvent(sessionId, {
248
326
  type: "permission_result",
249
- data: { requestId, behavior: finalResult.behavior, source },
327
+ data: eventData,
250
328
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
251
329
  });
252
330
  try {
@@ -651,28 +729,37 @@ function consumeQuery(params) {
651
729
  let initTimeoutId;
652
730
  const canUseTool = async (toolName, input, options2) => {
653
731
  const sessionId = await getSessionId();
732
+ const normalizedInput = normalizeToolInput(toolName, input, params.platform);
654
733
  const sessionInfo = params.sessionManager.get(sessionId);
655
734
  if (sessionInfo) {
656
735
  if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
657
736
  return { behavior: "deny", message: `Tool '${toolName}' is disallowed by session policy.` };
658
737
  }
659
738
  if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
660
- return { behavior: "allow" };
739
+ return {
740
+ behavior: "allow",
741
+ updatedInput: normalizePermissionUpdatedInput(normalizedInput)
742
+ };
661
743
  }
662
744
  }
663
745
  const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
746
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
747
+ const timeoutMs = params.permissionRequestTimeoutMs;
748
+ const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
664
749
  const record = {
665
750
  requestId,
666
751
  toolName,
667
- input,
668
- summary: summarizePermission(toolName, input),
752
+ input: normalizedInput,
753
+ summary: summarizePermission(toolName, normalizedInput),
669
754
  description: describeTool(toolName, params.toolCache),
670
755
  decisionReason: options2.decisionReason,
671
756
  blockedPath: options2.blockedPath,
672
757
  toolUseID: options2.toolUseID,
673
758
  agentID: options2.agentID,
674
759
  suggestions: options2.suggestions,
675
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
760
+ createdAt,
761
+ timeoutMs,
762
+ expiresAt
676
763
  };
677
764
  return await new Promise((resolve) => {
678
765
  let finished = false;
@@ -1394,13 +1481,16 @@ var TOOL_CATALOG = {
1394
1481
  description: "Run shell commands (e.g. npm install, git commit, ls) in the project directory.",
1395
1482
  category: "execute"
1396
1483
  },
1397
- Read: { description: "Read the contents of a file given its path.", category: "file_read" },
1484
+ Read: {
1485
+ description: "Read the contents of a file given its path (large files may hit per-call size caps; use offset/limit or Grep chunking).",
1486
+ category: "file_read"
1487
+ },
1398
1488
  Write: {
1399
1489
  description: "Create a new file or completely replace an existing file's contents.",
1400
1490
  category: "file_write"
1401
1491
  },
1402
1492
  Edit: {
1403
- description: "Make targeted changes to specific parts of an existing file without rewriting the whole file.",
1493
+ description: "Make targeted changes to specific parts of an existing file without rewriting the whole file (replace_all is substring-based).",
1404
1494
  category: "file_write"
1405
1495
  },
1406
1496
  Glob: {
@@ -1412,7 +1502,7 @@ var TOOL_CATALOG = {
1412
1502
  category: "file_read"
1413
1503
  },
1414
1504
  NotebookEdit: {
1415
- description: "Edit individual cells in Jupyter notebooks (.ipynb files).",
1505
+ description: "Edit individual cells in Jupyter notebooks (.ipynb files) (expects native Windows paths; this server normalizes /d/... when possible).",
1416
1506
  category: "file_write"
1417
1507
  },
1418
1508
  WebFetch: {
@@ -1433,6 +1523,10 @@ var TOOL_CATALOG = {
1433
1523
  AskUserQuestion: {
1434
1524
  description: "Ask the user a question and wait for their answer before continuing.",
1435
1525
  category: "interaction"
1526
+ },
1527
+ TeamDelete: {
1528
+ description: "Delete a team and its resources (may require all active members to shutdown_approved; cleanup may complete asynchronously).",
1529
+ category: "agent"
1436
1530
  }
1437
1531
  };
1438
1532
  function uniq(items) {
@@ -1487,7 +1581,7 @@ function buildInternalToolsDescription(tools) {
1487
1581
  const grouped = groupByCategory(tools);
1488
1582
  const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
1489
1583
  let desc = 'Start a new Claude Code agent session.\n\nLaunches an autonomous coding agent that can read/write files, run shell commands, search code, manage git, access the web, and more. Returns immediately with a sessionId \u2014 the agent runs asynchronously in the background.\n\nWorkflow:\n1. Call claude_code with a prompt \u2192 returns { sessionId, status: "running", pollInterval }\n2. Poll with claude_code_check (action="poll") to receive progress events and the final result\n3. If the agent needs permission for a tool call, approve or deny via claude_code_check (action="respond_permission")\n\n';
1490
- desc += "Defaults:\n- settingSources: ['user', 'project', 'local'] (loads ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md)\n- persistSession: true\n- sessionInitTimeoutMs: 10000\n- permissionRequestTimeoutMs: 60000\n- allowedTools/disallowedTools: [] (none)\n- resumeToken: omitted unless CLAUDE_CODE_MCP_RESUME_SECRET is set on the server\n\n";
1584
+ desc += "Defaults:\n- settingSources: ['user', 'project', 'local'] (loads ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md)\n- persistSession: true\n- sessionInitTimeoutMs: 10000\n- permissionRequestTimeoutMs: 60000\n- allowedTools/disallowedTools: [] (none)\n- resumeToken: omitted unless CLAUDE_CODE_MCP_RESUME_SECRET is set on the server\n- Permission prompts auto-deny on timeout; use claude_code_check actions[].expiresAt/remainingMs\n\n";
1491
1585
  desc += "Internal tools available to the agent (use allowedTools/disallowedTools to control approval policy; authoritative list returned by claude_code_check with includeTools=true):\n";
1492
1586
  for (const category of categories) {
1493
1587
  desc += `
@@ -1498,7 +1592,8 @@ function buildInternalToolsDescription(tools) {
1498
1592
  `;
1499
1593
  }
1500
1594
  }
1501
- desc += '\nUse `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
1595
+ desc += "\nSecurity: You MUST configure allowedTools/disallowedTools based on your own permission scope. Only allow tools that you yourself are authorized to perform \u2014 do not grant the agent broader permissions than you have. For example, if you lack write access to a directory, do not include Write/Edit in allowedTools. When in doubt, leave both lists empty and review each permission request individually via claude_code_check.\n\n";
1596
+ desc += 'Use `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
1502
1597
  return desc;
1503
1598
  }
1504
1599
 
@@ -1621,20 +1716,27 @@ function buildResult(sessionManager, toolCache, input) {
1621
1716
  }),
1622
1717
  nextCursor,
1623
1718
  availableTools,
1624
- actions: includeActions && status === "waiting_permission" ? pending.map((req) => ({
1625
- type: "permission",
1626
- requestId: req.requestId,
1627
- toolName: req.toolName,
1628
- input: req.input,
1629
- summary: req.summary,
1630
- decisionReason: req.decisionReason,
1631
- blockedPath: req.blockedPath,
1632
- toolUseID: req.toolUseID,
1633
- agentID: req.agentID,
1634
- suggestions: req.suggestions,
1635
- description: req.description,
1636
- createdAt: req.createdAt
1637
- })) : void 0,
1719
+ actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
1720
+ const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
1721
+ const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
1722
+ return {
1723
+ type: "permission",
1724
+ requestId: req.requestId,
1725
+ toolName: req.toolName,
1726
+ input: req.input,
1727
+ summary: req.summary,
1728
+ decisionReason: req.decisionReason,
1729
+ blockedPath: req.blockedPath,
1730
+ toolUseID: req.toolUseID,
1731
+ agentID: req.agentID,
1732
+ suggestions: req.suggestions,
1733
+ description: req.description,
1734
+ createdAt: req.createdAt,
1735
+ timeoutMs: req.timeoutMs,
1736
+ expiresAt: req.expiresAt,
1737
+ remainingMs
1738
+ };
1739
+ }) : void 0,
1638
1740
  result: includeResult && stored?.result ? redactAgentResult(stored.result, {
1639
1741
  includeUsage,
1640
1742
  includeModelUsage,
@@ -1796,8 +1898,96 @@ function executeClaudeCodeSession(input, sessionManager) {
1796
1898
  }
1797
1899
  }
1798
1900
 
1901
+ // src/resources/register-resources.ts
1902
+ var RESOURCE_SCHEME = "claude-code-mcp";
1903
+ var RESOURCE_URIS = {
1904
+ serverInfo: `${RESOURCE_SCHEME}:///server-info`,
1905
+ internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
1906
+ gotchas: `${RESOURCE_SCHEME}:///gotchas`
1907
+ };
1908
+ function asTextResource(uri, text, mimeType) {
1909
+ return {
1910
+ contents: [
1911
+ {
1912
+ uri: uri.toString(),
1913
+ text,
1914
+ mimeType
1915
+ }
1916
+ ]
1917
+ };
1918
+ }
1919
+ function registerResources(server, deps) {
1920
+ const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
1921
+ server.registerResource(
1922
+ "server_info",
1923
+ serverInfoUri.toString(),
1924
+ {
1925
+ title: "Server Info",
1926
+ description: "Static server metadata (version/platform/runtime).",
1927
+ mimeType: "application/json"
1928
+ },
1929
+ () => asTextResource(
1930
+ serverInfoUri,
1931
+ JSON.stringify(
1932
+ {
1933
+ name: "claude-code-mcp",
1934
+ node: process.version,
1935
+ platform: process.platform,
1936
+ arch: process.arch,
1937
+ resources: Object.values(RESOURCE_URIS),
1938
+ toolCatalogCount: deps.toolCache.getTools().length
1939
+ },
1940
+ null,
1941
+ 2
1942
+ ),
1943
+ "application/json"
1944
+ )
1945
+ );
1946
+ const toolsUri = new URL(RESOURCE_URIS.internalTools);
1947
+ server.registerResource(
1948
+ "internal_tools",
1949
+ toolsUri.toString(),
1950
+ {
1951
+ title: "Internal Tools",
1952
+ description: "Claude Code internal tool catalog (static + runtime-discovered).",
1953
+ mimeType: "application/json"
1954
+ },
1955
+ () => asTextResource(
1956
+ toolsUri,
1957
+ JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
1958
+ "application/json"
1959
+ )
1960
+ );
1961
+ const gotchasUri = new URL(RESOURCE_URIS.gotchas);
1962
+ server.registerResource(
1963
+ "gotchas",
1964
+ gotchasUri.toString(),
1965
+ {
1966
+ title: "Gotchas",
1967
+ description: "Practical limits and gotchas when using Claude Code via this MCP server.",
1968
+ mimeType: "text/markdown"
1969
+ },
1970
+ () => asTextResource(
1971
+ gotchasUri,
1972
+ [
1973
+ "# claude-code-mcp: gotchas",
1974
+ "",
1975
+ "- Permission approvals have a timeout (default 60s) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
1976
+ "- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
1977
+ "- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
1978
+ "- `NotebookEdit` expects native Windows paths; this server normalizes MSYS paths like `/d/...` when possible.",
1979
+ "- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
1980
+ '- Skills may become available later in the same session (early calls may show "Unknown").',
1981
+ "- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
1982
+ ""
1983
+ ].join("\n"),
1984
+ "text/markdown"
1985
+ )
1986
+ );
1987
+ }
1988
+
1799
1989
  // src/server.ts
1800
- var SERVER_VERSION = true ? "2.0.1" : "0.0.0-dev";
1990
+ var SERVER_VERSION = true ? "2.0.3" : "0.0.0-dev";
1801
1991
  function createServer(serverCwd) {
1802
1992
  const sessionManager = new SessionManager();
1803
1993
  const toolCache = new ToolDiscoveryCache();
@@ -2081,6 +2271,7 @@ action="respond_permission" \u2014 Approve or deny a pending permission request.
2081
2271
  };
2082
2272
  }
2083
2273
  );
2274
+ registerResources(server, { toolCache });
2084
2275
  const originalClose = server.close.bind(server);
2085
2276
  server.close = async () => {
2086
2277
  sessionManager.destroy();