A Rust-based MCP server that exposes local machine capabilities — file I/O, shell execution, system stats, and macOS notification triage — to AI clients over stdin/stdout. Features namespaced tool APIs, path traversal guards, strict command validation, symlink protection, and integration tests. Built with Tokio, Serde, and rusqlite.
LocalOps MCP Server
localops_mcp_server is a small Rust-based MCP server that communicates over stdin/stdout using JSON-RPC style messages. It exposes a set of local machine utilities for file access, shell command execution, system stats, macOS notification reading, simple file editing helpers, text diffing, Java rule lookup, and port cleanup.
The server is designed to be launched as a long-running process by an MCP-compatible client. It reads one JSON request per line from standard input and writes one JSON response per line to standard output.
What This Project Does
At runtime, the binary:
- Starts an MCP-style request loop on
stdin. - Accepts methods such as
initialize,tools/list, andtools/call. - Dispatches
tools/callrequests to locally implemented Rust functions. - Returns tool results as JSON text content.
The current toolset includes:
| Tool | Purpose |
| --- | --- |
| localops.system.stats | Returns current CPU and memory usage using sysinfo. |
| localops.files.find_by_extension | Recursively finds files by extension under a path. |
| localops.files.read | Reads a local file, with a 1 MB size cap. |
| localops.files.write | Writes or overwrites a file with supplied content. |
| localops.shell.run | Runs a zsh -c shell command and returns exit code, stdout, and stderr. |
| localops.shell.stream | Runs a command and streams stdout to stderr in real time. |
| localops.notifications.read | Reads recent macOS notifications and formats them as readable text. |
| localops.notifications.triage | Reads recent macOS notifications and returns structured JSON text for downstream triage. |
| localops.files.insert_after_pattern | Inserts a line after the first line matching an anchor pattern. |
| localops.files.insert_after_line | Inserts a line after a specific 1-based line number. |
| localops.files.delete_lines_containing | Removes lines containing a matching string. |
| localops.files.delete_line | Deletes a specific 1-based line number. |
| localops.text.diff_words | Produces a word-level diff between two long strings. |
| localops.repo.find_java_rule_source | Searches a repo for a Java file matching a rule name. |
| localops.net.clear_port | Kills a process bound to a port using lsof and kill -9. |
Project Structure
.
├── Cargo.toml
└── src
├── main.rs
├── mcp_types.rs
└── tools.rs
src/main.rs: MCP request loop and tool dispatch.src/mcp_types.rs: request and response-related data types.src/tools.rs: implementations for the server’s local tools.
Requirements
General
- Rust toolchain with
cargo zshavailable on the host machine
macOS-specific features
Some tools are macOS-specific:
read_notificationsget_notifications_for_triage
These tools read Apple’s local notifications database at:
~/Library/Group Containers/group.com.apple.usernoted/db2/db
They may require Full Disk Access depending on your macOS privacy settings.
External command dependencies
Some tools assume the following commands exist on the host:
zshlsofkill
Installation
Clone the repo and build it:
cargo build
Or run it directly without a separate build step:
cargo run
How To Run
This project is not an HTTP server. It is a line-oriented MCP process intended to be started by another tool or client.
Start the server
cargo run
After startup, the process waits for JSON messages on standard input.
Build a release binary
cargo build --release
The binary will be available at:
target/release/localops_mcp_server
You can then run it directly:
./target/release/localops_mcp_server
Example MCP Session
Send one JSON object per line.
Initialize
Input:
{"jsonrpc":"2.0","id":1,"method":"initialize"}
Expected response:
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{},"protocolVersion":"2024-11-05","serverInfo":{"name":"localops_mcp_server","version":"1.0.0"}}}
List tools
Input:
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
The server returns the available tool definitions and input schemas.
Call a tool
Input:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"localops.system.stats","arguments":{}}}
Example response shape:
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\"cpu_usage\":5.4,\"memory_used_gb\":12,\"memory_total_gb\":36}"}]}}
Tool Usage Examples
Find Rust files
{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"localops.files.find_by_extension","arguments":{"path":"./src","extension":"rs"}}}
Read a file
{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"localops.files.read","arguments":{"path":"Cargo.toml"}}}
Run a shell command
{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"localops.shell.run","arguments":{"command":"ls -la"}}}
Insert a line after a matching anchor
{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"localops.files.insert_after_pattern","arguments":{"path":"app.yml","anchor":"monitors:","content":" - name: sample"}}}
Delete a line by pattern
{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"localops.files.delete_lines_containing","arguments":{"path":"app.log","pattern":"[DEBUG_MCP]"}}}
Clear a port
{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"localops.net.clear_port","arguments":{"port":8080}}}
Development Notes
Dependencies
The project currently uses:
tokiofor the async runtimeserdeandserde_jsonfor JSON serializationsysinfofor CPU and memory metricsrusqlitewith bundled SQLite for notification DB accessplistfor decoding Apple notification payloadssimilarfor word-level diff generation
Current behavior worth knowing
initializereturns protocol version2026-04-27.notifications/initializedis accepted and only logs to stderr.- Unknown tool names return a JSON-RPC
Tool not founderror. - Unknown methods return a JSON-RPC
Method not founderror when anidis present. read_fileenforces a 1 MB size limit.run_commanduses a simple keyword blacklist, not a full sandbox.run_command_streamstreams stdout to stderr and returns only final completion status in the MCP response.
Security, Privacy, and Safety Warnings (Read This)
This server exposes powerful local-machine capabilities to an MCP client. Treat it like granting a program local admin-style reach into your workstation.
Command execution tools: run_command, run_command_stream
Impact if misused:
- Data loss (deleting files, changing permissions)
- Credential theft (reading SSH keys, cloud credentials)
- Network exfiltration (uploading local files)
- Persistence (installing launch agents, altering shell config)
Where the risk is enforced:
src/main.rsdispatches totools::validate_command_strict(...)(strict checks).- These checks are not a sandbox. Assume a determined attacker can still find ways around simple filters.
What you should do:
- Prefer an allowlist model (only allow known-safe commands like
ls,cat,cargo build). - Run the server under a low-privilege user.
- Do not run it on machines that have production secrets unless you fully trust the client.
File tools: localops.files.*
Impact if misused:
- Reading secrets:
~/.ssh/*,~/.aws/*,.env, keychains, config files - Modifying system or app state:
~/Library/*,/etc/*, launch agents, shell rc files - Corrupting repositories or configs by deleting/inserting lines
Where the risk is enforced:
src/tools.rspath validation rejects traversal/metacharacters, symlinks, and common sensitive paths.write_fileis capped in size and refuses common executable/script extensions.
What you should do:
- Restrict operations to a single safe workspace directory (recommended: add an allowlisted root).
- Avoid granting Full Disk Access unless you explicitly want notification tooling.
macOS notifications / Apple private DB reads: localops.notifications.*
These tools read Apple’s local notifications SQLite database:
~/Library/Group Containers/group.com.apple.usernoted/db2/db
Impact:
- Notifications can contain sensitive content (2FA codes, password reset links, internal incident details, customer data).
- If your MCP client forwards tool outputs to an LLM or remote service, you may leak private data.
What you should do:
- Treat notification tools as high sensitivity.
- Disable these tools in production / shared environments.
- Avoid enabling Full Disk Access unless absolutely necessary.
Limitations and Caveats
- The server assumes a trusted local environment. Several tools can modify files or run shell commands.
- Safety checks exist, but they are not a security boundary.
- Notification-reading features are specific to macOS and Apple’s internal notification DB layout.
- The
get_system_statstool name is generic; the implementation usessysinfoAPIs and is not tied to any specific hardware. - Tests exist under
tests/(runcargo test).
Troubleshooting
Build warnings
The project currently compiles, but emits a few warnings for unused imports, unused fields, and unnecessary mut bindings. These warnings do not prevent the binary from running.
Notification tools fail
Check:
- macOS is the host OS
- the notifications database exists
- the process has permission to read it
Shell tools fail
Check:
zshis installed- the invoked command exists on the machine
- the process has permission to access the requested files or paths
Suggested Next Improvements
- Add automated tests for MCP request handling and tool functions.
- Add stronger command execution safeguards or an allowlist model.
- Add a sample MCP client configuration for Claude Desktop or another host.
- Add structured logging and clearer error messages.
- Clean up compile warnings.