Skip to main content
Glama
HamzaOuadid

mcp-issue-tracker

by HamzaOuadid

mcp-issue-tracker

一个基于真实的、本地的、由 SQLite 支撑的问题跟踪器的 MCP 服务器——支持完整的 CRUD(搜索、获取、总结、创建、评论、关闭/重新打开),包含真实种子数据语料库,并沿用了本作品集其他 MCP 项目所使用的同一套认证透传 + 默认只读安全模式。

作为 20 个项目作品集中的项目 10 构建:“第二个独立的 MCP 服务器实现,与之前的项目共享相同的安全理念,但应用于不同领域。”

选择哪个变体,以及为什么

规格说明(10-second-mcp-server-docs-wiki-search-or-issue-tracker.md)提供了两种选择:文档/维基搜索,或问题跟踪器。我构建的是问题跟踪器。

理由:文档/维基服务器本质上只是对静态内容提供两个工具(search、fetch)。而问题跟踪器需要真实的数据模型(问题、评论、标签、状态转换)、真实的授权决策(谁能看什么、谁能写什么),以及一个自然的位置来演示安全模式中“写入门控”的一半——规格说明的非目标明确允许写入操作,前提是“有明确理由并以与参考实现相同的方式进行门控”,而 CRUD 正是这样的理由。它是该模式更具体有用的演示,而不仅仅是其中的只读部分。

Related MCP server: Lific

交叉引用:与 mcp-starter-template 共享的模式

此服务器有意复用姊妹项目 mcp-starter-template(本作品集中的项目 2)中的安全架构,而不是重新推导:

模式

mcp-starter-template

mcp-issue-tracker(本仓库)

配置驱动的工具分类

server.yaml:tools.<name>.read_only

相同的结构,相同的文件名 — server.yaml

启动时代码/配置交叉检查

registry.py:不匹配时抛出 ToolRegistrationError

几乎逐字移植 — registry.py

默认只读

除非在 allowed_write_tools 中,否则写入工具会被拒绝

相同 — 外加一个 dry_run 第二道门控(见下文)

认证透传

auth.py + identity.py:模拟 Bearer 令牌解析为真实的 User,绝不使用共享凭据

设计相同,使用符合领域的用户(token-alice/token-bob/token-admin)

结构化错误

errors.py:MCPError{code, message, retry_after?}

相同,另加 +NOT_FOUND 用于问题查找

审计跟踪

audit.py:JSONL + SQLite audit_log,每次调用都会记录

相同的双输出端设计

速率限制

limiter.py:固定窗口内每会话上限

相同,另加一个 get_rate_status 工具将其暴露出来(将规格说明中的 api_rate_state 数据模型变为可查询)

mcp-starter-template 在其自己的交叉引用部分也链接回本仓库,因此该模式从两个方向都有文档记录。

架构

Claude Desktop / Claude Code (MCP client)
        │  JSON-RPC over stdio
        ▼
  server.py            FastMCP tool definitions (mcp SDK) — 8 tools
        │
        ▼
  service.py            Guarded dispatch: auth → rate-limit → write-gate → dry-run → audit
        │
        ├── auth.py + identity.py    Bearer-token → User (mock IdP, never a shared credential)
        ├── config.py                Loads/validates server.yaml
        ├── registry.py              Tool read/write classification, code/config cross-check
        ├── limiter.py                Per-caller fixed-window rate/spend budget
        ├── audit.py                  Every call → JSONL + SQLite audit_log
        │
        ▼
  db.py                  Real SQLite CRUD: issues / comments / labels / issue_labels
        │
        ▼
  seed_data.py            15 real, hand-authored issues for the sibling `ragbench` project

每次工具调用都遵循同一条流水线:认证 → 速率限制 →(如果是写入操作)白名单检查 →(如果是写入操作)试运行或真实执行 → 审计日志。任何阶段的拒绝都会抛出结构化的 MCPError(绝不会崩溃,也绝不会静默无操作),并且仍然会写入审计跟踪。

数据模型

  • issues(id, title, body, status, team, created_by, assignee, created_at, updated_at)

  • comments(id, issue_id, author, body, created_at)

  • labels(id, name) / issue_labels(issue_id, label_id) — 多对多

  • audit_log(timestamp, session_id, user_id, tool_name, read_or_write, dry_run, allowed, latency_ms, error_code, detail) — 与规格说明中的数据模型完全一致

  • schema_meta(key, value) — 固定 schema_version(参见“风险”中的“目标 API 版本”边界情况)

工具(8 个——规格说明要求 3-5 个;写入操作已根据规格说明的非目标得到明确论证)

工具

读/写

成本

描述

search_issues

read

1

对可见问题进行全文搜索,可按 status/label 过滤

get_issue

read

1

完整详情:正文、标签、每条评论

list_labels

read

1

跟踪器已知的每个标签

summarize_issue

read

1

确定性的抽取式摘要 — 不调用 LLM(见下文)

get_rate_status

read

0

调用者在本窗口内剩余的调用/成本预算

create_issue

write

5

创建问题,范围限定在调用者所在的团队

add_comment

write

3

对可见的、打开的问题发表评论

set_issue_status

write

3

打开/关闭问题

为什么 summarize_issue 不调用 LLM: 此环境未配置 LLM API 密钥,而该工具的工作是将真实数据交给 外部 LLM 客户端(Claude Desktop 等)——它不应该自己调用 LLM。摘要完全是字符串逻辑:标题 + 状态 + 标签 + 截断的正文片段 + 评论数量 + 最近一条评论。确定性强、可测试,并且如实说明自身的性质。

安全模型(具体说明)

  • 认证透传:每个工具都接受一个 token 参数。它通过一个模拟的内存身份提供程序解析为真实的 User(user_id、team、is_admin)——这与 mcp-starter-template 相同的 DEV-ONLY(仅限开发)模式,并以相同方式记录在文档中(identity.py 的 docstring 明确说明,真实部署必须将其替换为真实的凭据验证)。没有备用身份:缺失/无效的令牌 → 一律 UNAUTHENTICATED。

  • 团队范围可见性:team=NULL 的问题是公开的;否则只有同团队调用者或管理员可见。token-alice(engineering)和 token-bob(docs)对相同的 search_issues("") 调用会看到不同的结果集——这一点直接在测试中得到断言,而不仅仅是口头声称。

  • 默认只读,两层门控:除非写入工具的名称在 allowed_write_tools 中,否则会被拒绝并返回 WRITE_NOT_ALLOWED。即便如此,全局 dry_run 标志(默认开启)也会使其返回一个合成的 {"dry_run": true, "would_create": {...}} 预览,而不是真正触碰数据库。只有两扇门都被显式打开,才会发生真实的变更。

  • 速率限制:固定窗口、按调用者令牌分配预算(calls_per_min 和 cost_per_session,工具成本来自注册表)。在会话中途耗尽预算后,该窗口内每次后续调用都会返回 RATE_LIMIT_EXCEEDED,并附带 retry_after——进程本身绝不会崩溃,其他调用者也不会受影响(已根据规格说明的边界情况进行了显式测试)。

  • 审计日志:每次调用——无论允许还是拒绝、真实执行还是试运行——都会在 audit_log(JSONL + SQLite)中生成一行记录。

安装

git clone https://github.com/HamzaOuadid/mcp-issue-tracker.git
cd mcp-issue-tracker
pip install -e .

需要 Python 3.10 或更高版本。依赖项:mcp(官方 Python MCP SDK)、pydantic、PyYAML — 全部由上面的命令安装。

用法

直接运行

mcp-issue-tracker

这会在 stdio(标准 MCP 传输方式)上启动服务器。它不是用来在终端里以交互方式运行的——而是由 MCP 客户端来启动。如果想手动尝试,请改用随附的演示脚本(见下文)。

注册到 Claude Desktop

添加到 claude_desktop_config.json(Windows:%APPDATA%\Claude\claude_desktop_config.json;macOS:~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "issue-tracker": {
      "command": "mcp-issue-tracker",
      "args": [],
      "env": {
        "MCP_ISSUE_TRACKER_DB": "C:/Users/you/.mcp-issue-tracker/issue_tracker.db",
        "MCP_ISSUE_TRACKER_CONFIG": "C:/path/to/mcp-issue-tracker/server.yaml"
      }
    }
  }
}

(如果 mcp-issue-tracker 不在 PATH 中,可以将 command 指向解释器:"command": "python", "args": ["-m", "mcp_issue_tracker.server"],并将 "cwd" 设置为仓库根目录;或者使用 venv 中 mcp-issue-tracker.exe 的完整路径。)

重启 Claude Desktop。然后问它类似 “使用 token-alice 在问题跟踪器中搜索 ragbench 错误” 的话——Claude 会为你调用 search_issues。每个工具都需要一个 token 参数(见下文模拟用户);真实部署会将其替换为实际的每用户 OAuth,与 mcp-starter-template 文档中记录的升级路径相同。

环境变量覆盖

变量

用途

默认值

MCP_ISSUE_TRACKER_DB

SQLite 数据库路径

~/.mcp-issue-tracker/issue_tracker.db

MCP_ISSUE_TRACKER_CONFIG

server.yaml 的路径

仓库根目录下的 server.yaml

MCP_ISSUE_TRACKER_AUDIT_JSONL

JSONL 审计日志路径

未设置则禁用

MCP_ISSUE_TRACKER_AUDIT_DB

SQLite 审计日志路径

未设置则使用内存

MCP_ISSUE_TRACKER_DRY_RUN

覆盖 dry_run(true/false)

来自 server.yaml(true)

MCP_ISSUE_TRACKER_ALLOWED_WRITES

以逗号分隔的、要加入白名单的工具名称

来自 server.yaml(空)

模拟用户

令牌

用户

团队

管理员

token-alice

Alice Nguyen

engineering

否

token-bob

Bob Reyes

docs

否

token-admin

Priya Shah

engineering

是(可查看所有团队)

为真实运行启用写入

默认情况下,每个写入工具都会被拒绝(WRITE_NOT_ALLOWED)。要真正创建问题/评论/变更状态:

export MCP_ISSUE_TRACKER_ALLOWED_WRITES="create_issue,add_comment,set_issue_status"
export MCP_ISSUE_TRACKER_DRY_RUN=false
mcp-issue-tracker

(PowerShell:$env:MCP_ISSUE_TRACKER_ALLOWED_WRITES = "create_issue,add_comment,set_issue_status",$env:MCP_ISSUE_TRACKER_DRY_RUN = "false"。)

演示运行(真实输出)

由 scripts/demo.py 生成,它会通过 python -m mcp_issue_tracker.server 启动真实服务器,并使用真实的 mcp SDK 客户端通过 stdio(mcp.client.stdio + ClientSession)驱动它——这确实是协议返回的真实结果,而不是手打的:

$ list_tools()
  - search_issues: Search issues visible to the caller (team-scoped + public issues).
  - get_issue: Fetch one issue's full detail: body, labels, and every comment.
  - list_labels: List every label known to the tracker.
  - summarize_issue: Deterministic extractive summary of one issue (no LLM call).
  - get_rate_status: Report the caller's remaining call/cost budget for the current rate-limit window.
  - create_issue: Create a new issue, scoped to the caller's team. Write, allowlist-gated, dry-run by default.
  - add_comment: Add a comment to an existing, visible, open issue. Write, allowlist-gated, dry-run by default.
  - set_issue_status: Open or close an issue. Write, allowlist-gated, dry-run by default.

$ search_issues(token="token-alice", query="ragbench eval")
  {
    "count": 3,
    "results": [
      {
        "id": 7,
        "title": "gate.py exits 0 even when --baseline file is missing",
        "status": "open",
        "team": "engineering",
        "labels": ["bug", "ci"]
      },
      {
        "id": 2,
        "title": "Support --k as a single int, not just a comma list",
        "status": "open",
        "team": null,
        "labels": ["cli", "enhancement"]
      },
      {
        "id": 1,
        "title": "eval crashes on queries.jsonl with a duplicate query_id",
        "status": "open",
        "team": "engineering",
        "labels": ["bug", "eval"]
      }
    ]
  }

$ search_issues(token="token-bob", label="docs")   # bob is on the docs team
  {
    "count": 3,
    "results": [
      { "id": 15, "title": "CLI help text for `ragbench eval --rerank` doesn't mention offline fallback", "team": "docs" },
      { "id": 8,  "title": "Add a copy-paste example for `report --format html` to the README", "team": "docs" },
      { "id": 4,  "title": "README missing a pointer to the pgvector migration path", "team": "docs" }
    ]
  }

$ get_issue(token="token-admin", issue_id=1)
  {
    "id": 1,
    "title": "eval crashes on queries.jsonl with a duplicate query_id",
    "status": "open",
    "team": "engineering",
    "labels": ["bug", "eval"],
    "comments": [
      { "id": 1, "author": "root-admin",
        "body": "Confirmed on a 40-query file with one accidental duplicate id. Repro attached in the linked gist." }
    ]
  }

$ summarize_issue(token="token-admin", issue_id=1)
  #1 "eval crashes on queries.jsonl with a duplicate query_id" (open) [bug, eval]: Running `ragbench eval
  ./index --queries queries.jsonl` raises an unhandled KeyError deep in metrics.py when two lines in the
  query file share the same query_id... | 1 comment(s); most recent from root-admin: "Confirmed on a
  40-query file with one accidental duplicate id. Repro attached in the linked gist."

$ list_labels(token="token-alice")
  ["bug", "ci", "cli", "docs", "dx", "enhancement", "eval", "good-first-issue",
   "hybrid", "ingest", "ops", "performance", "question", "rerank", "windows"]

$ get_rate_status(token="token-alice")
  { "calls_remaining": 27, "cost_remaining": 98, "reset_at_seconds": 59.938 }

$ create_issue(...)   # default config: write tools are NOT allowlisted
  ERROR: [WRITE_NOT_ALLOWED] Write tool 'create_issue' is not enabled. Add it to
  allowed_write_tools in server.yaml (or MCP_ISSUE_TRACKER_ALLOWED_WRITES) to allow it.

$ get_issue(token="token-bob", issue_id=1)   # issue 1 is engineering-scoped, bob is docs
  ERROR: [NOT_FOUND] Issue 1 was not found or is not visible to you.

$ search_issues(token="not-a-real-token")   # missing/invalid token
  ERROR: [UNAUTHENTICATED] Missing or invalid identity token; call rejected.

自己复现:

python scripts/demo.py

测试

pip install -e ".[dev]"
pytest tests/ -v

**88 个测试,全部通过。**覆盖率:

  • test_identity_auth.py — 模拟 IdP 解析、认证透传拒绝缺失/无效令牌、无回退身份

  • test_registry.py — 默认只读、允许列表门控、代码/配置分类不匹配在启动时快速失败

  • test_limiter.py — 固定窗口预算、会话级隔离、窗口重置、retry_after

  • test_audit.py — JSONL + SQLite 双接收器日志记录,被拒绝的调用携带 error_code

  • test_db.py — 真实 SQLite CRUD、团队范围可见性、SQL 注入形态的输入不会崩溃或泄露

  • test_tools_issues.py — 确定性摘要、参数验证

  • test_service_read.py — 四个读取工具针对真实种子语料库的端到端测试,包括“两个用户对同一查询看到不同结果”

  • test_service_write.py — 默认禁止写入、试运行预览与真实变更、关闭 issue 的评论阻止、跨团队写入拒绝

  • test_edge_cases.py — 会话中途速率限制耗尽时优雅降级(不会崩溃)、schema 版本固定、SQL 注入安全、缺少配置的回退

  • test_server_integration.py — 针对真实 MCP 协议的端到端测试:以子进程方式启动 python -m mcp_issue_tracker.server,并使用实际 mcp SDK 的 stdio 客户端(ClientSession)驱动它,确认 list_tools() 和 call_tool() 能通过真正的 JSON-RPC 工作,而不是手工编写的替代品

88 passed, 1 warning in ~15-27s

环境

  • Python 3.10+

  • mcp>=1.2.0(官方 Python MCP SDK — pip install mcp)、pydantic>=2.0、PyYAML>=6.0

  • SQLite(随 Python 捆绑)— 无需启动服务器,符合本作品集其余部分“用 SQLite 而非 Postgres/Docker”的约定

  • 不使用也不要求 LLM API 密钥 — summarize_issue 是纯字符串逻辑(参见架构)

风险 / 未决问题

  • 偏离了规格说明中“实时公共 API”的框架。 规格说明的第 5/10/11 节描述了对实时第三方 API(例如真实的 GitHub Issues API)的包装,并带有针对该 API 自身配额的真实速率限制。而本构建改用一个真实的、基于本地 SQLite 且具备真正 CRUD 的跟踪器,这是依据本作品集项目明确的环境说明(不使用 LLM 密钥,在任务允许的情况下优先使用本地数据而非实时第三方依赖)。后果:规格说明数据模型中的 api_rate_state 被实现为本服务器自己的每个调用方的预算(通过 get_rate_status 暴露),而非第三方 API 的配额;而“记录所针对的 API 版本”这一边界情况则改为实现为固定的本地 schema_version。这两点都在代码中(limiter.py、db.py)内联注明,因此这种替换并非静默进行。

  • 模拟身份,而非真正的 IdP。 明确为 DEV-ONLY,在 identity.py 的 docstring 中说明 — 与 mcp-starter-template 的态度相同。真实部署需要在 AuthMiddleware 之前加上 OAuth/JWT/mTLS。

  • 单写入者 SQLite。 对于演示/作品集服务器来说没问题;并发的多写入者部署将需要真正的数据库(ragbench 的 README 对自身 SQLite 的使用也明确做出了同样的权衡)。

  • 相对于规格说明的里程碑(第 8 节)的范围缩减: 没有用于查询审计日志的独立 CLI(可直接通过 AuditLogger.query() 或 sqlite3 issue_tracker.db 查询);没有打标签的发布(git tag) — 留给仓库所有者在发布后自行处理;标签管理没有专门的 delete_label/rename_label 工具(标签仅支持写入时创建,这足以演示该模式,而无需过度构建规格说明未要求的管理界面)。

作品集说明

这个项目和 mcp-starter-template 都刻意存在,是为了把同一个观点表达两次:MCP 安全理念(认证透传、默认只读、可审计、限流)是一个可重复的模式,而不是一次性工程。相同的模块、相同的测试方法、相同的故障模式以相同的方式处理 — 在一个仓库中应用于文档/配置领域,在另一个仓库中应用于真实的 issue 跟踪器。

许可证

MIT — 参见 LICENSE。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables agents to read and drive a local-first Kanban board for issue tracking, allowing them to list, create, update, and resolve issues from Claude Code sessions.
    11 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI clients to search and retrieve customer and support-ticket data from a SQLite database, and to create support tickets only when an explicit approval flag is supplied, with all actions validated and audit-logged.
    -
  • A
    license
    B
    quality
    C
    maintenance
    Enables local engineering workflow management by consolidating tickets, QA evidence, time tracking, root cause investigation, knowledge, and reporting into a single SQLite database, allowing generation of complete ticket packages for handoffs, dailies, or career evidence.
    23
    9 npm
    MIT