workspacemcp
π 20 tools
workspacemcp β Secure, auditable workspace management for AI agents
v1.0 β Secure workspace access for AI agents. Claude Code-style edit tools (
edit_file/multi_edit/apply_patch),cat -nreads, ripgrep search, glob, plus DLP secret redaction, human approval gates, auto-checkpointing, and full audit logging. Expose as an MCP server with 20 tools.
Part of the MCP AI suite: ragmcp (knowledge) Β· memorymcp (memory) Β· planningmcp (reasoning) Β· workspacemcp (interaction)
Philosophy: βThe Agent is a Guest, not the Master.β β Least privilege, sandboxed paths, read-only by default, human approval for dangerous operations.
What is workspacemcp?
workspacemcp gives AI agents controlled access to the filesystem β reading, writing, searching, and versioning files β while enforcing strict security at every step:
- Path sandboxing confines all operations to a workspace root; symlink escapes are blocked
- DLP content filtering scans every read and write for 15 secret/PII patterns, redacting before the LLM sees content
- Approval gates require human sign-off for writes to critical files (production configs,
.env, Dockerfiles, keys) - Auto-checkpointing snapshots files before every mutation for instant rollback
- Ambiguity guard refuses edit patches that match multiple locations β no silent corruption
- Full audit trail logs every action (read, write, edit, delete, search, checkpoint, approval, DLP event)
- Event bus streams 12 event types for real-time monitoring and integration
- MCP server exposes 20 tools natively to Claude Desktop, Cursor, or any MCP client
Security Flow
Every request passes through the full security chain before execution:
Quick Start
3-line usage
from workspacemcp import WorkspaceFactory
workspace = WorkspaceFactory.default()
entry = await workspace.read_file("src/main.py")
Agent usage
from workspacemcp import WorkspaceFactory
workspace = WorkspaceFactory.create(
root_path="/home/user/project",
file_store="local",
read_only=False,
allowed_write_patterns=["src/**", "tests/**"],
auto_checkpoint=True,
)
# Read a file (secrets are automatically redacted)
entry = await workspace.read_file("config.yaml")
print(entry.content) # API keys replaced with [REDACTED:generic_api_key]
# Edit with ambiguity guard (refuses if old_text matches >1 location)
await workspace.edit_file(
"src/main.py",
old_text='print("hello")',
new_text='print("world")',
description="Update greeting",
)
# Search across the workspace
results = await workspace.search_workspace(r"TODO|FIXME", glob="*.py")
for r in results:
print(f"{r.path}:{r.line}: {r.content}")
# Create a named checkpoint for rollback
cp = await workspace.create_checkpoint(label="before-refactor")
# Get workspace statistics
stats = await workspace.get_workspace_stats()
print(f"{stats.total_files} files, {stats.total_size_bytes} bytes")
MCP server
from workspacemcp import WorkspaceFactory
from workspacemcp.mcp_server import WorkspaceMCPServer
workspace = WorkspaceFactory.create(root_path=".", file_store="local")
WorkspaceMCPServer(workspace).run()
Or from the command line:
workspacemcp serve
Claude Desktop claude_desktop_config.json:
{
"mcpServers": {
"workspacemcp": {
"command": "workspacemcp",
"args": ["serve", "--root", "/path/to/project"]
}
}
}
Features
- π Path sandboxing β chroot-like confinement, symlink escape prevention via
os.path.realpath() - π‘οΈ DLP content filter β 15 secret/PII patterns detected and redacted (AWS keys, GitHub PATs, JWTs, passwords, credit cards, SSNs, and more)
- β Approval gates β human-in-the-loop for writes to critical files, with async approve/deny and configurable timeout
- πΈ Auto-checkpointing β automatic file snapshot before every write, edit, and delete
- βοΈ Ambiguity guard β edit patches that match multiple locations are refused, preventing silent corruption
- π File operations β read (
cat -nnumbered), write (atomic), edit (search & replace,replace_all), multi-edit (atomic all-or-nothing), apply-patch (unified git diff), move, delete, list - π§ Claude Code-style edits β
old_string/new_string, rich errors with match line numbers, and a freshness guard that refuses edits to a file changed since it was last read - π Fast search & glob β ripgrep-backed content search (Python fallback), case-sensitive option, and recursive
**glob sorted newest-first - π³ Semantic tree β project structure with file descriptions, languages, and token estimates
- π Workspace stats β file counts, size, language distribution, largest files, checkpoint and audit counts
- π Mermaid diagrams β export workspace structure as Mermaid for documentation
- π°οΈ Checkpoint versioning β create, list, and restore full workspace snapshots
- π Immutable audit log β every action logged with timestamp, agent ID, success/failure, and detail
- π‘ Event bus β 12 event types with subscribe/emit/async stream
- π MCP server β 20 tools, stdio transport, compatible with any MCP client
- π WorkspaceFactory β
default()/create()/from_env()/from_yaml() - π CLI β 20 commands for workspace management without writing code
- π’ Multi-namespace β namespace-based isolation for multi-agent setups
- ποΈ Multi-tenant isolation β
tenant_isolation=Truegives each namespace its own root subdirectory (root_path/<namespace>/), withlist_tenants()enumeration - π Suite integration β works with ragmcp, memorymcp, and planningmcp
Installation
# Minimal β local file store + on-disk SQLite, no external services
pip install mcpaisuite-workspacemcp
# With ragmcp integration (semantic search over workspace)
pip install "mcpaisuite-workspacemcp[ragmcp]"
# Full stack (ragmcp + memorymcp + planningmcp + webhooks)
pip install "mcpaisuite-workspacemcp[all]"
Requirements: Python 3.11+
Security
Path Sandboxing
All file operations are confined to the workspace root. The PathSandbox resolves every path through os.path.realpath() to prevent symlink escape attacks. Traversal attempts like ../../etc/passwd are caught and rejected as SandboxViolation.
sandbox.resolve("../../etc/passwd")
# => SandboxViolation: Path escapes workspace sandbox: ../../etc/passwd
Configuration controls:
| Setting | Default | Description |
|---|---|---|
root_path | "." | Workspace root β all paths are confined here |
read_only | True | Deny all writes unless explicitly allowed |
allowed_write_patterns | [] | Glob patterns for writable paths (e.g. ["src/**", "tests/**"]) |
restricted_paths | [".env", "*.key", "*.pem", "secrets/"] | Paths blocked even for reads |
approval_required_patterns | ["production.*", ".env*", "*.key", "Dockerfile", "docker-compose.*"] | Paths requiring human approval for writes |
DLP Patterns
The ContentFilter scans every file read and write for 15 secret and PII patterns:
| Pattern | Example Match |
|---|---|
aws_access_key | AKIA1234567890ABCDEF |
aws_secret_key | aws_secret_access_key = ... |
github_pat | ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
github_oauth | gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
generic_api_key | api_key = "sk-...", secret_key: ... |
generic_password | password = "hunter2" |
private_key | -----BEGIN RSA PRIVATE KEY----- |
jwt_token | eyJhbGciOiJIUzI1NiIs... |
slack_token | xoxb-... |
stripe_key | sk_live_... |
email | user@example.com |
phone | +1 (555) 123-4567 |
credit_card | 4111 1111 1111 1111 |
ssn | 123-45-6789 |
connection_string | postgres://user:pass@host/db |
On read: secrets are replaced with [REDACTED:pattern_name] before the agent sees the content.
On write: secrets are detected and logged to the audit trail.
Approval Gates
Writes to files matching approval_required_patterns are held until a human approves or denies via approve_operation(). The gate uses asyncio.Future to block the requesting coroutine, with a configurable timeout (default: 1 hour).
# Agent tries to write production config β blocked, waiting for approval
await workspace.write_file("production.yaml", new_config)
# Human approves from CLI or another tool
await workspace.approve_operation(request_id, approved=True)
Writes, edits and deletes that touch a path matching approval_required_patterns require approval when an approval gate is configured (deletes are not gated unconditionally β only when the path matches a sensitive pattern).
Multi-tenant isolation
By default, all namespaces share the same workspace root. When tenant_isolation=True, each namespace is confined to its own subdirectory under the root (root_path/<namespace>/), so tenants cannot read or write each otherβs files. The isolation is transparent to callers β file operations take the same relative paths regardless.
The PathSandbox.tenant_root(namespace) method resolves (and lazily creates) the per-tenant directory:
- When isolation is off, or the namespace is empty or
"default",tenant_root()returns the shared workspace root unchanged. - When isolation is on and a non-default namespace is given, it returns
root_path/<namespace>/, creating the directory if needed.
All path resolution then happens relative to that tenant root, and the sandbox still blocks traversal outside it β so a tenant cannot escape its subdirectory via ../.
Enable it via the factory:
# Per-tenant root directories
workspace = WorkspaceFactory.create(
root_path="/srv/workspaces",
tenant_isolation=True,
read_only=False,
)
# File ops carry a namespace; each tenant lands in its own subdirectory
await workspace.write_file("notes.txt", "...", namespace="acme") # β /srv/workspaces/acme/notes.txt
await workspace.read_file("notes.txt", namespace="globex") # β /srv/workspaces/globex/notes.txt
Or from environment variables:
export WORKSPACEMCP_TENANT_ISOLATION=true
export WORKSPACEMCP_ROOT=/srv/workspaces
workspace = WorkspaceFactory.from_env()
List the tenant namespaces that currently have workspace directories with PathSandbox.list_tenants():
sandbox = PathSandbox(workspace.config)
sandbox.list_tenants() # e.g. ["acme", "globex"] β sorted; ["default"] when isolation is off or no tenants exist
list_tenants() returns ["default"] when tenant isolation is disabled. When enabled, it lists the immediate subdirectories of the root (excluding dot-directories), sorted β falling back to ["default"] if none exist yet.
MCP Tools
workspacemcp exposes 20 tools via the MCP protocol (stdio transport):
| Tool | Description |
|---|---|
list_files | List files in a directory with .gitignore respect |
read_file | Read a file with cat -n line numbers, pagination, and DLP filtering (secrets redacted) |
write_file | Write a file with sandbox check, auto-backup, and approval gate |
edit_file | Replace old_string with new_string (unique match, or replace_all); rich errors with match line numbers |
multi_edit | Apply a sequence of edits to one file atomically (all-or-nothing) |
apply_patch | Apply a unified diff (git patch) across one or more files |
glob | Find files matching a glob pattern (supports **), sorted newest-first |
delete_file | Delete a file (requires approval, creates checkpoint first) |
search_workspace | Fast content search (ripgrep when available, regex fallback), case-sensitive option, context lines |
create_checkpoint | Snapshot workspace state (all files) for rollback |
restore_checkpoint | Rollback workspace to a previous checkpoint |
list_checkpoints | List available workspace checkpoints |
get_file_info | Get file metadata (size, modified, checksum, token estimate) |
get_semantic_tree | Get project structure with descriptions and token estimates |
get_workspace_stats | Get workspace statistics (files, size, languages) |
workspace_config | View workspace configuration (sandbox, DLP, approval settings) |
audit_log | Query audit log entries |
approve_operation | Approve or deny a pending write operation |
workspace_diagram | Export workspace structure as Mermaid diagram |
move_file | Move or rename a file in the workspace |
CLI
workspacemcp provides 20 CLI commands (including multi-edit, apply-patch, and glob):
# Start MCP server (stdio transport)
workspacemcp serve
workspacemcp serve --root /path/to/project --writable
workspacemcp serve --config workspace.yaml
# Initialize workspace configuration file
workspacemcp init --root . --namespace my_project
# List files
workspacemcp ls
workspacemcp ls src/ -r -p "*.py"
# Read a file
workspacemcp read src/main.py
# Write content to a file
workspacemcp write src/hello.py "print('hello')"
# Edit a file (search and replace)
workspacemcp edit src/main.py "old text" "new text"
# Delete a file
workspacemcp delete src/old_file.py
# Get file metadata
workspacemcp info src/main.py
# Search workspace content (regex)
workspacemcp search "TODO|FIXME" -g "*.py"
# Manage checkpoints
workspacemcp checkpoint create -l "before-refactor"
workspacemcp checkpoint list
workspacemcp checkpoint restore --id abc12345
# View audit log
workspacemcp audit
workspacemcp audit -a write_file -n 20
# Show workspace statistics
workspacemcp stats
# Show workspace configuration
workspacemcp config
# Show semantic project tree
workspacemcp tree --max-depth 3
# Approve or deny a pending operation
workspacemcp approve <request_id>
workspacemcp approve <request_id> --deny
# Export workspace as Mermaid diagram
workspacemcp diagram
# Move or rename a file
workspacemcp move src/old.py src/new.py
Factory
WorkspaceFactory provides four entry points:
WorkspaceFactory.default(root_path=".")
Zero-config: an in-memory file store with on-disk SQLite checkpoint and audit logs (workspacemcp.db, workspacemcp_audit.db). No external services required.
WorkspaceFactory.create(...)
Full configuration:
workspace = WorkspaceFactory.create(
root_path=".", # Workspace root directory
file_store="local", # "local" | "memory"
checkpoint_store="memory", # "memory" | "sqlite"
audit_logger="memory", # "memory" | "sqlite"
content_filter=True, # Enable/disable DLP scanning
read_only=True, # Read-only mode (default)
allowed_write_patterns=["src/**"], # Glob patterns for writable paths
restricted_paths=[".env", "*.key"], # Paths blocked even for reads
approval_required_patterns=["production.*"],# Paths requiring human approval
max_file_size_mb=10, # Max file size limit
auto_checkpoint=True, # Auto-snapshot before mutations
tenant_isolation=False, # Per-tenant root dirs (see Multi-tenant isolation)
namespace="default", # Namespace for multi-agent isolation
sqlite_path="workspacemcp.db", # SQLite path for persistent stores
)
WorkspaceFactory.from_env()
Reads configuration from environment variables:
| Variable | Default | Description |
|---|---|---|
WORKSPACEMCP_ROOT | "." | Workspace root path |
WORKSPACEMCP_FILE_STORE | "local" | File store backend |
WORKSPACEMCP_CHECKPOINT_STORE | "memory" | Checkpoint store backend |
WORKSPACEMCP_AUDIT | "memory" | Audit logger backend |
WORKSPACEMCP_READONLY | "true" | Read-only mode |
WORKSPACEMCP_TENANT_ISOLATION | "false" | Per-tenant root directories (multi-tenant isolation) |
WORKSPACEMCP_NAMESPACE | "default" | Namespace |
WORKSPACEMCP_SQLITE_PATH | "workspacemcp.db" | SQLite database path |
WorkspaceFactory.from_yaml(path)
Reads configuration from a YAML file and passes all keys to create().
Versioning: Checkpoints + Diff Engine
Checkpoints
Checkpoints snapshot the entire workspace state (all file checksums and contents) for rollback:
# Create a named checkpoint
cp = await workspace.create_checkpoint(label="before-refactor")
# List checkpoints
checkpoints = await workspace.list_checkpoints()
# Restore to a previous state
await workspace.restore_checkpoint(cp.id)
Auto-checkpointing creates a mini-checkpoint of each individual file before every write, edit, or delete β so even without explicit checkpoints, you can always recover the previous version.
Diff Engine with Ambiguity Guard
The DiffEngine applies search & replace patches with strict safety:
- Missing match β raises
ValueErrorif the search text is not found - Ambiguous match β raises
AmbiguousPatchErrorif the search text appears more than once - Single match β applies the replacement exactly once
This prevents the common failure mode where an LLMβs edit silently corrupts a file by replacing the wrong occurrence. The agent must provide enough surrounding context to make the match unique.
# This will raise AmbiguousPatchError if "import os" appears more than once
await workspace.edit_file("main.py", old_text="import os", new_text="import os\nimport sys")
The engine also provides compute_diff() for unified diff output and preview_patch() for dry-run previews.
Analysis: Semantic Tree, Search, Stats
Semantic Tree
Build a hierarchical view of the project structure with file metadata:
tree = await workspace.get_semantic_tree(max_depth=5)
# SemanticNode with: path, type (file/directory), description, children, size, language, estimated_tokens
Export as Mermaid diagram:
mermaid = await workspace.workspace_diagram()
Search
Regex-powered content search with glob filtering:
results = await workspace.search_workspace(
pattern=r"TODO|FIXME",
directory="src/",
glob="*.py",
max_results=50,
)
for r in results:
print(f"{r.path}:{r.line}: {r.content}")
# Each result includes context_before and context_after lines
Stats
stats = await workspace.get_workspace_stats()
# WorkspaceStatsModel:
# total_files, total_dirs, total_size_bytes,
# language_distribution ({"python": 42, "yaml": 5, ...}),
# largest_files, last_modified,
# checkpoint_count, audit_entry_count
Events
workspacemcp emits 12 event types through an async event bus:
| Event Type | Triggered When |
|---|---|
file.read | A file is read |
file.written | A file is written |
file.edited | A file is edited via search & replace |
file.deleted | A file is deleted |
checkpoint.created | A checkpoint is created (manual or auto) |
checkpoint.restored | A checkpoint is restored |
approval.requested | A write to a critical file is pending approval |
approval.granted | An approval request is granted |
approval.denied | An approval request is denied |
dlp.alert | DLP detects secrets in content |
sandbox.violation | A path escape attempt is detected |
config.changed | Workspace configuration is modified |
Subscribing to events
from workspacemcp.events import workspace_event_bus, WorkspaceEventType
# Subscribe to events for a specific namespace
queue = workspace_event_bus.subscribe(namespace="default")
# Or stream all events asynchronously
async for event in workspace_event_bus.stream():
print(f"{event.type}: {event.file_path} β {event.message}")
Configuration (YAML)
root_path: /home/user/project
file_store: local
checkpoint_store: sqlite
audit_logger: sqlite
sqlite_path: workspacemcp.db
read_only: false
allowed_write_patterns:
- "src/**"
- "tests/**"
- "docs/**"
restricted_paths:
- ".env"
- "*.key"
- "*.pem"
- "secrets/"
approval_required_patterns:
- "production.*"
- ".env*"
- "*.key"
- "Dockerfile"
- "docker-compose.*"
max_file_size_mb: 10
auto_checkpoint: true
namespace: my_project
content_filter: true
Load it with:
workspace = WorkspaceFactory.from_yaml("workspace.yaml")
Integration with ragmcp + memorymcp + planningmcp
workspacemcp is the interaction layer of the MCP AI suite. It works alongside:
- ragmcp (knowledge) β semantic search and retrieval-augmented generation. workspacemcp can use ragmcp to index and search workspace content by meaning.
- memorymcp (memory) β persistent cognitive memory for AI agents. Store facts learned while working in the workspace; retrieve relevant context across sessions.
- planningmcp (reasoning) β structured planning and task decomposition. Plans can include workspace operations as steps, with workspacemcp providing the secure execution layer.
Install integration extras:
pip install "mcpaisuite-workspacemcp[ragmcp]" # Semantic workspace search
pip install "mcpaisuite-workspacemcp[memorymcp]" # Cross-session workspace memory
pip install "mcpaisuite-workspacemcp[planningmcp]" # Plan-driven workspace operations
pip install "mcpaisuite-workspacemcp[all]" # Everything
Together, the four servers give an AI agent a complete cognitive stack: knowledge retrieval (ragmcp), persistent memory (memorymcp), structured reasoning (planningmcp), and secure workspace interaction (workspacemcp).
Development / Contributing
git clone https://github.com/gashel01/workspacemcp
cd workspacemcp
pip install -e ".[dev]"
# Run tests
pytest tests/ -v # 151 tests
# With coverage
pytest tests/ --cov=workspacemcp --cov-report=html
Project structure:
workspacemcp/
core/ β Base classes and Pydantic models (FileEntry, Checkpoint, AuditEntry, etc.)
filesystem/ β File store backends and path sandbox
sandbox.py β PathSandbox (chroot-like confinement)
local_store.py β Local filesystem backend
memory_store.py β In-memory file store (testing)
security/ β Security layer
dlp.py β ContentFilter (15 secret/PII patterns)
gates.py β ApprovalGate (human-in-the-loop)
audit.py β InMemoryAuditLogger, SQLiteAuditLogger
versioning/ β Checkpoint and diff engine
checkpoint.py β InMemoryCheckpointStore, SQLiteCheckpointStore
diff.py β DiffEngine with ambiguity guard
analysis/ β Workspace analysis tools
tree.py β SemanticTreeBuilder + Mermaid export
search.py β Content search engine
stats.py β WorkspaceStatsBuilder
integration/ β Integration with ragmcp, memorymcp, planningmcp
events.py β Event bus (12 event types, subscribe/emit/stream)
mcp_server.py β MCP server (20 tools, stdio transport)
factory.py β WorkspaceFactory (default/create/from_env/from_yaml)
pipeline.py β WorkspacePipeline (central orchestrator)
cli.py β CLI (20 commands)
Contributions are welcome. Please open an issue before submitting a large PR.
License
AGPL-3.0 β see LICENSE.
For commercial licensing (closed-source usage), contact the author.