Skip to content

Commit d0db9d8

Browse files
committed
fix(teams): remove create_webex_team; add bot-access guidance
Bots cannot create teams — POST /teams returns 401 for bot tokens regardless of scopes (verified live against the Webex API). This is a hard Webex platform restriction, not a configuration issue. Changes: - Remove create_webex_team (7 tools from 8; tools_count 35 → 34) - Update _map_exception_to_error: 404 now hints at bot membership requirement; 403 now mentions moderator role requirement - Add bot-access notes to all 7 remaining team tool docstrings - Add webex://help/teams resource explaining the full bot access model: what bots can/cannot do, how to get added to a team, 404 vs 403 semantics, and a typical workflow - Update CLAUDE.md: tool count, teams.py layout entry, explicit note on why create_webex_team is absent - Update tests: remove TestCreateWebexTeam; add TestBotAccessModel verifying create_webex_team is not exported and that 404/403 error messages mention membership/moderator requirements https://claude.ai/code/session_01NyYkRAictNQPcdWpepBbT3
1 parent cedd516 commit d0db9d8

5 files changed

Lines changed: 173 additions & 83 deletions

File tree

CLAUDE.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ src/webex_bot_mcp/
2929
Adaptive Card send/build tools
3030
memberships.py — Membership CRUD (list, add, update, delete) + space aliases
3131
people.py — get_webex_me, list_webex_people
32+
teams.py — Team tools (list, get, update, delete) + team membership tools
33+
Note: create_webex_team is intentionally absent — bots cannot
34+
create teams (Webex returns 401); only user/integration tokens can.
3235
3336
tests/
3437
test_messages.py — Unit tests (85 cases); mocks webexpythonsdk at sys.modules level
38+
test_teams.py — Unit tests (40 cases); same mock pattern
3539
3640
.github/
3741
workflows/
@@ -44,11 +48,16 @@ tests/
4448
## Key Conventions
4549

4650
### Tool count
47-
27 tools total: 5 room + 5 space-room aliases + 4 message + 2 space-message aliases +
51+
34 tools total: 5 room + 5 space-room aliases + 4 message + 2 space-message aliases +
4852
2 adaptive-card + 1 adaptive-card-space alias + 1 adaptive-card-builder +
49-
4 membership + 2 space-membership aliases + 2 people.
53+
4 membership + 2 space-membership aliases + 2 people +
54+
4 team + 3 team-membership.
5055
Update `tools_count` in the `server_version` resource (`src/webex_bot_mcp/main.py`) when adding tools.
5156

57+
Note: `create_webex_team` is intentionally not implemented. Bots cannot create teams —
58+
the Webex platform returns 401 for `POST /teams` with bot tokens regardless of scopes.
59+
Only user tokens or OAuth integrations with `spark:teams_write` can create teams.
60+
5261
### Error handling — always use structured responses
5362
Every tool must return via `create_error_response` or `create_success_response` from
5463
`tools/common.py`. Never return a bare `{'success': False, 'error': str(e)}` dict.

src/webex_bot_mcp/main.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
# People functions
5050
get_webex_me, list_webex_people,
5151
# Team functions
52-
list_webex_teams, create_webex_team, get_webex_team,
52+
list_webex_teams, get_webex_team,
5353
update_webex_team, delete_webex_team,
5454
list_webex_team_memberships, add_webex_team_membership,
5555
delete_webex_team_membership,
@@ -126,7 +126,6 @@ async def dispatch(self, request, call_next):
126126

127127
# Team management tools
128128
mcp.tool()(list_webex_teams)
129-
mcp.tool()(create_webex_team)
130129
mcp.tool()(get_webex_team)
131130
mcp.tool()(update_webex_team)
132131
mcp.tool()(delete_webex_team)
@@ -353,6 +352,89 @@ def webex_adaptive_cards_guide():
353352
"""
354353

355354

355+
@mcp.resource("webex://help/teams")
356+
def webex_teams_guide():
357+
"""Bot access model and guidance for Webex Teams tools"""
358+
return """# Webex Teams — Bot Access Guide
359+
360+
## What is a Team in Webex?
361+
362+
A team is a named group of people with a shared set of rooms. Teams are a higher-level
363+
construct than rooms: a team contains rooms, and all team members can see every room in
364+
the team. The team membership and team room membership are separate concepts.
365+
366+
## Bot Access Model (Verified Against the Webex API)
367+
368+
Bot tokens operate under stricter rules than user or integration tokens for the Teams API.
369+
Understanding these rules prevents confusing errors.
370+
371+
### What bots CAN do
372+
373+
| Tool | Requirement |
374+
|------|-------------|
375+
| `list_webex_teams` | Bot is a member of at least one team |
376+
| `get_webex_team` | Bot is a member of that team |
377+
| `list_webex_team_memberships` | Bot is a member of that team |
378+
| `update_webex_team` | Bot has the **moderator** role in that team |
379+
| `delete_webex_team` | Bot has the **moderator** role in that team |
380+
| `add_webex_team_membership` | Bot has the **moderator** role in that team |
381+
| `delete_webex_team_membership` | Bot has the **moderator** role in that team |
382+
383+
### What bots CANNOT do
384+
385+
| Operation | Reason |
386+
|-----------|--------|
387+
| **Create a team** | Hard platform restriction — `POST /teams` returns 401 for bot tokens regardless of scopes. This is not an error in the MCP; it is enforced by the Webex platform. |
388+
389+
`create_webex_team` is intentionally not implemented in this MCP server. To create a
390+
team, use a personal access token or an OAuth integration with the `spark:teams_write`
391+
scope.
392+
393+
## Getting the Bot Into a Team
394+
395+
Before any team tool will work, a human user must add the bot to the team:
396+
397+
1. **Via Webex UI**: Open the team in the Webex app → Members → Add member → paste the
398+
bot's email address (visible in `get_webex_me`).
399+
2. **Via API with a user token**:
400+
```
401+
POST https://webexapis.com/v1/team/memberships
402+
{ "teamId": "<id>", "personEmail": "<bot-email>", "isModerator": true }
403+
```
404+
Set `isModerator: true` if you want the bot to be able to update the team or manage
405+
members.
406+
407+
## Understanding 404 vs 403 Errors
408+
409+
- **404 "not found"** from a team tool usually means the bot is not a member of the team,
410+
not that the team ID is wrong. Verify the bot has been added first.
411+
- **403 "forbidden"** from a write tool means the bot is a regular member but not a
412+
moderator. Upgrade the bot's role in the team to moderator.
413+
414+
## Typical Workflow
415+
416+
```
417+
1. list_webex_teams()
418+
→ Empty? A human must add the bot to a team first (see above).
419+
420+
2. list_webex_teams()
421+
→ Returns teams the bot belongs to. Pick a team_id.
422+
423+
3. get_webex_team(team_id)
424+
→ Confirm team details.
425+
426+
4. list_webex_team_memberships(team_id)
427+
→ See who is in the team. Check bot's isModerator value.
428+
429+
5. add_webex_team_membership(team_id, person_email="...", is_moderator=False)
430+
→ Requires bot to be a moderator.
431+
432+
6. delete_webex_team_membership(membership_id)
433+
→ Requires bot to be a moderator.
434+
```
435+
"""
436+
437+
356438
@mcp.resource("webex://config/current")
357439
def webex_current_config():
358440
"""Current Webex bot configuration and status"""
@@ -1035,7 +1117,7 @@ def server_version():
10351117
"streamable-http", "stdio",
10361118
"error-handling", "versioning"
10371119
],
1038-
"tools_count": 35,
1120+
"tools_count": 34,
10391121
"resources_count": 11,
10401122
"prompts_count": 8,
10411123
"breaking_changes": {

src/webex_bot_mcp/tools/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
)
2828

2929
from .teams import (
30-
list_webex_teams, create_webex_team, get_webex_team,
30+
list_webex_teams, get_webex_team,
3131
update_webex_team, delete_webex_team,
3232
list_webex_team_memberships, add_webex_team_membership,
3333
delete_webex_team_membership,
@@ -56,7 +56,7 @@
5656
# People functions
5757
'get_webex_me', 'list_webex_people',
5858
# Team functions
59-
'list_webex_teams', 'create_webex_team', 'get_webex_team',
59+
'list_webex_teams', 'get_webex_team',
6060
'update_webex_team', 'delete_webex_team',
6161
'list_webex_team_memberships', 'add_webex_team_membership',
6262
'delete_webex_team_membership',

src/webex_bot_mcp/tools/teams.py

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
"""
22
Webex Teams management tools.
3+
4+
Bot-access model (verified against the Webex API):
5+
- Bots CANNOT create teams — POST /teams returns 401 for bot tokens regardless of scopes.
6+
Team creation requires a human user token or an OAuth integration token.
7+
- Bots can only SEE teams they have been explicitly added to by a human user.
8+
list_webex_teams returns an empty list until the bot is invited to at least one team.
9+
- Read operations (list, get, list memberships) work once the bot is a team member.
10+
- Write operations (update, delete team; add, delete members) require the bot to hold
11+
the team moderator role within that team.
12+
- To add the bot to a team, a human user must do so via the Webex UI or a user/integration
13+
token calling POST /team/memberships.
314
"""
415
from typing import Optional, Dict, Any
516
from .common import get_webex_api, create_error_response, create_success_response, WebexErrorCodes, WebexTokenMissingError
@@ -13,12 +24,18 @@ def _map_exception_to_error(e: Exception) -> Dict[str, Any]:
1324
return create_error_response(WebexErrorCodes.UNAUTHORIZED,
1425
"Invalid or expired bot token.")
1526
if 'not found' in error_str or '404' in error_str:
16-
return create_error_response(WebexErrorCodes.NOT_FOUND,
17-
"Team or membership not found.")
27+
return create_error_response(
28+
WebexErrorCodes.NOT_FOUND,
29+
"Team or membership not found. Note: bots can only access teams they have "
30+
"been added to — a human user must invite the bot to the team first."
31+
)
1832
if 'forbidden' in error_str or '403' in error_str:
19-
return create_error_response(WebexErrorCodes.FORBIDDEN,
20-
"Bot lacks permission for this operation. "
21-
"Ensure the bot has moderator privileges.")
33+
return create_error_response(
34+
WebexErrorCodes.FORBIDDEN,
35+
"Bot lacks permission for this operation. Team write operations "
36+
"(update, delete, add/remove members) require the bot to have the "
37+
"moderator role within the team."
38+
)
2239
if 'rate limit' in error_str or 'too many requests' in error_str:
2340
return create_error_response(WebexErrorCodes.RATE_LIMITED,
2441
"API rate limit exceeded. Please retry after delay.",
@@ -45,7 +62,7 @@ def _team_to_dict(t) -> Dict[str, Any]:
4562

4663

4764
def _team_membership_to_dict(m) -> Dict[str, Any]:
48-
d: Dict[str, Any] = {
65+
return {
4966
'id': m.id,
5067
'teamId': m.teamId,
5168
'personId': m.personId,
@@ -55,15 +72,18 @@ def _team_membership_to_dict(m) -> Dict[str, Any]:
5572
'isModerator': m.isModerator,
5673
'created': m.created,
5774
}
58-
return d
5975

6076

6177
def list_webex_teams(
6278
display_name: Optional[str] = None,
6379
max_results: Optional[int] = None
6480
) -> Dict[str, Any]:
6581
"""
66-
List Webex teams the authenticated bot belongs to.
82+
List Webex teams the bot belongs to.
83+
84+
Bot access note: only teams the bot has been explicitly added to are returned.
85+
If the list is empty, a human user must invite the bot to one or more teams first.
86+
Teams cannot be created by a bot — use a user/integration token for that.
6787
6888
Args:
6989
display_name: Filter teams by display name (optional)
@@ -88,38 +108,13 @@ def list_webex_teams(
88108
return _map_exception_to_error(e)
89109

90110

91-
def create_webex_team(
92-
name: str,
93-
description: Optional[str] = None
94-
) -> Dict[str, Any]:
95-
"""
96-
Create a new Webex team.
97-
98-
Args:
99-
name: Name of the team (required)
100-
description: Description of the team (optional)
101-
102-
Returns:
103-
Standardized response dictionary with success/error information
104-
"""
105-
try:
106-
params: Dict[str, Any] = {'name': name}
107-
if description:
108-
params['description'] = description
109-
110-
team = get_webex_api().teams.create(**params)
111-
return create_success_response(
112-
data={'team': _team_to_dict(team)},
113-
metadata={'operation': 'create_team', 'parameters_used': params}
114-
)
115-
except Exception as e:
116-
return _map_exception_to_error(e)
117-
118-
119111
def get_webex_team(team_id: str) -> Dict[str, Any]:
120112
"""
121113
Get detailed information about a specific Webex team.
122114
115+
Bot access note: the bot must be a member of the team. If the team exists but the
116+
bot has not been added to it, this will return a not-found error.
117+
123118
Args:
124119
team_id: Team ID to get details for (required)
125120
@@ -144,6 +139,9 @@ def update_webex_team(
144139
"""
145140
Update an existing Webex team's name and/or description.
146141
142+
Bot access note: the bot must be a team moderator. If the bot is only a regular
143+
member, this operation will fail with a forbidden error.
144+
147145
Args:
148146
team_id: Team ID to update (required)
149147
name: New name for the team (optional)
@@ -182,6 +180,9 @@ def delete_webex_team(team_id: str) -> Dict[str, Any]:
182180
"""
183181
Delete a Webex team.
184182
183+
Bot access note: the bot must be a team moderator. If the bot is only a regular
184+
member, this operation will fail with a forbidden error.
185+
185186
Args:
186187
team_id: Team ID to delete (required)
187188
@@ -210,6 +211,9 @@ def list_webex_team_memberships(
210211
"""
211212
List members of a Webex team.
212213
214+
Bot access note: the bot must be a member of the team. If the bot has not been
215+
added to the team, this will return a not-found error.
216+
213217
Args:
214218
team_id: Team ID to list members for (required)
215219
max_results: Maximum number of memberships to return (default 100, max 1000)
@@ -240,6 +244,9 @@ def add_webex_team_membership(
240244
"""
241245
Add a person to a Webex team.
242246
247+
Bot access note: the bot must be a team moderator to add members. If the bot is
248+
only a regular member, this operation will fail with a forbidden error.
249+
243250
Args:
244251
team_id: Team ID to add person to (required)
245252
person_id: Person ID to add (use this OR person_email)
@@ -278,9 +285,12 @@ def delete_webex_team_membership(membership_id: str) -> Dict[str, Any]:
278285
"""
279286
Remove a person from a Webex team by deleting their team membership.
280287
288+
Bot access note: the bot must be a team moderator to remove members. If the bot is
289+
only a regular member, this operation will fail with a forbidden error.
290+
Use list_webex_team_memberships to find the membership ID for a person.
291+
281292
Args:
282-
membership_id: Team membership ID to delete (required). Use
283-
list_webex_team_memberships to find the membership ID.
293+
membership_id: Team membership ID to delete (required)
284294
285295
Returns:
286296
Standardized response dictionary with success/error information

0 commit comments

Comments
 (0)