refactor: gpt 스트림 오류 최소화
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user