You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
None covers the host discarding modes at extraction time.
Environment
Claude Desktop
1.21459.3
macOS
15.7.7 (24G720), arm64
Affected extension
Fantastical 1.1.2 (ant.dir.gh.flexibits.fantastical-mcp), server.type: "binary", published by Flexibits Inc. via the in-app connector directory
Summary
Claude Desktop's extension extraction path writes every unpacked file at mode 0600, discarding the Unix permission bits stored in the .mcpb ZIP's external_attr. Files arrive owner-read/write only, with no execute bit for anyone.
type: node and type: python extensions survive this, because the executable being spawned is the interpreter (node), which lives outside the extension tree; the bundle's own files only need to be read, and 0600 grants owner read.
type: binary extensions do not survive it. The declared mcp_config.commandis a file inside the extension tree, so the host must exec() it — and exec() on a file without +x returns EACCES. The server never starts, on install and on every subsequent launch.
Evidence: controlled comparison
A leftover install staging directory (~/Library/Application Support/Claude/dxt-install-8UbDW8/package.mcpb) preserved an original bundle, allowing a direct before/after comparison on the same file from the same archive:
Bundle: "Control your Mac" 0.0.1, server.type: node
File: server/index.js
ZIP external_attr -> 0o644 (creator_system: 3 = Unix)
Extracted with `ditto` -> -rw-r--r-- (644)
Extracted by Claude Desktop -> -rw------- (600)
The archive stores well-formed Unix permissions with the correct creator-system byte. ditto honours them. Claude Desktop does not.
This is not a umask artifact: the user's shell umask is 0022, and .DS_Store files written into the same directory tree by Finder are 0644. 0600 is neither 0666 & ~0022 nor the stored mode — the host is applying 0600 itself.
The normalisation is uniform across every installed extension, from three unrelated publishers (Anthropic's own chrome-control, a third-party osascript server, and Flexibits' Fantastical): every file in all three is -rw-------.
The failure, for the binary case
ant.dir.gh.flexibits.fantastical-mcp as extracted by Claude Desktop:
mcp-server-Fantastical.log, repeating on every launch:
[Fantastical] [info] Initializing server...
[Fantastical] [info] Using MCP server command: /Users/<user>/Library/Application Support/Claude/
Claude Extensions/ant.dir.gh.flexibits.fantastical-mcp/server/FantasticalMCP.app/Contents/
MacOS/FantasticalMCP with path: { ... }
[Fantastical] [info] Server started and connected successfully
Failed to spawn process: Permission denied
[Fantastical] [info] Server transport closed
[Fantastical] [info] Server transport closed unexpectedly, this is likely due to the process
exiting early. ...
[Fantastical] [error] Server disconnected.
Note that Server started and connected successfully is logged before the spawn is known to have succeeded, and the actual cause (Failed to spawn process: Permission denied) is emitted un-timestamped and outside the [Fantastical] prefix. The user-visible surface is only "Server disconnected" and "Could not attach to MCP server Fantastical", neither of which points at permissions. This is a secondary reporting bug and is the reason the condition is hard to self-diagnose.
The bundle itself is sound
Everything except the mode bits is correct — this is not a publisher-side defect:
$ codesign -dv --verbose=2 FantasticalMCP.app
Identifier=com.flexibits.fantastical2.mac.mcp
Authority=Developer ID Application: Flexibits Inc. (85C27NK92C)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Notarization Ticket=stapled
TeamIdentifier=85C27NK92C
$ spctl -a -vv FantasticalMCP.app
accepted
source=Notarized Developer ID
$ file .../MacOS/FantasticalMCP
Mach-O universal binary with 2 architectures: [x86_64] [arm64]
No quarantine xattr. Signature valid, notarised, ticket stapled. The manifest declares type: binary exactly as the spec requires.
Then fully quit (⌘Q) and relaunch Claude Desktop. codesign --verify --deep --strict still passes afterwards (chmod does not affect the code directory hashes), and the server then completes a normal handshake:
The workaround does not survive extension auto-update, which re-extracts at 0600.
Note that reinstalling — the obvious user response, and the one the error text invites — cannot ever fix this, because each reinstall reproduces the same modes. Affected users will conclude the connector is simply broken.
Claude Desktop's MCPB extraction preserves ZIP-stored permissions without post-processing.
The comparison above shows this is false for Claude Desktop 1.21459.3 on macOS: a ZIP entry storing 0644 with creator_system: 3 lands on disk as 0600.
If extraction discards external_attr unconditionally, then #209's pack-side fix — forcing |0o111 into external_attr — cannot have any effect on Claude Desktop installs, regardless of how the bundle was packed or on which platform. The same applies to the proposed fix in #235. Both are correct and worth having for other consumers of .mcpb, but neither can resolve binary-entry-point EACCES on Claude Desktop while the host clobbers modes on the way in.
This may mean #57 is not actually fixed in the shipping product.
Suggested fix
In the host's extraction path, in order of preference:
Failing that, special-case the entry point: after extraction, if server.type == "binary", chmod +x the resolved entry_point. Cheap, targeted, and independent of what the archive stored.
Either way, stop normalising to 0600. If the intent is to restrict access to extension files, 0700/0755 achieves that for the owner without breaking exec().
Separately, in the logging path:
Do not log Server started and connected successfully before the spawn has been confirmed.
Surface EACCES on the entry point as an actionable message. A permissions failure on a path the host itself just wrote is detectable at install time — ideally mcpb-installed binary extensions should fail loudly at install rather than silently at every launch.
Reproduction
On macOS, install any extension with server.type: "binary" from the in-app connector directory (Fantastical 1.1.2 is a public, signed, notarised example).
ls -l the path named in server.entry_point under ~/Library/Application Support/Claude/Claude Extensions/<ext-id>/.
Observe mode 0600 — no execute bit — irrespective of the mode stored in the source .mcpb.
Observe Failed to spawn process: Permission denied in ~/Library/Logs/Claude/mcp-server-<Name>.log, and "Server disconnected" in the UI.
Preflight
Searched existing issues. Related but distinct:
mcpb packforce|0o111into the ZIPexternal_attrfor binary entry points. This report contradicts its stated premise (see "Impact on feat: enhanced structural validation for validate command #209" below).mcpb packon Windows omits Unix permission metadata. Pack-side; this report is extraction-side.type: binaryextension never routed to a launch handler; fails ENOENT. Different mechanism: that is handler resolution, this is EACCES on a correctly-resolved path.None covers the host discarding modes at extraction time.
Environment
ant.dir.gh.flexibits.fantastical-mcp),server.type: "binary", published by Flexibits Inc. via the in-app connector directorySummary
Claude Desktop's extension extraction path writes every unpacked file at mode
0600, discarding the Unix permission bits stored in the.mcpbZIP'sexternal_attr. Files arrive owner-read/write only, with no execute bit for anyone.type: nodeandtype: pythonextensions survive this, because the executable being spawned is the interpreter (node), which lives outside the extension tree; the bundle's own files only need to be read, and0600grants owner read.type: binaryextensions do not survive it. The declaredmcp_config.commandis a file inside the extension tree, so the host mustexec()it — andexec()on a file without+xreturnsEACCES. The server never starts, on install and on every subsequent launch.Evidence: controlled comparison
A leftover install staging directory (
~/Library/Application Support/Claude/dxt-install-8UbDW8/package.mcpb) preserved an original bundle, allowing a direct before/after comparison on the same file from the same archive:The archive stores well-formed Unix permissions with the correct creator-system byte.
dittohonours them. Claude Desktop does not.This is not a umask artifact: the user's shell umask is
0022, and.DS_Storefiles written into the same directory tree by Finder are0644.0600is neither0666 & ~0022nor the stored mode — the host is applying0600itself.The normalisation is uniform across every installed extension, from three unrelated publishers (Anthropic's own chrome-control, a third-party osascript server, and Flexibits' Fantastical): every file in all three is
-rw-------.The failure, for the binary case
ant.dir.gh.flexibits.fantastical-mcpas extracted by Claude Desktop:Manifest (verbatim from the installed bundle):
mcp-server-Fantastical.log, repeating on every launch:Note that
Server started and connected successfullyis logged before the spawn is known to have succeeded, and the actual cause (Failed to spawn process: Permission denied) is emitted un-timestamped and outside the[Fantastical]prefix. The user-visible surface is only "Server disconnected" and "Could not attach to MCP server Fantastical", neither of which points at permissions. This is a secondary reporting bug and is the reason the condition is hard to self-diagnose.The bundle itself is sound
Everything except the mode bits is correct — this is not a publisher-side defect:
No quarantine xattr. Signature valid, notarised, ticket stapled. The manifest declares
type: binaryexactly as the spec requires.Workaround
Then fully quit (⌘Q) and relaunch Claude Desktop.
codesign --verify --deep --strictstill passes afterwards (chmod does not affect the code directory hashes), and the server then completes a normal handshake:{"id":1,"jsonrpc":"2.0","result":{"capabilities":{"tools":{"listChanged":true}}, "protocolVersion":"2024-11-05","serverInfo":{"name":"Fantastical","version":"1.1.2"}}}The workaround does not survive extension auto-update, which re-extracts at
0600.Note that reinstalling — the obvious user response, and the one the error text invites — cannot ever fix this, because each reinstall reproduces the same modes. Affected users will conclude the connector is simply broken.
Impact on #209
PR #209's description states:
The comparison above shows this is false for Claude Desktop 1.21459.3 on macOS: a ZIP entry storing
0644withcreator_system: 3lands on disk as0600.If extraction discards
external_attrunconditionally, then #209's pack-side fix — forcing|0o111intoexternal_attr— cannot have any effect on Claude Desktop installs, regardless of how the bundle was packed or on which platform. The same applies to the proposed fix in #235. Both are correct and worth having for other consumers of.mcpb, but neither can resolve binary-entry-point EACCES on Claude Desktop while the host clobbers modes on the way in.This may mean #57 is not actually fixed in the shipping product.
Suggested fix
In the host's extraction path, in order of preference:
external_attrwhencreator_system == 3, asdittoandunzipdo. This is what feat: enhanced structural validation for validate command #209 assumes already happens.server.type == "binary",chmod +xthe resolvedentry_point. Cheap, targeted, and independent of what the archive stored.0600. If the intent is to restrict access to extension files,0700/0755achieves that for the owner without breakingexec().Separately, in the logging path:
Server started and connected successfullybefore the spawn has been confirmed.EACCESon the entry point as an actionable message. A permissions failure on a path the host itself just wrote is detectable at install time — ideallymcpb-installed binary extensions should fail loudly at install rather than silently at every launch.Reproduction
server.type: "binary"from the in-app connector directory (Fantastical 1.1.2 is a public, signed, notarised example).ls -lthe path named inserver.entry_pointunder~/Library/Application Support/Claude/Claude Extensions/<ext-id>/.0600— no execute bit — irrespective of the mode stored in the source.mcpb.Failed to spawn process: Permission deniedin~/Library/Logs/Claude/mcp-server-<Name>.log, and "Server disconnected" in the UI.