localmcp
by jihoo12
README.md
# localmcp
A local MCP server for AI coding workflows. It gives ChatGPT a constrained view of one project directory plus the tools needed to inspect, edit, test, commit, and push code.
## What it can do
localmcp exposes 16 tools:
- `workspace_info` — server, workspace, mode, and tool diagnostics
- `project_snapshot` — bootstrap a coding session with root files, a shallow tree, README content, git status, and diff statistics in one call
- `list_funcs` — discover hot-reloadable JavaScript functions from `functions/`
- `execute_func` — execute a discovered dynamic function by name and arguments
- `list_files` — list a directory
- `tree` — recursively inspect the workspace with depth and entry limits
- `search_files` — substring or regex search with globs and context lines
- `read_file` — read a whole file or a numbered line range
- `read_files` — batch-read up to 50 files/ranges in one MCP round trip
- `read_image` — return a workspace PNG, JPEG, GIF, or WebP as MCP image content for vision-capable clients/models
- `write_file` — create or replace a file
- `edit_file` — guarded exact-text replacement
- `apply_patch` — atomically apply multiple guarded edits to one file
- `delete_file` — delete a file
- `move_file` — move or rename a file
- `run_command` — execute a program directly without a shell
`project_snapshot` is intended as the first call in a coding session. It is language-agnostic: rather than interpreting a particular manifest or build system, it returns root files, a bounded `.gitignore`-aware tree, the root README (up to a configurable byte limit), and optional read-only git status plus diff statistics. Clients can use those facts to decide what to inspect next. It also returns structured content so clients do not need to parse the human-readable summary.
Dynamic functions live in `functions/*.js`. For security, `list_funcs` and `execute_func` are only registered in `yolo` mode because workspace files are writable and importing a workspace JavaScript module is equivalent to arbitrary code execution with the localmcp process permissions. Each module exports a default object with `name`, `description`, optional `inputSchema` metadata, and an async `run(args, context)` function. The included `functions/hello.js` is a minimal example.
Together these are enough for a coding-agent loop such as:
```text
inspect → search → read → edit → test → diff → commit → push
```
Workspace traversal respects the root `.gitignore` by default, and `.git` is always excluded. `tree` also accepts explicit exclude globs (for example `build*` or `.cache/**`) so generated trees can be omitted without changing `.gitignore`.
## Security model
All localmcp file tools are confined to `LOCALMCP_ROOT`. Absolute paths, `..` traversal, and symbolic-link escapes are rejected.
Command execution has two modes.
### Strict mode
`strict` is the default. `run_command` only accepts executables listed in `LOCALMCP_ALLOWED_COMMANDS`. Dynamic workspace JavaScript functions are disabled in this mode so writable files cannot bypass the command allowlist.
```bash
LOCALMCP_ROOT="$HOME/src/my-project" \
LOCALMCP_MODE=strict \
LOCALMCP_ALLOWED_COMMANDS="git,node,npm" \
npm start
```
With an empty allowlist, command execution is effectively disabled.
### YOLO mode
For a trusted coding workspace, `yolo` removes the executable allowlist:
```bash
LOCALMCP_ROOT="$HOME/src/my-project" \
LOCALMCP_MODE=yolo \
npm start
```
localmcp still requires the command `cwd` to resolve inside `LOCALMCP_ROOT`.
**YOLO mode is not an OS sandbox.** An executed program can still read or modify files outside the workspace, access the network, spawn other processes, or execute code with the permissions of the localmcp process. Use an OS/container sandbox if you need process-level isolation.
Commands use Node.js `spawn(..., { shell: false })`, so localmcp does not wrap command input in a shell. Child processes receive a minimal environment (`PATH`, home/temp/locale variables as applicable) instead of inheriting all server environment variables, reducing accidental secret leakage. `run_command` accepts an optional per-call `timeout_ms` (1–600000 ms), overriding `LOCALMCP_TIMEOUT_MS` for that invocation. Its MCP result includes structured `exit_code`, `signal`, `stdout`, `stderr`, `timed_out`, `truncated`, and `duration_ms` fields in addition to the human-readable text result.
## Requirements
- Node.js 18+
- Any command-line tools you want to execute must be installed and available on `PATH`
## Install
```bash
npm install
```
## Run
macOS/Linux:
```bash
LOCALMCP_ROOT="$HOME/src/my-project" npm start
```
PowerShell:
```powershell
$env:LOCALMCP_ROOT = "C:\src\my-project"
npm start
```
The default mode is `strict`.
The MCP endpoint is:
```text
http://127.0.0.1:8787/mcp
```
## Server-side tool logs
Every MCP tool call is logged to stderr with its tool name, status, duration, and summarized input:
```text
[localmcp] 2026-10-01T05:50:55.635Z tool=tree status=ok duration_ms=7.5 input={"path":".","max_depth":1,"max_entries":20,"respect_gitignore":true}
```
Large edit/file contents are summarized instead of being printed verbatim. These logs make it possible to follow the sequence of actions an AI client performs against the workspace.
## Configuration
- `PORT` — HTTP port (default `8787`)
- `LOCALMCP_ROOT` — exposed workspace root (default: current directory)
- `LOCALMCP_MODE` — execution mode: `strict` (default) or `yolo`
- `LOCALMCP_ALLOWED_COMMANDS` — comma-separated executable allowlist used by strict mode (default: empty)
- `LOCALMCP_TIMEOUT_MS` — default command timeout (default `120000`); individual `run_command` calls may override it with `timeout_ms`
- `LOCALMCP_MAX_OUTPUT` — maximum chars captured per stdout/stderr stream (default `200000`)
- `LOCALMCP_MAX_FILE` — maximum text file size in bytes (default `1000000`)
- `LOCALMCP_MAX_IMAGE` — maximum image size in bytes returned by `read_image` (default `10000000`)
- `LOCALMCP_APPROVAL_CODE` — optional fixed code entered on the local OAuth approval page; if omitted, a random code is generated and printed at startup
- `LOCALMCP_PUBLIC_URL` — public HTTPS origin used in OAuth metadata, for example `https://example.ngrok-free.app`; recommended whenever a tunnel or reverse proxy is used
- `LOCALMCP_ALLOWED_ORIGINS` — comma-separated exact browser origins permitted to call `/mcp` (default: none)
## Test
```bash
npm test
```
The test suite covers workspace path confinement, symlink escape rejection, command allowlisting, per-call command timeouts and truncation, gitignore/exclude-aware traversal, safe ranged reads, search, line slicing, guarded edits, and multi-edit patching.
## Test with MCP Inspector
```bash
npx @modelcontextprotocol/inspector@latest
```
Choose **Streamable HTTP** and connect to `http://127.0.0.1:8787/mcp`.
## Connect to ChatGPT
ChatGPT needs a reachable HTTPS `/mcp` URL. For development, expose port 8787 with a tunnel and use the resulting HTTPS URL ending in `/mcp`.
A public tunnel creates an entry point to your machine. Keep `LOCALMCP_ROOT` narrow and use `strict` mode. localmcp now exposes an OAuth 2.1 authorization-code flow with PKCE, protected-resource metadata, dynamic client registration, short-lived access tokens, and rotating refresh tokens.
When using a tunnel, pass its public origin explicitly so OAuth metadata and the MCP resource audience are stable:
```bash
localmcp "$HOME/src/my-project" \
--strict \
--allow git \
--public-url "https://your-tunnel.example"
```
At startup localmcp prints an **OAuth approval code**. In ChatGPT, create the MCP app using:
```text
https://your-tunnel.example/mcp
```
Choose OAuth. ChatGPT will open the localmcp authorization page in your browser. Enter the approval code printed by the server and choose **Allow**. ChatGPT then completes PKCE and stores its OAuth access/refresh tokens.
The OAuth endpoints are:
- `/.well-known/oauth-protected-resource`
- `/.well-known/oauth-authorization-server`
- `/oauth/register`
- `/oauth/authorize`
- `/oauth/token`
Use `--approval-code` or `LOCALMCP_APPROVAL_CODE` only when you intentionally want a stable approval code across restarts. This code is not an MCP bearer token; it is only used by the human authorization page.
Set `LOCALMCP_ALLOWED_ORIGINS` (or repeat `--origin`) only when browser-origin access to `/mcp` is required. OAuth discovery and token exchange do not require a CORS origin entry.
## Persistent OAuth sessions
localmcp persists OAuth client registrations, access tokens, refresh tokens, and the local approval code automatically. The default state file lives under your OS user state directory (for example `~/.local/state/localmcp/`) and is keyed by the localmcp instance identity, not the workspace. By default the public URL is the instance identity, so switching from one project directory to another keeps the same ChatGPT connection.
This means the normal flow is:
1. Connect ChatGPT and enter the approval code once.
2. Stop and restart localmcp whenever needed.
3. ChatGPT keeps using the existing connection and refreshes tokens without asking for the approval code again.
The state directory is created with owner-only permissions and the state file is written with mode `0600` on POSIX systems. Short-lived authorization codes are never persisted.
Set `LOCALMCP_OAUTH_STATE=/path/to/state.json` only if you need to override the default state location. Changing the workspace root does not change OAuth identity. Changing the public URL selects a different OAuth identity because tokens are bound to that protected resource. If you intentionally run multiple independent localmcp instances behind the same public URL, use `--instance-id <id>` or `LOCALMCP_INSTANCE_ID` to separate their stored credentials. On first startup after upgrading, localmcp automatically merges compatible legacy workspace-scoped OAuth state files for the same public URL into the new instance-scoped state.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues