Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Webex Bot MCP — Developer Guide

## Project Overview

A Model Context Protocol (MCP) server that bridges AI assistants with the Webex Teams API.
It exposes tools for managing rooms, messages, and memberships, plus resources (contextual
guides) and prompt templates for common workflows.

## Repository Layout

```
main.py — MCP server entry point; registers all tools/resources/prompts
config.py — WebexConfig dataclass; loaded via get_config() or WebexConfig.from_env()
health_check.py — Standalone diagnostic script; validates env and API connectivity
pyproject.toml — Project metadata and dependencies (uses uv)
Dockerfile — Multi-stage build; non-root user, health checks
docker-compose.yml — Compose stack with optional Prometheus + Grafana

tools/
__init__.py — Re-exports every tool function
common.py — Shared: WebexAPI client, create_error_response, create_success_response,
WebexErrorCodes, version constants
rooms.py — Room/space CRUD (list, create, update, get, delete) + space aliases
messages.py — Message send/list/delete + mention helpers + space aliases
memberships.py — Membership CRUD (list, add, update, delete) + space aliases
people.py — get_webex_me, list_webex_people

tests/
test_messages.py — Unit tests (38 cases); mocks webexpythonsdk at sys.modules level
```

## Key Conventions

### Tool count
31 tools total: 5 room + 5 space-room aliases + 4 message + 2 space-message aliases +
4 membership + 2 space-membership aliases + 2 people + 4 space-room/membership aliases.
Update `tools_count` in the `server_version` resource (`main.py`) when adding tools.

### Error handling — always use structured responses
Every tool must return via `create_error_response` or `create_success_response` from
`tools/common.py`. Never return a bare `{'success': False, 'error': str(e)}` dict.
Map exceptions with a local `_map_exception_to_error(e)` helper (see any tool module for
the pattern).

Error code ranges:
- `E001–E003` — client/argument errors (do not retry)
- `E401–E404` — auth/access errors (do not retry)
- `E500–E504` — server errors (retry with backoff)
- `E600–E602` — Webex-specific errors

### Response shape
```python
# Error
{'success': False, 'error_code': 'E001', 'message': '...', 'timestamp': '<ISO>', 'server_version': '...'}

# Success
{'success': True, 'data': {...}, 'timestamp': '<ISO>', 'server_version': '...', 'metadata': {...}}
```

### Space aliases
Every room/message/membership tool has a "space" alias that delegates to the room variant
and renames keys in the response (`room` → `space`, `rooms` → `spaces`, etc.).
Aliases live in the same module file as the canonical tool.

### `files` parameter
Always wrap a string URL in a list before sending to the SDK:
```python
params['files'] = [files] if isinstance(files, str) else files
```

## Running the Server

```bash
# stdio (default — for Claude Desktop / MCP clients)
uv run python main.py

# HTTP transport
uv run python main.py --transport streamable-http --host 0.0.0.0 --port 8000
```

Required environment variable: `WEBEX_ACCESS_TOKEN`

## Running Tests

```bash
python -m unittest discover -s tests -v
```

Tests mock `webexpythonsdk` via `sys.modules` so no real credentials are needed.

## Adding a New Tool

1. Implement the function in the appropriate `tools/*.py` module.
2. Use `create_error_response` / `create_success_response` for all returns.
3. Add a `_map_exception_to_error` call in the `except` block.
4. Export from `tools/__init__.py`.
5. Register with `mcp.tool()(your_function)` in `main.py`.
6. Update `tools_count` in the `server_version` resource in `main.py`.
7. Add unit tests in `tests/test_messages.py` (or a new test file).

## Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `WEBEX_ACCESS_TOKEN` | ✅ | — | Bot token from developer.webex.com |
| `WEBEX_DEBUG` | | `false` | Enable debug logging |
| `WEBEX_RATE_LIMIT_MESSAGES_PER_SECOND` | | `10` | Message rate limit |
| `WEBEX_RATE_LIMIT_API_CALLS_PER_MINUTE` | | `300` | API rate limit |
| `WEBEX_TIMEOUT_SECONDS` | | `30` | HTTP timeout |
| `LOG_LEVEL` | | `INFO` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
| `LOG_FORMAT` | | `text` | `text` or `json` |
| `METRICS_ENABLED` | | `false` | Enable metrics endpoint |
| `METRICS_ENDPOINT` | | — | Prometheus push endpoint |

## Dependency Management

Uses `uv`. The declared runtime dependency for dotenv is `python-dotenv>=1.0.0`
(not the unrelated `dotenv` PyPI package). After changing `pyproject.toml` run:
```bash
uv lock && uv sync
```
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def validate(self) -> list[str]:
if self.timeout_seconds <= 0:
issues.append("Timeout seconds must be positive")

if self.log_level not in ["DEBUG", "INFO", "WARN", "ERROR"]:
if self.log_level not in ["DEBUG", "INFO", "WARNING", "ERROR"]:
issues.append("Log level must be one of: DEBUG, INFO, WARN, ERROR")

if self.log_format not in ["text", "json"]:
Expand Down
31 changes: 18 additions & 13 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,32 @@
# Import all tool functions from the tools package
from tools import (
# Room functions
list_webex_rooms, create_webex_room, update_webex_room, get_webex_room,
# Space aliases
list_webex_spaces, create_webex_space, update_webex_space, get_webex_space,
list_webex_rooms, create_webex_room, update_webex_room,
get_webex_room, delete_webex_room,
# Space aliases
list_webex_spaces, create_webex_space, update_webex_space,
get_webex_space, delete_webex_space,
# Message functions
send_webex_message, list_webex_messages, send_webex_message_with_mentions,
send_webex_message, send_webex_message_with_mentions,
list_webex_messages, delete_webex_message,
# Space message aliases
send_webex_space_message, list_webex_space_messages,
# Membership functions
list_webex_memberships, add_webex_membership, update_webex_membership,
list_webex_memberships, add_webex_membership,
update_webex_membership, delete_webex_membership,
# Space membership aliases
list_webex_space_memberships, add_webex_space_membership,
# People functions
get_webex_me, list_webex_people
get_webex_me, list_webex_people,
)

# Import version and error handling from common
from tools.common import MCP_SERVER_VERSION, MCP_SPEC_VERSION

# Load environment variables from .env file
# Load environment variables before importing tools (tools/common.py reads them at import time)
load_dotenv()

# Validate required environment variables
webex_access_token = os.getenv("WEBEX_ACCESS_TOKEN")
if not webex_access_token:
raise ValueError("WEBEX_ACCESS_TOKEN environment variable is required")

# Initialize FastMCP with a name for the bot
mcp = FastMCP("Webex Bot MCP")
Expand All @@ -56,17 +57,20 @@
mcp.tool()(create_webex_room)
mcp.tool()(update_webex_room)
mcp.tool()(get_webex_room)
mcp.tool()(delete_webex_room)

# Space aliases (same functionality as rooms but with "space" terminology)
mcp.tool()(list_webex_spaces)
mcp.tool()(create_webex_space)
mcp.tool()(update_webex_space)
mcp.tool()(get_webex_space)
mcp.tool()(delete_webex_space)

# Message management tools
mcp.tool()(send_webex_message)
mcp.tool()(send_webex_message_with_mentions)
mcp.tool()(list_webex_messages)
mcp.tool()(delete_webex_message)

# Space message aliases
mcp.tool()(send_webex_space_message)
Expand All @@ -76,6 +80,7 @@
mcp.tool()(list_webex_memberships)
mcp.tool()(add_webex_membership)
mcp.tool()(update_webex_membership)
mcp.tool()(delete_webex_membership)

# Space membership aliases
mcp.tool()(list_webex_space_memberships)
Expand Down Expand Up @@ -767,12 +772,12 @@ def server_version():
"mcp_version": MCP_SPEC_VERSION,
"api_version": "v1",
"supported_features": [
"tools", "resources", "prompts",
"tools", "resources", "prompts",
"streamable-http", "stdio",
"error-handling", "versioning"
],
"tools_count": 26,
"resources_count": 8,
"tools_count": 31,
"resources_count": 10,
"prompts_count": 7,
"breaking_changes": {
"1.0.0": [
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "webex-bot-mcp"
version = "0.1.0"
description = "Add your description here"
version = "1.0.0"
description = "Model Context Protocol server for Webex Teams — manage rooms, messages, and memberships via AI assistants"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"dotenv>=0.9.9",
"python-dotenv>=1.0.0",
"fastmcp>=2.9.0",
"webexpythonsdk>=2.0.4",
]
Empty file added tests/__init__.py
Empty file.
Loading