Skip to main content
Glama

Claude Workspace Ops MCP

A Python MCP server for AI-assisted Google Drive organization. Claude can inspect a workspace and propose how to tidy it; deterministic Python and an explicit human approval decide what actually happens.

The interesting part is not that Claude can propose a folder structure. It's that a compromised model operating through the MCP interface cannot authorize an unsafe write, and the tests prove it rather than assert it. An attacker who already controls the operator's own terminal session is a different threat and sits outside that boundary — see Threat-model boundaries.

Python 3.11+ · MCP · Google Drive API · OAuth drive.file · Anthropic API · Pydantic · SQLite · pytest · ruff · mypy

 ┌────────────── model trust zone ──────────────┐   ┌──── human trust zone ────┐
 │  inspect ▶ propose ▶ deterministic validation│   │ cwops approve (TTY only) │
 └───────────────────────┬──────────────────────┘   └────────────┬─────────────┘
                         │     no MCP tool can approve           │
                         └──────────▶ apply_proposal ◀───────────┘
                                            │  single-use · plan-hash bound · expiring
                                            ▼
                              Google Drive  +  append-only audit log

Non-interactive approval fails closed: cwops approve refuses to run when stdin is not a terminal, --yes included, so a shell-capable agent cannot manufacture a human decision.


Engineering highlights

Designed and built solo — the MCP server, the Drive and Anthropic integrations, the validator, the approval gate, the storage layer, the fakes and the test suite. Third-party runtime code is limited to the MCP SDK, the Google and Anthropic clients, Pydantic and SQLite.

  • MCP server — nine typed tools; the published JSON Schema is derived from Pydantic models and pinned by a golden-snapshot test, so a contract change is a reviewed diff. stdio transport, with all logging on stderr so a stray print cannot corrupt the JSON-RPC stream.

  • Google Drive API integration — installed-app OAuth flow, bounded jittered retry, escaped query values, and a 404 that is never retried.

  • Least-privilege scopedrive.file is hard-coded; a stored token carrying anything broader is rejected at load time.

  • Deterministic validation — pure functions over a fetched snapshot. Unknown IDs, ungranted targets, path separators, cycles and anything outside the app-owned root are rejected, never repaired.

  • Proposal / dry-run workflow — plans are stored, hashed, previewed in plain language, and re-validated against live Drive state at apply time.

  • Approval outside the MCP/model trust zone — an approval row is created by one CLI command in a separate process; no tool creates one.

  • TTY-enforced approval boundary — that CLI fails closed without an interactive terminal, and --yes cannot bypass the check.

  • Plan-hash binding, expiry, single use — approvals are tied to a SHA-256 of the exact operation list, TTL-limited, and claimed by one atomic guarded UPDATE, with an explicit lifecycle state (none · active · consumed · expired · superseded).

  • Append-only audit log — one row per tool call and per CLI action, with correlation IDs, reason codes on refusal, and secret scrubbing on the way in.

  • Duplicate detection — checksum-exact vs. metadata-heuristic, with Google-native files reported as unscannable rather than silently unique.

  • Fakes at the Protocol seams — a fake Drive and a fake organizer make the entire suite deterministic and offline. No mocking library, no network.


Related MCP server: MCP Google Drive Server

Validated against real Google Drive

The gates are not only exercised against the fakes. A full end-to-end run with CWOPS_DRIVE_BACKEND=google against a live Google account:

Check

Result

OAuth consent

granted drive.file only

Files this app created

reachable

An unrelated pre-existing Drive file

not reachable — outside the grant

Workspace seeded

6 files

Duplicate detection

the exact duplicate pair found by checksum

propose_organization

11 operations — 5 folder creations + 6 moves

Approval

given by a human, through cwops approve in a terminal

apply_proposal

executed the exact hash-bound plan it was approved for

Outcome

11/11 operations succeeded; 6 files organized into 5 folders

Duplicate pair

both copies preserved — nothing was deleted

Destructive actions

none occurred; none exist to call

Audit trail

distinct cli (approve) and mcp_client (apply) actors recorded

The TTY boundary was verified against the installed Windows CLI as well: a non-interactive invocation is refused with approval_not_interactive, while a real terminal reaches the proposal lookup normally.


The three claims this project makes

1. Least privilege is structural, not procedural. The only OAuth scope requested is drive.file — Google's one non-sensitive Drive scope. It grants per-file access to files this app created or that the user explicitly opened/shared with it. The rest of the user's Drive is not "forbidden", it is invisible: a request for an ungranted file returns 404, not 403. There is no configuration option to widen this, and a stored token that somehow carries a broader scope is rejected at load time as a configuration error.

2. The model advises; deterministic code decides. Claude is consulted in exactly two places, and it answers in a schema that cannot express an unsafe action. It returns folder names and file assignments — there is no field for "rename", "delete", or "put this outside the workspace". Deterministic code then compiles that draft into operations whose parent folder comes from configuration, never from the model, and re-validates every one.

3. Approval lives in a different trust zone than the agent. An approval record is created only by cwops approve — a CLI command in a separate process that refuses to run without an interactive terminal, --yes included. No MCP tool creates one, and a shell-capable agent gets a pipe rather than a TTY, so an agent driving this server has no code path to approval. It can ask; it cannot produce.


Try it in 30 seconds, with no Google account

git clone https://github.com/Shineoverthemoon/claude-workspace-ops-mcp
cd claude-workspace-ops-mcp
python -m venv .venv && .venv/bin/pip install -e ".[dev]"   # Windows: .venv\Scripts\pip
cwops demo

cwops demo runs the entire pipeline against an in-memory Drive and an offline organizer, narrating each gate: what the app can see, deterministic duplicate detection, a proposed plan, a dry run, a real run refused for lack of approval, a human approval, a successful run, a replay refused because approvals are single-use, and the resulting audit trail.

It is also a test (test_demo_runs_the_whole_pipeline_offline), so the walkthrough in docs/demo.md cannot rot.

========================================================================
5. Real run WITHOUT approval  (must be refused)
========================================================================
  Refused: [approval_required] Proposal prop_241cb99db035 has no human
           approval. Run: cwops approve prop_241cb99db035
  Drive mutations so far: 0

The tools

Nine tools. Eight are read-only. One can write.

Tool

Model?

Writes?

drive_search

no

no

drive_list_folder

no

no

drive_read_file

no

no

find_duplicates

no

no

classify_documents

yes

no

propose_organization

yes

no

propose_operations

no

no

get_proposal

no

no

apply_proposal

no

yes — the only one

apply_proposal defaults to dry_run=true. A real run additionally requires CWOPS_ALLOW_MUTATIONS=true and an unconsumed, unexpired approval bound to that exact plan.

There is no delete, trash, overwrite, permission or sharing capability anywhere in this server. Only create-folder, rename and move exist, and all three are reversible. That is a deliberate MVP boundary, not an oversight — see Deliberate limits.

Duplicate detection is honest about what it can't know

  • checksum — exact match on Drive's md5Checksum + size. Trustworthy.

  • metadata — normalized title + size + MIME type. Always labelled heuristic.

Google-native Docs/Sheets/Slides have no checksum at all. Rather than silently treating them as unique, they are reported in a separate unscannable list with the reason. The metadata strategy exists precisely to catch the copies that checksum structurally cannot.


Running it against your own Google Drive

cp .env.example .env          # then edit
cwops auth                    # browser consent, drive.file only
cwops workspace init          # creates the app-owned root folder
                              # -> prints CWOPS_ROOT_FOLDER_ID to paste into .env
cwops workspace seed          # optional: demo files, incl. an exact duplicate pair

You need a Google Cloud project with the Drive API enabled and an OAuth client ID of type "Desktop app", downloaded as client_secret.json. That file and the token it produces stay on your machine — both are gitignored, and neither belongs in a commit.

Because drive.file is non-sensitive, the sensitive/restricted-scope verification review does not apply — the practical payoff of keeping the authorization surface narrow.

Scope and publishing status are separate concerns, though. While the OAuth app's publishing status is Testing, Google issues refresh tokens with a limited lifetime (currently 7 days), so you will periodically need to re-run cwops auth. Publishing to production has its own requirements.

To bring an existing file under the app's control, either move it into the workspace folder or open it with this app; then cwops grant <file_id|url> verifies and records the grant. Programmatic file-picking (Google Picker) is deliberately deferred — the GrantSource.PICKER value already exists so adding it later needs no schema change.

Wiring it into an MCP client

{
  "mcpServers": {
    "workspace-ops": {
      "command": "C:\\Projects\\claude-workspace-ops-mcp\\.venv\\Scripts\\cwops-mcp.exe",
      "env": {
        "CWOPS_DRIVE_BACKEND": "google",
        "CWOPS_AI_BACKEND": "claude",
        "CWOPS_ROOT_FOLDER_ID": "<from workspace init>",
        "CWOPS_ALLOW_MUTATIONS": "true"
      }
    }
  }
}

Transport is stdio, so stdout carries the JSON-RPC stream. Every log line goes to stderr as single-line JSON with a correlation ID; a stray print() would corrupt the protocol.


Where Claude is used — and where it deliberately isn't

Concern

Handled by

Classifying an ambiguous document

Claude (claude-opus-5, adaptive thinking, low effort)

Suggesting a folder taxonomy

Claude

Everything else

Deterministic Python

"Everything else" means: authorization, workspace containment, grant checks, name policy, duplicate detection, plan compilation, approval, execution ordering, retry, and audit. None of it consults a model, and none of it can be influenced by one.

Claude's output is treated as untrusted input. File names and contents are attacker-controllable, so a persuasive document could talk the model into suggesting something hostile. The system prompt says to ignore embedded instructions — but that is noise reduction, not the defence. The defence is that the validator rejects the result afterwards regardless of what the model was persuaded to say.

tests/test_ai_boundary.py demonstrates this with an organizer that returns deliberately hostile plans.


Architecture

src/cwops/
  config.py          env vars; drive.file scope hard-coded; fails fast
  logging.py         structured JSON to stderr + secret scrubbing
  models.py          the MCP contract (Pydantic -> published JSON Schema)
  clock.py           injectable time, so TTLs are tested not slept through
  container.py       dependency wiring: real backends vs. offline fakes
  workspace.py       read-side ops; keeps the grant ledger honest
  provision.py       CLI-only workspace bootstrap (2nd of 2 write sites)
  server.py          MCP wiring only
  cli.py             the human half of the approval gate
  drive/             DriveClient Protocol · OAuth · Google impl · retry · fake
  ai/                Organizer Protocol · Claude impl · prompts · fake
  rules/             naming · duplicates · validate   (pure functions, no I/O)
  store/             SQLite: proposals · approvals · grants · audit
  tools/             read (8 read-only) · organize · apply (the write path)

Two Protocol seams — DriveClient and Organizer — are what let every safety gate be built and fully tested before any real Google or Anthropic code existed, and what let the whole system run offline today.

Full data flow and trust zones: docs/architecture.md. The invariants and the threat model: docs/safety-model.md.


Testing

pytest                                   # 248 passed, 1 skipped, ~5s, no network
ruff check . && mypy                     # both clean (mypy's configured scope: src/cwops)
pytest --cov=cwops                       # 89% overall; rules/ 96-100%

Suite

What it proves

test_approval_gate.py

No tool can create an approval · single-use · TTL · plan-hash binding · tampering invalidates · operator switch · world-changed detection · approval lifecycle state

test_cli.py

Approval refused without an interactive terminal, --yes included, and the refusal audited · the human path still works

test_validate.py

23 adversarial plans from a JSON fixture — hallucinated IDs, path traversal, cycles, escapes — all rejected

test_ai_boundary.py

A hostile organizer cannot cause an unsafe operation

test_dry_run.py

Zero mutating calls across every read tool and every dry run — asserted against the fake's call log, plus a source-level check that only apply.py and provision.py mutate

test_duplicates.py

Exact vs. heuristic; Google-native files unscannable, not "unique"; output is deterministic

test_retry.py

Backoff bounded and jittered; 404 never retried; Drive query values escaped against injection

test_auth_scope.py

Only drive.file requested; a broader token is refused

test_audit.py

One row per call; refusals recorded; no secret can reach the table

test_tool_schemas.py

Golden snapshot of the published MCP contract

No test touches the network. Two fakes at the Protocol seams make that possible without mocking libraries.


Deliberate limits

Documented as scope, not omissions:

  • No destructive operations. No delete, trash, overwrite, permissions or sharing. Everything the MVP can do is reversible.

  • No Google Picker. Existing files come under management by being moved into the workspace or opened with the app. The picker grant source is reserved.

  • No Shared Drives, no multi-user, no concurrent proposals across processes.

  • No undo journal. Rename and move are reversible in principle; a one-command rollback is not built.

  • stdio transport only. No HTTP/SSE.

  • A partially-invalid plan is trimmed, not rejected. Rejected operations are dropped and shown in full — with reason codes — in the proposal, the preview, and the audit log, so the human approves an explicit list. A plan with nothing left cannot be approved at all.

Threat-model boundaries

Stated plainly, because a safety argument with no stated limits is marketing. Full version in docs/safety-model.md.

  • The operator is trusted. Anyone who can type at the operator's terminal can approve anything. The TTY requirement raises the bar to "can allocate a PTY"; it is not a defence against something that already owns your session.

  • The MCP client is assumed to be the intended one. The server does not authenticate its caller.

  • The SQLite file is a local, single-user trust boundary, not a tamper-proof ledger: write access to it means write access to approvals. The audit log is append-only by convention, not cryptographically chained.

  • drive.file does not protect files already granted. A file you opened with this app is genuinely reachable — which is why workspace containment is a separate control, tested separately.

  • Prompt injection is mitigated structurally, not by prompting. The system prompt says to ignore embedded instructions, but the actual defence is that the validator rejects whatever the model was persuaded to produce.

Secrets

.env, client_secret*.json, *token.json and *.db are gitignored. The Anthropic SDK reads ANTHROPIC_API_KEY from the environment directly; no credential is ever passed through this code. The audit log and every log line are scrubbed on the way out — test_audit.py asserts that a credential passed in by mistake cannot reach the database.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Shineoverthemoon/claude-workspace-ops-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server