-
Notifications
You must be signed in to change notification settings - Fork 38
feat(mcp): Claude connector-store readiness + Better Auth OAuth #2497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
andrew-bierman
wants to merge
103
commits into
development
Choose a base branch
from
plan/mcp-connector-store-readiness
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
c99f5c2
docs(mcp): add Claude Connector Store readiness plan
andrew-bierman cc3f3e4
chore(mcp): bump mcp sdk → 1.29, oauth provider → 0.7, agents → 0.13.2
andrew-bierman a06b296
feat(mcp): wrangler env structure, custom domain, version unification…
andrew-bierman c347b0a
feat(mcp): RFC 9728 + 8414 well-known metadata, scoped to custom doma…
andrew-bierman cc8cb8f
feat(mcp): gate /register with MCP_INITIAL_ACCESS_TOKEN + pre-registe…
andrew-bierman 8f4c9f9
feat(mcp): Better Auth trusted origins + login CSRF/Origin + CORS on …
andrew-bierman 9847c39
feat(mcp): scope-based admin gating; remove admin_login + X-PackRat-A…
andrew-bierman f7036f1
feat(mcp): packrat_ namespace + tool annotations on every tool (U7)
andrew-bierman a0556ed
feat(mcp): structured output + isError envelope + pagination clamps (U8)
andrew-bierman 731b277
feat(mcp): resources/list providers, search template, packrat://gloss…
andrew-bierman 7ddd7e1
feat(mcp): elicitations on destructive admin tools (U10)
andrew-bierman 032f12b
feat(mcp): branded login page + UX polish; SSO deferred (U11)
andrew-bierman 785b9c7
feat(landing,mcp): Terms of Service, Privacy MCP addendum, /health le…
andrew-bierman 06bceb4
feat(landing,mcp): public docs page, README, branding, submission pac…
andrew-bierman 219cfec
feat(mcp): rate-limit binding + per-user/per-tool gating + KV purge c…
andrew-bierman 96e6bd7
feat(mcp): structured logging + audit logs + onError hook + correlati…
andrew-bierman 4fe5cff
feat(mcp): real /health probing KV + API; new /status endpoint (U16)
andrew-bierman f169e6f
feat(mcp,ci): GH Actions PR test + tag deploy workflows; vitest-pool-…
andrew-bierman 871b1f0
feat(mcp): submission readiness script + completed submission packet …
andrew-bierman 640a4ac
chore(landing): pre-render MCP logo PNGs at 1024/512/256 from SVG
andrew-bierman ac00500
chore(mcp): wire real OAUTH_KV namespace IDs (prod + dev)
andrew-bierman e832842
docs(mcp): fix stale KV-namespace naming refs after live provisioning
andrew-bierman 8521da1
docs(mcp): add Better Auth OAuth Provider consolidation plan (refacto…
andrew-bierman 3f048c2
feat(api): @better-auth/oauth-provider plugin + consent page + Claude…
andrew-bierman f32f2fc
feat(mcp): JWT validation via remote JWKS for Better Auth tokens (U2)
andrew-bierman f0810a7
feat(mcp): cutover to pure protected resource — delete workers-oauth-…
andrew-bierman 6d309a6
chore(mcp): regenerate lockfile after U3+U4 dep removal
andrew-bierman 0bd27f6
chore(mcp): strip dead OAUTH_KV + cron + DCR secret; rewrite runbook …
andrew-bierman c652f69
test(mcp): retire workers-oauth-provider tests; rewrite fixtures for …
andrew-bierman 47da19d
chore(mcp,ci): readiness script + workflows for cross-origin AS archi…
andrew-bierman 2c4930c
docs(mcp): final docs sweep for post-refactor architecture + R11 dev-…
andrew-bierman 6fcfa3b
🚚 chore(api): relocate Claude OAuth seed to canonical src/db/ location
andrew-bierman 65cf7ab
♻️ refactor(api): transitional env-var rename BETTER_AUTH_* → PACKRAT_*
andrew-bierman ea8e7f7
✨ feat(api): add drizzle-seed for db:seed:dev local fake data
andrew-bierman 3c1ee01
♻️ refactor(api): modernize consent page — JSX via @kitajs/html + Ely…
andrew-bierman 5662863
♻️ refactor(api): unify all prod seeders on drizzle-seed pattern
andrew-bierman aad354f
🔒 chore(api): drop --force escape in db:seed:dev + sort package.jsons
andrew-bierman df22a89
✨ feat(guards): add isBoolean type guard
andrew-bierman 9a10b47
🔧 fix(mcp): replace raw typeof checks with @packrat/guards
andrew-bierman 9b0cc69
🔧 fix(mcp,landing): replace unsafe type casts with @packrat/guards
andrew-bierman 1ecda5d
📌 chore(deps): align jose + @cloudflare/vitest-pool-workers across pa…
andrew-bierman d58c33d
🔧 test(mcp): update submission-readiness test to match new db:seed:oa…
andrew-bierman 1768796
🔀 Merge branch 'origin/development' into plan/mcp-connector-store-rea…
andrew-bierman 1594391
🎨 chore: biome import-order auto-fixes from prior commit
andrew-bierman 701cc8c
🔀 Merge branch 'origin/development' (record proper merge history)
andrew-bierman ab57f9f
🙈 chore: gitignore .wrangler/ local state (dev/test artifacts)
andrew-bierman 4577692
fix(mcp,api): conform to SDK 1.29 + jose 6 type tightening from dev m…
andrew-bierman a90a51e
fix(mcp): narrow McpToolResult.content, fix nowIso/call/spy/jsx resid…
andrew-bierman 5a102d3
refactor(api): split JSX-free App contract into app.ts to stop JSX leak
andrew-bierman 0cda679
fix(schemas,mcp): give Eden proper error types + string-coerce query …
andrew-bierman 5c986f9
fix(mcp): align tool request bodies with merged API contract
andrew-bierman f616adb
fix(mcp): loosen call() to accept Eden's unknown error (revert schema…
andrew-bierman 3ceaa92
fix(schemas,mcp): type admin error responses explicitly for proper Ed…
andrew-bierman 63bbf78
fix: revert to z.any() error schema + unknown-tolerant call()
andrew-bierman 3c171b6
test(api): cover logger Sentry forwarding + exclude app.ts from unit …
andrew-bierman a183e18
refactor(mcp): resources.ts owned helpers to single object param (lint)
andrew-bierman d68e3b7
refactor(mcp): cors + index.ts owned helpers to object param (lint)
andrew-bierman 7f0e923
refactor(mcp,api): logger methods + emit to single object param (lint)
andrew-bierman 7958d7a
refactor(api,scripts): owned helpers to object param (lint)
andrew-bierman 8f2f696
refactor(mcp): remaining owned helpers to object param + framework al…
andrew-bierman 1a12169
test(mcp): update submission-readiness.test.ts call sites to object p…
andrew-bierman 72a1cc0
Merge remote-tracking branch 'origin/development' into plan/mcp-conne…
andrew-bierman fd5b9ce
fix(types): isolate api's @kitajs/html JSX from the root tsc program
andrew-bierman b225834
fix(api): make api tsconfig self-sufficient by extending root
andrew-bierman 76bd692
fix(api): load Cloudflare Workers types explicitly in api tsconfig
andrew-bierman 9bfc3b0
fix(api): use dated @cloudflare/workers-types/latest subpath for glob…
andrew-bierman e993654
fix(types): exclude only api's JSX files from root tsc, not all of pa…
andrew-bierman fc47660
fix(types): exclude consent-page.test.ts from root tsc (last @kitajs/…
andrew-bierman a5bacf8
fix(types): exclude api integration tests from root tsc + fix 6 laten…
andrew-bierman 1c43a69
fix(api): cfAccess.test.ts — derive KeyLike type (jose 6 dropped the …
andrew-bierman 7757402
docs(mcp): ADR-0001 — oauth-provider over the bundled mcp() plugin
andrew-bierman d9e784a
docs(mcp): add Risk status to ADR-0001
andrew-bierman 11bc95c
docs(mcp): reframe legacy KV cleanup as optional pre-launch housekeeping
andrew-bierman fd52725
docs(mcp): record that there's no legacy KV/secret to clean up (verif…
andrew-bierman c35f5c6
feat(api): CodeRabbit triage fixes + Workers-native Sentry observabil…
andrew-bierman 0bba876
Merge remote-tracking branch 'origin/development' into plan/mcp-conne…
andrew-bierman e245d31
fix(post-merge): resolve type/coverage/dep/lint failures after develo…
andrew-bierman ae4250f
fix(types): stop worker-configuration.d.ts re-leaking @kitajs/html JS…
andrew-bierman 2e952e5
Merge remote-tracking branch 'origin/development' into plan/mcp-conne…
andrew-bierman 44e7086
fix(post-merge2): pin oauth-provider 1.6.11 + re-enable mcp check-types
andrew-bierman 6f15a02
refactor(consent): extract server HTML into built @packrat/consent-ui…
andrew-bierman c7112d6
refactor(mcp tests): restore noUncheckedIndexedAccess honesty (drop `…
andrew-bierman 11dd927
ci(mcp): drop the OOMing type-check step (#2533); keep Biome + deploy…
andrew-bierman 25e0e28
♻️ Re-enable MCP type-check via type-erasing tool()/prompt() wrappers…
andrew-bierman 0498510
🐛 Exempt MCP tool()/prompt() wrappers from no-owned-max-params
andrew-bierman 645709e
Merge origin/development into plan/mcp-connector-store-readiness
andrew-bierman 6996020
✅ test(mcp): cover registerPrompts + prompt() wrapper
andrew-bierman ce46842
✅ test(mcp): real handler/helper coverage for the MCP surface (74% → …
andrew-bierman f4a6907
✅ test(mcp): cover error/optional branches → ratchet baseline met (98…
andrew-bierman f8d326f
✅ test(api): cover consent-route scope/role branches (81% → 94% branch)
andrew-bierman 90c7430
✅ test(api): restore branch coverage on merge-touched files (ratchet)
andrew-bierman cc93e91
🐛 test(mcp): fix tools-admin-handlers tsc — typed structured-error ac…
andrew-bierman ec7830a
♻️ chore(mcp): drop mcp-deploy.yml; deploy CF-native + version_metadata
andrew-bierman 5beb6a1
fix(mcp): address connector readiness review cleanup
andrew-bierman 0691910
chore(mcp): remove redundant dedicated test workflow
andrew-bierman d9b047b
chore(mcp): remove manual readiness workflow
andrew-bierman a30653d
fix(api-client): keep app route type behind client package
andrew-bierman 4e50e2d
fix(mcp): remove unlaunched umbrella scope
andrew-bierman 929cf56
👷 ci(checks): gate types two ways — root tsc + per-package turbo
andrew-bierman 3ce4fc0
🔒️ ci(types): every package/app now type-checks (28/28)
andrew-bierman 00c3228
Merge origin/development into plan/mcp-connector-store-readiness
andrew-bierman df77200
Fix MCP service version metadata
andrew-bierman 1ad09ca
Remove direct Expo API package dependency
andrew-bierman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /** | ||
| * Smoke tests for the legal pages (U12 of the MCP connector-store readiness | ||
| * plan). | ||
| * | ||
| * The landing app uses Next.js with `output: 'export'` and a node-environment | ||
| * vitest setup (see `vitest.config.ts`). React-server-component imports don't | ||
| * resolve cleanly in that env, so the route-level "GET returns 200 with this | ||
| * string" test the og-meta suite performs (against the built `out/` HTML) | ||
| * would be the only true smoke pattern. We don't run a full Next build inside | ||
| * this suite to keep it fast; instead, the assertions below operate on the | ||
| * source `.tsx` files for the two legal pages and on the shared | ||
| * `config/site.ts` block that wires them up. | ||
| * | ||
| * What we verify: | ||
| * - The Terms of Service page source exists, exports the standard metadata | ||
| * shape, and contains the load-bearing MCP, jurisdiction-TODO, and | ||
| * hello@packratai.com strings a reviewer will scan for. | ||
| * - The Privacy Policy page source contains the new MCP / connectors | ||
| * addendum (heading + key bullet content). | ||
| * - `siteConfig.footerLinks.legal` exposes BOTH Privacy and Terms, and | ||
| * `siteConfig.support` advertises the canonical mailto. | ||
| * | ||
| * If a route smoke pattern lands later (e.g. happy-dom env + RSC eval, or | ||
| * a separate `vitest --config out-export.config.ts` workspace), the | ||
| * file-text assertions can be replaced — the reviewer-facing intent is the | ||
| * stable contract. | ||
| */ | ||
| import { existsSync, readFileSync } from 'node:fs'; | ||
| import { resolve } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { siteConfig } from '../config/site'; | ||
|
|
||
| const APP_DIR = resolve(__dirname, '..'); | ||
| const TOS_PAGE = resolve(APP_DIR, 'app/terms-of-service/page.tsx'); | ||
| const PRIVACY_PAGE = resolve(APP_DIR, 'app/privacy-policy/page.tsx'); | ||
|
|
||
| describe('Terms of Service page (/terms-of-service)', () => { | ||
| it('source file exists', () => { | ||
| expect(existsSync(TOS_PAGE)).toBe(true); | ||
| }); | ||
|
|
||
| const source = existsSync(TOS_PAGE) ? readFileSync(TOS_PAGE, 'utf8') : ''; | ||
|
|
||
| it('exports a Next.js metadata block with title/description/robots', () => { | ||
| expect(source).toContain('export const metadata'); | ||
| expect(source).toContain("title: 'Terms of Service | PackRat'"); | ||
| expect(source).toMatch(/description:\s*'[^']+'/); | ||
| expect(source).toMatch(/robots:\s*\{[^}]*index:\s*true/); | ||
| }); | ||
|
|
||
| it('renders the "Terms of Service" heading', () => { | ||
| expect(source).toContain('>Terms of Service<'); | ||
| }); | ||
|
|
||
| it('covers MCP connector provisions', () => { | ||
| // Reviewers grep for "MCP" — this section is the new content this unit | ||
| // ships and is what Anthropic's policy expects to find. | ||
| expect(source).toMatch(/MCP/); | ||
| expect(source).toContain('mcp.packratai.com'); | ||
| expect(source).toMatch(/mcp:admin/); | ||
| expect(source).toMatch(/OAuth/); | ||
| }); | ||
|
|
||
| it('includes the outdoor-safety disclaimer', () => { | ||
| expect(source).toMatch(/inherent risks/i); | ||
| }); | ||
|
|
||
| it('surfaces the canonical support contact', () => { | ||
| expect(source).toContain('hello@packratai.com'); | ||
| }); | ||
|
|
||
| it('leaves the operator-jurisdiction TODO marker in place', () => { | ||
| // U12 deliberately ships with a placeholder jurisdiction (Delaware) and a | ||
| // TODO so the operator can replace it after legal review. The check | ||
| // prevents the TODO from being silently lost in a future edit. | ||
| expect(source).toMatch(/TODO\(operator\): set jurisdiction/); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Privacy Policy page (/privacy-policy) — MCP addendum', () => { | ||
| it('source file exists', () => { | ||
| expect(existsSync(PRIVACY_PAGE)).toBe(true); | ||
| }); | ||
|
|
||
| const source = existsSync(PRIVACY_PAGE) ? readFileSync(PRIVACY_PAGE, 'utf8') : ''; | ||
|
|
||
| it('renders the new "MCP Connector & Third-Party Clients" section heading', () => { | ||
| expect(source).toContain('MCP Connector & Third-Party Clients'); | ||
| }); | ||
|
|
||
| it('explains OAuth token storage and rotation', () => { | ||
| expect(source).toMatch(/refresh token/i); | ||
| expect(source).toMatch(/Cloudflare KV/); | ||
| expect(source).toMatch(/60 minutes/); | ||
| }); | ||
|
|
||
| it('clarifies what MCP clients do NOT see', () => { | ||
| expect(source).toMatch(/never sees your\s+PackRat password|never sees your password/i); | ||
| expect(source).toMatch(/conversation content/i); | ||
| }); | ||
|
|
||
| it('points users at hello@packratai.com for deletion', () => { | ||
| expect(source).toContain('hello@packratai.com'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('siteConfig wiring (U12)', () => { | ||
| it('exposes BOTH Privacy and Terms in the footer legal block', () => { | ||
| const titles = siteConfig.footerLinks.legal.map((l) => l.title); | ||
| expect(titles).toContain('Privacy'); | ||
| expect(titles).toContain('Terms'); | ||
|
|
||
| const hrefs = siteConfig.footerLinks.legal.map((l) => l.href); | ||
| expect(hrefs).toContain('/privacy-policy'); | ||
| expect(hrefs).toContain('/terms-of-service'); | ||
| }); | ||
|
|
||
| it('exposes the canonical support contact', () => { | ||
| expect(siteConfig.support).toBeDefined(); | ||
| expect(siteConfig.support.email).toBe('hello@packratai.com'); | ||
| expect(siteConfig.support.mailto).toBe('mailto:hello@packratai.com'); | ||
| }); | ||
| }); | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /** | ||
| * Smoke tests for the MCP public docs page (U13). | ||
| * | ||
| * Same vitest-against-source approach as `legal.pages.test.ts`: the landing | ||
| * app uses Next.js `output: 'export'` and a node-only vitest env, so we | ||
| * can't import the RSC route directly. Assertions operate on the page | ||
| * source plus the generated `mcp-catalog.json` to verify reviewer-facing | ||
| * invariants the connector-store submission will be evaluated against. | ||
| * | ||
| * What we verify: | ||
| * - The page source exists, exports the standard metadata shape with | ||
| * `robots.index: true` (Anthropic must be able to crawl the docs URL). | ||
| * - The Quickstart / Scopes / Example prompts / Tool catalog / Resources | ||
| * / Privacy & security / Reviewer test account sections all render. | ||
| * - Three example prompts appear (per Software Directory Policy). | ||
| * - The Claude.ai custom-connector install URL is exactly the production | ||
| * MCP endpoint string. | ||
| * - `mcp-catalog.json` is present and non-trivial — the page renders | ||
| * from it, so a missing or empty JSON would surface as a build-time | ||
| * RSC error in production. | ||
| */ | ||
|
|
||
| import { existsSync, readFileSync } from 'node:fs'; | ||
| import { resolve } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| const APP_DIR = resolve(__dirname, '..'); | ||
| const PAGE = resolve(APP_DIR, 'app/mcp/page.tsx'); | ||
| const CATALOG = resolve(APP_DIR, 'data/mcp-catalog.json'); | ||
|
|
||
| describe('MCP public docs page (/mcp)', () => { | ||
| it('source file exists', () => { | ||
| expect(existsSync(PAGE)).toBe(true); | ||
| }); | ||
|
|
||
| const source = existsSync(PAGE) ? readFileSync(PAGE, 'utf8') : ''; | ||
|
|
||
| it('exports a Next.js metadata block (indexable)', () => { | ||
| expect(source).toContain('export const metadata'); | ||
| expect(source).toMatch(/title:\s*'PackRat MCP Connector \| PackRat'/); | ||
| expect(source).toMatch(/robots:\s*\{\s*index:\s*true/); | ||
| }); | ||
|
|
||
| it('renders the hero heading', () => { | ||
| expect(source).toMatch(/Plan trips, build packs, check weather/); | ||
| }); | ||
|
|
||
| it('exposes the production MCP endpoint URL verbatim', () => { | ||
| // The submission packet, the public docs page, and the worker's | ||
| // resourceMetadata MUST all advertise the same URL. A diff here is the | ||
| // canary on a drift that breaks Anthropic's audience verification. | ||
| expect(source).toContain('https://mcp.packratai.com/mcp'); | ||
| }); | ||
|
|
||
| it('lists the three OAuth scopes', () => { | ||
| // Sourced from the JSON dump at render time, but the header / table | ||
| // copy refers to them inline; the smoke test asserts both. | ||
| for (const scope of ['mcp:read', 'mcp:write', 'mcp:admin']) { | ||
| expect(source).toContain(scope); | ||
| } | ||
| }); | ||
|
|
||
| it('uses the Anthropic "custom connector" terminology', () => { | ||
| expect(source).toMatch(/custom connector/i); | ||
| }); | ||
|
|
||
| it('ships ≥ 3 example prompts (Software Directory Policy)', () => { | ||
| // Each example prompt is wrapped in a <blockquote>; count those. | ||
| const blockquotes = source.match(/<blockquote/g) ?? []; | ||
| expect(blockquotes.length).toBeGreaterThanOrEqual(3); | ||
| }); | ||
|
|
||
| it('points reviewers at the submission-packet doc for credentials', () => { | ||
| expect(source).toContain('docs/mcp/submission-packet.md'); | ||
| // And explicitly states credentials are NOT on the public page. | ||
| expect(source).toMatch(/do not publish credentials/i); | ||
| }); | ||
|
|
||
| it('links the legal / privacy / support surfaces', () => { | ||
| expect(source).toContain('/privacy-policy'); | ||
| expect(source).toContain('/terms-of-service'); | ||
| expect(source).toContain('hello@packratai.com'); | ||
| }); | ||
|
|
||
| it('points to the developer README and the implementation plan', () => { | ||
| expect(source).toContain('packages/mcp/README.md'); | ||
| expect(source).toContain( | ||
| 'docs/plans/2026-05-22-001-feat-mcp-connector-store-readiness-plan.md', | ||
| ); | ||
| expect(source).toContain('docs/mcp/runbook.md'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mcp-catalog.json (build-time data source for /mcp)', () => { | ||
| it('is present and parses as JSON', () => { | ||
| expect(existsSync(CATALOG)).toBe(true); | ||
| const raw = readFileSync(CATALOG, 'utf8'); | ||
| // Must round-trip cleanly — the page imports it as a typed module. | ||
| expect(() => JSON.parse(raw)).not.toThrow(); | ||
| }); | ||
|
|
||
| const raw = existsSync(CATALOG) ? readFileSync(CATALOG, 'utf8') : '{}'; | ||
| const catalog = JSON.parse(raw) as { | ||
| totalTools?: number; | ||
| counts?: { byClassification?: Record<string, number> }; | ||
| tools?: Array<{ name: string; classification: string }>; | ||
| endpoint?: string; | ||
| }; | ||
|
|
||
| it('contains ≥ 80 tools (sanity floor matching the U7 annotations test)', () => { | ||
| expect(catalog.totalTools ?? 0).toBeGreaterThanOrEqual(80); | ||
| expect((catalog.tools ?? []).length).toBe(catalog.totalTools ?? -1); | ||
| }); | ||
|
|
||
| it('every tool name starts with the packrat_ prefix', () => { | ||
| for (const t of catalog.tools ?? []) { | ||
| expect(t.name).toMatch(/^packrat_/); | ||
| } | ||
| }); | ||
|
|
||
| it('partitions tools into read / write / admin classifications', () => { | ||
| const c = catalog.counts?.byClassification ?? {}; | ||
| expect(c.read).toBeGreaterThan(0); | ||
| expect(c.write).toBeGreaterThan(0); | ||
| expect(c.admin).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('advertises the production endpoint URL', () => { | ||
| expect(catalog.endpoint).toBe('https://mcp.packratai.com/mcp'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
These assertions are tied to source text, not page behavior.
Reading
.tsxfiles and grepping literals will miss regressions in the rendered legal pages and metadata contract, while also failing on harmless refactors like extracting shared constants. Please move this to a render/build-level check of the actual page output.As per coding guidelines "Avoid testing implementation details; test observable behaviour."
🤖 Prompt for AI Agents