refactor: gpt 스트림 오류 최소화

This commit is contained in:
donghyeon-ka
2026-09-17 15:34:01 +09:00
parent d7ceca39a0
commit 0a8c8e55a8
11 changed files with 957 additions and 29 deletions
+36 -13
View File
@@ -67,7 +67,7 @@ Typical tasks include:
- "Install Node.js packages and build the project."
- "Upload a file, verify its hash, and move it into place."
Internally, these actions are provided through 20 MCP tools for shell execution, long-running processes, and filesystem operations.
Internally, these actions are provided through 24 MCP tools for shell execution, recoverable managed tasks, low-level process control, and filesystem operations.
## How it works
@@ -83,7 +83,8 @@ The MCP transport is stateless, but long-running command sessions are kept in me
## Key features
- Shell commands, complete scripts, builds, tests, package installation, Git, and service management
- Output polling, stdin delivery, and termination control for long-running processes
- Recoverable long-running tasks with stable task keys, duplicate-run protection, compact responses, and full on-disk logs
- Low-level output polling, stdin delivery, and termination control for interactive processes
- Read, write, edit, transfer, and delete host files, including absolute paths
- Built-in static Bearer authentication and OAuth 2.1/DCR/PKCE for compatible MCP clients
- Stateless JSON transport per request, per-process output retention, and response size limits
@@ -93,13 +94,32 @@ The MCP transport is stateless, but long-running command sessions are kept in me
### Execution and processes
- `exec_command`: Run shell commands, builds, tests, package installation, Git, service management, and log inspection
For long-running, non-interactive builds, tests, package installation, Docker builds, migrations, and similar work, prefer the managed-task tools:
- `start_managed_task`: Start a shell task under a stable `taskKey`, persist its complete output to a log file, and reuse an existing retained task instead of accidentally running it twice
- `read_managed_task`: Recover a task by `taskKey`, optionally wait for completion, and return compact status, important diagnostic lines, and a bounded log tail
- `list_managed_tasks`: List running and recently retained tasks by stable key after a client/message-stream interruption
- `cancel_managed_task`: Terminate a managed task by stable key
The low-level process tools remain available when interactive stdin or exact cursor-based output is required:
- `exec_command`: Run a shell command directly and return immediately when it completes or expose its low-level process session
- `run_script`: Run complete scripts with Bash, sh, Node.js, Python, or an arbitrary interpreter
- `write_stdin`: Write input to a long-running process and retrieve subsequent output
- `read_process`: Poll output using a cursor and inspect process termination state
- `terminate_process`: Send `SIGINT`, `SIGTERM`, or `SIGKILL` to a managed process group
- `list_processes`: List running or recently completed process sessions
A typical resilient workflow is:
1. Call `start_managed_task` with a stable key such as `root-ci`.
2. If the client response is interrupted, call `list_managed_tasks` instead of starting the command again.
3. Continue with `read_managed_task` using the same key and a longer `waitMs`.
4. Inspect the returned status/highlights/tail. Read the full log file only when deeper diagnostics are actually needed.
5. Use `restartCompleted=true` only when a fresh run is intentionally required.
Managed task identity is retained in service memory for the same period as process sessions. A service restart therefore loses the `taskKey` mapping even though a previously written temporary log file may still exist.
### Filesystem
- `list_directory`, `stat_path`, `read_file`, `write_file`
@@ -109,7 +129,7 @@ The MCP transport is stateless, but long-running command sessions are kept in me
Relative paths are resolved from `MCP_DEFAULT_CWD`, while absolute paths and `~/...` paths are also allowed. Uploads and downloads use base64 chunk transfer with `nextOffset`.
The server provides 20 tools in total. `remove_path` permanently deletes targets without using a trash folder, and `apply_patch` uses the host's `git apply --unsafe-paths`.
The server provides 24 tools in total. `remove_path` permanently deletes targets without using a trash folder, and `apply_patch` uses the host's `git apply --unsafe-paths`.
### Tool safety and authentication metadata
@@ -117,11 +137,11 @@ Every tool explicitly publishes all four MCP safety hints. The values describe t
| Behavior | Tools | `readOnlyHint` | `destructiveHint` | `idempotentHint` | `openWorldHint` |
|---|---|---:|---:|---:|---:|
| Read-only, closed world | `list_directory`, `stat_path`, `read_file`, `download_file`, `hash_file`, `read_process`, `list_processes` | `true` | `false` | `true` | `false` |
| Read-only, closed world | `list_directory`, `stat_path`, `read_file`, `download_file`, `hash_file`, `read_process`, `list_processes`, `read_managed_task`, `list_managed_tasks` | `true` | `false` | `true` | `false` |
| Additive and idempotent | `make_directory` | `false` | `false` | `true` | `false` |
| Destructive and idempotent | `upload_file`, `copy_path`, `move_path`, `remove_path`, `chmod_path` | `false` | `true` | `true` | `false` |
| Destructive and non-idempotent, closed world | `write_file`, `replace_in_file`, `apply_patch`, `terminate_process` | `false` | `true` | `false` | `false` |
| Destructive and non-idempotent, open world | `exec_command`, `run_script`, `write_stdin` | `false` | `true` | `false` | `true` |
| Destructive and non-idempotent, closed world | `write_file`, `replace_in_file`, `apply_patch`, `terminate_process`, `cancel_managed_task` | `false` | `true` | `false` | `false` |
| Destructive and non-idempotent, open world | `exec_command`, `run_script`, `write_stdin`, `start_managed_task` | `false` | `true` | `false` | `true` |
These annotations are advisory client metadata, not access control. They do not replace authentication, which is enforced by the built-in HTTP layer or an upstream gateway when configured. When built-in OAuth is enabled, every tool advertises the `oauth2` security scheme with the `mcp:tools` scope through `_meta.securitySchemes`. Static-Bearer-only and built-in-auth-disabled (`MCP_ALLOW_NO_AUTH`) deployments intentionally omit this OpenAI extension: a pre-shared Bearer token is neither `noauth` nor `oauth2`, while disabling built-in authentication may represent a deliberately anonymous endpoint, upstream authentication, or private-network access. The process cannot infer that external policy honestly, so authentication, if any, remains connection- or deployment-level.
@@ -142,7 +162,7 @@ These annotations are advisory client metadata, not access control. They do not
- If an older client sends a stale `Mcp-Session-Id` header, the server ignores it for request processing.
- An authenticated `GET /mcp` or `DELETE /mcp` request returning `405 Method Not Allowed` is expected. Missing or invalid authentication may produce `401 Unauthorized` before the request reaches that method check. The server does not maintain a server-push SSE session.
- MCP transport sessions and command process `sessionId` values are unrelated. A process `sessionId` returned by `exec_command` can be reused by later HTTP requests to `write_stdin`, `read_process`, and `terminate_process`.
- Running and retained process state is stored in service memory and is lost when the service restarts.
- Running and retained process state, including managed-task key mappings, is stored in service memory and is lost when the service restarts. Managed-task full logs are written beneath the host temporary directory and are not a durable task registry.
## Requirements
@@ -327,7 +347,8 @@ sudo journalctl -u remote-dev-mcp -o cat | grep '"event":"mcp_request"'
- `Error fetching OAuth configuration`: Check `MCP_OAUTH_ENABLED`, the public URL, and the Nginx proxy for `/.well-known/` routes.
- `401 Unauthorized` on MCP requests: Check the Bearer token or OAuth access token.
- `403 Host header is not allowed`: Add the request domain to `MCP_ALLOWED_HOSTS`.
- A command returns a `sessionId` instead of completing immediately: Poll it with `read_process` or send input with `write_stdin`.
- A managed task is still running: Reuse its `taskKey` with `read_managed_task`; after a client interruption, call `list_managed_tasks` before starting anything again.
- A low-level command returns a `sessionId` instead of completing immediately: Poll it with `read_process` or send input with `write_stdin`.
- MCP requests are independent stateless POST requests. An authenticated `GET /mcp` or `DELETE /mcp` returning `405 Method Not Allowed` is expected and means the server does not provide a separate SSE stream. Authentication failures may return `401 Unauthorized` first.
- Service restart behavior: Managed process state and unexchanged authorization codes are lost. OAuth client registrations and issued tokens remain in the state file.
@@ -342,14 +363,14 @@ npm run build
The default tests use a real Streamable HTTP MCP client and cover:
- Bearer authentication, stateless request processing, and request tracing headers
- Success paths, failure paths, and input boundary cases for all 20 tools
- Success paths, failure paths, and input boundary cases for all 24 tools
- Interactive stdin, output pagination, timeouts, termination, and completed-process retention
- UTF-8 character boundaries, strict base64 validation, file modes, and copy/move conflicts
- Unified diff validation, application, reverse application, and 3-way application
### Full E2E verification against a running external MCP server
From a separate source checkout with development dependencies installed, you can verify all 20 tools against a real HTTPS endpoint:
From a separate source checkout with development dependencies installed, you can verify all 24 tools against a real HTTPS endpoint:
```bash
MCP_E2E_URL='https://mcp.example.com/mcp' \
@@ -396,12 +417,14 @@ This verification executes real commands on the target server and creates, modif
|---|---|
| `src/http-server.ts` | Stateless Streamable HTTP, OAuth routing, and health endpoint |
| `src/mcp-server.ts` | MCP server metadata and tool registration |
| `src/exec-tools.ts` | Command, script, and long-running process tools |
| `src/exec-tools.ts` | Low-level command, script, and process-control tools |
| `src/managed-task-manager.ts` | Stable task-key registry, full task logs, deduplication, recovery, and compact summaries |
| `src/managed-task-tools.ts` | High-level managed-task MCP schemas and tool registration |
| `src/file-service.ts` | File reading, writing, transfer, and path operations |
| `src/file-tools.ts` | Filesystem tools and input schemas |
| `src/oauth.ts` | DCR, PKCE, token issuance/refresh/revocation, and approval UI |
| `deploy/` | systemd, environment-file, and Nginx examples |
| `test/all-tools.integration.test.ts` | E2E tests for all 20 tools and external endpoints |
| `test/all-tools.integration.test.ts` | E2E tests for all 24 tools and external endpoints |
| `test/` | Configuration, file, process, MCP, and OAuth unit/integration tests |
## License