personal-mcp-bridge
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@personal-mcp-bridgeread welcome.md from my notes directory"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
personal-mcp-bridge
A minimal, read-only bridge that lets an MCP client (or any HTTP caller) browse a few allowlisted local directories safely. Every tool is read-only; there is no write, edit, delete, or execute path anywhere in the surface.
Tools are available through the MCP endpoint POST /mcp, the localhost-only
compatibility dispatcher POST /call, and dedicated HTTP endpoints where noted:
list_roots/GET /roots- list the directories you allowlisted, by aliaslist_files/GET /files- list one allowlisted directoryread_file/GET /read- read one bounded file inside an allowlisted rootread_file_range/GET /read-range- read a bounded line rangetail_file/GET /tail- read the end of a bounded fileread_multiple_files- read several bounded files through MCP or/callget_file_info/GET /file-info- inspect one allowlisted path's metadatasearch/GET /search- search text files under an allowlisted root
It is intentionally small.
read_file, read_multiple_files, and search also understand
PDF/DOCX/PPTX/XLSX, citation-prefixed (e.g. [p.3], [Sheet1!2-5]), when you
install the optional documents extra (see below). Still read-only, and it
degrades gracefully: without the extra, those formats are simply not there.
Read output is character-bounded: read_file/search accept an optional
max_chars (capped by BRIDGE_READ_MAX_CHARS) and report the applied limits.
What this is not
This is not a full personal automation layer. It does not run agents, write files, run shell commands, drive a browser, or keep any memory, audit, or cache database. It is an alpha, read-only file bridge with safe defaults. If you came looking for a do-everything assistant runtime, this is the deliberately boring, auditable subset.
Related MCP server: Local Files MCP Server
Safety model in one paragraph
Fail closed. With no roots configured, every request is blocked. Paths are
relative-only with no traversal, no drive letters, and no symlink escapes.
Common secret, runtime, cache, and generated paths (.env, token/secret/
credential-like files, runtime/settings.json, .git, virtualenvs,
node_modules, caches) are excluded from listing, reads, and search. In
public/tunnel mode the bridge refuses to start without a strong token, refuses
tokens passed in the URL, refuses the generic /call endpoint for any
forwarded/remote request, and never emits an absolute local path. See
SECURITY.md and THREAT_MODEL.md.
Install
Requires Python 3.10+.
python -m pip install -e .
# or, just install the runtime deps:
python -m pip install starlette uvicornTo also read PDF/DOCX/PPTX/XLSX files, install the optional documents
extra (pypdf, python-docx, python-pptx, openpyxl). The base install stays
tiny without it:
python -m pip install -e ".[documents]"Run the demo (no real files touched)
The demo allowlists only the bundled synthetic demo/mock_data directory and
exercises all three tools in-process:
python demo/run_demo.pyYou will see list_roots, a file read, a search hit on mock data, and a
traversal attempt being refused.
Run the server against your own files
Allowlist one or more directories, then start the bridge on loopback:
export BRIDGE_ROOTS="notes=/path/to/notes;docs=/path/to/work/docs"
python -m personal_mcp_bridge
# serving on http://127.0.0.1:8787Then:
curl http://127.0.0.1:8787/roots
curl "http://127.0.0.1:8787/files?root=notes&path=."
curl "http://127.0.0.1:8787/read?root=notes&path=welcome.md&max_chars=4000"
curl "http://127.0.0.1:8787/read-range?root=notes&path=welcome.md&start_line=1&line_count=20"
curl "http://127.0.0.1:8787/tail?root=notes&path=welcome.md&last_lines=20"
curl "http://127.0.0.1:8787/file-info?root=notes&path=welcome.md"
curl "http://127.0.0.1:8787/search?root=notes&q=budget&max_chars=4000"On loopback with no token set, local calls are allowed for convenience. To
require a token even locally, set BRIDGE_TOKEN and send
Authorization: Bearer <token>.
Runtime limits
Read output uses character caps for schema-facing text; request bodies and search-file scanning use byte caps. Restart the bridge and reconnect/re-register the MCP connector after changing any of these, since some clients cache schemas.
Variable | Default | Purpose |
|
| Maximum returned characters for read/search tools. |
|
| Largest file searched/read by helpers that scan full files. |
|
| Maximum search matches before truncation. |
|
| Maximum accepted JSON request body size. |
|
| Largest PDF/DOCX/PPTX/XLSX accepted for parsing. |
|
| Sheets read per workbook. |
|
| Rows read per sheet. |
|
| Columns read per row. |
|
| Maximum entries returned by |
|
| Per-peer HTTP rate limit. In public/tunnel mode a finite default (600/min) applies if unset; setting it to |
|
| OOXML preflight: maximum member count. |
|
| OOXML preflight: maximum total uncompressed size. |
|
| OOXML preflight: maximum single-member uncompressed size. |
|
| OOXML preflight: maximum compression ratio per member. |
Compatibility aliases BRIDGE_MAX_READ_CHARS, BRIDGE_MAX_READ_BYTES, and
BRIDGE_MAX_FILE_BYTES are still accepted for existing setups.
read_file streams only the bytes it will return, and read_file_range and
tail_file stream a bounded window from a fixed-size chunk reader, so they
work on files of any size, including files that are one enormous line (or have
no newlines at all), without scaling peak memory to the file size. Requests
bodies are streamed and bounded too: consumption stops with 413 the moment
more than BRIDGE_HTTP_MAX_BODY_BYTES has been received, and Content-Length
is not trusted as the only enforcement. Documents are the exception at the
parse layer: their third-party libraries read whole files, so oversized ones
are refused before parsing (BRIDGE_DOC_MAX_FILE_BYTES).
HTTP and error behavior
POST /mcpreturns200for JSON-RPC responses and202 Acceptedwith no body for valid notifications (a well-formed envelope with noid), as the MCP Streamable HTTP transport requires. A malformed object is not a notification: it receives a JSON-RPC error response (-32600/-32602), and an empty HTTP body returns400 invalid-json. Anotifications/*method carrying anidis an error (-32600), never a silent 202.Error responses use stable codes, grouped into categories, rather than raw exception text:
Tool execution (including
read_multiple_filesper-file results):path-excluded,file-not-found,not-a-directory,invalid-argument,permission-denied,unknown-root,archive-rejected,internal-error.HTTP transport / input:
invalid-json,invalid-content-length,request-body-too-large,json-object-required,unexpected-json-rpc-response.Authentication / exposure:
token-in-url-refused,invalid-token,host-not-allowed,origin-not-allowed,generic /call is localhost-only.Rate limit:
rate-limited(HTTP 429 withRetry-After).POST /callusesinvalid-argumentfor invalid envelopes andunknown-toolfor unknown tools. Unexpected failures are logged with the exception type, stack frames, and an incident id (never the raw exception message, which may embed tokens or paths) and surfaced only asinternal-error, so an OS-level exception can never leak an absolute local path.
JSON bodies are parsed strictly:
NaN/Infinity/-Infinityare rejected and parser errors map to400 invalid-json(never an unhandled 500). Bodies accumulate into one bounded buffer, so a many-chunk request stays close to the configured limit in memory. A JSON-RPC response object arriving onPOST /mcp(this server never issues server-to-client requests) is rejected with400 unexpected-json-rpc-responserather than a misleading-32600with HTTP 200.Filesystem names that Python decodes with
surrogateescape(a POSIX filename with undecodable bytes, e.g.b"bad-\xff.txt"->"bad-\udcff.txt") are never surfaced in JSON, because a lone surrogate cannot be encoded to strict UTF-8. Discovery (list_files,search) skips such entries so one undecodable filename does not break a listing; direct reads of such a path return the stablepath-excludederror; andINCLUDE_LOCAL_PATHS=1debug output omits any unsafe local path. The sametext_safetyhelper backs the incoming-JSON lone-surrogate rejection.Tool arguments are validated server-side against each tool's schema, and
POST /calluses the same exact-type validation, including its envelope:toolmust be a non-empty string andargsmust be an object when present — nostr()coercion and no collapsing of falsyargs. MCPtools/call.argumentsis optional (omitted means{}); falsy values are rejected. Missing required properties, unknown properties, wrong types (a string is not a boolean, a boolean is not an integer), out-of-range integers (e.g.start_lineabove1000000), over-length strings (paths capped at 1024 chars), and bad array items are refused with aninvalid-paramserror. A tool that runs and fails (missing file, excluded path, unknown root) stays a normal MCP result withisError: true.read_multiple_filestreatsmax_charsas an aggregate caller-visible payload-string budget, not a serialized JSON or HTTP response cap. It counts the requested path, the generated reference, the error code, and the file content as Python Unicode characters, plus a fixed conservative per-item allowance for structure. Once exhausted, remaining caller-supplied paths are not emitted and a top-leveltruncatedflag is set (using the budget exactly with nothing omitted is not truncation). The response reportslimits.max_payload_chars/limits.used_payload_chars.The MCP
initializehandshake validates its parameters (protocolVersionnon-empty string,capabilitiesobject,clientInfoobject with non-emptyname/version) and rejects malformed requests with-32602. A supported requested protocol version is echoed back; an unsupported one is answered with the server's own selected version (2025-06-18) rather than claimed. This in-protocol negotiation is distinct from the HTTPMCP-Protocol-Versionheader check, which refuses the request outright with400.Requests over the limit return
429withrate-limitedand aRetry-Afterheader. The limiter is keyed by socket peer address only and never trustsX-Forwarded-FororForwarded.GET /healthreports bothconfigured_rate_limit_per_minuteandeffective_rate_limit_per_minute(in public mode an unset variable still applies the finite default).
Browser access control
Loopback binding is not a boundary on its own: a page you merely visit can aim a
fetch() at 127.0.0.1. So every request must carry a Host that names this
machine, and any Origin present must be loopback or explicitly allowed.
Requests that fail return 403 with host-not-allowed or origin-not-allowed.
Variable | Default | Purpose |
| (none) | Comma-separated exact origins, e.g. |
| (none) | Comma-separated hostnames, e.g. |
curl and most MCP clients send no Origin, and those requests are accepted;
token auth still applies. If you reach the bridge by any name other than
localhost, 127.0.0.1, or ::1, add it to BRIDGE_ALLOWED_HOSTS.
BRIDGE_ALLOWED_ORIGINS is an anti-rebinding allowlist, not CORS. The
bridge never emits Access-Control-Allow-Origin and does not handle OPTIONS
preflight, so a browser-based cross-origin application cannot read responses
from the bridge. That is deliberate: this is a file bridge for MCP clients and
curl, not a public web API, and there is no wildcard CORS anywhere. To reach
the bridge from a browser extension or same-origin web app, proxy it server-side
from your own origin.
Excluded paths
The bridge skips common secret, runtime, generated-output, dependency, and cache paths during listing and search, and direct reads are refused for those paths. This includes:
.envand any.env.*variant, plus.envrctoken/secret/credential-prefixed filenames
SSH private keys (
id_rsa,id_ed25519, ...) and the.sshdirectorycredential stores:
.netrc,.git-credentials,.npmrc,.pypirc,kubeconfig,service-account.json, and the.aws,.kube,.docker,.gnupgdirectorieskey material and keystores by extension:
.pem,.key,.crt,.p12,.pfx,.jks,.keystore,.kdbx,.p8,.der,.csr,.ppk,.asc,.gpgdatabases and backups:
.db,.sqlite,.sqlite3,.bak,.backupruntime/settings.json,.git, virtualenvs,node_modules, common caches, and generated output folders
Two things worth knowing about how the matching works. The prefix rules are
blunt on purpose: a file named tokenizer.py or tokens.css is hidden because
it starts with token. Renaming it or scoping the root more narrowly is the fix;
the bridge prefers hiding a harmless file over exposing a credential. And .log
files are readable, since tailing a log is a normal reason to use this bridge.
This list is a safety net, not a guarantee. It cannot know what a secret looks like in your filesystem. Do not allowlist a directory if you would not be comfortable with an authorized client reading the ordinary text files inside it.
Exposing it beyond localhost
Don't, unless you mean it. If you put this behind a tunnel, set
BRIDGE_PUBLIC_MODE=1 and a strong BRIDGE_TOKEN (>=32 chars). The bridge will
refuse to start otherwise. Even then, only the dedicated read-only endpoints are
remote-reachable; the generic /call dispatch stays localhost-only.
Status
Alpha. Read-only. Expect rough edges. Issues and PRs welcome.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceEnables file system operations such as listing, reading, and creating files within a scoped local project directory. It provides a secure way to manage local files through standardized MCP tools built with FastMCP.
- FlicenseAqualityDmaintenanceProvides safe local file operations through MCP, including reading, writing, searching, organizing, and protected deletion with configurable path restrictions.122
- FlicenseAqualityDmaintenanceEnables listing, reading, and searching local files and directories through MCP tools.4
- Flicense-qualityDmaintenanceEnables accessing and managing files from configured folders with filtering and size limits, allowing listing, reading, and searching files via MCP tools and resources.
Related MCP Connectors
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
Scans MCP servers for tool poisoning, prompt injection and supply chain risks.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/wva2ccyk-prog/personal-mcp-bridge'
If you have feedback or need assistance with the MCP directory API, please join our Discord server