feat(linear-mcp): implement the cartridge against the real Linear GraphQL API#83
Merged
Merged
Conversation
…phQL API
linear-mcp was a scaffold, not an implementation. boj_cartridge_invoke
discarded json_args and returned canned {"status":"stub"}; mod.js proxied to
127.0.0.1:7732, a backend that exists nowhere in the repo; the manifest
declared REST and auth "none" for an API that is GraphQL-only and always
requires a key. minter.toml said domain "Comms" while the cartridge lives in
project-management/ and claimed GraphQL, contradicting its own cartridge.json.
Rewrite mod.js against https://api.linear.app/graphql with 27 tools spanning
Linear and its related services: issues, comments, projects, milestones, teams,
cycles, labels, workflow states, users, documents, initiatives, attachments.
Two Linear-specific traps, both silent, now handled and pinned as regressions:
- Personal API keys (lin_api_...) go in Authorization RAW. The reflex is to
prefix "Bearer", which 401s; Bearer is correct only for OAuth2 tokens.
- Linear reports rate limiting as HTTP 400 carrying extensions.code
RATELIMITED, not HTTP 429. Checking only for 429 (as todoist-mcp does)
misreads a rate-limit as a generic bad request.
The Zig FFI keeps stub semantics on purpose: it is the ADR-0006 conformance
layer with no HTTP transport, so cartridge.json leaves available false. That is
what keeps the Foundry truthfulness probe honest — the probe passes here only
because the cartridge does not claim to serve Linear over the FFI.
Tests map onto the proven-tests-and-benches taxonomy: ContractTest enforces
tool-name parity across cartridge.json, mod.js and the FFI TOOLS table (the
three had drifted); UnitTest/PropertyTest cover validation, identifier routing
and page clamping; RegressionTest pins the two traps above. Benchmarks replace
a placeholder that timed 1000 times and measured nothing.
Also refresh the stale site/catalog.json entry, which still advertised
0.1.0 / REST / auth:none.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 282 issues detected
View findings[
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/planner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/types.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/rollback.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/rollback_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/planner_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/lsp_client.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/lsp_client_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/parser.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/integration_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/parser_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
…cted The usage example wrote a literal lin_api_ prefix, which Hypatia flags as a Generic API key. Source the value from vault-mcp instead, which is what production does anyway. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 281 issues detected
View findings[
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/planner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/types.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/rollback.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/rollback_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/planner_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/lsp_client.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/lsp_client_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/parser.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/integration_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server-cartridges/boj-server-cartridges/cartridges/cross-cutting/orchestration/stack-orchestrator-mcp/adapter/parser_test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
linear-mcpwas a scaffold, not an implementation. This makes it real: 27 tools overhttps://api.linear.app/graphql, covering Linear and its related services — issues, comments, projects, milestones, teams, cycles, labels, workflow states, users, documents, initiatives, attachments.Why it could not have worked before
boj_cartridge_invokediscarded its arguments (_ = json_args;) and returned canned{"status":"stub"}. Its own comment: "Grade D Alpha stubs".mod.jsproxied to127.0.0.1:7732— a backend that exists nowhere in this repo.RESTandauth: nonefor an API that is GraphQL-only and always requires a key.minter.tomlsaiddomain = "Comms"while the cartridge lives inproject-management/and claimed GraphQL — contradicting its owncartridge.json.Two Linear traps, both silent, now pinned as regression tests
lin_api_...) go inAuthorizationRAW. The reflex is to prefixBearer, which 401s.Beareris correct only for OAuth2 access tokens.extensions.code: "RATELIMITED"— not HTTP 429. Code checking only for429(astodoist-mcpcurrently does) misreads a rate-limit as a generic bad request.Truthfulness
The Zig FFI keeps stub semantics on purpose — it is the ADR-0006 conformance layer and has no HTTP transport — so
cartridge.jsonleavesavailablefalse. That is what keeps the Foundry truthfulness probe honest here: the probe passes only because the cartridge does not claim to serve Linear over the FFI. Settingavailable: truewhile the FFI still stubs would be precisely the over-claim the probe exists to catch.Tests
Mapped onto the
proven-tests-and-benchestaxonomy (src/ProvenTests/Taxonomy.idr):tests/parity_test.sh) — enforces tool-name parity acrosscartridge.json,mod.jsand the FFITOOLStable. These three had drifted: 7 tools declared while the Idris/Zig ABI already modelled 16 actions.ENG-123vs UUID), priority range 0–4, page-size clamping.true1000 times and measured nothing. Now: dispatch overhead against a stubbed transport (a real CI gate) plus an opt-in live-latency tier.Verified green: FFI builds, 17/17 Zig tests, 12/12 Deno tests, 27-tool parity, Foundry harness PASS.
Also refreshes the stale
site/catalog.jsonentry, which still advertised0.1.0 / REST / auth:none.Follow-up raised separately
site/catalog.jsonmarks 139/139 cartridgesavailable: true, while only 12/142cartridge.jsonfiles actually set it. The Foundry truthfulness probe is gated on that flag, so for ~127 cartridges it never fires — yet the published catalog advertises them all as available. The gate reads one field; consumers read another.🤖 Generated with Claude Code