@youcloud/mantis-mcp 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # 更新记录
2
2
 
3
+ ## [1.0.5] - 2026-10-19
4
+
5
+ - 用户引用兼容无前导零的十进制编号字符串,统一为数字后指派、筛选与恢复;补充明确的格式错误提示,继续拒绝含糊类型和不安全整数。
6
+ - 缺陷上下文补齐备注附件索引、所属备注与去重,避免顶层附件为空时遗漏分析材料。
7
+
8
+ ## [1.0.4] - 2026-10-19
9
+
10
+ - 修复 Windows 下操作账本目录同步失败导致所有写工具及操作核查不可用的问题;保留文件同步与替换失败的阻断行为,并清理失败的临时文件。
11
+ - 本地异常诊断保留已知系统错误码,不暴露路径、凭据或底层异常正文。
12
+ - 增加跨平台账本、`submit_fix` / `add_note` 写入与恢复回归,覆盖 Windows 故障模拟及真实文件错误。
13
+
3
14
  ## [1.0.1] - 2026-10-18
4
15
 
5
16
  - `MANTIS_BASE_URL` 未设置或为空白时默认使用 `https://mantis.umlife.net`,仍可显式指定其他部署地址。
package/README.md CHANGED
@@ -1,21 +1,21 @@
1
1
  # Mantis MCP
2
2
 
3
- 内部研发团队使用的 MantisBT MCP 服务,通过 stdio 为 Coding Agent 提供缺陷查询、修复登记、附件上传和可恢复的工作流操作。
3
+ 内部研发团队使用的 MantisBT MCP 服务,通过 stdio 为 Coding Agent 提供缺陷创建、查询、分配、修复、关闭等全生命周期管理和可恢复操作。
4
4
 
5
- 项目与问题反馈:[内部 GitLab](https://git.umlife.net/ai-tools/mantis-mcp)。npm 包:[@youcloud/mantis-mcp](https://www.npmjs.com/package/@youcloud/mantis-mcp)。
5
+ 项目与问题反馈:[内部 GitLab](https://git.umlife.net/ai-tools/mantis-mcp/-/work_items)。npm 包:[@youcloud/mantis-mcp](https://www.npmjs.com/package/@youcloud/mantis-mcp)。
6
6
 
7
- ## 开始使用
7
+ ## 三分钟上手
8
8
 
9
- 需要 Node.js ≥ 22(至少 22.12),并准备 MantisBT API 令牌(在“我的账号 → 个人资料 → API令牌”下创建)。
9
+ 需要 Node.js ≥ 22(至少 22.12),并准备 [MantisBT](https://mantis.umlife.net) API 令牌(在“我的账号 → 个人资料 → API令牌”下创建)。
10
10
 
11
- ### CodeX
11
+ #### CodeX
12
12
  ```bash
13
13
  codex mcp add mantis \
14
14
  --env MANTIS_API_KEY="$MANTIS_API_KEY" \
15
15
  -- npx -y @youcloud/mantis-mcp@latest
16
16
  ```
17
17
 
18
- ### Claude Code
18
+ #### Claude Code
19
19
  ```bash
20
20
  # Claude Code 不加 --scope user 时,默认仅在添加时所在的项目中生效
21
21
  claude mcp add --scope user mantis \
@@ -23,12 +23,12 @@ claude mcp add --scope user mantis \
23
23
  -- npx -y @youcloud/mantis-mcp@latest
24
24
  ```
25
25
 
26
- ### 全局安装
26
+ #### 全局安装
27
27
  ```bash
28
28
  npm install -g @youcloud/mantis-mcp
29
29
  ```
30
30
 
31
- ### 自定义配置
31
+ #### 自定义配置
32
32
 
33
33
  1. 在 Mantis 生成个人 API Key,通过 `MANTIS_API_KEY` 传入;默认连接内部 Mantis,详见[服务配置](docs/configuration.md)。
34
34
  2. 按[客户端接入](docs/clients.md)配置 Codex、Claude Code、Cursor 或 Trae。
@@ -24,11 +24,18 @@ export class Issues {
24
24
  }
25
25
  }
26
26
  export function visibleCollection(issue, key, emptyArraysOmitted) {
27
- if (Array.isArray(issue[key]))
28
- return issue[key];
29
- if (!(key in issue) && emptyArraysOmitted && ['attachments', 'notes', 'relationships'].includes(key))
30
- return [];
31
- return undefined;
27
+ const items = Array.isArray(issue[key]) ? issue[key]
28
+ : !(key in issue) && emptyArraysOmitted && ['attachments', 'notes', 'relationships'].includes(key) ? [] : undefined;
29
+ if (key !== 'attachments' || !items)
30
+ return items;
31
+ // REST 将备注附件放在 notes[].attachments;只合并当前响应已可见的附件,不修改原始事实快照。
32
+ const attachments = new Map(items.map(item => [item.id, item]));
33
+ for (const note of Array.isArray(issue.notes) ? issue.notes : []) {
34
+ for (const attachment of Array.isArray(note.attachments) ? note.attachments : []) {
35
+ attachments.set(attachment.id, { ...attachments.get(attachment.id), ...attachment, note_id: note.id });
36
+ }
37
+ }
38
+ return [...attachments.values()];
32
39
  }
33
40
  export function facts(issue, baseUrl) {
34
41
  const keys = ['id', 'summary', 'project', 'category', 'priority', 'severity', 'status', 'resolution', 'version', 'fixed_in_version', 'target_version', 'updated_at'];
@@ -168,9 +168,11 @@ export class Context {
168
168
  let item;
169
169
  if (section === 'notes')
170
170
  item = { id: raw.id, reporter: minimalUser(raw.reporter), text: raw.text, view_state: raw.view_state,
171
+ ...(Array.isArray(raw.attachments) && raw.attachments.length ? { attachment_ids: raw.attachments.map((a) => a.id) } : {}),
171
172
  updated_at: raw.updated_at, view_url: `${session.baseUrl}/view.php?id=${issue.id}#bugnote${raw.id}` };
172
173
  else if (section === 'attachments')
173
174
  item = { id: raw.id, filename: raw.file_name ?? raw.filename, size: raw.size, content_type: raw.content_type,
175
+ ...(raw.note_id !== undefined ? { note_id: raw.note_id } : {}),
174
176
  download_url: `${session.baseUrl}/file_download.php?file_id=${raw.id}&type=bug`, view_url: output.issue.view_url };
175
177
  else if (section === 'monitors')
176
178
  item = minimalUser(raw);
@@ -1,9 +1,17 @@
1
1
  import { z } from 'zod';
2
+ import { getSystemErrorMap } from 'node:util';
2
3
  import { RestError } from '../client.js';
3
4
  import { ConnectionConfigError } from '../connection-config.js';
5
+ const systemErrorCodes = new Set([...getSystemErrorMap().values()].map(([name]) => name));
4
6
  export const id = z.number().int().positive().describe('正整数编号');
5
7
  export const text = z.string().refine(v => v.trim().length > 0, '不可为空白').describe('非空正文');
6
- const ref = z.union([z.literal('me'), id]).describe('当前用户 me 或明确的用户编号');
8
+ const refError = '用户引用须为 "me"、正整数用户编号或其十进制字符串(无前导零,最大 9007199254740991);用户名请先用 get_project_context 查询编号';
9
+ // 仅兼容明确的十进制编号,避免数字强转把布尔值、数组或指数写法当成用户。
10
+ const userId = z.number().int({ error: refError }).positive({ error: refError });
11
+ const userIdString = z.string().regex(/^[1-9][0-9]*$/, refError)
12
+ .refine(value => Number.isSafeInteger(Number(value)), refError).transform(Number);
13
+ const ref = z.union([z.literal('me'), userId, userIdString], { error: refError })
14
+ .describe('当前用户 "me"、正整数用户编号或无前导零的十进制编号字符串;不接受用户名');
7
15
  const iso = z.iso.datetime({ offset: true }).describe('含时区的 ISO8601 时间');
8
16
  const evidence = z.array(z.strictObject({ kind: z.enum(['url', 'text']), value: text }));
9
17
  const environment = { environment: text.optional(), version_id: id.optional() };
@@ -87,7 +95,10 @@ export function problem(error) {
87
95
  : error.status === 403 || error.status === 401 ? 'MANTIS_FORBIDDEN'
88
96
  : error.status === 404 ? 'NOT_FOUND_OR_NOT_VISIBLE' : error.status === 412 ? 'REVISION_CONFLICT'
89
97
  : !error.definiteRejection ? error.method === 'GET' ? 'SERVICE_UNAVAILABLE' : 'WRITE_OUTCOME_UNKNOWN' : 'MANTIS_REJECTED', message: error.message };
90
- return { code: 'SERVICE_UNAVAILABLE', message: '本地服务或读取不可用;未包含敏感错误正文' };
98
+ // 只展示 Node.js 已知的系统错误码,不回显任意 code、路径或底层异常正文。
99
+ const code = error instanceof Error ? error.code : undefined;
100
+ const diagnostic = typeof code === 'string' && systemErrorCodes.has(code) ? `(系统错误:${code})` : '';
101
+ return { code: 'SERVICE_UNAVAILABLE', message: `本地服务或读取不可用${diagnostic};未包含敏感错误正文` };
91
102
  }
92
103
  const record = z.record(z.string(), z.unknown());
93
104
  export const stepSchema = z.strictObject({ name: z.string(), state: z.enum(['not_started', 'in_flight', 'succeeded', 'failed', 'unknown']),
@@ -35,20 +35,23 @@ export class Operations {
35
35
  const temp = join(this.dir, `${record.operation_id}.${randomUUID()}.tmp`);
36
36
  const handle = await open(temp, 'wx', 0o600);
37
37
  try {
38
- await handle.writeFile(JSON.stringify(record));
39
- await handle.sync();
40
- }
41
- finally {
42
- await handle.close();
43
- }
44
- try {
45
- await rename(temp, join(this.dir, `${record.operation_id}.json`));
46
- const directory = await open(this.dir, 'r');
47
38
  try {
48
- await directory.sync();
39
+ await handle.writeFile(JSON.stringify(record));
40
+ await handle.sync();
49
41
  }
50
42
  finally {
51
- await directory.close();
43
+ await handle.close();
44
+ }
45
+ await rename(temp, join(this.dir, `${record.operation_id}.json`));
46
+ // Windows 不支持此目录同步方式;文件写入、同步和替换失败仍须阻断后续业务写入。
47
+ if (process.platform !== 'win32') {
48
+ const directory = await open(this.dir, 'r');
49
+ try {
50
+ await directory.sync();
51
+ }
52
+ finally {
53
+ await directory.close();
54
+ }
52
55
  }
53
56
  }
54
57
  finally {
package/docs/cookbook.md CHANGED
@@ -58,6 +58,8 @@
58
58
 
59
59
  ## 写入公共参数与生命周期
60
60
 
61
+ 用户引用 `assignee`(指派、受理、查询)和 `reporter`(查询)接受 `"me"`、正整数编号或无前导零的十进制编号字符串,例如 `5` 与 `"5"` 等价。编号不得超过 `9007199254740991`;不接受空白、布尔值、数组、指数写法或用户名。按姓名或登录名操作时,先用 `get_project_context` 的 `members` 区块和 `member_query` 确认用户编号,再传入引用。
62
+
61
63
  | 参数 | 规则 |
62
64
  |---|---|
63
65
  | `operation_id` | 每个新动作独立 UUID;真实写入必填,同次核查和恢复保持不变 |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@youcloud/mantis-mcp",
3
- "version": "1.0.3",
4
- "description": "面向研发工作流的 MantisBT MCP 服务,支持缺陷查询、修复登记、附件和可恢复操作",
3
+ "version": "1.0.5",
4
+ "description": "面向研发工作流的 MantisBT MCP 服务,支持缺陷创建、查询、分配、修复、关闭等全生命周期管理和可恢复操作",
5
5
  "license": "MIT",
6
6
  "homepage": "https://git.umlife.net/ai-tools/mantis-mcp",
7
7
  "bugs": {