AI Server Commander is a self-hosted bridge that lets approved AI assistant clients run bounded terminal commands on a machine you control. OpenAI/ChatGPT and Anthropic/Claude are first-class client families; additional clients and protocols are welcome when they do not compromise functionality, reliability, or performance for those two.
It exposes the same execution core through two primary client adapters:
- REST/OpenAPI for ChatGPT Custom GPT Actions and automation clients.
- Remote MCP + OAuth for Claude and other MCP-capable clients.
The server does not provide model access or credits. It receives authenticated tool calls, applies local policy and limits, executes the requested command on the host, and returns a structured result.
Caution
AI Server Commander can execute real shell commands with the permissions of its operating-system user. It is not a sandbox. Run it as a dedicated unprivileged user, keep it behind HTTPS, enable SAFE_MODE, and expose it only to clients and users you trust.
- One bounded command executor shared by REST and MCP.
- Inline commands and multi-line script mode.
- Per-command timeouts and output limits.
- Independent activity IDs for concurrent commands.
- Targeted interruption by
activityId. - Optional
SAFE_MODEdenylist for obviously destructive commands. - Process-group termination on timeout or interruption on POSIX hosts.
- Structured results with output, exit code, timeout, interruption, blocking and truncation metadata.
- Lightweight activity logs and scoped notices.
- Generated OpenAPI document at
/openapi.json. - Remote MCP endpoint at
/mcp. - OAuth discovery, dynamic client registration, authorization code + PKCE, access tokens and refresh tokens.
- MCP tool title, input/output schemas, risk annotations, OAuth security schemes and structured content.
- Backward-compatible legacy REST endpoint for existing Custom GPT Actions.
ChatGPT Custom GPT / REST client
│ HTTPS + Bearer token
▼
REST / OpenAPI adapter ─────┐
│
Claude / remote MCP client ├── shared bounded executor ── host shell
│ HTTPS + OAuth │ │
▼ │ ├── SAFE_MODE
MCP adapter ──────────────┘ ├── timeout/output caps
├── activity log
└── notices
See docs/architecture.md for request flows, trust boundaries and the module map.
- Node.js 20 or newer.
- Linux, macOS or another host with a compatible shell.
- A public HTTPS URL for ChatGPT or remote MCP clients.
- A dedicated, minimally privileged operating-system account for production use.
Windows may work for basic commands, but process-group termination is POSIX-specific.
git clone https://github.com/Jhacarreiro/ai-server-commander.git
cd ai-server-commander
npm installThe first npm start launches an interactive setup and writes config.json.
npm startFor non-interactive deployment:
cp config.example.json config.json
chmod 600 config.jsonMinimal configuration:
{
"port": 3000,
"productionDomain": "https://commander.example.com",
"authToken": "replace-with-a-long-random-secret",
"mcpToken": "replace-with-a-separate-long-random-secret"
}Generate tokens with a cryptographically secure tool:
openssl rand -hex 32SAFE_MODE=true npm startLocal checks:
curl http://127.0.0.1:3000/openapi.json
npm run check
npm testUse a reverse proxy such as Nginx, Caddy or a managed tunnel. Forward the original host and scheme so OAuth metadata contains the correct public URL.
See docs/deployment.md for systemd, Nginx, upgrades and rollback.
| Key | Required | Purpose |
|---|---|---|
port |
Yes | Local TCP port used by the Node server. |
productionDomain |
Yes | Exact public origin, such as https://commander.example.com. Required for correct remote OAuth metadata behind a proxy. |
authToken |
Yes | Bearer token for REST and approval code for the built-in OAuth consent page. |
mcpToken |
No | Separate pre-shared token for MCP clients that support token auth. Falls back to authToken when omitted. |
config.json contains secrets and is ignored by Git. Keep it mode 600 and never paste it into issues or logs.
LocalTunnel support was removed in v1.0.8 because its pinned HTTP dependency chain could not be updated safely. Existing configurations with useLocalTunnel: true now fail with migration guidance. Use a maintained HTTPS reverse proxy or tunnel and set productionDomain explicitly.
| Variable | Default | Purpose |
|---|---|---|
SAFE_MODE |
false |
Enables the built-in destructive-command denylist. Recommended for production. |
COMMAND_TIMEOUT_MS |
120000 |
Server-wide maximum command duration. Client requests can ask for less, not more. |
MAX_OUTPUT_CHARS |
12000 |
Server-wide maximum returned output. |
MAX_SCRIPT_BODY_BYTES |
524288 |
Maximum script/request body size. |
OAUTH_STATE_PATH |
runtime/oauth-state.json |
Persistent OAuth client and token-hash state. |
OAUTH_AUTH_CODE_TTL_SECONDS |
300 |
Authorization-code lifetime. |
OAUTH_ACCESS_TOKEN_TTL_SECONDS |
3600 |
Access-token lifetime. |
OAUTH_REFRESH_TOKEN_TTL_SECONDS |
2592000 |
Refresh-token lifetime. |
SHELL |
Host default | Shell used for inline execution and as script-mode fallback. |
NODE_ENV |
unset | Standard Node environment label. |
See .env.example. The application does not automatically load .env; set variables through your shell, process manager or service unit.
Custom GPT Actions use the REST/OpenAPI adapter and remain the most broadly compatible ChatGPT path.
- Deploy AI Server Commander on a public HTTPS origin.
- In the Custom GPT builder, add an Action.
- Import
https://commander.example.com/openapi.json. - Configure API-key authentication as a Bearer token.
- Use the
authTokenvalue fromconfig.json. - Add or adapt prompt.md as the GPT instructions.
- Test first with a read-only command such as
pwd && hostname.
Legacy GET request:
GET /api/runTerminalScript?command=pwd%20%26%26%20hostname
Authorization: Bearer <authToken>Preferred POST request:
POST /v1/commands/execute
Authorization: Bearer <authToken>
Content-Type: application/json
{
"mode": "inline",
"command": "pwd && hostname",
"cwd": "/srv/project",
"timeoutMs": 45000,
"maxOutputChars": 12000
}The remote MCP endpoint is:
https://commander.example.com/mcp
The server implements MCP protocol version 2025-03-26. initialize always returns that version and does not echo a different client-requested version. Clients that cannot continue on 2025-03-26 disconnect during negotiation, which matches the MCP lifecycle rules.
An empty JSON-RPC batch ([]) is invalid and returns HTTP 400 with JSON-RPC -32600. A POST that contains only notifications (no id) still returns HTTP 202 with no body.
The primary tool is run_terminal_command.
| Field | Type | Notes |
|---|---|---|
command |
string | Exact command for inline mode. |
script |
string | Multi-line script body. Supplying it defaults the mode to script. |
mode |
inline or script |
Optional explicit mode. |
cwd |
string | Must be an existing readable directory. Invalid paths are rejected. |
shell |
string | Script-mode shell, for example /bin/sh. |
timeoutMs |
integer | Requested timeout, capped by server policy. |
maxOutputChars |
integer | Requested output limit, capped by server policy. |
The server publishes:
/.well-known/oauth-protected-resource
/.well-known/oauth-protected-resource/mcp
/.well-known/oauth-authorization-server
/.well-known/openid-configuration
/oauth/register
/oauth/authorize
/oauth/token
/oauth/revoke
The built-in flow supports dynamic client registration, authorization code + PKCE, refresh-token rotation and RFC-style token revocation. The authorization page asks for the server authToken as the approval code.
OAuth state is persisted atomically at OAUTH_STATE_PATH. Client secrets, authorization codes, access tokens and refresh tokens are stored only as SHA-256 hashes; raw values are returned to the client only when issued. The state file is forced to mode 600 on supported POSIX filesystems. After upgrading from an in-memory-only release, existing clients must authorize once; credentials issued by v1.0.8 or later survive normal restarts.
The MCP descriptor includes OAuth security schemes, a compatibility mirror in _meta, risk annotations, an output schema and structuredContent. Whether a specific ChatGPT account or surface can add a custom remote MCP server depends on the current ChatGPT plan and client capabilities. Keep the REST Action path available until the target workflow is validated.
curl -sS \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"mode":"inline","command":"pwd && hostname","timeoutMs":5000}' \
https://commander.example.com/v1/commands/executecurl -sS \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"mode":"script",
"shell":"/bin/sh",
"script":"set -e\npwd\nhostname\n",
"timeoutMs":5000
}' \
https://commander.example.com/v1/commands/execute{
"message": "Command executed successfully.",
"activityId": "cmd_...",
"output": "...",
"exitCode": 0,
"timedOut": false,
"interrupted": false,
"blocked": false,
"outputTruncated": false,
"maxOutputChars": 12000,
"mode": "inline",
"notices": []
}When exactly one command is active:
POST /api/interrupt
Authorization: Bearer <authToken>When several commands may be active, target one explicitly:
POST /api/interrupt
Authorization: Bearer <authToken>
Content-Type: application/json
{
"activityId": "cmd_..."
}Activity endpoints:
GET /api/activity
GET /api/activity/status
GET /api/activity/index
POST /api/activity/context
Notice endpoints:
POST /api/notices
GET /api/notices/pending
POST /api/notices/:id/ack
Activity records use command hashes, byte counts and redacted previews rather than complete script bodies by default. Treat generated runtime/ data as potentially sensitive operational metadata.
AI Server Commander provides controls, not isolation:
- Authentication gates the HTTP interfaces.
- The shared executor caps duration and returned output.
SAFE_MODEblocks a small set of obviously destructive patterns.- Invalid working directories fail closed.
- Temporary script files are mode-restricted and deleted after execution.
- Activity previews redact common token and secret patterns.
- MCP risk annotations tell compatible clients that terminal execution may be destructive and open-world.
It does not provide:
- a container or VM sandbox;
- a complete shell parser or comprehensive command policy;
- per-user operating-system isolation;
- a path allowlist;
- protection from every form of command composition or shell indirection;
- encrypted OAuth metadata at rest (secret and token values are hashed, while non-secret client metadata remains readable);
- rate limiting.
Recommended production controls:
- Run as a dedicated unprivileged user.
- Do not add the service user to
docker,sudoor other privileged groups unless explicitly required. - Enable
SAFE_MODE=true, but do not treat it as a sandbox. - Restrict network exposure with a firewall, access proxy or VPN where client requirements permit.
- Use separate high-entropy
authTokenandmcpTokenvalues. - Rotate tokens after accidental disclosure.
- Review activity and system service logs.
- Require human confirmation for commands that write, delete, restart services, change permissions or access credentials.
See SECURITY.md for vulnerability reporting.
npm run check
npm testThe smoke suite covers:
- bounded executor behavior;
- REST GET/POST compatibility;
- multi-line scripts and request-size limits;
- invalid working directories;
- concurrency and targeted interruption;
- MCP initialization, advertised protocol version, empty-batch rejection and execution;
- OAuth metadata, PKCE, persistent state, restart continuity, refresh rotation and revocation;
- native setup and Firestore service-account compatibility;
SAFE_MODEresults;- OpenAPI generation and version alignment.
CI runs checks on supported Node versions for every push and pull request.
Set productionDomain to the exact external HTTPS origin and forward Host and X-Forwarded-Proto from the reverse proxy.
Confirm that every release uses the same OAUTH_STATE_PATH and that the service user can read and write it. Upgrading from v1.0.7 or earlier requires one new authorization because those releases kept OAuth state only in memory. A missing, moved or deleted state file also requires reauthorization.
The server only implements MCP protocol version 2025-03-26 and always returns that version from initialize. A client that cannot continue on 2025-03-26 is expected to disconnect. Confirm the client supports that version rather than expecting the server to echo an older or newer request.
Pass an explicit cwd. It must exist and be readable by the service user.
The effective timeout is the lower of the client request and COMMAND_TIMEOUT_MS.
Check outputTruncated. Increase the requested maxOutputChars and, if needed, the server-wide MAX_OUTPUT_CHARS cap. Prefer commands that filter output before returning it.
Check these URLs from outside your network:
curl -i https://commander.example.com/.well-known/oauth-protected-resource/mcp
curl -i https://commander.example.com/.well-known/oauth-authorization-server
curl -i https://commander.example.com/mcpThe unauthenticated /mcp request should return 401 with a WWW-Authenticate challenge pointing to protected-resource metadata.
- Architecture
- Deployment and upgrades
- Changelog
- Roadmap
- Custom GPT instruction starter
- Contributing
- Security policy
- Support
- Code of Conduct
- Attribution
AI Server Commander is a small self-hosted project maintained on a best-effort basis. The command-execution surface is intentionally narrow. New capabilities should normally be implemented once in shared core code and exposed through both REST and MCP adapters with matching safety semantics.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request. Do not include deployment secrets, private hostnames, personal paths, access tokens, logs or production state.
Licensed under the MIT License. See NOTICE.md for project attribution.