Multi-Agent System in Practice: Building an Automated Weekly Report Generator with LangChain + MCP
Why Weekly Reports Need Multi-Agent?
Weekly reports are every developer's pain point — not because they're hard to write, but because collecting data is tedious:
- Pull completed tasks from Jira
- Count code commits from Git
- Get meeting notes from Slack/Teams
- Check production alerts from monitoring
- Finally "polish" it into a format management likes
If one Agent can only do one thing, let multiple Agents collaborate — that's the value of multi-agent systems.
System Architecture
┌─────────────────────────────────────────────────────────────┐
│ Orchestrator Agent │
│ LangChain AgentExecutor │
└──────────┬──────────┬──────────┬──────────┬────────────────┘
│ │ │ │
┌─────▼────┐ ┌───▼────┐ ┌──▼───┐ ┌───▼────┐
│ Data │ │ Code │ │Meeting│ │ Report │
│ Collector│ │ Analyst│ │Summary│ │Generator│
└─────┬────┘ └───┬────┘ └──┬───┘ └───┬────┘
│ │ │ │
┌─────▼──────────▼──────────▼──────────▼────────────────┐
│ MCP Server Layer │
│ [Jira MCP] [Git MCP] [Slack MCP] [Monitor MCP] │
└───────────────────────────────────────────────────────┘
Step 1: Define Agents
1.1 Data Collector Agent
from langchain_openai import ChatOpenAI
from langchain_mcp import MCPToolkit
jira_toolkit = MCPToolkit(url="http://localhost:8081/mcp/sse")
git_toolkit = MCPToolkit(url="http://localhost:8082/mcp/sse")
data_collector_agent = {
"name": "data_collector",
"role": "Data Collection Expert",
"goal": "Collect work data from Jira and Git for this week",
"tools": jira_toolkit.get_tools() + git_toolkit.get_tools(),
"llm": ChatOpenAI(model="gpt-4o", temperature=0)
}
1.2 Code Analyst Agent
code_analyst_agent = {
"name": "code_analyst",
"role": "Code Analysis Expert",
"goal": "Analyze code changes this week and extract key insights",
"tools": git_toolkit.get_tools(),
"llm": ChatOpenAI(model="gpt-4o", temperature=0)
}
1.3 Meeting Summarizer Agent
slack_toolkit = MCPToolkit(url="http://localhost:8083/mcp/sse")
meeting_summarizer_agent = {
"name": "meeting_summarizer",
"role": "Meeting Summary Expert",
"goal": "Extract key decisions and action items from meeting notes",
"tools": slack_toolkit.get_tools(),
"llm": ChatOpenAI(model="gpt-4o", temperature=0)
}
1.4 Report Generator Agent
email_toolkit = MCPToolkit(url="http://localhost:8084/mcp/sse")
report_generator_agent = {
"name": "report_generator",
"role": "Report Generation Expert",
"goal": "Consolidate all data into a well-structured weekly report",
"tools": email_toolkit.get_tools(),
"llm": ChatOpenAI(model="gpt-4o", temperature=0.3)
}
Step 2: Implement MCP Servers
2.1 Jira MCP Server
from mcp.server import Server, Tool
from jira import JIRA
server = Server("jira-mcp-server")
jira_client = JIRA(
server="https://yourcompany.atlassian.net",
basic_auth=("email", "api_token")
)
@server.tool(name="get_completed_issues", description="Get completed Jira issues in a date range")
async def get_completed_issues(start_date: str, end_date: str, assignee: str = None):
jql = f'updated >= "{start_date}" AND updated <= "{end_date}" AND status = Done'
if assignee:
jql += f' AND assignee = "{assignee}"'
issues = jira_client.search_issues(jql, maxResults=50)
results = []
for issue in issues:
results.append({
"key": issue.key,
"summary": issue.fields.summary,
"status": issue.fields.status.name,
"assignee": issue.fields.assignee.displayName if issue.fields.assignee else "Unassigned",
"priority": issue.fields.priority.name,
"updated": issue.fields.updated
})
return {"issues": results, "total": len(results)}
2.2 Git MCP Server
from git import Repo
from datetime import datetime, timedelta
server = Server("git-mcp-server")
@server.tool(name="get_commit_stats", description="Get Git commit statistics for a time range")
async def get_commit_stats(repo_path: str, author: str = None, days: int = 7):
repo = Repo(repo_path)
since = datetime.now() - timedelta(days=days)
commits = list(repo.iter_commits(since=since))
if author:
commits = [c for c in commits if author in c.author.name]
stats = {
"total_commits": len(commits),
"insertions": 0,
"deletions": 0,
"daily_breakdown": {}
}
for commit in commits:
stats["insertions"] += commit.stats.total["insertions"]
stats["deletions"] += commit.stats.total["deletions"]
day = commit.committed_datetime.strftime("%Y-%m-%d")
stats["daily_breakdown"][day] = stats["daily_breakdown"].get(day, 0) + 1
return stats
Step 3: Orchestrate with LangGraph
from langgraph.graph import StateGraph, END
from typing import TypedDict, Dict, Any
class WeeklyReportState(TypedDict):
user: str
week_start: str
week_end: str
jira_data: Dict[str, Any]
git_data: Dict[str, Any]
meeting_data: Dict[str, Any]
report: str
def collect_jira_data(state):
result = data_collector_agent.invoke({
"input": f"Get completed Jira tasks for {state['user']} from {state['week_start']} to {state['week_end']}"
})
state["jira_data"] = result
return state
def collect_git_data(state):
result = code_analyst_agent.invoke({
"input": f"Analyze code commits for {state['user']} from {state['week_start']} to {state['week_end']}"
})
state["git_data"] = result
return state
def collect_meeting_data(state):
result = meeting_summarizer_agent.invoke({
"input": f"Extract meeting key info from {state['week_start']} to {state['week_end']}"
})
state["meeting_data"] = result
return state
def generate_report(state):
prompt = f"""Generate a weekly report based on:
Jira: {state['jira_data']}
Git: {state['git_data']}
Meetings: {state['meeting_data']}
Include: Work summary, Key achievements, Risks, Next week plan"""
result = report_generator_agent.invoke({"input": prompt})
state["report"] = result
return state
workflow = StateGraph(WeeklyReportState)
workflow.add_node("collect_jira", collect_jira_data)
workflow.add_node("collect_git", collect_git_data)
workflow.add_node("collect_meeting", collect_meeting_data)
workflow.add_node("generate_report", generate_report)
workflow.add_edge("collect_jira", "collect_git")
workflow.add_edge("collect_git", "collect_meeting")
workflow.add_edge("collect_meeting", "generate_report")
workflow.add_edge("generate_report", END)
workflow.set_entry_point("collect_jira")
app = workflow.compile()
Step 4: Execute
result = app.invoke({
"user": "zhangsan",
"week_start": "2026-06-05",
"week_end": "2026-06-11",
"jira_data": {},
"git_data": {},
"meeting_data": {},
"report": ""
})
print(result["report"])
Production Enhancements
Scheduled Execution
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
@scheduler.scheduled_job('cron', day_of_week='fri', hour=17, minute=0)
async def weekly_report_job():
users = get_all_team_members()
for user in users:
result = await app.ainvoke({
"user": user,
"week_start": get_week_start(),
"week_end": get_week_end(),
})
await send_report_email(user, result["report"])
scheduler.start()
Pitfalls
Pitfall 1: MCP Server Connection Timeout
Symptom: Agent calls to MCP Server occasionally timeout
Cause: Jira API is slow (P99 > 5s), MCP default timeout is 3s
Fix: Increase MCP Client timeout
toolkit = MCPToolkit(url="http://localhost:8081/mcp/sse", timeout=30)
Pitfall 2: Agent Hallucination — Fabricating Tasks
Symptom: Report generator sometimes "invents" tasks not in the data
Cause: LLM creativity, temperature too high
Fix: Use temperature=0 for data collection, emphasize "only use provided data" in prompt
Pitfall 3: Concurrent MCP Connection Conflicts
Symptom: Connection errors during parallel collection
Cause: Multiple Agents using the same MCP Client
Fix: Each Agent gets its own MCP Client instance
Summary
Multi-agent systems turn weekly report generation from "manual assembly" to "automated pipeline":
- Clear division of labor: Each Agent does one thing well
- MCP decoupling: Agents access tools through MCP Servers, easy to swap and test
- LangGraph orchestration: Supports serial, parallel, and conditional workflows
- Production-ready: Scheduled execution, error retry, hallucination control
Multi-agent isn't "multiple chatbots" — it's "a software system" where each Agent is a microservice and MCP is the API gateway.
Try these browser-local tools — no sign-up required →