A reusable MCP (Model Context Protocol) notification server designed to be the shared communication layer in a multi-agent architecture. Built as the natural evolution of the AI Deployment Agent (Approach B).
Any AI agent that needs to send notifications connects to this server as a dependency rather than implementing notification logic itself. One server, all channels, shared by all agents.
In the AI Deployment Agent, email notification was implemented directly inside the deployment MCP server. This works well for a single agent. But as a system grows to multiple agents (deployment, incident response, monitoring, alerts), each agent duplicating notification logic is the wrong pattern.
The correct architecture is a dedicated notification server that all agents share:
Deployment Agent ----+
Incident Agent ----|----> MCP Notification Server
Monitoring Agent ----+ (this repo)
Alert Agent ----+
This is the MCP composition pattern: multiple single-responsibility servers, each doing one thing well, orchestrated by the agent layer.
| Tool | Purpose |
|---|---|
send_email |
Send email via SMTP (plain text or HTML) |
send_slack_message |
Post message to a Slack channel |
send_teams_message |
Post a card to Microsoft Teams via webhook |
schedule_reminder |
Schedule a notification to fire after N minutes |
list_reminders |
List all scheduled and fired reminders |
cancel_reminder |
Cancel a pending reminder by ID |
get_notification_history |
Query audit log with optional filters |
| Component | Technology |
|---|---|
| MCP server | FastMCP (mcp>=1.25) |
| Python smtplib + SMTP (Gmail, Outlook, or custom) | |
| Slack | Slack Web API (chat.postMessage) |
| Teams | Incoming webhook (MessageCard) |
| Reminders | Python threading + in-memory store |
| Agent consumption | LangChain ReAct + LangGraph ReAct |
| LLM | GPT-4o (temperature=0) |
mcp-notification-server/
server.py FastMCP server -- all 8 tools defined here
email_tool.py SMTP email sending logic
slack_tool.py Slack Web API message sending
teams_tool.py Teams webhook message sending
reminder_tool.py In-memory reminder scheduler
history_tool.py Audit log reader with filtering
config.py Centralized environment variable config
examples/
langchain/
langchain_agent.py LangChain ReAct agent consuming this server
langgraph/
langgraph_agent.py LangGraph ReAct agent consuming this server
requirements.txt
Dockerfile
.env.example
logs/ Audit trail of all notifications sent
git clone https://github.com/ChandraMohanBusam/mcp-notification-server
cd mcp-notification-server
pip install -r requirements.txtcp .env.example .envFill in your values. Minimum required for email:
SMTP_USER=notifications@your-company.com
SMTP_PASSWORD=your-gmail-app-password
EMAIL_FROM=notifications@your-company.comFor Slack, add:
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_DEFAULT_CHANNEL=#generalFor Teams, add:
TEAMS_WEBHOOK_URL=https://outlook.office.com/webhook/your-urluvicorn server:mcp --host 0.0.0.0 --port 8002- Make the server publicly accessible via ngrok:
ngrok http 8002 - In claude.ai: Settings > Integrations > Add MCP server
- URL:
https://your-ngrok-url.ngrok.io/sse
Now you can say to Claude: "Send an email to team@company.com about the deployment."
from langchain.agents import initialize_agent, AgentType
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.tools import load_mcp_tools
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def run():
async with streamablehttp_client("http://localhost:8002/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# Loads all tools from MCP server automatically
tools = await load_mcp_tools(session)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = initialize_agent(
tools, llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
result = await agent.arun(
"Send an email to team@company.com that deployment 2026.3.0.71 succeeded"
)
print(result)The key line is load_mcp_tools(session). LangChain reads the MCP server's tool manifest and automatically creates LangChain Tool objects. No manual tool definitions needed on the client side.
See the full example: examples/langchain/langchain_agent.py
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def run():
async with streamablehttp_client("http://localhost:8002/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# Same tool loading as LangChain
tools = await load_mcp_tools(session)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# LangGraph: create_react_agent instead of initialize_agent
agent = create_react_agent(llm, tools)
# LangGraph: ainvoke with messages list instead of arun
result = await agent.ainvoke({
"messages": [{"role": "user", "content":
"Send a Slack message to #deployments that 2026.3.0.71 is live, "
"then email team@company.com the same update."
}]
})
print(result["messages"][-1].content)See the full example with memory checkpointing: examples/langgraph/langgraph_agent.py
Both consume this MCP server identically via load_mcp_tools(). The difference is in the agent layer above.
| LangChain ReAct | LangGraph ReAct | |
|---|---|---|
| Agent creation | initialize_agent(tools, llm, ...) |
create_react_agent(llm, tools) |
| Invocation | agent.arun(prompt) |
agent.ainvoke({"messages": [...]}) |
| Memory/state | Manual implementation | Built-in checkpointer support |
| Human-in-loop | Manual implementation | Built-in interrupt support |
| Best for | Simple conversational tasks | Structured multi-step flows |
Use LangChain when the task is straightforward and conversational. Use LangGraph when you need memory between turns, human approval checkpoints, or guaranteed step ordering.
"Send an email to team@company.com about the deployment"
"Post a Slack message to #alerts that the service is down"
"Send a Teams alert with red color that the database is unreachable"
"Remind me in 30 minutes via Slack to check the deployment logs"
"What reminders do I have scheduled?"
"Cancel reminder a1b2c3d4"
"Did the email to team@company.com go out successfully?"
"Show me failed notifications from today"
All notifications are logged to logs/notifications.log:
2026-04-25 10:15:32 [INFO] [EMAIL] SUCCESS | to=team@company.com | subject=Deployment Complete
2026-04-25 10:15:45 [INFO] [SLACK] SUCCESS | channel=#deployments
2026-04-25 10:15:52 [INFO] [TEAMS] SUCCESS | title=Deployment Success
2026-04-25 10:16:00 [INFO] [REMINDER] SCHEDULED | id=a1b2c3d4 | delay=30min | channel=slack
2026-04-25 10:46:01 [INFO] [REMINDER] FIRED | id=a1b2c3d4
The get_notification_history tool reads this log and answers natural language questions about it.
This server is Approach B from the AI Deployment Agent.
In that project, email notification was implemented directly inside the deployment MCP server (Approach A). This works for a single agent. When multiple agents need notifications, this standalone server becomes the shared layer that all agents connect to.
Approach A (single agent): Notification logic lives inside each agent's MCP server.
Approach B (this repo): Notification logic lives in one shared MCP server. All agents connect to it as a dependency. One place to update, one audit log for all notifications across all agents.
docker build -t mcp-notification-server .
docker run -d \
--env-file .env \
-v $(pwd)/logs:/app/logs \
-p 8002:8002 \
mcp-notification-serverGmail requires an App Password for SMTP access.
- Go to your Google Account
- Security > 2-Step Verification > App passwords
- Create a new app password for Mail
- Use the 16-character password as SMTP_PASSWORD in your .env
MIT License. Use freely, adapt to your needs, contributions welcome.