Slack MCP — Python Client + C# Server
A complete Model Context Protocol (MCP) implementation with four architectural layers.
| Layer | Language | File(s) |
|---|---|---|
| 1 — Users | Python 3.11+ | client-python/main.py |
| 2 — Prompt + Agent | Python 3.11+ | client-python/agent/agent.py |
| 3 — MCP Client/Server | Python + C# | client-python/transport/mcp_client.py · server-csharp/ |
| 4 — Slack | C# / .NET 8 | server-csharp/Services/SlackService.cs |
Architecture
👤 User(s)
(human operators / end-users)
│ natural language input
▼
┌──────────────────────────────────────────────────────────┐
│ Prompt + Agent Layer │
│ │
│ Claude (Anthropic) ──or── External Agent │
│ • interprets user intent │
│ • decides which MCP tool(s) to call │
│ • formats results back to the user │
└──────────────────────┬───────────────────────────────────┘
│ tool calls
▼
┌──────────────────────────────────────────────────────────┐
│ Python MCP Client (client-python/transport/) │
│ │
│ McpClient │
│ └── StdioTransport │
│ ├── spawns: dotnet run SlackMcpServer │
│ └── JSON-RPC 2.0 over stdin / stdout │
└──────────────────────┬───────────────────────────────────┘
│ stdio
┌──────────────────────▼───────────────────────────────────┐
│ C# MCP Server (server-csharp/) │
│ │
│ Program.cs (stdio loop) │
│ └── ToolRegistry (dispatch) │
│ └── SlackService (Slack Web API) │
└──────────────────────┬───────────────────────────────────┘
│ HTTPS
┌──────────────────────▼───────────────────────────────────┐
│ Slack │
│ │
│ • send_message → post to channel or thread │
│ • get_history → read recent messages │
│ • list_channels → browse workspace channels │
│ • create_channel → open a new public channel │
│ • add_reaction → emoji-react to a message │
│ • list_users → enumerate workspace members │
└──────────────────────────────────────────────────────────┘
MCP Protocol Flow
Python Client C# Server
│ │
│── initialize ────────────────►│
│◄─ InitializeResult ───────────│
│── initialized (notification) ►│
│── tools/list ────────────────►│
│◄─ ListToolsResult ────────────│
│── tools/call {name, args} ───►│
│◄─ CallToolResult ─────────────│
Layer Responsibilities
Layer 1 — 👤 Users (main.py)
Entry point for human operators. Provides an interactive REPL or single-prompt CLI. Has no knowledge of Slack or MCP — it only calls agent.handle(prompt).
Layer 2 — Prompt + Agent (agent/agent.py)
Interprets natural language intent and decides which MCP tool to call. The _resolve_intent() method is rule-based by default — replace it with a real LLM call (see comments in the file). Formats raw tool output into human-readable responses.
Layer 3 — MCP Transport (transport/mcp_client.py + server-csharp/)
Pure protocol layer. The Python side spawns the C# process and speaks JSON-RPC 2.0 over stdio. The C# side receives requests, dispatches to ToolRegistry, and writes responses. Neither side knows about user intent.
Layer 4 — Slack (server-csharp/Services/SlackService.cs)
Deepest layer. Each of the six methods maps directly to one Slack Web API endpoint. No MCP concepts here — just HTTP calls and typed DTOs.
Prerequisites
| Tool | Version |
|---|---|
| Python | 3.11+ |
| .NET SDK | 8.0+ |
| Slack Bot Token | xoxb-… |
Slack Bot Scopes Required
Go to api.slack.com/apps → OAuth & Permissions → Bot Token Scopes:
| Scope | Used by |
|---|---|
chat:write |
send_message |
channels:history |
get_history |
channels:read |
list_channels |
channels:write |
create_channel |
reactions:write |
add_reaction |
users:read |
list_users |
Quick Start
# 1. Set your Slack bot token
export SLACK_BOT_TOKEN="xoxb-your-token-here"
# 2. Build the C# server
cd server-csharp
dotnet build
# 3. Run the interactive client
cd ../client-python
python main.py --server-cmd "dotnet run --project ../server-csharp/SlackMcpServer.csproj"
# Or run a single prompt
python main.py --prompt "List all channels"
Connecting a Real LLM (Claude)
Open client-python/agent/agent.py and replace _resolve_intent() with:
import anthropic
def _resolve_intent(self, prompt: str) -> Intent:
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=self._client.tools, # pass MCP schemas directly
messages=[{"role": "user", "content": prompt}],
)
tool_use = next(b for b in response.content if b.type == "tool_use")
return Intent(
tool=tool_use.name,
args=tool_use.input,
explanation=prompt,
)
Install the SDK: pip install anthropic
Running Tests
cd client-python
pip install pytest pytest-asyncio
python -m pytest tests/ -v
Tests are split by layer:
tests/test_agent.py— Layer 2: intent resolution and response formattingtests/test_transport.py— Layer 3: JSON-RPC transport and MCP client
Project Structure
mcp-slack/
├── server-csharp/ # Layer 3 (server) + Layer 4
│ ├── SlackMcpServer.csproj
│ ├── Program.cs # stdio JSON-RPC loop
│ ├── Models/
│ │ └── McpModels.cs # JSON-RPC + MCP protocol types
│ ├── Services/
│ │ └── SlackService.cs # Layer 4 — Slack Web API
│ └── Tools/
│ └── ToolRegistry.cs # MCP tool definitions + dispatcher
│
└── client-python/ # Layers 1, 2, 3 (client)
├── main.py # Layer 1 — User entry point
├── agent/
│ ├── __init__.py
│ └── agent.py # Layer 2 — Prompt + Agent
├── transport/
│ ├── __init__.py
│ └── mcp_client.py # Layer 3 — MCP transport
├── tests/
│ ├── conftest.py
│ ├── test_agent.py
│ └── test_transport.py
├── pytest.ini
└── requirements.txt
Troubleshooting
| Issue | Fix |
|---|---|
SLACK_BOT_TOKEN is not set |
export SLACK_BOT_TOKEN="xoxb-..." |
channel_not_found |
Use a Channel ID like C012AB3CD, not #general |
not_in_channel |
Invite the bot to the channel first |
| Server won't start | Run dotnet build in server-csharp/ first |
| Intent not recognized | Extend _resolve_intent() or plug in a real LLM |