Skip to content

Commit ed830d0

Browse files
ysyneuclaude
andcommitted
docs: align tool descriptions with glossary and document supported transports
Replace "collaboration space" with "channel" in tool descriptions, parameter descriptions, and READMEs to match the canonical Flashduty glossary (flashduty-docs/glossary.md). User-facing strings only — wire-level identifiers, JSON tags, and MCP parameter names are unchanged. Also add a "Supported Transports" table to README.md / README_zh.md clarifying: - stdio: supported - Streamable HTTP (/mcp, /flashduty): supported - Standalone SSE (legacy GET /sse): not supported (server returns 405 by design) Adds TEST_PLAN_param_audit.md capturing the wider audit sweep that surfaced this glossary drift. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4e0f520 commit ed830d0

8 files changed

Lines changed: 184 additions & 11 deletions

File tree

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ The Flashduty MCP Server is a [Model Context Protocol (MCP)](https://modelcontex
1010
- Extracting and analyzing data from Flashduty.
1111
- Building AI-powered tools and applications that interact with Flashduty.
1212

13+
### Supported Transports
14+
15+
| Transport | Supported | Notes |
16+
|---|---|---|
17+
| **stdio** || `flashduty-mcp-server stdio`. Use this for local hosts (Cursor, Claude Desktop, etc.). |
18+
| **Streamable HTTP** || `flashduty-mcp-server http`. Endpoints: `/mcp` (canonical) and `/flashduty` (legacy alias). Public instance: `https://mcp.flashcat.cloud/mcp`. |
19+
| **Standalone SSE (HTTP/SSE)** || The legacy SSE transport (separate `GET /sse` endpoint) is **not supported**. The server returns `405 Method Not Allowed` for `GET` requests by design. Use Streamable HTTP — it already streams responses over `POST` per the MCP spec. |
20+
21+
> If your MCP host only supports the legacy SSE transport, upgrade the host or use `stdio` locally.
22+
1323
---
1424

1525
## Remote Flashduty MCP Server
@@ -286,7 +296,7 @@ The following toolsets are available (all are on by default). You can also use `
286296
| `changes` | Change record query | 1 |
287297
| `status_page` | Status page management | 4 |
288298
| `users` | Member and team query | 2 |
289-
| `channels` | Collaboration space and escalation rules | 2 |
299+
| `channels` | Channels and escalation rules | 2 |
290300
| `fields` | Custom field definitions | 1 |
291301

292302
**Total: 16 tools**
@@ -316,8 +326,8 @@ The following toolsets are available (all are on by default). You can also use `
316326
- `query_members` - Query members with optional filters
317327
- `query_teams` - Query teams with member details
318328

319-
### `channels` - Collaboration Space and Escalation Rules (2 tools)
320-
- `query_channels` - Query collaboration spaces with enriched data (team and creator names)
329+
### `channels` - Channels and Escalation Rules (2 tools)
330+
- `query_channels` - Query channels with enriched data (team and creator names)
321331
- `query_escalation_rules` - Query escalation rules for a channel
322332

323333
### `fields` - Custom Field Definitions (1 tool)

README_zh.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ Flashduty MCP Server 是一个基于 [Model Context Protocol (MCP)](https://mode
1010
- 从 Flashduty 抽取和分析数据
1111
- 构建与 Flashduty 交互的 AI 工具和应用
1212

13+
### 支持的传输协议
14+
15+
| 协议 | 是否支持 | 说明 |
16+
|---|---|---|
17+
| **stdio** || `flashduty-mcp-server stdio`,适用于本地 MCP 客户端(Cursor、Claude Desktop 等)。 |
18+
| **Streamable HTTP** || `flashduty-mcp-server http`,对外路径:`/mcp`(推荐)与 `/flashduty`(兼容别名)。公共实例:`https://mcp.flashcat.cloud/mcp`|
19+
| **Standalone SSE(HTTP/SSE)** || 旧版独立的 SSE 传输(独立的 `GET /sse` 端点)**不支持**。服务对 `GET` 请求返回 `405 Method Not Allowed`。请改用 Streamable HTTP——它已按 MCP 规范在 `POST` 响应中通过流式方式返回数据。 |
20+
21+
> 如果你的 MCP 客户端只支持旧版 SSE 传输,请升级客户端或在本地改用 stdio。
22+
1323
---
1424

1525
## 远程服务

TEST_PLAN_param_audit.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# MCP Server — Parameter Mismatch Test Plan
2+
3+
Hunt for silent parameter mismatches between MCP tool request bodies and backend input structs. Same class of bug as the `channel_id``channel_ids` issue fixed in PR #43.
4+
5+
## Scope
6+
7+
20 tools across 6 toolsets in `pkg/flashduty/`:
8+
9+
| Toolset | Tools |
10+
|---|---|
11+
| incidents | QueryIncidents, QueryIncidentTimeline, QueryIncidentAlerts, ListSimilarIncidents, CreateIncident, UpdateIncident, AckIncident, CloseIncident |
12+
| changes | QueryChanges |
13+
| status_page | QueryStatusPages, ListStatusChanges, CreateStatusIncident, CreateChangeTimeline |
14+
| users | QueryMembers, QueryTeams |
15+
| channels | QueryChannels, QueryEscalationRules |
16+
| fields | QueryFields |
17+
18+
## Confirmed bugs (same class as PR #43)
19+
20+
### 1. `query_members``member_name` dropped
21+
- **File:** `pkg/flashduty/users.go:73`
22+
- **Sends:** `{"p": 1, "limit": 20, "member_name": "<name>"}`
23+
- **Backend expects** (`fc-pgy/cmd/server/controller/member/member.go:38-45`):
24+
```go
25+
type memberListInput struct {
26+
RoleID uint64 `json:"role_id"`
27+
Page int `json:"p"`
28+
Limit int `json:"limit"`
29+
Orderby string `json:"orderby"`
30+
Asc bool `json:"asc"`
31+
Query string `json:"query"`
32+
}
33+
```
34+
- **Impact:** Search-by-name returns all members; `member_name` is silently ignored.
35+
- **Fix:** Send `query: <name>` instead of `member_name: <name>`.
36+
37+
### 2. `query_teams``team_name` dropped
38+
- **File:** `pkg/flashduty/users.go:170`
39+
- **Sends:** `{"p": 1, "limit": 20, "team_name": "<name>"}`
40+
- **Backend expects** (`fc-pgy/cmd/server/controller/team/team.go:311-318`):
41+
```go
42+
type listTeamInput struct {
43+
Page int `json:"p"`
44+
Limit int `json:"limit"`
45+
Orderby string `json:"orderby"`
46+
Asc bool `json:"asc"`
47+
PersonID uint64 `json:"person_id"`
48+
Query string `json:"query"`
49+
}
50+
```
51+
- **Impact:** Search-by-name returns all teams; `team_name` is silently ignored.
52+
- **Fix:** Send `query: <name>` instead of `team_name: <name>`.
53+
- **Bonus:** backend also supports `person_id` filter the MCP tool doesn't currently expose.
54+
55+
## Suspected / needs runtime verification
56+
57+
### 3. `create_incident``assigned_to` shape
58+
- **File:** `pkg/flashduty/incidents.go:413-416`
59+
- **Sends:** `{"assigned_to": {"type": "assign", "person_ids": [123, 456]}}`
60+
- Verify the backend accepts the nested `{type, person_ids}` wrapper vs expecting flat `person_ids` at the top level.
61+
62+
## Runtime test procedure
63+
64+
For each tool, log the outbound HTTP body (look for `msg=duty request` lines in server logs) and confirm the JSON keys match the backend struct tags.
65+
66+
### Confirmed-bug tests
67+
| # | MCP call | Expected outbound body | Red flag if body contains |
68+
|---|---|---|---|
69+
| 1 | `query_members {name: "alice"}` | `{"p":1, "limit":20, "query":"alice"}` | `"member_name"` |
70+
| 2 | `query_teams {name: "backend"}` | `{"p":1, "limit":20, "query":"backend"}` | `"team_name"` |
71+
72+
### Regression tests (post-PR #43)
73+
| # | MCP call | Expected outbound body |
74+
|---|---|---|
75+
| 3 | `query_incidents {channel_ids: "100", start_time: T0, end_time: T1}` | `{"channel_ids":[100], ...}` |
76+
| 4 | `query_incidents {channel_ids: "100,200", ...}` | `{"channel_ids":[100,200], ...}` |
77+
| 5 | `query_incidents {...no channel_ids...}` | body MUST NOT contain `channel_ids` |
78+
| 6 | `query_changes {channel_ids: "100"}` | `{"channel_ids":[100], ...}` |
79+
80+
### Smoke tests for the other 17 tools
81+
Auditor flagged these as clean; verify once by calling each with minimal args and confirming no `400 invalid parameter` from backend:
82+
83+
Incidents: `query_incident_timeline`, `query_incident_alerts`, `list_similar_incidents`, `create_incident`, `update_incident`, `ack_incident`, `close_incident`.
84+
Status page: `query_status_pages`, `list_status_changes`, `create_status_incident`, `create_change_timeline`.
85+
Channels: `query_channels`, `query_escalation_rules`.
86+
Fields: `query_fields`.
87+
88+
### Special case — `create_incident assigned_to`
89+
Call `create_incident` with `assigned_to: "100,101"`. Fetch the created incident and confirm persons 100 and 101 are actually listed as responders. If they're not, the wrapper shape is wrong.
90+
91+
## Exit criteria
92+
93+
- Tests 1–2: fail → open follow-up PR renaming `member_name`/`team_name``query`.
94+
- Tests 3–6: pass → PR #43 fix is correct end-to-end.
95+
- Smoke tests: 17/17 return 200 or a documented business error (not `400 invalid parameter`).
96+
- Test for `assigned_to`: responders actually assigned.
97+
- Glossary pass (section below): tool schemas use canonical terms only (no "collaboration space").
98+
99+
## Audit method reproducibility
100+
101+
To re-audit after new tools are added:
102+
1. For each `mcp.NewTool("<name>", ...)` call in `pkg/flashduty/*.go`, capture the `mcp.With*` schema.
103+
2. Locate the corresponding `makeRequest(ctx, "POST", "/<endpoint>", requestBody)`.
104+
3. Grep the backend repos (`fc-event`, `fc-pgy`, `monit-webapi`) for the handler input struct tagged with matching `json:` fields.
105+
4. Compare field names, types (singular vs plural), and required fields.
106+
5. Flag any MCP-side key that doesn't appear as a backend `json:` tag — that key is silently dropped.
107+
108+
## Glossary pass — canonical terminology
109+
110+
Goal: keep user-visible strings (tool descriptions, parameter descriptions, READMEs) aligned with the canonical
111+
Flashduty glossary at `flashduty-docs/glossary.md` so agents and humans see consistent wording.
112+
113+
### Terms checked (from `flashduty-docs/glossary.md`)
114+
115+
| Chinese | Canonical English | Misnomers to hunt |
116+
|---|---|---|
117+
| 协作空间 | channel (lowercase in prose; "Channel" as class/section title) | collaboration space, collab space |
118+
| 故障 | incident | outage, alarm |
119+
| 告警 / 报警 | alert | alarm |
120+
| 集成来源 / 数据源 | integration | data source |
121+
| 分派策略 | escalation rule ||
122+
| 成员 | member | — (wire uses `person_id`; see "Skipped" below) |
123+
| 处理人员 / 响应人员 | responder | acker, assignee |
124+
125+
zh README (`README_zh.md`) is source of truth and already uses 协作空间; no zh-side edits were required.
126+
127+
### Files edited in this pass
128+
129+
- `pkg/flashduty/channels.go``queryChannelsDescription`, `channel_id` param description
130+
- `pkg/flashduty/incidents.go``channel_ids` and `channel_id` param descriptions
131+
- `pkg/flashduty/changes.go``channel_ids` param description
132+
- `pkg/flashduty/tools.go``channels` toolset description
133+
- `README.md` — toolset table + `channels` section heading + `query_channels` bullet
134+
- `e2e/README.md` — TestQueryChannels comment + limitations bullet
135+
136+
### Verification
137+
138+
1. `go build ./...` compiles cleanly.
139+
2. `rg -i "collaboration"` in the repo returns no hits.
140+
3. Start the server and call `list_tools` via MCP; confirm the `query_channels`, `query_escalation_rules`,
141+
`query_incidents`, `query_changes`, and `create_incident` schemas describe channels using "channel" (not "collaboration space").
142+
4. Run existing snapshot tests (`go test ./pkg/flashduty/...`) — snapshots for these tools do not embed tool
143+
descriptions, so they should still pass without updates.
144+
145+
### Skipped / uncertain
146+
147+
- `person_ids` parameter and its description in `pkg/flashduty/users.go:26` and `pkg/flashduty/incidents.go:379`.
148+
Glossary says 成员 → "member", but `person_ids` is the wire-level backend field name. The description text
149+
explains what the parameter accepts ("Comma-separated person IDs"), so renaming the description would misalign
150+
with the literal parameter name. Leaving as-is; a future full rename would need to touch wire payload too.
151+
- All `Person*` Go identifiers, struct fields, and JSON tags (wire-level, not user-facing).
152+
- `partial_outage` / `full_outage` enum values in status-page tools — these are wire-level component status
153+
enums, not references to Flashduty 故障.

e2e/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Note: Debug mode has slightly reduced coverage as it doesn't test Docker integra
5151
### Read-Only Tools Tests
5252

5353
- **TestQueryIncidents**: Tests querying incidents
54-
- **TestQueryChannels**: Tests querying collaboration spaces
54+
- **TestQueryChannels**: Tests querying channels
5555
- **TestQueryMembers**: Tests querying members
5656

5757
### Incident Lifecycle Tests
@@ -60,7 +60,7 @@ Note: Debug mode has slightly reduced coverage as it doesn't test Docker integra
6060

6161
## Limitations
6262

63-
1. **Resource Cleanup**: Unlike GitHub's API, Flashduty doesn't support deleting incidents or collaboration spaces. Tests that create resources will close them instead of deleting them.
63+
1. **Resource Cleanup**: Unlike GitHub's API, Flashduty doesn't support deleting incidents or channels. Tests that create resources will close them instead of deleting them.
6464

6565
2. **Test Account**: It's recommended to use a dedicated test APP key to avoid polluting production data.
6666

pkg/flashduty/changes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func QueryChanges(getClient GetFlashdutyClientFn, t translations.TranslationHelp
2323
ReadOnlyHint: ToBoolPtr(true),
2424
}),
2525
mcp.WithString("change_ids", mcp.Description("Comma-separated change IDs for direct lookup.")),
26-
mcp.WithString("channel_ids", mcp.Description("Filter by collaboration space IDs. Comma-separated for multiple.")),
26+
mcp.WithString("channel_ids", mcp.Description("Filter by channel IDs. Comma-separated for multiple.")),
2727
mcp.WithNumber("start_time", mcp.Description("Query start time in Unix timestamp (seconds). Must be < end_time. Max range: 31 days. Defaults to 1 hour ago.")),
2828
mcp.WithNumber("end_time", mcp.Description("Query end time in Unix timestamp (seconds). Defaults to now.")),
2929
mcp.WithString("type", mcp.Description("Filter by change type.")),

pkg/flashduty/channels.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/flashcatcloud/flashduty-mcp-server/pkg/translations"
1515
)
1616

17-
const queryChannelsDescription = `Query collaboration spaces (channels) by IDs or name. Returns channel info with team details.`
17+
const queryChannelsDescription = `Query channels by IDs or name. Returns channel info with team details.`
1818

1919
// QueryChannels creates a tool to query channels
2020
func QueryChannels(getClient GetFlashdutyClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
@@ -182,7 +182,7 @@ func QueryEscalationRules(getClient GetFlashdutyClientFn, t translations.Transla
182182
Title: t("TOOL_QUERY_ESCALATION_RULES_USER_TITLE", "Query escalation rules"),
183183
ReadOnlyHint: ToBoolPtr(true),
184184
}),
185-
mcp.WithNumber("channel_id", mcp.Required(), mcp.Description("Collaboration space (channel) ID to query escalation rules for.")),
185+
mcp.WithNumber("channel_id", mcp.Required(), mcp.Description("Channel ID to query escalation rules for.")),
186186
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
187187
ctx, client, err := getClient(ctx)
188188
if err != nil {

pkg/flashduty/incidents.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func QueryIncidents(getClient GetFlashdutyClientFn, t translations.TranslationHe
3030
mcp.WithString("incident_ids", mcp.Description("Comma-separated incident IDs for direct lookup. If provided, other filters are ignored.")),
3131
mcp.WithString("progress", mcp.Description("Filter by status. Valid values: Triggered, Processing, Closed. Comma-separated for multiple."), mcp.Enum("Triggered", "Processing", "Closed", "Triggered,Processing", "Processing,Closed", "Triggered,Closed", "Triggered,Processing,Closed")),
3232
mcp.WithString("severity", mcp.Description("Filter by severity level. Valid values: Info, Warning, Critical."), mcp.Enum("Info", "Warning", "Critical")),
33-
mcp.WithString("channel_ids", mcp.Description("Filter by collaboration space IDs. Comma-separated for multiple.")),
33+
mcp.WithString("channel_ids", mcp.Description("Filter by channel IDs. Comma-separated for multiple.")),
3434
mcp.WithNumber("start_time", mcp.Description("Query start time in Unix timestamp (seconds). Required if no incident_ids. Must be < end_time. Max range: 31 days.")),
3535
mcp.WithNumber("end_time", mcp.Description("Query end time in Unix timestamp (seconds). Required if no incident_ids. Must be within data retention period.")),
3636
mcp.WithString("title", mcp.Description("Keyword search in incident title.")),
@@ -374,7 +374,7 @@ func CreateIncident(getClient GetFlashdutyClientFn, t translations.TranslationHe
374374
}),
375375
mcp.WithString("title", mcp.Required(), mcp.Description("Incident title. Length: 3-200 characters."), mcp.MinLength(3), mcp.MaxLength(200)),
376376
mcp.WithString("severity", mcp.Required(), mcp.Description("Incident severity level."), mcp.Enum("Info", "Warning", "Critical")),
377-
mcp.WithNumber("channel_id", mcp.Description("Collaboration space ID to associate the incident with.")),
377+
mcp.WithNumber("channel_id", mcp.Description("Channel ID to associate the incident with.")),
378378
mcp.WithString("description", mcp.Description("Incident description. Max 6144 characters."), mcp.MaxLength(6144)),
379379
mcp.WithString("assigned_to", mcp.Description("Comma-separated person IDs to assign as responders. Use query_members to find IDs.")),
380380
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {

pkg/flashduty/tools.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func DefaultToolsetGroup(getClient GetFlashdutyClientFn, readOnly bool, t transl
5656
group.AddToolset(users)
5757

5858
// Channels toolset (2 tools)
59-
channelsToolset := toolsets.NewToolset("channels", "Collaboration space and escalation rule tools").
59+
channelsToolset := toolsets.NewToolset("channels", "Channel and escalation rule tools").
6060
AddReadTools(
6161
toolsets.NewServerTool(QueryChannels(getClient, t)),
6262
toolsets.NewServerTool(QueryEscalationRules(getClient, t)),

0 commit comments

Comments
 (0)