Skip to content

feat(linear-mcp): implement the cartridge against the real Linear GraphQL API#83

Merged
hyperpolymath merged 2 commits into
mainfrom
feat/linear-mcp-real-graphql
Jul 13, 2026
Merged

feat(linear-mcp): implement the cartridge against the real Linear GraphQL API#83
hyperpolymath merged 2 commits into
mainfrom
feat/linear-mcp-real-graphql

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Jul 13, 2026

Copy link
Copy Markdown
Owner

What

linear-mcp was a scaffold, not an implementation. This makes it real: 27 tools over https://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_invoke discarded its arguments (_ = json_args;) and returned canned {"status":"stub"}. Its own comment: "Grade D Alpha stubs".
  • mod.js proxied to 127.0.0.1:7732 — a backend that exists nowhere in this 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.

Two Linear traps, both silent, now pinned as regression tests

  1. Personal API keys (lin_api_...) go in Authorization RAW. The reflex is to prefix Bearer, which 401s. Bearer is correct only for OAuth2 access tokens.
  2. Linear reports rate limiting as HTTP 400 carrying extensions.code: "RATELIMITED"not HTTP 429. Code checking only for 429 (as todoist-mcp currently 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.json leaves available false. 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. Setting available: true while the FFI still stubs would be precisely the over-claim the probe exists to catch.

Tests

Mapped onto the proven-tests-and-benches taxonomy (src/ProvenTests/Taxonomy.idr):

  • ContractTest (tests/parity_test.sh) — enforces tool-name parity across cartridge.json, mod.js and the FFI TOOLS table. These three had drifted: 7 tools declared while the Idris/Zig ABI already modelled 16 actions.
  • UnitTest / PropertyTest — argument validation, identifier routing (ENG-123 vs UUID), priority range 0–4, page-size clamping.
  • RegressionTest — the two traps above.
  • Benchmarks — replace a placeholder that timed true 1000 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.json entry, which still advertised 0.1.0 / REST / auth:none.

Follow-up raised separately

site/catalog.json marks 139/139 cartridges available: true, while only 12/142 cartridge.json files 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

…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>
Comment thread cartridges/domains/project-management/linear-mcp/README.adoc Fixed
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 282 issues detected

Severity Count
🔴 Critical 45
🟠 High 131
🟡 Medium 106

⚠️ Action Required: Critical security issues found!

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>
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 281 issues detected

Severity Count
🔴 Critical 44
🟠 High 131
🟡 Medium 106

⚠️ Action Required: Critical security issues found!

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

@hyperpolymath
hyperpolymath merged commit 5b4cad8 into main Jul 13, 2026
28 of 29 checks passed
@hyperpolymath
hyperpolymath deleted the feat/linear-mcp-real-graphql branch July 13, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants