await-mcp
# await-mcp
Block agent execution until a condition is met — no more `sleep N` loops or returning early.
## Problem
When agents run long operations (cloud builds, CI tests, deployments), they either:
1. **`sleep N` then check** — inaccurate, wastes turns, model may give up
2. **Return and let the user remind them** — breaks automation
## Solution
An MCP server that provides **blocking await tools**. The agent calls a tool, and the MCP server blocks (polling internally) until the condition is met. The agent is "stuck" on the tool call until it returns.
```
Agent: Start build → build ID 12345
Agent: Wait for build → await_command("curl -sf .../build/12345 | grep -q done") → BLOCKS
[progress] Check #1 (0s): exit=1, running...
[progress] Check #2 (30s): exit=1, running...
[progress] Check #3 (60s): exit=0, success!
Agent: Build succeeded! Proceeding...
```
## How It Works
### Key insight: MCP tool calls are blocking
MCP clients **block on MCP tool calls** — the agent loop awaits the tool result. The MCP server can hold the connection open as long as needed (up to the configured timeout).
### Progress notifications
The client generates a `progressToken` for each MCP tool call and listens for `notifications/progress`. The server sends progress updates with this token, so the user sees real-time polling status in the UI.
### Timeout configuration
| Level | Default | Configurable via |
|-------|---------|-----------------|
| MCP server (connection-level) | client-dependent | `timeout` in the client's MCP server config |
| Per-tool-call | 1 hour (3600s) | `timeout_seconds` parameter in the tool call |
Set a large connection-level `timeout` in your client config to allow very long operations.
## Tools
### `await_command`
Polls a shell command until it exits with code 0 (success) or 2 (failure).
- **Exit 0**: condition met → return `{ status: "success" }`
- **Exit 2**: condition failed → return `{ status: "failed" }`
- **Other exit code**: still running → keep polling
- **Timeout**: return `{ status: "timeout" }`
```json
{
"command": "curl -sf https://ci.example.com/build/123/status | grep -q done",
"timeout_seconds": 3600,
"interval_seconds": 30
}
```
### `await_url`
Polls a URL until it returns the expected HTTP status code.
```json
{
"url": "http://localhost:3000/health",
"expected_status": 200,
"body_contains": "ready",
"timeout_seconds": 600,
"interval_seconds": 10
}
```
### `await_file`
Waits for a file to exist (and optionally contain specific content).
```json
{
"path": "/tmp/build-status",
"contains": "SUCCESS",
"timeout_seconds": 3600,
"interval_seconds": 10
}
```
## Installation
### 1. Clone and install dependencies
```bash
git clone https://github.com/adlternative/await-mcp.git
cd await-mcp
npm install
```
Requires Node.js 18+ (uses the built-in global `fetch`).
### 2. Register the MCP server with your client
Replace `/path/to/await-mcp` with the absolute path where you cloned the repo.
#### opencode
Add to your `opencode.json` (project) or `~/.config/opencode/opencode.json` (global):
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"await": {
"type": "local",
"command": ["node", "/path/to/await-mcp/server.mjs"],
"enabled": true
}
}
}
```
See the [opencode MCP docs](https://opencode.ai/docs/mcp-servers/) for more options.
#### Claude Code
Register via the CLI:
```bash
claude mcp add await -- node /path/to/await-mcp/server.mjs
```
Or add it manually to your `.mcp.json` (project scope) or `~/.claude.json`:
```json
{
"mcpServers": {
"await": {
"command": "node",
"args": ["/path/to/await-mcp/server.mjs"]
}
}
}
```
#### Qoder CLI
Add to `~/.qoder/settings.json`:
```json
{
"mcpServers": {
"await": {
"command": "node",
"args": ["/path/to/await-mcp/server.mjs"],
"cwd": "/path/to/await-mcp",
"timeout": 7200000,
"alwaysAllow": ["await_command", "await_url", "await_file"]
}
}
}
```
- `timeout: 7200000` — 2 hour max per tool call (overrides the default)
- `alwaysAllow` — skip permission prompts for the await tools
## Usage Examples
### Cloud build
```
Use await_command to wait for the build to complete:
command: "curl -sf https://ci.example.com/build/<id> | jq -e '.status == \"success\"' && exit 0 || exit 1"
interval_seconds: 30
timeout_seconds: 3600
```
### Service health check
```
Use await_url to wait for the service to be ready:
url: "https://my-service.example.com/health"
expected_status: 200
interval_seconds: 10
timeout_seconds: 600
```
### File-based signaling
```
Use await_file to wait for a status file:
path: "/tmp/deploy-status"
contains: "SUCCESS"
interval_seconds: 5
timeout_seconds: 1800
```
## Architecture
```
┌──────────────┐ MCP (stdio) ┌──────────────┐
│ MCP client │ ◄──────────────────► │ await-mcp │
│ (agent) │ │ (server) │
│ │ tools/call ──────► │ │
│ agent │ │ poll loop │
│ blocked │ ◄─ progress notif │ run check │
│ waiting │ │ sleep │
│ │ ◄─ result ──────── │ return │
│ continues │ │ │
└──────────────┘ └──────────────┘
```
### Why not just sleep?
- `sleep N` is a guess — too short and you check too early, too long and you waste time
- Each check is a **separate agent turn**, consuming tokens and risking the model giving up
- `await-mcp` does the polling **inside the MCP server**, not in the agent loop
## Future Improvements
- **`await_webhook`**: Two-phase (register + wait) for push-based notifications from CI/CD
- **WebSocket support**: For real-time push instead of polling
- **Composite conditions**: Wait for multiple conditions (AND/OR)
## License
MIT
TDQS
Scored across 3 tools
Each tool targets a distinct condition type: shell command, URL, or file. The purpose of each is clear, and there is no meaningful overlap in their intended use cases. An agent can easily select the right tool based on the resource it needs to wait on.
All tools follow the consistent verb_noun pattern of 'await_' followed by the target resource (command, url, file). This makes the tool names predictable and easy to remember. The naming convention is uniform throughout the set.
With three tools, the server is well-scoped for its purpose of providing wait-for-condition primitives. Each tool covers a distinct and common category of waiting (shell, HTTP, file), so the count feels neither thin nor excessive for the domain.
The three tools cover the primary methods for blocking on external signals: command exit status, URL availability/response, and file presence/content. This set provides a solid foundation for waiting on common CI/CD, deployment, and build scenarios. While a generic 'sleep' tool is absent, the command-based approach can handle arbitrary delays, making the surface reasonably complete.