Skip to main content
Glama

mcp2term

by FreddyE1982
README.md12.3 kB
# mcp2term An implementation of a Model Context Protocol (MCP) server that grants safe, auditable access to a system shell. The server streams stdout and stderr in real time while capturing rich metadata for plugins and downstream consumers. ## Features - **Full command execution** with configurable shell, working directory, environment variables, and timeouts. - **Live streaming** of stdout and stderr via MCP log notifications so clients observe progress as it happens. - **Robust chunked streaming** that handles large stdout/stderr volumes without blocking or truncation. - **Plugin architecture** that exposes every function, class, and variable defined in the package, enabling extensions to observe command lifecycles or inject custom behaviour. - **Remote file management** tools allowing safe file creation, printing, line-range replacement, exact line lookups, and unified diff patching via the `manage_file` tool and `filetool` client command. - **Automatic ngrok tunneling** so HTTP transports are reachable without additional manual setup. - **Typed lifespan context** shared with MCP tools for dependency access and lifecycle management. - **Structured tool responses** including timing information to make results easy for agents to consume. - **Console mirroring** so operators always see the command stream, stdout, and stderr on the hosting terminal by default. - **Automatic launch-directory export** that prepends the directory the server was started from to ``PYTHONPATH`` so Python tooling invoked through ``run_command`` can immediately resolve local packages. ## Installation ```bash pip install -e . ``` The project targets Python 3.12 or newer. ## Configuration `ServerConfig` reads settings from environment variables: | Variable | Description | Default | | --- | --- | --- | | `MCP2TERM_SHELL` | Shell executable used for commands. | `/bin/bash` | | `MCP2TERM_WORKDIR` | Working directory for commands. | Current directory | | `MCP2TERM_INHERIT_ENV` | When `true`, inherit the parent environment. | `true` | | `MCP2TERM_EXTRA_ENV` | JSON object merged into the command environment. | `{}` | | `MCP2TERM_PLUGINS` | Comma-separated dotted module paths to load as plugins. | *(none)* | | `MCP2TERM_COMMAND_TIMEOUT` | Default timeout in seconds for commands. | unlimited | | `MCP2TERM_STREAM_CHUNK_SIZE` | Bytes read from stdout/stderr per chunk while streaming. | `65536` | | `MCP2TERM_LONG_COMMAND_NOTICE_DELAY` | Seconds to wait before emitting long-running command notices. | `2.0` | | `MCP2TERM_LONG_COMMAND_NOTICE_INTERVAL` | Interval in seconds between long-running command notices. | `5.0` | | `MCP2TERM_CONSOLE_ECHO` | Mirror commands and output to the server console (`true`/`false`). | `true` | | `MCP2TERM_CHAT_TERMINAL` | Set to `disabled` to suppress the console-integrated messaging bridge. Legacy values are accepted but ignored. | *(unused)* | ## Running the server ```bash mcp2term --transport stdio ``` Change `--transport` to `sse` or `streamable-http` to use the corresponding MCP transports. `--log-level` controls verbosity and `--mount-path` overrides the HTTP mount location when relevant. While the server is running it mirrors every executed command, stdout chunk, and stderr chunk to the hosting console. Set `MCP2TERM_CONSOLE_ECHO=false` to suppress the mirroring when embedding the server into log-sensitive environments. When running with the `streamable-http` transport the MCP endpoint is served from the `/mcp` path (or `--mount-path` plus `/mcp` when a custom mount is provided). The CLI prints the fully qualified URL, including the `/mcp` suffix, to make tunnelling targets such as ngrok easy to copy. ## MCP tools The server exposes two tools for remote command management: `run_command(command: str, working_directory: Optional[str], environment: Optional[dict[str, str]], timeout: Optional[float]], command_id: Optional[str])` The tool returns structured JSON containing: - `command_id`: unique identifier assigned to the invocation - `command`: executed command string - `working_directory`: resolved working directory - `return_code`: process exit code (non-zero for failure) - `stdout` / `stderr`: aggregated output - `started_at` / `finished_at`: ISO 8601 timestamps - `duration`: execution duration in seconds - `timed_out`: boolean flag indicating whether a timeout occurred While a command runs the server emits stdout and stderr chunks as MCP log messages, preserving ordering through asynchronous streaming. Clients can reuse `command_id` values when making follow-up requests. `cancel_command(command_id: str, signal_value: Optional[str | int])` Sending `cancel_command` forwards a signal (defaulting to `SIGINT`) to the running process identified by `command_id`. The response includes the numeric `signal`, its symbolic `signal_name`, and a `delivered` flag confirming whether the process was still active when the signal was sent. `send_stdin(command_id: str, data: Optional[str], eof: bool = False)` Use `send_stdin` to stream additional input to an interactive command. The tool accepts optional text payloads and an `eof` flag that closes the stdin pipe once all required data has been delivered. The response reports whether the input was accepted so clients can retry or surface helpful diagnostics. `manage_file(path: str, *, operation: str, content: Optional[str] = None, pattern: Optional[str] = None, line: Optional[int] = None, start_line: Optional[int] = None, end_line: Optional[int] = None, encoding: str = "utf-8", create_parents: bool = False, overwrite: bool = False, create_if_missing: bool = True, escape_profile: str = "auto", follow_symlinks: bool = True, use_regex: bool = False, ignore_case: bool = False, max_replacements: Optional[int] = None, anchor: Optional[str] = None, anchor_use_regex: bool = False, anchor_ignore_case: bool = False, anchor_after: bool = False, anchor_occurrence: Optional[int] = None)` `manage_file` powers the `filetool` client command and exposes a broad suite of line-aware editing operations. The `escape_profile` parameter controls how inline `--content` payloads are normalised before they reach the server: - `auto` (default) mirrors the original behaviour and expands `\n`, `\t`, `\r`, and `\0` sequences when the payload would otherwise be a single line. - `none` disables all inline decoding so payloads arrive exactly as typed, perfect for binary-friendly workflows or when backslashes carry semantic meaning. - Additional profiles can be registered by extensions to enforce organisation-specific escaping rules. The selected profile is forwarded to plugins via the `FileOperationEvent` payload so observability tooling can respond appropriately. Recent updates add top-of-file editing and pattern-driven substitutions to the toolbox: - `prepend` injects content at the start of a file and respects `--create-if-missing` so you can bootstrap brand new files with headers in a single command. - `insert` now accepts literal or regex anchors via `--anchor`, `--anchor-after`, `--anchor-ignore-case`, and `--anchor-occurrence`, making it easy to land changes relative to sentinel text without counting lines. - `substitute --pattern PATTERN --content TEXT` performs literal or regex-based replacements while streaming structured metadata (matched pattern, replacement counts, and flags such as `--ignore-case` or `--max-replacements`) back to the caller. Example usages: ```bash # Create a multi-line file from a single-shell command using the default profile. filetool write docs/roadmap.txt --content 'phase-one\\nphase-two\\nphase-three' # Append literal escape sequences without rewriting them by selecting the "none" profile. filetool append docs/roadmap.txt --content 'literal\\nvalue' --escape-profile none # Use stdin for bulk updates while still labelling the request for plugins. cat release.diff | filetool patch docs/roadmap.txt --stdin --escape-profile auto ``` ## Plugins Plugins implement the `PluginProtocol` (via a module-level `PLUGIN` object) and can register `CommandStreamListener` instances to observe command lifecycle events. When the server starts it loads modules listed in `MCP2TERM_PLUGINS`, exposing the entire `mcp2term` namespace through the plugin registry for inspection or extension. A minimal plugin skeleton: ```python from dataclasses import dataclass from mcp2term.plugin import CommandStreamListener, PluginProtocol, PluginRegistry @dataclass class EchoListener(CommandStreamListener): async def on_command_stdout(self, event): print(event.data, end="") async def on_command_start(self, event): print(f"Starting: {event.request.command}") async def on_command_stderr(self, event): print(f"[stderr] {event.data}", end="") async def on_command_complete(self, event): print(f"Finished with {event.return_code}") class ShellEchoPlugin(PluginProtocol): name = "shell-echo" version = "1.0.0" def activate(self, registry: PluginRegistry): registry.register_command_listener(EchoListener()) class AuditListener: async def on_file_operation(self, event): print(f"{event.operation} {event.path}: {event.result.message}") registry.register_file_operation_listener(AuditListener()) PLUGIN = ShellEchoPlugin() ``` Listeners registered through `register_file_operation_listener` receive `FileOperationEvent` instances containing the original request arguments, the resolved path, the `FileOperationResult`, and any warning emitted during processing. This makes it straightforward to build auditing, notification, or synchronization plugins that react to remote edits in real time without modifying the core server. ## Development Run the test suite with: ```bash pytest ``` Tests are parameterised to run with or without dependency stubbing, ensuring full execution paths remain verified. ## Ngrok integration By default `mcp2term` opens an ngrok tunnel whenever you run the server with the `sse` or `streamable-http` transports. The tunnel exposes the local HTTP endpoint using the ngrok agent that must already be authenticated (for example via `ngrok config add-authtoken`). Unless overridden, the server now requests the reserved domain `alpaca-model-easily.ngrok-free.app` so clients always receive a predictable hostname. Control the integration with the following environment variables: | Variable | Description | Default | | --- | --- | --- | | `MCP2TERM_NGROK_ENABLE` | Enable or disable automatic tunnel creation. | `true` | | `MCP2TERM_NGROK_TRANSPORTS` | Comma-separated transports that should be tunnelled (`stdio`, `sse`, `streamable-http`). | `sse,streamable-http` | | `MCP2TERM_NGROK_BIN` | Path to the `ngrok` executable. | `ngrok` | | `MCP2TERM_NGROK_API_URL` | Base URL for the local ngrok API. | `http://127.0.0.1:4040` | | `MCP2TERM_NGROK_REGION` | Optional ngrok region to target. | *(none)* | | `MCP2TERM_NGROK_LOG_LEVEL` | ngrok log level (`debug`, `info`, `warn`, `error`). | `info` | | `MCP2TERM_NGROK_EXTRA_ARGS` | JSON array of additional CLI arguments passed to ngrok. | `[]` | | `MCP2TERM_NGROK_ENV` | JSON object merged into the ngrok process environment. | `{}` | | `MCP2TERM_NGROK_START_TIMEOUT` | Seconds to wait for tunnel provisioning. | `15` | | `MCP2TERM_NGROK_POLL_INTERVAL` | Seconds between tunnel status checks. | `0.5` | | `MCP2TERM_NGROK_REQUEST_TIMEOUT` | HTTP timeout for API calls. | `5` | | `MCP2TERM_NGROK_SHUTDOWN_TIMEOUT` | Seconds to wait for ngrok to terminate gracefully. | `5` | | `MCP2TERM_NGROK_CONFIG` | Optional path to an ngrok configuration file. | *(none)* | | `MCP2TERM_NGROK_HOSTNAME` / `MCP2TERM_NGROK_DOMAIN` / `MCP2TERM_NGROK_EDGE` | Custom host bindings to request from ngrok. | `alpaca-model-easily.ngrok-free.app` for domain | Use the `--disable-ngrok` flag when running `mcp2term` to opt out of tunneling for a single invocation. The configuration also records the directory where the server process was launched and exports it to ``PYTHONPATH``. This mirrors running ``export PYTHONPATH=$(pwd)`` before starting the server so that any Python code executed via ``run_command`` inherits the same module search path even when the working directory is overridden.

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/FreddyE1982/mcp2term'

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