djav1985/v-mcp-copilot-feedback
If you are the rightful owner of v-mcp-copilot-feedback and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
The MCP Human Handoff Server is designed to facilitate seamless interaction between coding agents and human reviewers, allowing agents to escalate questions for human review through a secure and efficient process.
MCP Human Handoff Server
This project contains an MCP server that lets coding agents (such as GitHub Copilot agents) escalate
questions to humans for review. The server exposes an MCP tool (ask_question) and resource
(resource://get_reply/{question_id}/{auth_key}) for the agent while simultaneously serving a secure
Flask web UI for human reviewers. Notifications are delivered through Pushover so that humans can
respond quickly.
The implementation follows the official MCP Python SDK and is designed to run locally via Docker Compose or in production with HTTPS enabled.
Features
- Agent integration – Agents call the
ask_questiontool to submit a question and receive poll instructions, including anauth_keythat must be supplied when reading theget_replyresource. - Secure reply polling –
resource://get_reply/{question_id}/{auth_key}returns the human response once available and gracefully handles expiry after a configurable TTL (5 minutes by default). - Human review UI –
/answer_question/<auth_key>/<question_id>serves a styled HTML form that validates credentials, shows preset answers, and accepts custom free-text answers. - Pushover notifications – Each question triggers a push notification that embeds a secure review link for the human reviewer.
- In-memory TTL storage – Questions and answers are maintained in memory with per-question expiry and immutable replies.
- Configurable TTL overrides – Individual questions can customize their TTL (including zero-second expirations) for tight workflows and testing scenarios.
- Consistent polling metadata – Shared helpers keep MCP poll instructions and reply templates aligned between tools and resources.
Project Structure
mcp-server/
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── CHANGELOG.md
├── README.md
├── server/
│ ├── main.py
│ ├── mcp_server.py
│ ├── flask_server.py
│ ├── requirements.txt
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── ask_question.py
│ │ └── get_reply.py
│ ├── user/
│ │ ├── __init__.py
│ │ ├── answer_form.html
│ │ └── style.css
│ └── utility/
│ ├── __init__.py
│ ├── config.py
│ ├── context_manager.py
│ └── pushover.py
└── test/
├── __init__.py
├── conftest.py
├── test_mcp_server.py
├── test_tools.py
├── test_user.py
└── test_utility.py
The explicit server/utility/__init__.py ensures the module can be imported directly, e.g.
from server.utility.config import get_config.
Configuration
Set the following environment variables. When using Docker Compose, these defaults are
defined inline, but you can override them or provide a .env file for local development:
| Variable | Description | Default |
|---|---|---|
PUSHOVER_TOKEN | Application token for Pushover | unset (notifications disabled) |
PUSHOVER_USER | User or group key for Pushover | unset |
SERVER_URL | Base URL used in notification links | http://localhost:8000 |
MCP_API_KEY | Shared secret that clients must send via the X-API-Key header | required |
QUESTION_TTL_SECONDS | TTL before a pending question expires | 300 |
POLL_INTERVAL_SECONDS | Poll frequency hint sent to agents | 30 |
FALLBACK_ANSWER | Reply returned when TTL expires | Sorry, no human could be reached. Please use your best judgment. |
FLASK_HOST / FLASK_PORT | Bind address and port for the Flask UI | 0.0.0.0 / 8000 |
MCP_HOST / MCP_PORT | Bind address and port for the MCP server | 0.0.0.0 / 8765 |
MCP_TRANSPORT | Transport used by server.run() (stdio, sse, or streamable-http) | streamable-http |
Local Development
-
Create a virtual environment and install dependencies:
python -m venv .venv source .venv/bin/activate pip install -e .[dev] -
Provide a
.envfile with the variables above (at minimumMCP_API_KEY). -
Run the Flask and MCP servers together:
python -m server.mainThe MCP server listens on
MCP_PORTwhile the human review UI is served onFLASK_PORT. -
Run the automated checks:
ruff check . pytest
Docker Compose
A ready-to-run Compose file is provided:
docker compose up --build
This launches the MCP transport alongside the Flask UI in a single container. Expose the HTTP
endpoints internally or terminate TLS at an upstream proxy for production deployments. The Compose
file now ships with sensible defaults for every runtime variable, so you can start the stack without
maintaining a separate .env file.
Docker Image Publishing
GitHub Actions automation in builds the project image and, when appropriate, pushes it to Docker Hub. The workflow:
- Runs on pull requests targeting
mainordevto ensure Docker changes build cleanly. - Publishes images for pushes to the
mainanddevbranches. - Tags each published image with the branch name (for example,
mainordev) and the commit SHA—no rollinglatesttag is produced.
To enable publishing, configure the following repository secrets with your Docker Hub credentials:
| Secret | Purpose |
|---|---|
DOCKERHUB_USERNAME | Docker Hub username or organization name. |
DOCKERHUB_TOKEN | Access token or password with permission to push images. |
DOCKERHUB_REPOSITORY | Target repository in username/image format. |
After the secrets are set, pushes to main or dev automatically update the Docker Hub image. You
can also trigger the workflow manually from the GitHub Actions tab to produce ad-hoc builds from
either branch.
MCP Workflow Summary
- Agent calls
ask_question– The tool generates aquestion_idandauth_key, stores the request with a TTL, triggers a Pushover notification, and tells the agent to poll theget_replyresource every 30 seconds. - Human answers via Flask UI – The notification link opens the review page where the human can select a preset answer or provide a free-text response.
- Agent polls
resource://get_reply/{question_id}/{auth_key}– Before an answer arrives, the resource returns a pending payload. Once answered, the response is immutable. If the TTL expires first, an"expired"status is returned with the configured fallback answer.
Running Tests
ruff check .
pytest
Both commands must succeed before submitting changes.
License
This project is released under the .