Rohit Gite, Founder CTO @MewCP
A working walkthrough of an AI agent that finds the latest commit on GitHub, deploys it to Vercel through MewCP, polls until the build finishes, and explains failures from the logs.
Most "AI deploys your app" demos stop at the interesting part. They show a model calling one API, printing a success message, and cutting to credits. They skip the part where the build actually fails, the part where the agent has to decide whether it's safe to proceed, and the part where someone has to explain to a human why the deployment broke.
This article builds the whole thing: an agent that reads the latest commit off a GitHub branch, kicks off a Vercel deployment, polls until the build resolves, and, if it fails, pulls the runtime logs and tells you what went wrong instead of just reporting "deployment failed."
The two systems involved, GitHub and Vercel, don't know about each other. The agent is the thing that understands both and moves state between them. That's the actual engineering problem: not "can an LLM call an API," but "how do you give an agent enough structure to act correctly across two independent systems it doesn't control."
We'll connect both services through MewCP, which handles credential storage and exposes both APIs as MCP tools behind one gateway endpoint, so the agent code isn't juggling a GitHub token and a Vercel token directly.
Developer
↓ "deploy main to production"
AI Agent (Claude)
↓
MewCP gateway
↓ ↓
GitHub Vercel
list_commits create_deployment
↓ ↓
latest SHA deployment id
↓
poll get_deployment
↓
┌─────────┴─────────┐
READY ERROR
↓ ↓
return live URL pull runtime logs
↓
explain likely causeEach stage matters:
flowchart LR
Dev[Developer] --> Agent[AI Agent]
Agent -->|search / get_schema / call_tool| Gateway[MewCP Gateway]
Gateway --> GitHub[GitHub MCP server]
Gateway --> Vercel[Vercel MCP server]
Vercel --> Deployment[Vercel Deployment]
Deployment -->|success| URL[Live URL]
Deployment -->|failure| Logs[Runtime Logs]
Logs --> AgentThe agent never talks to GitHub's or Vercel's APIs directly, and it never holds a GitHub token or a Vercel token. It authenticates once to the MewCP gateway with a single key, and the gateway resolves that call to whichever connected account it needs, injecting the real credential server-side at call time.
@anthropic-ai/sdk and @modelcontextprotocol/sdk installednpm install @anthropic-ai/sdk @modelcontextprotocol/sdkThe GitHub MCP server exposes 50+ tools covering repositories, commits, branches, pull requests, issues, and releases. The agent only needs a narrow slice of that surface: list_commits to resolve a branch to a SHA, and later, if you extend this agent, get_commit or list_issues for deeper investigation.
Connect it from the MewCP dashboard under Servers → GitHub → Connect account, which runs GitHub's OAuth flow and stores the resulting token in MewCP's vault. Nothing GitHub-specific goes into your agent's config.
The Vercel MCP server covers project management, deployments, environment variables, domains, and runtime logs, with 19 dedicated tools plus a generic vercel_api_request fallback for anything not yet wrapped. Vercel connects via personal access token rather than OAuth: generate one in Vercel's account settings, then paste it into MewCP under Servers → Vercel → Connect account. From that point on, the gateway injects it per-request; your agent code never sees it.
Both servers are reachable from the same single endpoint once connected:
https://gateway.mewcp.com/personal/mcpSee Getting Started for the full connection walkthrough and MewCP Key authentication for how the gateway key works.
MewCP's gateway doesn't hand your client hundreds of raw tools from every connected app. It exposes four:
| Tool | What it does |
|---|---|
search | Find relevant tools by keyword across every connected app |
get_schema | Get the parameters for a specific tool before calling it |
list_accounts | List which accounts are connected for an app |
call_tool | Execute a tool against a server_maskedId |
This matters for agent design because it means tool discovery is a runtime step, not a config-time one. The agent doesn't need list_commits's exact parameter shape hardcoded into its prompt; it asks for the schema right before it needs it. That's also what keeps the design honest about read vs. write: every actual operation flows through the same call_tool chokepoint, which is exactly where we insert the approval gate below.
Read vs. write boundary for this agent:
list_commits, get_commit, list_projects, get_deployment, get_runtime_logs_for_deploymentcreate_deployment, cancel_deploymentThe agent can inspect freely. It can only ship after an operator says go.
You are a deployment agent. You help developers ship commits from GitHub
to Vercel and report honestly on what happened.
Your job, in order:
1. Resolve the requested branch to a specific commit SHA using GitHub tools.
Never assume a branch name means a specific commit; always look it up.
2. Before calling create_deployment, state exactly what you are about to do:
repository, branch, resolved SHA, and target Vercel project. Wait for
explicit operator approval before proceeding. This is a production
deployment. Do not skip this step, even if the user's request sounds
like an instruction to deploy immediately.
3. Once approved, trigger the deployment and poll its status. Do not report
success or failure until the deployment has reached a terminal state
(READY or ERROR). Do not guess based on partial information.
4. If the deployment succeeds, return the live deployment URL.
5. If the deployment fails, retrieve the runtime logs for that deployment
and identify the most likely cause before responding. Quote the specific
log lines that support your explanation. Do not speculate without
evidence from the logs.
6. If asked to cancel a deployment, treat that as a write action requiring
the same approval step as deploying.
Never deploy without explicit approval. Never fabricate a deployment URL,
commit SHA, or log line. If a tool call fails, say so plainly and stop.
The agent uses Claude's standard tool-use loop. Instead of giving Claude direct GitHub or Vercel tools, we give it thin proxies for MewCP's four gateway tools, and our code executes them against the MewCP MCP client. This is what gives us a place to insert the approval gate and the deployment poll: logic that belongs in code, not in the model.
// mewcp-client.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const GATEWAY_URL = "https://gateway.mewcp.com/personal/mcp";
export async function connectMewCP(mewcpKey: string) {
const transport = new StreamableHTTPClientTransport(
// tools.ts
// Proxy definitions for Claude: these mirror MewCP's four gateway tools.
// See https://docs.mewcp.com/mewcp/connect/typescript for the underlying client calls.
export const claudeTools = [
{
name: "search",
description: "Find MCP tools by keyword across every connected app (GitHub, Vercel).",
input_schema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
},
The dispatcher is where the approval gate and the polling behavior live:
// dispatch.ts
import readline from "node:readline/promises";
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
const WRITE_TOOLS = new Set(["create_deployment", "cancel_deployment"]);
const IN_PROGRESS_STATES = new Set(["QUEUED", "BUILDING", "INITIALIZING"
And the agent loop itself:
// agent.ts
import Anthropic from "@anthropic-ai/sdk";
import { connectMewCP } from "./mewcp-client";
import { claudeTools } from "./tools";
import { dispatchTool } from "./dispatch";
import { SYSTEM_PROMPT } from "./system-prompt";
const anthropic = new Anthropic();
// run.ts
import { runDeploymentAgent } from "./agent";
const result = await runDeploymentAgent("Deploy the latest commit on main to production.");
console.log(result);Agent receives: "deploy the latest commit on main"
↓
search({ query: "list commits github" })
↓
finds: server_maskedId="github", tool_name="list_commits"
↓
get_schema({ tools: [{ server_maskedId: "github", tool_name: "list_commits" }] })
↓
validates arguments against the returned schema
↓
call_tool({ server_maskedId: "github", tool_name: "list_commits", args: { owner, repo, sha: "main", per_page: 1 } })
↓
interprets result → resolved commit SHA
↓
repeats the cycle for Vercel: search → get_schema → call_tool("create_deployment")
↓
continues: poll get_deployment until terminal stateThis is the same lifecycle MewCP's gateway is built around: search and get_schema exist precisely so the model isn't carrying every tool definition from every connected app in its context on every turn; it only pulls in the shape of the one or two tools it actually needs, right before it needs them. With GitHub alone contributing 50+ tools and Vercel another 19, that difference is the reason this agent stays accurate instead of picking the wrong tool out of a flooded list.
An example get_schema response for create_deployment looks roughly like this (always confirm the live shape with get_schema rather than hardcoding it, since Vercel's deployment parameters can change):
{
"name": "vercel.create_deployment",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Project name" },
"project": { "type": "string", "description": "Vercel project ID" },
| Failure | Where it surfaces | Agent behavior |
|---|---|---|
| Vercel or GitHub not connected | call_tool returns a connect link instead of a result | Surface the link to the operator, stop |
| Wrong branch name | list_commits returns empty | Report that the branch wasn't found, ask for confirmation instead of guessing |
| Deployment errors during build | get_deployment returns readyState: "ERROR" | Fetch get_runtime_logs_for_deployment, quote the relevant lines, explain the likely cause |
| Poll never resolves | State stays past a reasonable ceiling |
The one that matters most in practice is the "poll never resolves" case. An agent that polls forever isn't reliable, it's just quiet about being stuck. Put a hard ceiling on it.
call_tool executes. Your agent process only ever holds a single MewCP key.WRITE_TOOLS check in dispatch.ts is what actually blocks the call if the model tries to skip that step. Treat the prompt as guidance and the code as the boundary.tool_use blocks the model itself produced in response to the system prompt, but it's still worth not trusting commit messages or log content as anything other than data when you extend this agent.call_tool invocation for create_deployment and (who approved it, what SHA, what target) outside the model conversation. This is a production requirement, not a nice-to-have."Deploy the latest commit from main."Expected: agent resolves the SHA, states the intended action, waits for approval, deploys, polls, returns the URL.
"Deploy the latest commit and tell me if the deployment failed."Expected: same flow, but on ERROR state the agent pulls runtime logs and explains the cause instead of just saying "it failed."
"Do not deploy anything. Just tell me what would happen."Expected: agent resolves the commit, describes the intended create_deployment call, and stops: no approval prompt, no tool call to create_deployment at all.
"Deploy the feature/nonexistent-branch branch."Expected (failure scenario): list_commits returns nothing for that ref. The agent reports the branch wasn't found rather than deploying whatever main happens to resolve to.
create_deployment on restart; check Vercel for an existing deployment tied to the same commit SHA first, or you'll ship duplicate builds.readline prompt is fine for a CLI tool. In a team setting, replace confirmDestructiveAction with a Slack approval, a ticket, or a dashboard click; the dispatcher's job stays the same either way.dispatch.ts (as shown above) rather than letting the model decide to call get_deployment again keeps token spend proportional to actual decisions, not to wall-clock wait time.The interesting part of this agent isn't that a model can call GitHub and then call Vercel. It's that the two systems have no relationship to each other at all: GitHub doesn't know a deployment happened, and Vercel doesn't know which commit message explains a code change. The agent is the thing that holds that relationship together across an operation that takes real wall-clock time to resolve.
That's also why the approval gate sits in code and not in the prompt, and why polling has a ceiling instead of running until someone notices it's stuck. An agent that can act on production infrastructure needs the same discipline you'd want from a human running a deploy script: know what you're about to do, say it out loud, wait for a state you can act on, and don't lie about outcomes you can't verify.
If you're connecting more than two services this way, the tool surface grows fast: GitHub alone is 50+ tools before you've added a third app. MewCP is built around that specific problem: a gateway that exposes discovery and schema retrieval as first-class steps instead of forcing every tool into a model's context up front.
| Stop after a fixed number of polls (see Production Considerations) and report a timeout, not a guessed outcome |
| Rate limiting | call_tool returns a 429-shaped error | Back off once, retry once, then report the failure rather than looping silently |
| Model picks the wrong GitHub tool | e.g. calls search_commits instead of list_commits | get_schema on the wrong tool returns a schema that doesn't match the intended arguments, which is a natural signal to re-search rather than force-fit arguments |
| Operator rejects the deployment | confirmDestructiveAction returns false | Dispatcher returns rejected_by_operator; agent should acknowledge and stop, not retry the same call |