Code-review agent as an MCP tool#
Build a LangGOAP agent that reviews GitHub pull requests and expose it as an MCP tool so any MCP client (Claude Desktop, Cursor, …) can call it on demand.
The layering: LangGraph already serves MCP at the deployment
layer. Every langgraph dev / langgraph deploy deployment
serves /mcp routes by default (see the disable_mcp field in the
langgraph-cli schema, which defaults to False). LangGOAP does
not ship a parallel MCP server; instead it provides a one-command
scaffolder that turns a GoapGraph into a langgraph dev-ready
deployment directory.
Three steps:
Define a small LangGOAP code-review agent.
Run
langgoap deploy-init(orscaffold_deployment) to generate a deployable LangGraph directory.Run
langgraph devand point an MCP client at the resulting endpoint.
Steps 1 and 2 execute inside this notebook. Step 3 is documented
with snippets — langgraph dev is a long-running server and an
MCP client is a separate process, so they can’t run in a Jupyter
cell.
Backed by tests/integration/test_langgraph_deploy_scaffold.py.
Step 1 — define the code-review agent#
A four-action plan:
Action |
Preconditions |
Effect |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
We define the agent as a no-argument factory function. This is the
shape langgraph dev expects: a callable that returns a configured
graph the entrypoint can compile.
from typing import Any
from langgoap import ActionSpec, GoalSpec, GoapGraph
def build_graph() -> GoapGraph:
"""Build the code-review GoapGraph.
The actions below stub their effects so the notebook is runnable
without GitHub credentials. In production each ``execute`` would
call the real API (octokit / gh CLI / a LangChain tool) and emit
its result into world_state.
"""
actions = [
ActionSpec(
name='fetch_diff',
preconditions={'pr_url': True},
effects={'diff_fetched': True},
execute=lambda ws: {
'diff_fetched': True,
'diff_text': '# (real impl: fetch from GitHub)',
},
),
ActionSpec(
name='analyze_diff',
preconditions={'diff_fetched': True},
effects={'analysis_complete': True},
execute=lambda ws: {'analysis_complete': True},
),
ActionSpec(
name='run_static_checks',
preconditions={'diff_fetched': True},
effects={'static_checks_done': True},
execute=lambda ws: {'static_checks_done': True},
),
ActionSpec(
name='draft_review',
preconditions={
'analysis_complete': True,
'static_checks_done': True,
},
effects={'review_drafted': True},
execute=lambda ws: {
'review_drafted': True,
'review_text': 'LGTM with minor suggestions.',
},
),
ActionSpec(
name='post_review',
preconditions={'review_drafted': True},
effects={'review_posted': True},
execute=lambda ws: {'review_posted': True},
),
]
return GoapGraph(actions=actions)
# Quick sanity check inside the notebook.
graph = build_graph().compile()
result = graph.invoke({
'goal': GoalSpec(conditions={'review_posted': True}),
'world_state': {'pr_url': True},
})
print(f"status: {result['status']}")
print(f"steps: {[h.action_name for h in result['execution_history']]}")
status: goal_achieved
steps: ['fetch_diff', 'analyze_diff', 'run_static_checks', 'draft_review', 'post_review']
Step 2 — scaffold the deployment#
scaffold_deployment (or langgoap deploy-init from the CLI) emits
the four files langgraph dev needs:
langgraph.json— deployment config; points at the entrypoint.graph.py— entrypoint that imports the user factory and binds module-levelgraph = factory().compile()..env.example— environment variables to fill in.README.md— three-step run instructions including Claude Desktop config.
We scaffold into a temp directory for the demo; in real use you’d scaffold into a sibling directory of your agent module.
import tempfile
from pathlib import Path
from langgoap import scaffold_deployment
tmp_root = Path(tempfile.mkdtemp(prefix='code_review_deploy_'))
deploy_dir = tmp_root / 'code_review_deploy'
scaffold_deployment(
deploy_dir,
# Replace ``__main__`` with the real importable module path of
# ``build_graph`` in your project (e.g. ``my_agent.review:build_graph``).
graph_factory_module='my_agent.review',
graph_factory_attr='build_graph',
agent_name='code_review',
agent_description=(
'A LangGOAP agent that reviews GitHub pull requests by '
'fetching the diff, analyzing it, running static checks, '
'drafting a review, and posting it back to GitHub.'
),
extra_dependencies=('langchain-openai>=0.3', 'PyGithub>=2.1'),
)
print('Files written:')
for f in sorted(deploy_dir.iterdir()):
print(f' {f.relative_to(tmp_root)}')
Files written:
code_review_deploy/.env.example
code_review_deploy/README.md
code_review_deploy/graph.py
code_review_deploy/langgraph.json
Inspect the generated langgraph.json#
This is the file langgraph dev reads on startup. Note graphs.code_review
points at ./graph.py:graph — the symbol the entrypoint binds. MCP
routes are enabled by default (no disable_mcp: true here).
print((deploy_dir / 'langgraph.json').read_text())
{
"python_version": "3.12",
"graphs": {
"code_review": "./graph.py:graph"
},
"dependencies": [
"langgoap",
"langchain-openai>=0.3",
"PyGithub>=2.1"
],
"env": ".env"
}
Inspect the generated graph.py entrypoint#
print((deploy_dir / 'graph.py').read_text())
"""LangGraph deployment entrypoint.
Generated by ``langgoap deploy-init`` /
``langgoap.integrations.langgraph_deploy.scaffold_deployment``.
``langgraph dev`` and ``langgraph deploy`` look for the
module-level ``graph`` symbol referenced from ``langgraph.json``.
We import the user-supplied factory, call it to construct a
configured :class:`~langgoap.graph.builder.GoapGraph`, then
compile it — the resulting :class:`CompiledStateGraph` is what
LangGraph serves over its routes (including ``/mcp``).
"""
from __future__ import annotations
from my_agent.review import build_graph
# Build + compile. Add ``checkpointer=...`` to .compile() if your
# agent needs persistence across requests.
_goap_graph = build_graph()
graph = _goap_graph.compile()
Step 3 — run langgraph dev (manual)#
From the deployment directory:
cd code_review_deploy
cp .env.example .env # fill in OPENAI_API_KEY, GITHUB_TOKEN, etc.
langgraph dev
langgraph dev brings up the LangGraph API server on http://127.0.0.1:2024.
MCP routes are at /mcp. Verify with curl:
curl http://127.0.0.1:2024/mcp/tools | jq
You should see code_review listed as an MCP tool with the input schema
derived from the graph’s GoapState TypedDict.
Step 4 — point Claude Desktop at the MCP endpoint#
Add to ~/.config/Claude/claude_desktop_config.json (macOS:
~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"code_review": {
"transport": {
"type": "http",
"url": "http://127.0.0.1:2024/mcp"
}
}
}
}
Restart Claude Desktop. The code_review tool now appears in
Claude’s tool list. Ask:
“Review PR #1234 in repo LangGOAP/LangGOAP.”
Claude calls the MCP tool, which hits langgraph dev’s /mcp
endpoint, which invokes the LangGOAP code_review graph. The
graph plans (fetch_diff → analyze → run_static_checks → draft_review → post_review), executes each action, and returns the
final world_state — review posted to GitHub.
What just happened (architecturally)#
LangGOAP wrote zero MCP code. The transport, tool listing, request lifecycle, and JSON-RPC framing are all LangGraph’s responsibility — exactly the layering CLAUDE.md’s first-class LangChain ecosystem citizen rule prescribes.
The user-facing surface from LangGOAP is two things: the
GoapGraphbuilder you already write, and thescaffold_deploymenthelper that gets you to a deployable directory in one call.Every other MCP feature LangGraph adds in the future (streaming progress events, capability negotiation, multi-tool deployments, authentication) flows through automatically — we don’t have to re-implement them in LangGOAP.
For deployments that need richer MCP tool descriptions than the TypedDict-derived defaults (e.g. semantic info from
GoalSpec), the right home for that enhancement is upstream inlanggraph-apirather than a parallel layer here.
See langgoap/integrations/langgraph_deploy.py for the scaffolder
source and tests/integration/test_langgraph_deploy_scaffold.py
for the integration-test contract this notebook builds on.
Next steps#
Replace the stub
executecallables with real GitHub / LangChain tool calls; the scaffolder makes no assumption about action implementations.Pair
langgoap deploy-initwithlanggraph devfor local iteration; switch tolanggraph deployfor hosted production deployments. Both serve/mcpby default.See
examples/tutorials/deepagents_integration.ipynbfor embedding aGoapGraphas a tool inside another agent instead of (or in addition to) deploying it standalone.