Skip to main content
Glama

local-code-mcp

A security-first Model Context Protocol (MCP) server for inspecting and editing multiple local repositories over STDIO. Repository roots are configured on the server and exposed to clients only through explicit project aliases.

Tiếng Việt · WSL + ChatGPT Secure MCP Tunnel runbook

Design goals

  • No arbitrary shell tool.

  • No client-supplied repository roots.

  • No mutable “current project” state.

  • Independent path sandboxing for every configured project.

  • Read-only Git access.

  • Atomic text-file writes with secret and path protections.

  • Project-specific, allowlisted test commands executed with spawn() and shell: false.

Related MCP server: local-code-mcp

Requirements

  • Node.js 18 or newer

  • npm

  • Git for the Git tools

  • Optional: ripgrep for faster search_code; the server has a built-in literal-search fallback when rg is unavailable

End-to-end flow

From a clean checkout to a client that can call tools:

Step

Command

Result

1. Install and verify

npm install && npm run typecheck && npm test && npm run build

dist/index.js exists

2. Configure roots

copy start-mcp.example.sh to start-mcp.sh, edit PROJECTS_CONFIG

aliases mapped to absolute roots

3. Smoke-test locally

./start-mcp.sh

Configured projects: … on stderr, then it waits for JSON-RPC; stop with Ctrl+C

4a. Use from Codex CLI

codex mcp add local-code -- /absolute/path/to/start-mcp.sh

tools available in Codex

4b. Use from ChatGPT

tunnel-client run --profile <profile>

tools available in a developer-mode app — full setup in the Secure MCP Tunnel runbook

5. Verify from the client

list_projectslist_fileswrite_file to a throwaway path, then delete it

reads and writes reach the right repository

6. After changing this server

npm run typecheck && npm test && npm run build, then restart whatever spawns dist/index.js

clients pick up the new build

Step 6 matters: tunnel-client and Codex spawn dist/index.js once and keep that process, so a rebuild alone changes nothing until the parent is restarted.

For ChatGPT specifically, the runbook covers the parts this README does not: installing tunnel-client, creating the tunnel and runtime key, initializing and running the profile, the developer-mode app, verification, and troubleshooting.

Install and verify

npm install
npm run typecheck
npm test
npm run build

The build output is dist/. The server entry point is dist/index.js.

Configure projects

Set PROJECTS_CONFIG to a non-empty JSON object whose keys are aliases and whose values are absolute repository paths:

export PROJECTS_CONFIG='{
  "project-a": "/home/example/projects/project-a",
  "project-b": "/home/example/projects/project-b",
  "project-c": "/home/example/projects/project-c"
}'

At startup, the server:

  • accepts aliases matching [a-zA-Z0-9_-]+;

  • requires every root to be an existing absolute directory;

  • canonicalizes every root with realpath;

  • rejects duplicate canonical roots; and

  • exits before accepting requests if the configuration is missing or invalid.

Only aliases are logged or returned to clients. Clients cannot submit raw repository roots.

Run

node dist/index.js

This is a STDIO server. It normally waits silently for JSON-RPC input; operational logs go to stderr, while stdout is reserved for MCP traffic.

For Codex CLI, copy start-mcp.example.sh to an untracked start-mcp.sh, replace the example paths, make it executable, and register it:

chmod 0755 /absolute/path/to/local-code-mcp/start-mcp.sh
codex mcp add local-code -- /absolute/path/to/local-code-mcp/start-mcp.sh
codex mcp list

To connect this private STDIO server to ChatGPT, follow the Secure MCP Tunnel runbook. In short: tunnel-client init --profile <profile> …, tunnel-client doctor --profile <profile> --explain, then keep tunnel-client run --profile <profile> in the foreground while ChatGPT discovers and calls tools.

After changing server code

npm run typecheck && npm test && npm run build

Then restart the process that spawns dist/index.jstunnel-client run (Ctrl+C, start it again) or the Codex session. Until that restart, clients keep talking to the previously loaded build.

Tool reference

Except for list_projects, every tool requires a project alias.

Tool

Inputs

Behavior and limits

list_projects

none

Returns configured aliases only.

list_files

project, optional path, optional max_depth

Lists from path (default .). Depth defaults to 4 and is capped at 20. Output is capped at 10,000 entries. Skips symlinks and every denylisted path, so blocked names are not disclosed.

read_file

project, path, optional start_line, optional end_line

Reads UTF-8 text with numbered lines. Rejects binary files and files larger than 2,000,000 bytes. Response text is capped at 500,000 characters.

search_code

project, query, optional path, optional file_pattern, optional max_results

Performs a literal search from path (default .). query and file_pattern are limited to 500 characters and cannot contain NUL or newlines. The requested match limit defaults to 100 and is capped at 1,000, and means the same total number of matching lines on both the rg path and the built-in fallback. Hits are reported relative to the project root, and denylisted files are excluded on both paths. Response text is capped at 500,000 characters.

write_file

project, path, content

Creates or replaces one UTF-8 text file, up to 2,000,000 bytes, using a same-directory temporary file and atomic rename. Replaced files keep their previous mode and ownership; new files are created 0644. The parent directory must already exist.

git_status

project

Runs git status --short in the selected root.

git_diff

project, optional staged

Runs git diff, or git diff --staged when staged is true.

git_log

project, optional limit

Runs git log --oneline; the default limit is 20 and the maximum is 50.

run_tests

project, test, optional args

Runs only a server-defined command for the selected alias. Timeout: 120 seconds. Combined stdout/stderr capture is capped independently at 500,000 characters.

Test allowlist

The allowlist is the TEST_COMMANDS map in src/git-tools.ts. Each entry is a fixed argv, never a shell string, and deployments edit the map for their own aliases:

Project alias

Test name

Command

project-a

unit

deployment-defined argv

project-b

unit

deployment-defined argv

project-c

test

deployment-defined argv

Other aliases can use repository and Git tools, but run_tests returns COMMAND_NOT_ALLOWED until a command is explicitly added for that alias. Test arguments are passed as an argv array, limited to 20 strings, and reject shell metacharacters, newlines, absolute paths, and .. path components.

Security model

Path containment

Every repository path is resolved relative to the selected canonical root. Lexical traversal, absolute paths outside the root, and symlink escapes are rejected with PATH_OUTSIDE_PROJECT_ROOT. Directory listings do not follow symlinks.

Blocked content

Direct paths passed to read_file and write_file reject these names anywhere in the relative path:

  • directories: .git, .ssh, .aws, .gnupg, node_modules, and vendor;

  • files: .env, .env.*, *.pem, *.key, *.p12, *.pfx, id_rsa, id_ed25519, and authorized_keys.

.git is blocked in full rather than only .git/objects: git executes the values of core.fsmonitor, core.pager, and aliases from .git/config, and runs .git/hooks/*, so a writable .git would turn this server's own Git tools into arbitrary command execution.

The denylist is applied by read_file, write_file, list_files, and both search_code implementations, so blocked files are neither read, written, listed, nor matched. It is still a last line of defense, not a substitute for choosing narrow project roots and running the process with least privilege.

Commands and Git

All child processes use spawn(command, args, { shell: false }) with a fixed command and argument structure. There is no generic command-execution tool. Git exposure is intentionally limited to status, diff, and log; the server does not expose commit, push, reset, clean, checkout, merge, or rebase operations.

Child processes do not inherit the server environment. They receive only PATH, HOME, LANG, LC_ALL, TZ, TERM, USER, and LOGNAME, so PROJECTS_CONFIG and any tunnel credentials stay in the server process. A deployment that needs another variable (for example COMPOSER_HOME) lists its name in MCP_CHILD_ENV_ALLOWLIST, which can never re-add PROJECTS_CONFIG.

Writes

write_file validates the destination before writing, refuses blocked paths and symlink escapes, writes a mode-0600 temporary file in the destination directory, and atomically renames it into place. Because rename replaces the inode, the mode and ownership of the file being replaced are restored explicitly before the rename; otherwise an edit would narrow an existing 0644 repository file to 0600 and break the runtime user that serves it. New files are created 0644. The file and its directory are synced so the replacement survives power loss, not only a process crash. It does not create parent directories. Filesystem errors are mapped to safe error codes without returning stack traces.

Logging

Each tool call writes one JSON record to stderr containing timestamp, alias, tool, the requested path (or .), success status, and duration; failures also carry the returned code and an operator-only detail field. File contents, configured roots, and environment values are not included. Because rejected client input is still logged, operators must protect stderr logs appropriately.

Error codes

Expected failures include:

  • PROJECT_CONFIG_INVALID, PROJECT_ALIAS_INVALID, PROJECT_ROOT_INVALID

  • PROJECT_NOT_FOUND, INVALID_ARGUMENT, UNKNOWN_TOOL

  • PATH_OUTSIDE_PROJECT_ROOT, BLOCKED_FILE

  • FILE_TOO_LARGE, BINARY_FILE

  • COMMAND_NOT_ALLOWED, COMMAND_FAILED, COMMAND_TIMEOUT

  • WRITE_PERMISSION_DENIED, WRITE_FAILED

  • INTERNAL_ERROR for anything unmapped

MCP tool failures are returned with isError: true and a text payload in the form CODE: message. Only the codes above are ever returned: an unmapped failure becomes INTERNAL_ERROR: Tool execution failed, because a raw errno such as EACCES carries a Node message containing server-side absolute paths, and reporting an internal fault as INVALID_ARGUMENT sends the client into retrying arguments that were never wrong. Messages are redacted of configured roots and truncated to 2,000 characters; the full cause is written to stderr only.

Development

npm run typecheck
npm test
npm run build

npm run typecheck checks src/ and tests/ against tsconfig.json; npm run build emits dist/ from tsconfig.build.json.

Add or update tests whenever path handling, blocked content, command execution, writes, or project isolation changes. Keep Git access read-only and never add a generic shell interface.

Files

  • src/index.ts — MCP tool registration, dispatch, and client-safe error mapping

  • src/config.ts — project-alias configuration

  • src/security.ts — root containment, blocked-path policy, root redaction

  • src/fs-tools.tslist_files, read_file, search_code

  • src/git-tools.ts — read-only Git tools and the TEST_COMMANDS allowlist

  • src/write-file.ts — validated atomic writes

  • src/command.ts — bounded spawn() wrapper and child-environment allowlist

  • src/errors.ts — error-code allowlist and redaction

  • src/tool-input.ts, src/limits.ts — argument validation and shared bounds

  • src/logger.ts — structured stderr audit records

  • tests/ — security, write, filesystem-tool, error-mapping, command, and isolation tests

  • start-mcp.example.sh — local launcher template

Handling secrets

Never commit API keys, .env files, tunnel credentials, local tunnel profiles, private certificates, or a machine-specific start-mcp.sh. Review repository changes before committing.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all related MCP servers

Related MCP Connectors

  • A MCP server built for developers enabling Git based project management with project and personal…

  • An MCP server for deep research or task groups

  • An MCP server that gives your AI access to the source code and docs of all public github repos

View all MCP Connectors

Latest Blog Posts

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/jutlyne/codex-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server