Connecting Local Tools via MCP
Connect local files, scripts, and services while keeping the host machine protected.
Why local access is sensitive
A local MCP server can often reach files, environment variables, processes, network services, and credentials that a hosted model cannot see directly. That convenience changes the threat model: a confused model, malicious document, or compromised dependency may turn a harmless-looking request into local compromise. Treat every local capability as a privileged API. Begin with a written inventory of directories, commands, sockets, and network destinations, then remove access that the workflow does not require.
Sandbox the process
Run the server with a dedicated user, restricted filesystem permissions, and an isolated working directory. Containers, macOS sandboxing, or operating-system policy tools can reduce blast radius, but configuration must be tested rather than assumed. Keep credentials out of the process environment when possible, and never mount an entire home directory for convenience. If a tool needs network access, allowlist destinations and protocols. Isolation is valuable because validation bugs and dependency vulnerabilities are inevitable possibilities.
from pathlib import Path
ALLOWED_ROOT = Path("/srv/project-docs").resolve()
def read_document(requested: str) -> str:
path = (ALLOWED_ROOT / requested).resolve()
if ALLOWED_ROOT not in path.parents:
raise ValueError("path is outside the approved directory")
if not path.is_file():
raise FileNotFoundError(path.name)
return path.read_text(encoding="utf-8")[:20_000]Expose files safely
For a file tool, define approved roots and resolve every requested path to a canonical absolute path before access. Reject traversal, symbolic-link escapes, device files, hidden credential directories, and paths outside the allowlist. Decide whether reads may include binary data, how large a response can be, and whether the user must approve each file. Use stable resource identifiers instead of letting a model construct arbitrary paths. Return metadata and bounded content, with pagination for larger documents.
Expose scripts carefully
A generic execute command tool is usually too powerful because it turns model output into arbitrary code execution. Replace it with named operations that accept typed arguments and map to fixed commands or library functions. Validate arguments, environment, working directory, runtime, output size, and time limit. Drop privileges and disable shell interpretation where possible. If arbitrary execution is unavoidable for a controlled development environment, isolate it, require confirmation, record the exact command, and make the risk visible to the user.
Protect local services
Localhost is not a security boundary by itself. A tool that forwards requests to a local database, Docker socket, browser profile, or administrative endpoint may expose powerful capabilities. Use explicit host and port allowlists, authenticated connections, safe HTTP methods, and response limits. Prevent server-side request forgery by rejecting user-controlled URLs or resolving them against a fixed destination map. Separate read and write tools so approval and authorization can differ. Review local service logs as well.
Approval and user experience
The host should show users what local action is proposed, which resource it targets, and what arguments will be used before a mutation or process launch. Approval should bind to the exact operation and expire quickly; a broad “allow everything” switch defeats the point. Make read-only actions visible too when they involve sensitive directories. Explain failures without leaking paths or secrets. Good approval UX reduces accidental consent by making the consequence understandable rather than hiding it behind protocol details.
Audit and recover
Record a request identifier, authenticated principal, tool name, normalized target, approval result, duration, exit status, and affected object when available. Do not store complete file contents, tokens, or private command output by default. Keep backups and recovery procedures for tools that change local data, and test whether cancellation actually stops child processes. Review logs for repeated denied paths, unusual volume, and unexpected destinations. Local integrations are maintainable when you can reconstruct an incident without collecting more sensitive data than necessary.
Walkthrough: safe document reading
Resolve a requested document against an approved root, canonicalize it, verify that the resolved path remains inside that root, check the extension and size, and read only a bounded range. Return a document ID, title, byte range, and content as data. The model may ask for “the SSH key file,” but the tool should reject protected paths before opening anything. A symlink test is essential because a path that looks harmless can resolve outside the approved directory.
Trade-offs for local access
A local integration offers low latency and access to private tools, but its blast radius is larger than a hosted read-only API. Containers reduce exposure but can create a false sense of safety if sockets, mounts, or credentials remain broad. User confirmation helps with mutations but does not replace sandboxing or server authorization. Narrow named operations are less convenient than arbitrary scripts, yet they are far easier to review, rate-limit, and recover.
Practical exercise: local threat model
Choose a local file or script integration and list its approved roots, identities, network destinations, child processes, and secrets. Attempt path traversal, a symlink escape, an oversized read, a shell metacharacter, and a cancelled child process. Write the expected log entry and user-facing error for each attack. Finish by removing one permission that the first design did not actually need.
Sources and further reading
These primary or specialist references informed the concepts in this guide. Product details can change, so verify current documentation before implementation.