SILO-MCP

by gouthamkallempudi

Not rated
GitHub

About

MCP server that moves files between your filesystem and cloud storage.

Details

Author
gouthamkallempudi
Categories
Developer Tools, File Management, Other, Automation

Setup friction and credential shape per platform

Dropbox, Yandex Disk, S3, GCS, and Azure Blob use static credentials read once. Google Drive, OneDrive, Box, and Google Photos are all Big-Tech OAuth2 with short-lived (~1h) access tokens — this server calls each platform's token endpoint fresh before every API request rather than caching, keeping the correctness story simple at the cost of one extra fast (~200ms) request per call.

The credential fields above only get you a token — the token also needs the right scope, or every call fails with a permissions error regardless of how correctly it's stored. Configured on the platform's side, not here:

](#platform-specific-notes)[!TIP] Dropbox is the one most likely to bite you: amissing_scope401 with an otherwise-correct token almost always means the scopes were changedafterthe token was generated. Regenerate the token, don't just re-save it.

local_pathfor uploads and the destination filename for downloads are both LLM-supplied tool-call arguments, so both are confined to one configured root each, in both directions:

- SILO_MCP_UPLOAD_ROOT(default~/silo-mcp/uploads) — files outside this directory can't be uploaded. This matters a lot here: without it, a manipulated conversation could ask the server to upload an arbitrary local file (SSH keys,.env, browser credential stores) to a cloud account — persistent, shareable, no visible platform artifact warning anyone something left the machine.
- SILO_MCP_DOWNLOAD_ROOT(default~/silo-mcp/downloads) — downloads can't be written outside this directory via a crafted filename.

Uploads are capped at 5 GiB (MAX_UPLOAD_BYTES) and downloads at 5 GiB (SILO_MCP_MAX_DOWNLOAD_BYTES, overridable). Downloads are streamed to disk and aborted mid-flight if they run over the cap, so an unexpectedly huge file can't exhaust memory. Credentials are stored in the OS keyring, never in the SQLite DB; the one-time OAuth authorization flow uses PKCE and a randomstatevalue (RFC 8252) so the loopback redirect can't be forged.

- Google Photos is upload-only.Google removed the Photos Library API's read-library scopes in March 2025 — an app can now only manage media items it created itself. Browsing or downloading a user's existing library needs the interactive "Picker API" (a web UI session), which a headless MCP tool call can't drive.list_files/download_file/search_filesall return a clear error rather than pretending to work.There is also no delete capability at all for Google Photos, in this project or Google's API— anything uploaded is permanent until removed manually via photos.google.com.
- Google Drive and Box address files by id, not path, for download/delete —upload'sremote_pathis used as a filename (Drive root / Box root folder only, no folder targeting in v1); to download/delete you need the id fromlist_files/search_filesfirst. OneDrive, Dropbox, and Yandex Disk use real paths throughout, no asymmetry.
- Object stores don't support search— only prefix listing vialist_files.search_filesreturns a clear "not supported" error. Yandex Disk has no dedicated search endpoint either and behaves the same way (path listing vialist_filesonly).
- Upload endpoints here are all single-shot (Dropbox ≤150MB, OneDrive ≤4MB); large-file chunked/resumable upload isn't implemented for any platform yet.

upload_file,delete_file, andcreate_share_linkall requireconfirm=true— they either mutate remote state or hand out a bearer-credential link.download_file/list_files/search_filesdon't gate, since they only write locally.

Dropbox, Google Drive, and Google Photos have been verified end-to-endover the actual MCP protocol— an MCP client spawns the server, does tool discovery, and calls the tools by name, exactly as Claude Desktop/ Code would — not just via direct Python calls.

Issues and PRs reporting real-account testing results for the remaining platforms are very welcome.

A self-hosted MCP server that moves files between your filesystem and cloud storage — nine platforms, one tool surface. Upload, download, list, search, delete, generate share links, and copy filesdirectly from one cloud to another, without giving an LLM raw filesystem access or a bag of platform-specific credentials to juggle.

It's not "local-first" — the files live in the cloud and every operation talks to a remote provider API. Whatstayslocal is the part that matters for trust: the server runs as a subprocess on your machine, your credentials sit in the OS keyring (never in the cloud, a config file, or a tool-call argument), and the transfer audit log is a local SQLite file.

Document stores:Dropbox · Google Drive · OneDrive · Box · Google Photos (upload-only) · Yandex DiskObject stores:S3-compatible (AWS S3, Cloudflare R2, MinIO) · Google Cloud Storage · Azure Blob Storage

- Copy a file directly from one cloud to another— "copy my Dropbox/photosfile to my S3 bucket", "migrate this Drive file to OneDrive" — in a single tool call, no manual download-then-upload. The one thing a single-vendor storage integration structurally can't do.
- Move files in and out of cloud storage— "uploadreport.pdfto Dropbox", "downloadnotes.txtfrom Google Drive so I can use it" — across all nine platforms with one set of tools.
- Browse and searchyour document stores by folder or query, and list object-store buckets by key prefix.
- Generate shareable linksto a file — presigned/signed URLs on the object stores and Box (with expiry), platform share links on the rest.
- Bridge to other MCP servers— adownload_filehere lands a local path you can hand straight to another server (e.g. a social-posting server'smedia_paths).
- Keep a local audit trail— every upload, download, delete, and share link is recorded in a local SQLite log you can query withlist_transfers.

All of it runs through your MCP client in natural language — seeExample workflows.

Quick start:pip install -e .→ addsilo-mcpto your MCP client config →silo-mcp-accounts add dropbox --label personal→ ask your client to list your Dropbox files. Full steps inInstallationandAdd accountsbelow.

- What can you do with Silo MCP?
-
Why
-
Architecture
-
Requirements
-
Installation
-
Configure your MCP client
-
Supported clients
-
Using with Ollama
-
Add accounts
-
Example workflows
-
Tools
-
Supported platforms & tools
-
Setup friction and credential shape per platform
-
Path safety
-
Platform-specific notes
-
Safety
-
Security
-
Testing
-
Troubleshooting
-
Related
-
License

Most "give the LLM your cloud storage" setups fall into one of two traps: a single-platform integration that's dead the moment you switch providers, or unrestricted local file access that lets a manipulated conversation read or upload anything on disk. Silo MCP is built against both:

- One tool surface, nine platforms.upload_file,download_file,list_files,search_files,delete_file,create_share_linkbehave the same regardless of which platform you point them at — object stores (bucket+key) and document stores (path-based) share oneFileStorecontract.
- Confined by design, not by convention.local_pathfor uploads and the destination for downloads are both LLM-supplied tool-call arguments, so both are hard-confined to configured root directories — a manipulated conversation can't ask this server to read an SSH key or write a download somewhere it shouldn't.
- Mutations requireconfirm=true.upload_file,delete_file, andcreate_share_linkall mutate remote state or create a bearer-credential link and require deliberate confirmation.download_file/list_files/search_filesonly write locally, so they don't gate.
- Multi-account from the start.Every tool takes an optionalaccountlabel — run a work Dropbox and a personal Dropbox side by side without reconfiguring anything.

flowchart TB subgraph Client["MCP Client"] direction LR CD["Claude Desktop / Code<br/>(stdio)"] OL["Ollama bridge<br/>(streamable-http / SSE)"] end subgraph Silo["Silo MCP Server (server.py)"] direction TB Tools["Tool surface<br/>upload_file · download_file · list_files<br/>search_files · delete_file · create_share_link"] Paths["paths.py<br/>upload/download root containment"] Registry["stores/registry.py<br/>resolve(platform, account)"] Accounts["accounts.py<br/>credential storage + OAuth refresh"] History[("db.py<br/>SQLite transfer log")] end subgraph Backends["FileStore implementations"] direction LR Doc["Document stores<br/>Dropbox · Drive · OneDrive<br/>Box · Photos · Yandex Disk"] Obj["Object stores<br/>S3-compatible · GCS · Azure Blob"] end FS[("Local filesystem<br/>upload/download roots")] Cred[("OS credential store<br/>Windows / macOS / Linux keyring")] CD --> Tools OL --> Tools Tools --> Paths --> FS Tools --> Registry Registry --> Doc Registry --> Obj Registry --> Accounts --> Cred Tools --> History

Every tool call resolves a(platform, account)pair to aFileStoreimplementation and a credential fromaccounts.py, checks any local path against the upload/download roots, executes against the real platform API, and logs the result — the same shape regardless of which of the nine backends is on the other end.

- Python 3.11+
- An OS credential storekeyringcan use (Windows Credential Manager, macOS Keychain, or a Secret Service provider on Linux) — credentials are never written to disk in plaintext or passed through an MCP tool call.

git clone https://github.com/gouthamkallempudi/silo-mcp.git cd silo-mcp python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1 pip install -e .

Object-store SDKs are optional extras — only install what you need:

pip install -e ".[s3]" # AWS S3 / Cloudflare R2 / MinIO pip install -e ".[gcs]" # Google Cloud Storage pip install -e ".[azure]" # Azure Blob Storage pip install -e ".[all]" # all three

A core install (no extras) covers Dropbox/Drive/OneDrive/Box/Google Photos/Yandex Disk with no dependency beyondhttpx— the server degrades gracefully if an object-store SDK isn't installed rather than failing to start.

Add to your client's MCP config (e.g. Claude Desktop'sclaude_desktop_config.json, or Claude Code's.mcp.json):

{ "mcpServers": { "silo-mcp": { "command": "silo-mcp" } } }

silo-mcpmust resolve onPATHinside the environment your client launches the server from — if you installed into a virtualenv, pointcommandat that venv'ssilo-mcpexecutable directly (e.g./path/to/silo-mcp/.venv/bin/silo-mcp) instead of relying on shell activation.

Defaults tostdio, what every desktop MCP client (Claude Desktop, Claude Code, Cursor, etc.) spawns as a subprocess — no network port opens. For a client that can't spawn a local subprocess and needs to reach the server over HTTP instead (seeUsing with Ollamabelow), set:

SILO_MCP_TRANSPORT=streamable-http SILO_MCP_HOST=127.0.0.1 SILO_MCP_PORT=8000 silo-mcp

SILO_MCP_TRANSPORTalso acceptssse(the older HTTP transport, kept for clients that haven't moved to streamable-http yet).SILO_MCP_HOST/SILO_MCP_PORTare only read for the two HTTP transports and default to127.0.0.1:8000.

[!WARNING] This server has no built-in auth for HTTP mode — don't bind it to0.0.0.0or expose it past localhost without putting a reverse proxy with auth in front of it, since every tool call reaches your cloud storage accounts.

Any MCP client that can launch a local stdio server works — the server uses only standard MCP tool calls, no client-specific features. Verified and expected-to-work clients:

[!TIP] Ifsilo-mcpisn't on the launching shell'sPATH(common with virtualenvs), setcommandto the venv's executable directly, e.g.C:\\path\\to\\silo-mcp\\.venv\\Scripts\\silo-mcp.exeon Windows or/path/to/.venv/bin/silo-mcpelsewhere.

Ollama doesn't speak MCP natively — it needs an MCP client in the loop that turns Ollama's tool-calling into MCP tool calls, the same role Claude Desktop/Code play for Claude. This server doesn't ship that bridge (kept out of scope to stay a plain server package), but any MCP-aware Ollama client works once you point it at streamable-http instead of stdio:
- Start the server in HTTP mode:SILO_MCP_TRANSPORT=streamable-http silo-mcp.
- Point your Ollama-side MCP client athttp://127.0.0.1:8000/mcp.
- Use a tool-calling-capable model (e.g.llama3.1,qwen2.5) — Ollama only routes tool calls for models that support thetoolsAPI field.

Credentials are added via a CLI with hidden input, never through an MCP tool call, and stored in your OS credential store:

silo-mcp-accounts add dropbox --label personal silo-mcp-accounts add google_drive --label personal silo-mcp-accounts add onedrive --label personal silo-mcp-accounts add box --label personal silo-mcp-accounts add google_photos --label personal silo-mcp-accounts add yandex_disk --label personal silo-mcp-accounts add s3 --label personal silo-mcp-accounts add gcs --label personal silo-mcp-accounts add azure_blob --label personal silo-mcp-accounts list

These four platforms need arefresh_tokenbeforesilo-mcp-accounts addwill accept them — and getting thefirstone requires a one-time browser authorization, not just a client_id/secret pair. Register an app on each platform's developer console, then run:

silo-mcp-accounts oauth google_drive --label personal silo-mcp-accounts oauth google_photos --label personal silo-mcp-accounts oauth onedrive --label personal silo-mcp-accounts oauth box --label personal

This opens your browser to the platform's consent screen, listens onhttp://localhost:8765/callbackfor the redirect, exchanges the code for a refresh_token, and stores the account — no need to runaddafterward.

- Google (Drive + Photos share one app):Google Cloud Console→ new project → APIs & Services → enable theGoogle Drive APIandPhotos Library API→ OAuth consent screen (External is fine for personal testing, add yourself as a test user) → Credentials → Create OAuth client ID → typeDesktop app. Desktop-app clients accept anyhttp://localhost:<port>redirect without pre-registering it, so no redirect URI setup needed.
- OneDrive:
Azure Portal→ App registrations → New registration → platformMobile and desktop applications→ add redirect URIhttp://localhost:8765/callbackexactly (Azure requires an exact match). API permissions → Microsoft Graph → addFiles.ReadWriteandoffline_access(delegated). Certificates & secrets → new client secret.
- Box:
Box Developer Console→ Create new app →Custom AppUser Authentication (OAuth 2.0)→ under Configuration, set Redirect URI tohttp://localhost:8765/callbackexactly (Box also requires an exact match) and check the scopes you need (Read/write files).

If you use--portto pick a different local port, use that same port in the redirect URI you register.

Yandex Disk is a static token like Dropbox — norefresh_token, so it's not part of thesilo-mcp-accounts oauthbootstrap flow above. Its OAuth app config uses animplicit grant: the token comes back directly in the browser, no code-exchange step to script.
-
oauth.yandex.com→Create app(or reuse an existing one).
- UnderPlatforms, checkWeb servicesand set the redirect URI tohttps://oauth.yandex.com/verification_code— Yandex's own built-in page that just displays the token, no local listener needed for this one.
- UnderPermissions, grant Disk access:cloud_api:disk.readandcloud_api:disk.write(orcloud_api:disk.app_folderinstead ofdisk.writeif you want to scope it to an app-specific folder rather than the whole disk).
- Save the app and note itsID.
- Visithttps://oauth.yandex.com/authorize?response_type=token&client_id=<your-app-id>in a browser, approve access — the token is shown directly on the redirected page.
- silo-mcp-accounts add yandex_disk --label personal, paste the token.

Runningaddonce per platform gets old fast when you're configuring several at once. Two faster paths — both still end up in the OS keyring, never in a file or env var this project persists itself:

Import file— copy the template, fill in real values, import in one shot.silo-accounts.example.yamlat the repo root has an entry for all nine platforms with the exact field names each one needs and a comment on where to get each value:

cp silo-accounts.example.yaml silo-accounts.yaml # edit silo-accounts.yaml with real values, delete platforms you don't use silo-mcp-accounts import silo-accounts.yaml

JSON works too (sameplatform -> label -> fieldsshape), dispatched by file extension — anything not ending in.jsonis parsed as YAML.

silo-accounts.yaml/.json,credentials.yaml/.json, and any.local.yaml/.jsonare already in.gitignore, but treat that as a backstop, not the plan —delete the file right after importing it.It's plaintext secrets on disk for as long as it exists, gitignored or not.

Env vars— for scripted or CI setup where a file isn't practical,addreadsSILO_MCP_CRED_<PLATFORM>_<FIELD>before prompting:

SILO_MCP_CRED_DROPBOX_ACCESS_TOKEN=sl.xxx silo-mcp-accounts add dropbox --label personal

Env vars are more exposure-prone than a file you delete (process listings, shell history, crash dumps) — prefer the import file for anything beyond quick automated test setup.

Once the server is connected, you don't call the tools directly — you ask your MCP client (Claude Desktop, Claude Code, etc.) in plain language and it picks the right tool and arguments. These examples were all exercised end-to-end over the real MCP protocol against live Dropbox, Google Drive, and Google Photos accounts.

Dropbox / OneDrive / Yandex Disk(real paths):

- "Uploadreport.pdffrom my uploads folder to Dropbox."
- "What's in the root of my Dropbox? Find anything namedinvoice."
- "Downloadnotes.txtfrom Dropbox and give me a shareable link to it."
- "Deleteold-draft.txtfrom my Dropbox."

Google Drive / Box(id-addressed — search first, then act on the id):

- "Findbudget.xlsxin my Google Drive." → then "Download that one."
- "Share that file with a public link."

- "Upload this screenshot to my Google Photos."

Object stores — S3 / GCS / Azure Blob(need a bucket/container):

- "Uploadbackup.zipto my S3 bucketmy-backups."
- "List everything underlogs/in bucketmy-backups."
- "Give me a 1-hour presigned link tobackup.zipinmy-backups."

Cross-cloud copy(one platform straight to another):

- "Copyreport.pdffrom my Dropbox to my Google Drive."
- "Migrate everything I just found in Drive over to my S3archivebucket."

- "Grab my headshot from Google Drive so I can attach it to a post." —download_filelands a local path another MCP server can use.

The full mapping of phrasings to tool calls:

Because Google Drive and Box address files by id, a natural flow there is two steps — "findbudget.xlsxin my Drive" (search_files, returns the id), then "download that one" / "share that one" using the id the client just saw. The client handles that chaining for you.

Mutating actions (upload_file,delete_file,create_share_link) requireconfirm=true, so a good client will show you exactly what it's about to do and only proceed once you approve — a delete or a public link never happens silently from a vague request.

targetis the bucket/container name for object stores — ignored for document stores.

expires_in_secondsis honored natively on S3, GCS, Azure Blob, and Box (presigned/signed URLs, or Box'sunshared_at). Dropbox, Google Drive, OneDrive, and Yandex Disk create a link too, but none of their APIs support expiry on a personal/non-Business account, so the parameter is accepted for interface consistency but not enforced there. Not available on Google Photos (upload-only).

Gated behindconfirm=trueeven though it doesn't move or delete data — the returned URL is itself a bearer credential, the same exfiltration concernupload_filehas.

copy_filemoves a filedirectly from one platform to another— "copy my Dropbox/photos/id.pngto my S3backupsbucket", "migrate this Drive file to OneDrive" — in a single tool call. This is the one thing a single-vendor storage integration structurally can't do; it's the payoff of putting every backend behind one interface.

sequenceDiagram actor User participant Client as MCP Client<br/>(Claude) participant Silo as Silo MCP participant Src as Source cloud<br/>(Dropbox) participant Dst as Destination cloud<br/>(Google Drive) User->>Client: "Copy report.pdf from Dropbox to my Google Drive" Client->>Silo: copy_file(from=dropbox, to=google_drive, confirm=false) Silo-->>Client: dry run — will copy /report.pdf → google_drive:report.pdf Client-->>User: About to copy Dropbox → Google Drive. Approve? User->>Client: yes Client->>Silo: copy_file(…, confirm=true) Note over Silo: stream through a temp file<br/>the server controls Silo->>Src: download /report.pdf Src-->>Silo: bytes → temp file on disk Silo->>Dst: upload from temp file Dst-->>Silo: new file id + metadata Note over Silo: delete temp file,<br/>log 'copy' to the audit trail Silo-->>Client: copied ✓ Client-->>User: Done — report.pdf is now in your Google Drive

The LLM never touches the file bytes or an intermediate path — it just issues onecopy_filecall and the server handles the download → temp → upload → cleanup, gated on your approval.

- It streams through a temporary local file the server creates and deletes — you never handle an intermediate download/upload, and the temp path is never a caller-supplied argument (so it isn't subject to the upload-root containment check the wayupload_fileis).
- from_remote_pathis addressed however thesourceplatform expects for a download (a path for Dropbox/OneDrive/Yandex/object stores; a fileidfor Google Drive/Box — search first to get it).to_remote_pathdefaults to the source's basename; pass it explicitly when the source is id-addressed.
- from_target/to_targetare the bucket/container names when either end is an object store.
- Requiresconfirm=true— it writes to the destination. Recorded in the audit log as acopy, with both ends captured.

[!NOTE] The copy is bounded by the same 5 GiB download/upload caps, and the source download is streamed to disk, so a large cross-cloud copy won't buffer the whole file in memory.

Which operations each platform supports, and its authentication model. ✅ supported · — not supported.target= the bucket/container name object stores require.

[!NOTE] Google Photos isupload-only— Google removed read-library API access in March 2025. Object stores and Yandex Disk have no content search, only prefix/path listing. Details inPlatform-specific notes.

Setup friction and credential shape per platform

Dropbox, Yandex Disk, S3, GCS, and Azure Blob use static credentials read once. Google Drive, OneDrive, Box, and Google Photos are all Big-Tech OAuth2 with short-lived (~1h) access tokens — this server calls each platform's token endpoint fresh before every API request rather than caching, keeping the correctness story simple at the cost of one extra fast (~200ms) request per call.

The credential fields above only get you a token — the token also needs the right scope, or every call fails with a permissions error regardless of how correctly it's stored. Configured on the platform's side, not here:

[!TIP] Dropbox is the one most likely to bite you: amissing_scope401 with an otherwise-correct token almost always means the scopes were changedafter*the token was generated. Regenerate the token, don't just re-save it.

local_pathfor uploads and the destination filename for downloads are both LLM-supplied tool-call arguments, so both are confined to one configured root each, in both directions:

- SILO_MCP_UPLOAD_ROOT(default~/silo-mcp/uploads) — files outside this directory can't be uploaded. This matters a lot here: without it, a manipulated conversation could ask the server to upload an arbitrary local file (SSH keys,.env, browser credential stores) to a cloud account — persistent, shareable, no visible platform artifact warning anyone something left the machine.
- SILO_MCP_DOWNLOAD_ROOT(default~/silo-mcp/downloads) — downloads can't be written outside this directory via a crafted filename.

Uploads are capped at 5 GiB (MAX_UPLOAD_BYTES) and downloads at 5 GiB (SILO_MCP_MAX_DOWNLOAD_BYTES, overridable). Downloads are streamed to disk and aborted mid-flight if they run over the cap, so an unexpectedly huge file can't exhaust memory. Credentials are stored in the OS keyring, never in the SQLite DB; the one-time OAuth authorization flow uses PKCE and a randomstatevalue (RFC 8252) so the loopback redirect can't be forged.

- Google Photos is upload-only.Google removed the Photos Library API's read-library scopes in March 2025 — an app can now only manage media items it created itself. Browsing or downloading a user's existing library needs the interactive "Picker API" (a web UI session), which a headless MCP tool call can't drive.list_files/download_file/search_filesall return a clear error rather than pretending to work.There is also no delete capability at all for Google Photos, in this project or Google's API— anything uploaded is permanent until removed manually via photos.google.com.
- Google Drive and Box address files by id, not path, for download/delete —upload'sremote_pathis used as a filename (Drive root / Box root folder only, no folder targeting in v1); to download/delete you need the id fromlist_files/search_filesfirst. OneDrive, Dropbox, and Yandex Disk use real paths throughout, no asymmetry.
- Object stores don't support search— only prefix listing vialist_files.search_filesreturns a clear "not supported" error. Yandex Disk has no dedicated search endpoint either and behaves the same way (path listing vialist_filesonly).
- Upload endpoints here are all single-shot (Dropbox ≤150MB, OneDrive ≤4MB); large-file chunked/resumable upload isn't implemented for any platform yet.

upload_file,delete_file, andcreate_share_linkall requireconfirm=true— they either mutate remote state or hand out a bearer-credential link.download_file/list_files/search_filesdon't gate, since they only write locally.

Dropbox, Google Drive, and Google Photos have been verified end-to-endover the actual MCP protocol— an MCP client spawns the server, does tool discovery, and calls the tools by name, exactly as Claude Desktop/ Code would — not just via direct Python calls.

Issues and PRs reporting real-account testing results for the remaining platforms are very welcome.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.