mcp-issue-tracker
mcp-issue-tracker
一个基于真实的、本地的、由 SQLite 支撑的问题跟踪器的 MCP 服务器——支持完整的 CRUD(搜索、获取、总结、创建、评论、关闭/重新打开),包含真实种子数据语料库,并沿用了本作品集其他 MCP 项目所使用的同一套认证透传 + 默认只读安全模式。
作为 20 个项目作品集中的项目 10 构建:“第二个独立的 MCP 服务器实现,与之前的项目共享相同的安全理念,但应用于不同领域。”
选择哪个变体,以及为什么
规格说明(10-second-mcp-server-docs-wiki-search-or-issue-tracker.md)提供了两种选择:文档/维基搜索,或问题跟踪器。我构建的是问题跟踪器。
理由:文档/维基服务器本质上只是对静态内容提供两个工具(search、fetch)。而问题跟踪器需要真实的数据模型(问题、评论、标签、状态转换)、真实的授权决策(谁能看什么、谁能写什么),以及一个自然的位置来演示安全模式中“写入门控”的一半——规格说明的非目标明确允许写入操作,前提是“有明确理由并以与参考实现相同的方式进行门控”,而 CRUD 正是这样的理由。它是该模式更具体有用的演示,而不仅仅是其中的只读部分。
交叉引用:与 mcp-starter-template 共享的模式
此服务器有意复用姊妹项目 mcp-starter-template(本作品集中的项目 2)中的安全架构,而不是重新推导:
模式 |
|
|
配置驱动的工具分类 |
| 相同的结构,相同的文件名 — |
启动时代码/配置交叉检查 |
| 几乎逐字移植 — |
默认只读 | 除非在 | 相同 — 外加一个 |
认证透传 |
| 设计相同,使用符合领域的用户( |
结构化错误 |
| 相同,另加 |
审计跟踪 |
| 相同的双输出端设计 |
速率限制 |
| 相同,另加一个 |
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 个;写入操作已根据规格说明的非目标得到明确论证)
工具 | 读/写 | 成本 | 描述 |
| read | 1 | 对可见问题进行全文搜索,可按 |
| read | 1 | 完整详情:正文、标签、每条评论 |
| read | 1 | 跟踪器已知的每个标签 |
| read | 1 | 确定性的抽取式摘要 — 不调用 LLM(见下文) |
| read | 0 | 调用者在本窗口内剩余的调用/成本预算 |
| write | 5 | 创建问题,范围限定在调用者所在的团队 |
| write | 3 | 对可见的、打开的问题发表评论 |
| 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 文档中记录的升级路径相同。
环境变量覆盖
变量 | 用途 | 默认值 |
| SQLite 数据库路径 |
|
|
| 仓库根目录下的 |
| JSONL 审计日志路径 | 未设置则禁用 |
| SQLite 审计日志路径 | 未设置则使用内存 |
| 覆盖 | 来自 |
| 以逗号分隔的、要加入白名单的工具名称 | 来自 |
模拟用户
令牌 | 用户 | 团队 | 管理员 |
| Alice Nguyen | engineering | 否 |
| Bob Reyes | docs | 否 |
| 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_aftertest_audit.py— JSONL + SQLite 双接收器日志记录,被拒绝的调用携带error_codetest_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,并使用实际mcpSDK 的 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.0SQLite(随 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。
This server cannot be installed
Maintenance
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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