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)提供了两种选择:文档/维基搜索,或问题跟踪器。我构建的是问题跟踪器

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

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

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

模式

mcp-starter-template

mcp-issue-tracker(本仓库)

配置驱动的工具分类

server.yamltools.<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.pyMCPError{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 参数。它通过一个模拟的内存身份提供程序解析为真实的 Useruser_idteamis_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_mincost_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)、pydanticPyYAML — 全部由上面的命令安装。

用法

直接运行

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_runtrue/false

来自 server.yamltrue

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.0PyYAML>=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.pydb.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

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Run AI customer support from your terminal: conversations, knowledge base, and chat widget.

  • Shortcut project management. Create, update, search stories and manage workflows.

  • Securely search and manage workspace context files for AI agents and teams.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HamzaOuadid/mcp-issue-tracker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server