Skip to main content
Glama
DaisukeDaisuke

local-mcp-chatgpt-tunnel

Local MCP ChatGPT Tunnel

Windows上で動作するstdio形式のMCPサーバーを、OpenAI公式のSecure MCP Tunnelを経由してChatGPT Developer Modeへ接続するためのローカルGatewayです。 複数のstdio MCPを1つに集約し、ツール名の名前空間化、公開ツールの除外、パス許可、直列実行、遅延起動を設定ファイルから制御できます。

ChatGPTからローカルstdio MCPへ接続する構成

インストール方法

[!IMPORTANT] Windows環境での導入手順はINSTALL.mdを参照してください。

Related MCP server: Windows Local MCP

セキュリティ警告

[!WARNING] 自分のWindows PC、自分自身のOpenAI Platform Organization、自分自身のChatGPT Workspaceでのみ使用する個人用ツールです。 任意コード実行機能を持つMCPを接続できるため、第三者への共有や公開用Pluginとしての運用は想定していません。

codexサンドボックスについて

[!IMPORTANT] 2026年8月11日のバージョンでは、codexサンドボックスを直接使用し、境界不整合や任意コード実行からPCを保護するメカニズムが実装されました。 一般的なシェルや任意の実行ファイルの選択は提供されませんが、固定のruntimeで既存スクリプトを実行するcodex-scriptが同梱され、これはelevatedまたはunelevatedサンドボックスが必須です。 その他の同梱MCPや外部のstdio MCPも、MCPごとにCodexサンドボックス内で起動できるため、可能な限りelevatedモードで境界を強化することを今後推奨します。

AIによる実装について

[!CAUTION] このリポジトリはChatGPT 5.6 Sol Highによって実装されました。 AI生成コードが含まれるため、誤りや脆弱性が残っている可能性があります。 実際の使用前にコードと設定内容を確認し、利用者自身の責任で使用してください。

何ができるか

  • ChatGPTからWindows上のstdio MCPサーバーを呼び出せる

  • 複数のMCPを1つのMCPにまとめ、<prefix>__<tool>形式のツール名としてまとめる

  • config/gateway.tomlに任意のstdio MCPを追加できる

  • MCPごとに許可するディレクトリとファイルを制限できる

  • 危険なツールを名前や部分文字列で非公開にできる

  • 並行実行させたくないMCPをserial_groupで直列化できる

  • 特定のMCP全体を必要に応じて無効化できる

  • 公開済みツールをフル識別子またはprefixで検索できる内蔵ディレクトリを必要に応じて公開できる

このリポジトリが行わないこと

  • OpenAI Responses APIやChat Completions APIの呼び出し

  • 独自AIエージェント、独自のハーネス、モデル料金計算の実装

  • 公開MCP URLやローカルの受信ポートの提供

  • Node.js、Git、ripgrep、Python、tunnel-client.exeの自動インストール

  • Ghidra MCP、Chrome DevTools MCP、DQ9 MCPなど開発者MCPの再配布

Secure MCP Tunnelへの接続は公式tunnel-client.exeが行います。このリポジトリは、その標準入出力へ接続するローカルMCP Gatewayと同梱MCPを提供します。

対応環境

現在の導入方法はWindows 11向けです。 実行にはNode.js LTSとOpenAI公式tunnel-client.exeを使用します。</。<br> 同梱のファイル検索にはripgrepを、GitHub Actionsの確認にはGitHub CLIを使用します。診断スクリプトはnodenpmghrgpy`を確認します。 macOSとLinux向けの導入手順、Docker構成、受信ポートを開く構成は用意していません。

使い始めるまで

使い始めるまでの手順は、INSTALL.mdにまとめています。 大まかな流れは次のとおりです。

  1. 必要なソフトウェアと公式tunnel-client.exeを手動で用意する

  2. config/gateway.example.tomlconfig/gateway.tomlにコピーし、絶対パスを書き換える

  3. OpenAI Platformで個人用Tunnelとruntime専用APIキーを作成する

  4. Tunnel IDとruntime APIキーをWindowsのユーザー環境変数に保存する

  5. 診断後にstart.cmdでTunnelを起動する

  6. ChatGPT Developer Modeで個人用Tunnelを選択する

設定や権限を推測して進めず、必ずINSTALL.mdを上から順に確認してください。

同梱MCP

MCP

公開ツールの例

用途

safe-files

list_files, search_text, file_info, read_text, write_text_file, replace_text, copy, move, apply_patch

許可されたWorkspace内のrgによるリスト取得、UTF-8検索、複数ファイル・行範囲読み取り、ファイル情報、ファイル読み書き、Workspace間のコピー・移動、パッチ適用を含む安全なファイル操作

safe-images

read_image

PNG、JPEG、WebPをChatGPTの画像コンテンツとして読み取る

safe-download

download_zip

許可されたソースを単一ファイルでもZIPとしてもChatGPTへ渡す

gitmcp

get_policy, branches, create_branch, checkout, list_worktrees, create_worktree, remove_worktree, status, diff, stage_paths, unstage_paths

許可されたリポジトリに対するローカルGit操作。commit / push / pull / cloneは含まない

git-capability

commit, push, pull, clone_repositoryのいずれか1つ + Workspace制御

--mode=ごとに1Gitcapabilityだけを独立したMCPプロセスとして公開し、sandboxの境界、署名、network権限を分離する

gh-workflow

list_runs, watch_run, cancel_run, view_run, view_run_jobs, view_failed_logs, list_workflows, view_workflow_yaml

明示的に許可されたGitHubリポジトリのActionsの実行状態の表示とrunのキャンセル

codex-script

roots, get_working_directory, set_working_directory, run_scriptまたはcheck_file

起動時に固定されたmjs / Node.js / Python / PHPのruntimeで、許可されたWorkspace内の既存スクリプトの実行または構文チェック。sandbox = "elevated"または"unelevated"が必須

internet

download_file

onlineworkspace境界内でHTTP/HTTPSから1ファイルを取得

archive

extract_archive, create_zip, create_7z

固定した7-Zipのみでアーカイブの作成と展開

codespace

list_codespacesrootsgit_rootsearch_textsshcopy_to_codespacestop_codespacelist_temporary_public_deploymentsopen_temporary_public_deploymentclose_temporary_public_deployment

既存のGitHub Codespacesだけを扱い、リモート検索・SSH・コピー・停止・一時的なpublic deploymentを実行。localhostやローカル待受の自動検出は行わず、Codespacesの新規作成ツールも持たない

同梱MCPは外部npm依存関係を持ちません。すべてのツールがoutputSchemaを宣言します。 Gatewayはisolated__createisolated__listisolated__closeを公開し、同梱MCPのすべてのツールに対して一意なisolatedIdを必須にします。isolated__createは1つ以上の絶対ディレクトリをworkspaces配列で受け取るほか、その分離がどのAI/sessionのどの作業に使うのかを示すpurposeも必須です。Gatewayは自動的にcreatedAtを記録し、isolated__listでは各prefixのlastOperationAtも返します。またIDごとに複数のWorkspaceとMCPごとの相対パスの基準を保持します。同梱MCPプロセス自体を複製するわけではなく、呼び出しごとに対象IDのroot群を渡します。通常のbuilt-in tool callは、Gatewayで不必要な直列化はせず、子MCP側の並列処理に任し、codespaceだけは同じcode_spaceの競合を防ぐキューを持ちます。 Gatewayは起動時に同梱MCPごとにランダム鍵を生成し、isolatedId、正規化済み基準パス、root群をHMAC-SHA-256で署名してプライベート引数として渡します。同梱MCPは未署名・改ざん済み・構造が不正なコンテキストを拒否し、公開引数からのrootrootsworkspaceworkspacesの上書きも拒否します。 Gatewayは起動したすべての子MCPに<prefix>__get_gateway_access_scopeを追加します。同梱MCPはisolatedIdを付けて呼び出し、そのIDに適用される基準ディレクトリ、root群、設定値、正規化済み許可・拒否パスを確認できます。 許可範囲外のパスが拒否された場合、エラー本文で現在許可されているディレクトリとファイルを正規化済みの絶対パスで返します。同梱MCPの共通出力形式ではstructuredContent.result.accessScopeにも同じリストを返します。拒否後にAIが別の作業ディレクトリを推測して再試行するする必要はありません。

safe-files

safe-filesで外向けに「MCP root」と呼ばれるものは、指定されたisolatedIdに保存されている現在の基準ディレクトリです。相対パスはこの基準に基づいて解決され、set_working_directoryは同じIDのrootセット内でのみ基準を変更します。別のIDや共有MCPプロセスの状態は変更しません。 read_textはMCP rootからの相対パスと絶対パスの両方を受け入ますが、正規化後および実際のパス解決後の対象が許可ディレクトリ内に残る場合にのみ読み取ります。 主な機能は次のとおりです。

  • 固定されたrg --files --hiddenによる再帰的なリスト

  • 固定されたrgによるUTF-8テキスト検索

  • UTF-8テキストの読み書きと完全一致置換

  • サイズ制限付きbase64ファイル転送

  • ディレクトリ作成

  • 許可されたWorkspace間での通常ファイルのコピーと移動

  • 内蔵パーサーまたは固定されたgit applyによるパッチ適用 copymoveは現在のMCP rootからの相対パスと絶対パスを受け入れ、複数のallowed_directories間でも通常ファイルを転送できます。送信元と送信先の両方に許可・拒否ポリシーを適用し、シンボリックリンク、ディレクトリ、既存の転送先への上書きを拒否します。移動はドライブをまたぐ場合にも安全に行えるよう、排他的なコピーに成功してから送信元を削除し、削除に失敗した送信先を戻します。パス文字列はシェルへ渡さず、記号を命令として解釈しません。 再帰リストでは常に.git内部を除外し、パッチでは.git内部を対象にできません。許可rootの外側、シンボリックリンクによる外側へのエスケープ、高確度で認証情報とみなされる内容も拒否します。一般シェル、PowerShell、任意コマンド実行ツールは含まれません。

safe-images

safe-imagesは読み取り専用です。PNG、JPEG、WebPの拡張子とマジックバイトを照合し、初期状態では8 MiB・50メガピクセルまでに制限します。 SVG、HEIC、空ファイル、許可root外のパス、シンボリックリンク、UNCパス、NTFS代替データストリームを拒否します。

safe-download

safe-downloadは読み取り専用で、単一ファイルまたはディレクトリを常にZIPとして返します。safe-filesとは別にcwdと許可リストを設定して、ChatGPTへ渡してもよいソースのみを公開する。 ディレクトリは固定されたrg --files --hiddenでリストされ、.git内、ROM、JPEG、State、秘密鍵形式、資格情報とみなせる内容、許可範囲外、シンボリックリンクを拒否します。disallowed_path_globsが設定されている場合は、ユーザー指定のglobsexcludePathsを適用する前に対象ディレクトリ全体を確認し、拒否パターンに一致するファイルまたはフォルダが1つでも存在すればZIP作成全体を拒否します。エラーには一致した拒否パターンと対象パスが含まれます。

internet

internetは、任意のHTTP/HTTPS URLから1ファイルを取得する同梱MCPです。公開されているツールはdownload_fileのみで、保存先はGateway署名付きisolatedIdのworkspace内に限定されます。既存ファイルの上書き、UNCやADSパス、workspace外への書き込み、任意のヘッダー・cookie・クレデンシャル注入は受け付けません。途中で失敗した場合は一時ファイルを削除します。 このMCPは必ずsandbox = "onlineworkspace"で起動します。onlineworkspaceは、Codexのworkspace-write filesystem境界を維持しながら、そのpermission profileのnetwork部分のみを有効にします。sandbox = "never"へフォールバックすることはありません。

archive

archiveは、

codespace is a bundled MCP that only operates on GitHub Codespaces that already exist. Use the name returned by list_codespaces as the codespaceId for each tool. Tools for creating, rebuilding, changing machines, explicitly starting, or deleting Codespaces are not implemented. Startup implicitly begins an existing Codespace via an SSH or copy connection, and when work finishes you can explicitly stop it with stop_codespace, which runs gh codespace stop -c <name>. Before stopping, any async SSH for that same Codespace owned by that isolated session is canceled, and the SSH readiness cache is also discarded. Stopping does not delete the Codespace itself or saved changes. There is no path by which the AI can whimsically proliferate Codespaces. The same codespaceId is owned by only one isolated session; if a different new isolated session touches it, ownership transfers on a last-writer-wins basis. The old session cannot automatically reclaim it, and on a conflict error it is instructed to check purpose in isolated__list and lastOperationAt with the codespace prefix, then ask the user to decide. Only calls for the same codespaceId are serialized at the Gateway; other bundled MCPs such as files are not serialized unnecessarily. The MCP itself always starts with sandbox = "onlineworkspace". --gh-executable=<absolute-gh.exe-path> is required. To cover configurations where the normal gh auth login credentials saved in Windows Credential Manager are not visible to the sandbox user, you can optionally specify --token-file=<absolute-file>. This file is passed to the Codex permission profile as a fixed read-only trust input, and its contents are set to GH_TOKEN only inside the MCP. The parent environment's GH_TOKEN / GITHUB_TOKEN are not inherited. User SSH keys are not configured, read, or permitted. The keys needed for SSH/cp are auto-generated by the Codespace MCP itself via ssh-keygen inside a non-hidden temporary runtime directory prepared by the Gateway, and are deleted when the Gateway closes the child MCP. This internal directory is write-permitted only in the Codex permission profile and is not added to allowed_directories or the Gateway's normal file access scope. gh.exe and the token file cannot be placed inside a writable allowed_directories. ssh receives the remote command as a token array rather than a single string, and rejects shell expansion / metacharacters such as whitespace, quotes, !, @, backticks, $, ;, &, and pipes. timeoutMs is the hard runtime of the underlying operation itself, separate from how long it is waited on as a synchronous response. Normally it waits synchronously for syncWaitMs, with a default/maximum of 10,000 ms. If it does not complete, processing is not stopped; it is moved to the shared async registry and an asyncId is returned. syncWaitMs=0..1000 is treated as immediately async, and if async=true, syncWaitMs is ignored and an asyncId is returned immediately from the start. get_async_status returns all async operations held by that isolation at once when asyncId is omitted, and with an individual ID you can retrieve details/completed results. get_async_logs is for retrieving the full held stdout/stderr of process-backed jobs. wait_async remains as a compatibility implementation, but normally use get_async_status / get_async_logs so that long MCP responses do not hold the Gateway/tunnel. copy_to_codespace sends local files/directories selected from under sourceDirectory via either a paths enumeration or globs to a remote: destination explicitly specified by the caller. The MCP does not infer or auto-prepend remote:. For example, with paths=["scripts/a.js"], the file is placed at /workspaces/project/scripts/a.js under remote:/workspaces/project; the basename alone is not flattened directly under the destination. Only the necessary remote parent directories are created with a fixed helper to preserve hierarchy, and each selection is copied to its corresponding destination. copy_from_codespace is the reverse direction: it copies a single remote:/workspaces/<workspace>/... source explicitly specified by the caller into an existing destination directory in the signed local workspace. The remote source is inspected before copying, and symlinks/special entries, excessive entry counts, and transfers of CODESPACE_MCP_MAX_TRANSFER_BYTES or more are rejected. It is also rejected if the local target basename already exists. In both copy tools, only the remote side may have remote:; the local side must be a local path, and ambiguous specifications such as no remote protocol or remote on both sides are rejected. Because gh codespace cp has a known GitHub CLI issue where even an existing remote path fails with No such file or directory without -e, -e is always added in both directions. remote: itself is explicitly specified by the caller; the MCP does not auto-prepend it. SSH readiness is checked only when needed with a fixed echo started probe, and only when a cp fails during cache reuse is it retried once after the probe. Remote search enumerates only workspaces directly under /workspaces with roots, and git_root can retrieve the Git top-level of a specified path. search_text is a first-class ripgrep search equivalent to files__search_text, and requires searchBase=/workspaces/<workspace>/... on every call. /, /workspaces, home, /etc, etc. cannot be used as search roots. searchBase is re-validated after remote realpath. The query and glob are not concatenated into an SSH command string; they are base64-encoded and passed to a fixed remote script via stdin. .git is always excluded from search, each file is capped at 16 MiB, and the number of results is also limited to 500 at most. ripgrep_version can confirm rg --version, and install_ripgrep does nothing if rg already exists; only when it does not exist does a fixed installer use one of apt/dnf/yum/apk to install it and then re-check the version. Package names or arbitrary shell strings cannot be passed from tool arguments. list_temporary_public_deployments retrieves the temporary public candidates for Codespaces already recognized on the GitHub side, along with browseUrl / port / visibility. This is not a tool that scans localhost, local PC listening sockets, browser tabs, or local development servers, and it does not auto-detect ports. If there are zero candidates on the GitHub side, it does not return an empty array as a normal result; instead it returns a corrective error explicitly stating that "this is not a failure of local port auto-detection" and that it will not do localhost exploration, port scanning, URL guessing, or detour to gh codespace ports forward. open_temporary_public_deployment targets only one Codespace port explicitly specified by the caller, first requests a public visibility change from GitHub, and after success confirms and returns the complete https://...app.github.dev URL. If the exact port is known, call this tool directly and do not make list_temporary_public_deployments a precondition. Do not infer that .devcontainer forwardPorts is needed solely because the list returned zero entries. If GitHub rejects the specified port, return that actual error and do not explore localhost to guess an alternative port. close_temporary_public_deployment also returns the same explicitly specified port to private and closes the temporary public exposure. None of these create a localhost port tunnel, and they do not create or delete GitHub-side forward entries themselves. The returned browseUrl can be used as-is from Chrome DevTools etc. to verify connectivity of the temporary deployment.

gitmcp

gitmcp executes only local operations on Git repositories inside allowed directories, using fixed Git subcommands and options. --git-executable=<absolute-path> is required at startup, and only that executable is launched with shell=false. It does not accept a general shell or arbitrary Git arguments, and does not support direct .git editing, hook addition, branch deletion, or force operations. add_all, stage_paths, unstage_paths that rewrite the index, along with commit, push, pull, and clone_repository, were moved to a separate git-capability MCP for boundary separation. The old --disable-push, --disable-pull, and --disable-clone are accepted as no-ops so that old gateway.toml files do not become unstartable, but setting them to false does not restore the moved capabilities. You can use status, listing tracked files, checking branches/remotes/history, working tree or staged diffs, show of a specific commit, switching to and checking out existing branches, creating branches with a specified parent commit, and creating, listing, and normally deleting worktrees within the allowed root. Branch deletion, primary worktree deletion, and forced deletion of dirty or locked worktrees are not implemented. To respect .gitignore and standard ignore settings, status does not display ignored untracked files. .gitattributes, .git/info/attributes, global attributes, line-ending conversion such as core.autocrlf, system/global clean/smudge filters, external diffs, and textconv are also respected just as in normal Git. Executable settings placed in the repository's .git/config or worktree config are rejected in advance. Since system/global filters and diff helpers may execute as intended, it is recommended to configure gitmcp, which has file operation permissions, to run inside the Codex OS sandbox where possible. In the Windows Codex sandbox, the permission profile's :minimal read grants system read roots such as C:\Program Files, so the standard C:\Program Files\Git\cmd\git.exe can be used without additional read settings. Only when specifying a Git outside the system read root, such as Portable Git, add that Git installation directory to sandbox_read_only_directories. sandbox = "never" is also available for backward compatibility. list_worktree_files enumerates tracked files and non-ignored untracked files using Git's own exclude determination. check_ignore returns the ignore rules applied to each path and the final determination; check_attributes returns effective values for text, binary, diff, merge, filter, line-ending attributes, etc. get_effective_config excludes credential.*, author name, and email address from query targets, and returns settings relevant to local gitmcp behavior such as core.autocrlf, filters, attributes, and diff/textconv, with scope and origin. Safety measures are not about disabling Git settings entirely, but are limited to rejecting executable hooks, helpers, filters, external diff/textconv, merge drivers, signing programs, proxies, and custom transport settings placed in the repository's own .git/config or worktree config. Hooks, fsmonitor, file/ext protocols, and interactive credential prompts are disabled. get_policy lets you confirm the current policy in a machine-readable form. If you directly specify a submodule or nested Git repository as repositoryPath, you can retrieve that repository's own status, diff, log, etc. There is no tool that recursively searches under the parent repository to automatically enumerate all nested repositories.

git-capability

git-capability is a bundled MCP that registers mcp/git-capability/server.mjs multiple times with --mode=stage|commit|push|pull|clone, separating Git capabilities by use case. Each registration is an independent [mcp_servers.<name>], so sandbox, allowed_directories, timeout, and serial_group can be chosen individually. sandbox = "never" is not prohibited; users who need compatibility with the Git index, signing agents, and the network can also choose the conventional path. In all modes, --git-executable=<absolute-path> is fixed at startup, and the Git executable, repositoryPath, environment variables, and arbitrary Git arguments cannot be selected from tool arguments. Via the Gateway, the same HMAC-signed isolated workspace as a normal bundled MCP is required. If the repository's .git/config or worktree config contains executable hook / helper / filter / diff / merge driver / signing program / proxy / transport settings, they are rejected before capability execution. stage mode exposes only add_all, stage_paths, and unstage_paths, and repository selection is fixed from the signed workspace/base. When starting a sandboxed Git MCP, the Gateway itself scans for .git directories that exist at that time under the writable root and adds the specific Git metadata write roots to the Codex permission profile. This is based on the Codex-side implementation where is_metadata_write_denied / has_explicit_write_entry_for_metadata_path permits more specific explicit write entries within protected metadata. .git/hooks, .git/objects/info, and .git/modules remain write-denied, and existing .git/config, config.worktree, commondir, and gitdir are also write-denied. Therefore staging and normal branch/worktree metadata updates are available inside the sandbox, but submodule addition is out of scope. .git directories newly created after startup are not auto-permitted, so git init and in-sandbox clones are not handled by this mechanism. During staging, standard ignore, attributes, line-ending conversion, and system/global clean filters are respected, and denied worktree paths are rejected. The only tool argument in commit mode is message; it commits only the already-staged index with git commit --no-verify -m <message>. It has no staging capability or repository selection. System/global commit signing settings are preserved, so in configurations that need access to a signing agent, you can set only the commit MCP to sandbox = "never" while leaving the larger gitmcp inside the sandbox. push and pull fix --remote= and one or more --repository=OWNER/REPO at startup, normalize the GitHub repository identity from the remote URL, and match it against the allowlist. Thus https://github.com/OWNER/REPO.git and git@github.com:OWNER/REPO.git are treated as the same repository, but partial matches of repository names are not permitted. To allow multiple workspaces in one capability, repeat --repository=. The old --expected-remote-url=<exact-url> is also available for backward compatibility. Tool calls do not accept remote, URL, or refspec. Push sends only the current branch without force and does not rewrite upstream settings. Pull fetches the fixed remote, applies the path policy to the incoming tree, and then reflects it into the same-named current branch with --ff-only. clone abolishes the startup --url= and instead accepts url, a new child directory name, and an optional depth on the tool side. url accepts http://, https://, ssh://user@host/path, and user@host:path forms for any host. Credential embedding in HTTP(S) URLs and password embedding in SSH URLs are rejected, while inherited credential paths such as normal Git credential helpers, askpass, and SSH agent / SSH settings are preserved. After fetching with --no-checkout, the allowed/denied paths of the incoming tree are inspected before checkout, and on failure only the clone destination newly created by that call is deleted. Submodule recursion and arbitrary parents are not exposed.

gh-workflow

gh-workflow checks the execution status of GitHub Actions for GitHub repositories explicitly permitted via the startup argument --repository=OWNER/REPO, and cancels explicitly specified runs. --repository= can be specified multiple times, and repositories not specified cannot be selected. If exactly one repository is permitted, it can be omitted in each tool; if multiple are permitted, specifying the target repository is required. In the configuration example, DaisukeDaisuke/desmume_webassembly is specified, and the MCP itself is disabled by default. In addition to tools equivalent to gh run list --branch main --limit 3, gh run watch RUN_ID --exit-status, gh run cancel RUN_ID, and gh run view RUN_ID, you can retrieve job lists, full logs, failure logs, workflow lists, workflow summaries, and workflow YAML. cancel_run passes only a validated decimal run ID and the permitted repository as fixed arguments. Workflow dispatch, rerun, delete, artifact download, and gh api are not exposed. gh is launched directly from spawn with shell=false, with subcommands and options fixed. The run ID, branch, and workflow identifier are each validated individually, stdin is closed, and output size is limited. Always specify the child process's cwd explicitly in gateway.toml. For authentication, you can use the GitHub CLI settings saved by local gh auth login.

codex-script

codex-script is a bundled MCP that fixes the execution runtime at MCP startup with --runtime=mjs|nodejs|python|php and --runtime-executable=<absolute-path>, and executes only scripts that already exist in the permitted Workspace. You can register the same server.mjs multiple times and expose it under independent prefixes such as mjs_script, nodejs_script, python_script, and php_script. --mode=run exposes run_script, and --mode=check exposes check_file. run_script launches only the runtime itself; check_file launches only the fixed checker: Node.js --check, Python py_compile, or PHP -l. In addition to the backward-compatible single filePath specification, check_file can check up to 500 files at once with filePaths, and returns pass, fault, and messages only for failed files. The stdout/stderr of successful checkers is not returned. A general shell, arbitrary executable selection, arbitrary environment variable injection, and npm script or package manager calls are not exposed. Arguments are passed as literal argv, stdin is closed, and timeout and output size are limited. The Gateway treats codex-script as isBundled, applying the signed base / roots selected by isolatedId and the normal path policy. Furthermore, if codex-script specifies sandbox = "never" in gateway.toml, it is rejected at configuration load time; the MCP process itself must be started inside an elevated or unelevated Codex Windows sandbox. Rather than creating a separate sandbox for each script call, the fixed runtime operates as a child process of the already-sandboxed MCP. In --mode=run, which executes arbitrary code, the code runs inside the permitted Workspace, so keep allowed_directories to the necessary minimum and place the runtime, Codex CLI, and MCP executable outside the writable root. disallowed_directories and disallowed_files can be used as exact deny entries in the outer Codex permission profile. disallowed_path_globs cannot be safely converted equivalently into the arbitrary-code sandbox, so they are rejected in both run and check; if needed, replace them with exact denies or narrow allowed_directories itself.

Adding an arbitrary stdio MCP

The launch command and arguments of the MCP to connect are described in [mcp_servers.<name>] in config/gateway.toml, not in the Gateway itself.

private_use_only = true
publish_tool_directory = false
[mcp_servers.example]
command = "py"
args = ['C:\path\to\server.py']
cwd = 'C:\path\to'
enabled = true
prefix = "example"
annotation_config = true
startup_timeout_sec = 30
tool_timeout_sec = 1800
allowed_directories = ['C:\work\project']
allowed_files = ['C:\Users\owner\Downloads\one-upload-file.png']
[mcp_servers.example.env]
EXAMPLE_CONFIG = 'C:\path\to\config.json'

Only valid stdio MCPs are started as child processes, and the original tool name tool_name is exposed on the ChatGPT side as example__tool_name. Entries with enabled = false are not started. tool_output_token_limit copied from Codex settings, per-tool approval settings, and items the Gateway does not recognize are ignored. They have no effect on this Gateway.

Tool annotations for external MCPs

External MCPs are published with annotations returned by child MCPs as the baseline, while missing readOnlyHint, destructiveHint, idempotentHint, and openWorldHint are filled in with explicit values. If a child MCP returns only readOnlyHint = true, then unless explicitly specified, destructiveHint = false and idempotentHint = true are filled in. At Gateway startup, if the TOML specified by tool_annotations_path does not exist, it is created, and if the [tool_annotations.<prefix>] corresponding to a valid external MCP prefix does not exist, it is appended to the end. Tool identifiers obtained from child MCPs are also appended to [tool_annotations.<prefix>.tools] as UNCLASSIFIED. Existing prefix settings and tool assignments are not overwritten, and tools that have disappeared are not automatically deleted either. Bundled MCPs define annotations in each server.mjs, so annotation_config = false is set in gateway.toml. For external MCPs, it is treated as true when omitted. In the automatically generated TOML, the following abbreviated names and the meanings of the four hints are described in comments. open_world_hint overrides the openWorldHint for the entire prefix, and open_world_tools overrides the openWorldHint for individual tools.

[tool_annotations.chrome-devtools]
default = "LOCAL_STATE_ANNOTATIONS"
open_world_hint = true
[tool_annotations.chrome-devtools.tools]
take_snapshot = "READ_ONLY_ANNOTATIONS"
click = "UNCLASSIFIED"
[tool_annotations.chrome-devtools.open_world_tools]
take_snapshot = false
click = true

UNCLASSIFIED is an unclassified marker that preserves the existing values of annotations returned by the child MCP and fills in only the missing hints. When classifying, change the value of each tool identifier to one of READ_ONLY_ANNOTATIONS, LOCAL_STATE_ANNOTATIONS, LOCAL_DESTRUCTIVE_IDEMPOTENT_ANNOTATIONS, LOCAL_DESTRUCTIVE_NON_IDEMPOTENT_ANNOTATIONS, or LOCAL_ADDITIVE_IDEMPOTENT_ANNOTATIONS.

Gateway Configuration

User decisions are respected

Gateway behavior is determined by the settings the user explicitly specifies in config/gateway.toml. It does not auto-detect and register MCPs, nor does it automatically rewrite configuration files. As an exception, only the tool annotations of external MCPs are appended to the independent TOML specified by tool_annotations_path for unregistered prefixes and newly discovered tool identifiers. New tools become UNCLASSIFIED, and gateway.toml, existing prefixes, and existing tool settings are not modified. The MCPs to connect, their launch commands, arguments, working directories, environment variables, enable/disable status, Codex sandbox mode, read-only paths for the sandbox, tools not to expose, path allow/deny ranges, serial execution, and delayed startup are all chosen by the user. The Gateway reads, validates, and applies those settings, but it does not infer safety or use cases on the user's behalf to add settings or expand the allowed scope. config/gateway.example.toml is a configuration example, not a "magic script" that is applied as-is. It is structured so that users can check only the necessary items, write them into config/gateway.toml, and understand for themselves the programs that will actually launch and the features that will be exposed.

No general-purpose command execution is provided

This repository does not bundle a general-purpose command runner that exposes Windows user privileges as-is, such as a general shell, PowerShell, command prompt, arbitrary executable selection, or arbitrary environment variable injection. The exception is the codex-script described above, which fixes the execution runtime at MCP startup and executes only existing scripts within the allowed Workspace inside the Codex Windows sandbox. This is not a mechanism that makes arbitrary code safe through path permission alone; it is a restricted script runner that mandates an OS sandbox. Directly exposing general arbitrary code execution could unintentionally leak connection information such as Tunnel IDs and runtime API keys, and if abused, an attacker could execute arbitrary operations with Windows user privileges. Therefore, even when adding an external arbitrary code execution MCP, use sandbox = "elevated" or "unelevated" and keep the writable root to the minimum necessary. For tasks that do not require local execution, such as code generation or transformation, continue to prioritize the ChatGPT-side sandbox. If you only need to pass local sources, you can ZIP only the files allowed by safe-download.

Protection of Gateway execution code

Setting protect_gateway_app = true treats the Gateway's own app directory as read-only from child MCPs even if it overlaps with the allowed Workspace. The default in the Gateway code is false, but it is true in the bundled configuration example. For sandboxed MCPs, a more specific read entry is added to the Codex permission profile, and for safe-files, write, replace, move-source deletion, patch, and subdirectory creation under it are denied while read and file_info are maintained. In file_info, protected targets are displayed as prohibited=true. This setting does not serve as an OS boundary if a child process with sandbox = "never" is compromised to the point of arbitrary code execution. With never, the child itself can ignore the Gateway path policy, so use the Codex OS sandbox for use cases that require forcibly denying writes to executed code.

Path permissions

allowed_directories permits the specified directory and everything beneath it, and allowed_files permits only the specified files by exact match. The Gateway recursively inspects the tool arguments of all child MCPs and matches keys such as path, filePath, files, and directory, as well as strings that look like absolute paths, against the allowlist. Relative paths are resolved from the target MCP's cwd. The automatically added <prefix>__get_gateway_access_scope for each child MCP returns the configuration values used for this inspection and the normalized effective scope. This is a tool for the AI to directly check the current Gateway state instead of guessing the working directory or allowed paths from past conversations.

allowed_directories = ['C:\work\project']
allowed_files = ['C:\Users\owner\Downloads\upload.png']
disallowed_directories = ['C:\work\project\private']
disallowed_files = ['C:\work\project\.env']
disallowed_path_globs = ['**.ssh**']

disallowed_path_globs is a deny glob applied to the entire normalized path, targeting both files and folders. * matches any string that does not cross a path separator, ** matches any string including path separators, and ? matches any single character other than a path separator. For example, '**.ssh**' denies uniformly any path that contains .ssh anywhere. On Windows, \ and / are treated as the same separator, and case is not distinguished. On macOS and Linux, / is treated as the separator, and case is distinguished. Errors at denial time display that the path was denied by disallowed_path_globs, the matched glob, and the normalized target path. The Gateway-side inspection is a guard for tool arguments passed from ChatGPT to child MCPs. The bundled safe-files, safe-images, safe-download, gitmcp, git-capability, and codex-script also use signed Workspace context and each MCP's own path validation. For third-party MCPs, internal file access cannot be restricted by the Gateway's argument guard alone, so use sandbox = "elevated" or "unelevated" as needed to launch the MCP process itself inside the Codex OS sandbox.

Format of MCP server configuration

Like Codex's MCP configuration, the Gateway uses a format that groups per-MCP settings into the [mcp_servers.<name>] table. It is not a compatibility feature that reads Codex configuration files as-is; it only recognizes the items implemented by the Gateway. When adding an MCP to config/gateway.toml, do not prefix it with # for comments; write it as follows. The following is a template containing all options recognized by the Gateway.

private_use_only = true
protect_gateway_app = true
publish_tool_directory = false
tool_annotations_path = "tool-annotations.toml"

[mcp_servers.my_server]
command = 'C:\Program Files\nodejs\node.exe'
args = ['C:\path\to\server.mjs', '--example=value']
cwd = 'C:\work\project'
enabled = true
sandbox = "elevated"
codex_executable = 'C:\Users\owner\AppData\Roaming\npm\codex.cmd'
sandbox_read_only_directories = ['C:\path\to\read-only-data']
prefix = "my_server"
annotation_config = true
dangerous_allow_gateway_config_access = false
startup_timeout_sec = 30
tool_timeout_sec = 1800
serial_group = "my_server"
deferred = true
blocked_tools = ["dangerous_tool"]
blocked_tool_substrings = ["script", "shell", "execute"]
allowed_directories = ['C:\work\project']
allowed_files = ['C:\Users\owner\Downloads\upload.png']
disallowed_directories = []
disallowed_files = []
disallowed_path_globs = []

[mcp_servers.my_server.start_after]
server = "controller"
tool = "prepare_my_server"

[mcp_servers.my_server.stop_after]
server = "controller"
tool = "stop_my_server"

[mcp_servers.my_server.env]
EXAMPLE_CONFIG = 'C:\path\to\config.json'

Item

Description

private_use_only

Required setting for the entire Gateway. For safety verification, it must be set to true.

publish_tool_directory

When set to true, exposes the built-in tools gateway__list_available_tools, gateway__get_prefix_list, and gateway__get_config. When omitted or set to false, they are not exposed.

tool_annotations_path

TOML file for the external MCP annotations settings. Relative paths are resolved against the directory containing gateway.toml; when omitted, it defaults to tool-annotations.toml in the same directory.

[mcp_servers.<name>]

A unit that defines one stdio MCP connection. <name> must be unique within the Gateway, and for each unit you configure the launch method, Codex sandbox, prefix, path policy, exposed tools, and lifecycle. If enabled = false, the settings are retained but the unit is excluded from startup targets.

command

The executable or command that launches the child MCP. Required unless enabled = false. With sandbox = "elevated", a native .exe with an absolute path on Windows is required. With unelevated and never, names found on PATH can also be used.

args

Specifies the arguments to pass to command as a string array. When omitted, the process is launched with no arguments.

cwd

The working directory of the child MCP. Relative paths are made absolute based on the directory containing gateway.toml; when omitted, that directory is used. When the sandbox is enabled, it must be contained within at least one allowed_directories entry.

enabled

When set to false, keeps the settings but excludes that MCP from startup targets. Enabled by default when omitted.

sandbox

The launch boundary for the child MCP. One of four values: "never", "elevated", "unelevated", or "onlineworkspace"; defaults to "never" when omitted. With elevated, unelevated, and onlineworkspace, the MCP process itself is launched via the Codex Windows sandbox. onlineworkspace is a mode dedicated to Internet MCPs that enables network access while maintaining the workspace-write filesystem boundary. In codex-script, never and onlineworkspace are rejected.

codex_executable

The absolute path to the Codex CLI, required when sandbox != "never". On Windows, the npm shim codex.cmd can also be used. It must resolve to an existing regular file and cannot be placed within a writable root of allowed_directories.

sandbox_read_only_directories

An array of absolute directories additionally passed to the Codex permission profile as read-only when the sandbox is enabled. They are not made writable roots like allowed_directories. Empty when omitted.

prefix

The prefix for tool names exposed to ChatGPT. The original tool_name is exposed as <prefix>__<tool_name>. When omitted, <name> from [mcp_servers.<name>] is used.

annotation_config

Specifies whether to apply the external annotations settings. Defaults to true when omitted. Set to false when the MCP has complete annotations of its own, such as bundled MCPs.

dangerous_allow_gateway_config_access

Defaults to false. When false, the pre-resolution and actual paths of the loaded gateway.toml are added to the protected targets and are rejected by the path policies of the Gateway and child MCPs even if they are within an allowed root. Setting it to true removes only this protection. As the name suggests, this is a dangerous compatibility setting.

startup_timeout_sec

The number of seconds to wait for the child MCP to start and initialize. Specify a positive number; defaults to 30 seconds when omitted.

tool_timeout_sec

The number of seconds to wait for a child MCP tool call. Specify a positive number; defaults to 1800 seconds when omitted.

request_timeout_sec

A compatibility alias for tool_timeout_sec. If both are present, tool_timeout_sec takes precedence, so use tool_timeout_sec in new configurations.

serial_group

Serializes tool calls of MCPs that share the same value. Use for resources that should not be operated concurrently, such as the same browser or repository.

deferred

When true, the MCP is not launched at Gateway initialization and is delayed until the tool specified by start_after succeeds. Defaults to false when omitted.

blocked_tools

Specifies tool names not to expose to ChatGPT as a string array of exact matches.

blocked_tool_substrings

Specifies substrings of tool names not to expose to ChatGPT. Matching is case-insensitive and strings are not treated as globs or regular expressions.

allowed_directories

Allows access to the specified absolute-path directories and everything beneath them. When the sandbox is enabled, they also become writable roots of the Codex permission profile.

allowed_files

Allows only the files at the specified absolute paths, matched exactly. When the sandbox is enabled, they are also passed to the Codex permission profile as individually readable paths.

disallowed_directories

Specifies by absolute path the directories and everything beneath them that are rejected even if they are within the allowed scope.

disallowed_files

Specifies by absolute path the files that are rejected even if they are within the allowed scope.

disallowed_path_globs

Specifies rejection globs applied to the entire normalized path. Both files and folders are targeted.

[mcp_servers.<name>.start_after]

Launches this MCP after the tool of another MCP specified by server and tool succeeds. Usually combined with deferred = true.

[mcp_servers.<name>.stop_after]

Stops this MCP after the tool of another MCP specified by server and tool succeeds.

[mcp_servers.<name>.env]

Environment variables additionally passed to the child MCP. Values can be strings, numbers, or booleans. Environment variables reserved for the Gateway's path policy cannot be overridden.

Normal MCP starts with deferred = false or when omitted. In that case, start_after is not needed. With sandbox = "elevated" or "unelevated", the network in the Codex permission profile is disabled, and only sandbox = "onlineworkspace" enables the network. In any sandbox-enabled mode, allowed_directories is configured as write, while allowed_files and sandbox_read_only_directories are configured as read. Additionally, the MCP executable's directory, known interpreter entry script directories, and for bundled MCPs the Gateway's app directory are added as read as needed. codex_executable must be placed outside the writable root, and for elevated and onlineworkspace, the command itself must also be placed outside the writable root. Even for sandboxed external MCPs, disallowed_directories, disallowed_files, and protected gateway.toml specified with absolute paths are passed as deny in the Codex permission profile, so exact deny holes inside the write root can be used. On the other hand, Gateway-specific disallowed_path_globs have no guarantee of equivalent conversion to Codex-side glob semantics, so they are rejected with fail-closed behavior for sandboxed external MCPs. Bundled MCPs are an exception to this compatibility check because they also validate the Gateway's glob deny policy themselves. Remote MCP configuration via url is rejected. The Codex-specific tool_output_token_limit is read but not used, and has no effect on this Gateway. The Gateway's own environment variables LOCAL_MCP_FILES_MAX_RESPONSE_BYTES and LOCAL_MCP_CODESPACE_MAX_RESPONSE_BYTES can change the upper limit in bytes of the final JSONL response that files__* and codespace__* return to the tunnel, respectively. When either is omitted, the default is 15KB (15360 bytes). When the limit is exceeded, the actual returned string size is shown in KB, MB, or GB, and instead of the result body, only the first 1024 bytes (1KB) of the original final JSONL is returned as a debug preview. The warning "Destructive operations may have already been performed." is retained. For files__*, the use of downloads__download_zip is suggested; for codespace__*, saving large output to a file and retrieving it with codespace__copy_from_codespace, or narrowing the query, is suggested. This Gateway final response limit applies only to files__* and codespace__*, and does not apply to the separate limits of downloads__* or images__*. Inside the Codespace MCP, CODESPACE_MCP_MAX_OUTPUT_BYTES also limits the amount of stdout/stderr retained, with a default of 15KB. This value can also be changed via an environment variable.

Built-in tool directory

Specifying publish_tool_directory = true at the top level exposes gateway__list_available_tools, gateway__get_prefix_list, and gateway__get_config. gateway__get_prefix_list returns the prefixes of currently running MCPs that have published tools, plus the Gateway's built-in gateway and, when a bundled MCP is running, isolated. gateway__list_available_tools and gateway__get_prefix_list reference only the public tool registry that the Gateway already holds. gateway__get_config returns, from the configuration loaded at startup, only each MCP's name, prefix, allowed_directories, allowed_files, disallowed_directories, disallowed_files, disallowed_path_globs, and sandbox read-only paths as JSON. It does not reload the configuration file, and does not return the configuration file's own path, env, args, command, or any other secret values. MCPs with enabled = false are not started; gateway__list_available_tools returns only their names in disabledProxyNames, and gateway__get_config returns only their names in disabledServerNames. If the input is omitted, all currently available tools are returned; if prefix is specified, results are filtered by case-insensitive prefix matching of the full identifier. If zero items match, it does not error and returns all items instead. The returned tool information is only the unabbreviated public name and description, such as chrome-devtools__click. Input schemas, output schemas, launch commands, arguments, paths, environment variables, and rejected tool names are not returned. enabledProxyCount is the number of MCPs enabled in the configuration, and rejectedToolCount is the number of tools that were rejected from publication by started MCPs. At Gateway initialization, the [gateway] INFO log records only the tools rejected from publication, one by one, and does not enumerate the individual published tool names. Instead, it records the found/rejected/published counts per prefix, prefixes with enabled = false, prefixes that failed to start, and overall totals.

Excluding published tools

Exact tool name matches can be made non-public with blocked_tools, and case-insensitive partial matches with blocked_tool_substrings.

blocked_tools = ["dangerous_tool"]
blocked_tool_substrings = ["script", "shell", "execute"]

blocked_tool_substrings is not a glob or regular expression. For example, "script" targets all of evaluate_script, runScript, and SCRIPT_debug.

Serial execution and deferred startup

MCPs that should not operate on the same resources simultaneously can be placed in the same serial_group. MCPs with deferred = true are not started at initialization and can be started with start_after after a specified tool of another MCP succeeds. Similarly, they can be stopped with stop_after.

[mcp_servers.browser]
command = "node"
args = ["browser-server.mjs"]
cwd = ".."
enabled = true
prefix = "browser"
deferred = true
serial_group = "browser"
[mcp_servers.browser.start_after]
server = "controller"
tool = "prepare_browser"
[mcp_servers.browser.stop_after]
server = "controller"
tool = "stop_browser"

Security assumptions

[!WARNING] The command in gateway.toml executes a local program. Only register trusted MCPs. Some commands fetch and execute MCP programs from the internet directly. Commands specified in gateway.toml are executed on the actual PC with the Windows user's privileges, not inside a sandbox. Do not specify untrusted MCPs.

The Gateway refuses to start with administrator privileges and does not pass environment variables that appear to be the parent process's secrets directly to child MCPs. However, it does not isolate files readable by the same Windows user at the OS level. It is recommended to associate the Tunnel only with your own Platform Organization and your own ChatGPT Workspace, and to configure the runtime API key with no permissions other than Tunnels Read + Use. See SECURITY.md and INSTALL.md for details.

SDK

Download the main branch ZIP and tunnel-client-source, attach them to ChatGPT, and send the following prompt to have it create a signature-compatible bundled stdio MCP to add to this repository. Simply registering the generated MCP as a normal external MCP will not send the Gateway's signed isolated workspace. Place it as mcp/<name>/server.mjs and also apply the diff to register it in BUNDLED_SERVER_PATHS in app/server-config.mjs. Replace <Describe the MCP tools you need here.> with a specific description of the tools you want to create and the targets they operate on.

The attached local-mcp-chatgpt-tunnel-main.zip is the SDK and reference implementation. Inspect it before writing code.
Create a new bundled stdio MCP at mcp/<name>/server.mjs for the following purpose:

<Describe the MCP tools you need here.>

Requirements:
- Return the complete mcp/<name>/server.mjs file, the exact app/server-config.mjs BUNDLED_SERVER_PATHS patch required to mark it as bundled, and the minimal config/gateway.toml entry.
- Do not modify the attached archive directly. Return complete replacement content or an exact patch for every required file.
- Use only Node.js built-in modules and the repository's existing local helpers unless I explicitly permit another dependency.
- Follow the repository's MCP protocol handling, JSON Schema conventions, outputSchema declarations, tool annotations, error handling, stdout/stderr separation, timeouts, and bounded-output design.
- Write only JSON-RPC protocol messages to stdout. Write diagnostics and logs to stderr.
- Import createBundledIsolation and environmentWithoutBundledIsolationKey from ../../app/bundled-isolation.mjs. Every tools/call operation must run through createBundledIsolation().run(arguments, operation) before any side effect or path access.
- In Gateway mode, LOCAL_MCP_GATEWAY_ISOLATION_KEY is present. Every call must require and verify the private __localMcpIsolation envelope. Missing, malformed, unsupported-version, unsigned, or incorrectly signed envelopes must fail closed before the public tool executes. Do not implement an unsigned fallback while the key is present.
- The Gateway sends the signature and the paths permitted for that call together in this private argument. This is the envelope shape; the signature placeholder below is not a valid signature:
  {
    "__localMcpIsolation": {
      "version": 1,
      "roots": ["C:\\work\\project"],
      "base": "C:\\work\\project",
      "signature": "<64 hexadecimal HMAC-SHA-256 characters>"
    }
  }
- Verify HMAC-SHA-256 over exactly JSON.stringify({ base, roots }) using LOCAL_MCP_GATEWAY_ISOLATION_KEY, compare signatures in constant time, require one or more absolute roots, and require base to be an absolute path inside at least one root. Prefer the repository helper instead of duplicating the cryptographic code.
- Treat the verified roots and base as the only authoritative path context in Gateway mode. roots are the directories the operation may access; base is the current relative-path base. Never replace them with process.cwd(), a public argument, a cached global root, or a path remembered from another call.
- Reject public arguments named root, roots, workspace, workspaces, or equivalent nested variants. Public tool input must not override the signed path context.
- Never expose LOCAL_MCP_GATEWAY_ISOLATION_KEY or pass it to subprocesses. When spawning a child process, use environmentWithoutBundledIsolationKey() or an equivalent explicit environment filter.
- Shell injection must be impossible under all circumstances. Treat every MCP argument, path, filename, identifier, option, and environment-derived value as untrusted input.
- Never pass a constructed or user-controlled command string to a shell. Do not use child_process.exec, execSync, spawn with shell: true, cmd.exe /c, powershell -Command, bash -c, or sh -c.
- When a native program is genuinely required, invoke a fixed executable directly with spawn or execFile, shell: false, a fixed subcommand, and individually validated arguments. Use an explicit allowlist and a -- separator where the target program supports it.
- Do not expose a general-purpose command runner, arbitrary script execution, arbitrary executable selection, arbitrary environment-variable injection, or unrestricted native-program arguments.
- Implement `roots`, `get_working_directory`, and `set_working_directory` only when the MCP has a real filesystem, repository, workspace, current-directory, input-directory, or output-directory concept. If the capability has no directory concept, do not add these tools and do not invent a meaningless root.
- When those directory tools are applicable, `roots` must return only the verified signed roots and current base, `get_working_directory` must return the verified base, and `set_working_directory` must accept an absolute path or a path relative to the current base, resolve it to an existing directory inside one signed root, apply every deny rule, and return the canonical absolute path. Gateway interception and direct standalone behavior must both remain safe.
- Any stdio MCP that performs filesystem operations must support and enforce these exact configuration arrays:
  `allowed_directories = []`
  `allowed_files = []`
  `disallowed_directories = []`
  `disallowed_files = []`
  `disallowed_path_globs = []`
- Apply the allowlist and denylist to every filesystem operation, including working-directory changes. Deny rules must take precedence over allow rules.
- Resolve relative filesystem paths from the verified base. Absolute paths may be accepted only when they remain inside a verified root and pass the complete configured allow/deny policy.
- Reject parent traversal that escapes a signed root, root-relative ambiguity, drive-relative paths, UNC paths unless explicitly required and safely constrained, NTFS alternate data streams, and any syntax that could reinterpret the target. Canonicalize existing paths and verify the real target remains inside a signed root after symlink resolution.
- Do not require callers to provide redundant absolute paths when the same target can be identified safely relative to the verified base.
- A tool that accepts input files must accept multiple files as an array unless the underlying operation can inherently and safely operate on exactly one file. Validate every file independently and enforce bounded file counts, sizes, and output sizes.
- For build-related tools, require the caller to select a narrow project, target, package, configuration, or input-file set. Do not default to building an entire workspace or repository when a narrower target is possible. Keep the executable, subcommand, and build options fixed or allowlisted.
- This stdio MCP is not executed inside the ChatGPT sandbox. It runs on the user's real Windows PC with the permissions of the current Windows user. Remove unsafe capabilities by design instead of relying on the model to ask for confirmation.
- Do not download, install, update, or access the network unless I explicitly require that behavior. If network access is required, restrict destinations and operations to an explicit allowlist.
- Close child-process stdin, impose timeouts and output limits, handle cancellation and termination, and return structured MCP errors without crashing the process.
- Include clear tool descriptions, strict input schemas, strict output schemas, accurate annotations, and a short security explanation for every capability.
- Prefer a small, auditable implementation. Do not add convenience features that expand the security boundary beyond the stated purpose.

Diagnostics and testing

Detects required commands and checks versions. No installation or configuration changes are performed.

node app\doctor.mjs

Repository tests can be run with the following.

npm test

There are no external npm dependencies.

License

This repository itself is under the MIT License. For third-party components such as the official tunnel-client.exe, see THIRD_PARTY_NOTICES.md.

Notes

Is connecting from ChatGPT to a local MCP a "gray-area workaround"?

https://gist.github.com/DaisukeDaisuke/0d0af93dd8cb376a36879702afb176ee

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
<1hResponse 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

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables ChatGPT to securely operate a single Windows development workspace via a local MCP server, offering file editing, Git status, static analysis, approved test/build, and limited ADB operations with audit logging.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A Windows proof-of-concept MCP server that connects ChatGPT developer-mode to a local Codex CLI via Secure MCP Tunnel, exposing a small set of read-only, allowlisted tools in an isolated workspace.
    1
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.

  • Security-first WordPress MCP server. 129 tools for Claude, ChatGPT, Gemini. Free on wp.org.

  • OCR, transcription, file extraction, and image generation for AI agents via MCP.

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/DaisukeDaisuke/local-mcp-chatgpt-tunnel'

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