License: MIT Release Python Dependencies: none
Per-project folder scoping for Claude Desktop.
Claude Desktop's local MCP servers are global. Every server you add in
claude_desktop_config.json is visible to every chat and every Project, and a
filesystem server is locked to whatever directories you pass it at startup. There is no
built-in way to say "this folder belongs to that Project and nothing else."
projectfs is one small local MCP server that adds that missing layer:
- You define named projects, each mapped to a set of local folders.
- Each project gets a random token.
- File operations (
read_file,write_file,list_dir, ...) only work when the caller passes a valid project token, and only ever touch that project's folders. - You put a project's token in that Claude Project's instructions. Now that Project can read and write its own folder, and other chats can't.
One running server handles all your projects. Adding or moving a folder is a CLI command
or a tool call — no editing claude_desktop_config.json, no restart.
It is a single standard-library Python file. No dependencies.
Not affiliated with Anthropic. "Claude" is a trademark of Anthropic.
- Download
projectfs.mcpbfrom the latest release. - Open it with Claude Desktop (or Settings → Extensions → Install from file).
- Enable it.
The bundle keeps its config at ~/.projectfs/config.json. Manage projects with the CLI
(Option B step 3 onwards) — there is no chat tool for attaching folders, by design.
-
Copy
server.pysomewhere stable, e.g.~/projectfs/server.py. -
Add it to
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) /%APPDATA%\Claude\claude_desktop_config.json(Windows):{ "mcpServers": { "projectfs": { "command": "python3", "args": ["/absolute/path/to/server.py"] } } }On macOS,
/usr/bin/python3is a safe absolute value. On Windows usepython. -
Create a project and attach a folder:
python3 server.py add my-notes python3 server.py link my-notes ~/Documents/Notes -
Fully quit Claude Desktop and reopen it.
# attach a folder to a project (creates the project)
python3 server.py link my-notes ~/Documents/Notes
# see the project, its token, and a ready-to-paste instruction block
python3 server.py list
list prints something like:
== my-notes ==
project_token: pf_xxxxxxxxxxxxxxxxxxxxxxxx
folder: /Users/you/Documents/Notes
--- paste into this project's Claude instructions: ---
For all file operations use the projectfs tools with
project_token "pf_xxxxxxxxxxxxxxxxxxxxxxxx". Save every document you produce as a
file in this project's folder via write_file.
Paste that block into the Claude Project's custom instructions. Done.
Say you keep a research project's files in ~/Documents/field-notes and you want one
Claude Desktop Project — and only that Project — to read and write them.
1. Attach the folder and get the token
$ python3 server.py link field-notes ~/Documents/field-notes
created project field-notes
attached field-notes -> /Users/you/Documents/field-notes
project_token: pf_EXAMPLEprojectTOKENxxxxxx
$ python3 server.py list
== field-notes ==
project_token: pf_EXAMPLEprojectTOKENxxxxxx
folder: /Users/you/Documents/field-notes
--- paste into this project's Claude instructions: ---
For all file operations use the projectfs tools with
project_token "pf_EXAMPLEprojectTOKENxxxxxx". Save every document you produce as a
file in this project's folder via write_file.
2. Put the token in the Claude Project's instructions
For all file operations use the projectfs tools with
project_tokenpf_EXAMPLEprojectTOKENxxxxxx. Save every document you produce as a file in this project's folder viawrite_file, and read existing files withread_file/list_dirbefore answering.
3. Use it in that Project's chats
You: What's already in the folder? Then draft
outline.mdfor the paper.Claude: (calls
list_dir→ seessources.md,interview-01.md) The folder hassources.mdandinterview-01.md. Here's a draft outline — (callswrite_fileoutline.md) saved tooutline.md.
Any other chat that doesn't carry that token can't touch ~/Documents/field-notes. A
second project (taxes, client-acme, ...) gets its own link call, its own token, and
its own isolated folder set, all served by the same running projectfs.
Run as python3 server.py <cmd> (or ./projectfs <cmd> with the bundled wrapper).
| Command | What it does |
|---|---|
add <project> |
create a project, print its token |
link <project> <folder> |
attach an existing folder to a project (creates it if new) |
unlink <project> <folder> |
detach a folder |
rm <project> |
delete a project entry |
list |
every project: folders, token, paste-ready instruction block |
init and admin-token still exist for older setups but are no longer needed — the
admin token gated a chat-side remap tool that has since been removed.
Config location, in order of precedence:
$PROJECTFS_CONFIGconfig.jsonnext toserver.py(if it exists)~/.projectfs/config.json
| Tool | Needs | Notes |
|---|---|---|
list_projects |
— | names + folders only, never tokens |
list_dir |
project_token |
path optional, defaults to the first folder |
read_file |
project_token |
UTF-8 text; 5 MiB cap |
write_file |
project_token |
creates parent dirs inside the project's folders |
append_file |
project_token |
good for running logs |
delete_file |
project_token |
files only |
move_file |
project_token |
source and dest both inside the project's folders |
Paths may be absolute (must resolve inside an attached folder) or relative (resolved
against the project's first folder). Symlink and .. escapes are rejected, and each
operation re-opens the resolved path component-by-component with O_NOFOLLOW so a
symlink swapped in after the check can't redirect it outside the folder. Writes cap at
10 MiB. Directory delete/move is refused.
The project token is the access boundary. Any caller that has a project's token and
the projectfs tools can read and write that project's folders — so:
- Put a token only in the instructions of the Project it belongs to.
- Don't paste tokens into ad-hoc chats.
- Rotate a token by editing
config.json(orrm+ re-addthe project).
Attaching and detaching folders is CLI-only. There is no MCP tool for it, so a chat
can't quietly attach ~/ to a project and read everything — even one that has somehow
obtained a token can only reach folders already mapped to that project.
- This is scoping and blast-radius control, not a sandbox. The server runs as your user account with your permissions. Don't attach a folder you would not want a misbehaving or prompt-injected chat to write in.
- A caller with no token can still call
list_projects(names and folder paths, no file contents). - macOS: folders under
~/Desktop,~/Documents,~/Downloads, and iCloud Drive are protected by the OS privacy layer (TCC). Claude Desktop may be able to create a file there but not list or read the directory. Keep projectfs folders outside those, or grant Claude Desktop Full Disk Access. - The server logs startup and error lines to stderr; Claude Desktop captures these in
~/Library/Logs/Claude/mcp*.log. - Anthropic may add native per-project MCP scoping to Claude Desktop, which would make this unnecessary.
MIT — see LICENSE.