Closes #2248. Completes SEP-2640 support across all three clients, and closes the one obligation #2234 deliberately left open. - `checkSkillFrontmatterMatch` compares a served SKILL.md's own YAML frontmatter against the entry the listing advertised, field by field. No digest can cover this: a digest is taken over the bytes the server served, so it proves the file was not altered in transit and says nothing about whether the listing described it honestly. Needs a real YAML parser; `yaml` was already a root dependency, so this adds no package — but it does newly put it on core/'s import graph, hence the three bundler `external` lists. - `resources/directory/read`: result schemas defined against the normative text, `InspectorClient.readResourceDirectory`, a Directory section on the Skills screen, and `--method resources/directory/read` in the CLI. The client refuses the call locally when the server did not declare `directoryRead`, which is the SEP's MUST NOT. A child the directory lists but the entry does not is marked `not listed` rather than merged into the manifest — the SEP calls a directory result a live observation and forbids treating it as extending the manifest. - CLI: `skills/list`, `skills/get` and `resources/directory/read`, plus `--verify` — one NDJSON report per skill, a summary on stderr, exit 7 on a violation. Reads are sequential; a conforming manifest may declare 512 entries. - TUI: a Skills pane, shown only when the server declares the extension. Each row carries its conformance verdict as a glyph as well as a colour, since the pane is read over ssh and through script(1). Enter verifies. - `verifySkills` re-throws `AuthRecoveryRequiredError` rather than recording it per file: it says the session's authorization expired, so absorbing it would report N identical read failures and swallow the error the TUI and the web commands key off to reauthorize. Two open questions settled, both in code comments where they will be found: `skills/get` carries no caching attributes because SEP-2640 leaves the question open in as many words, so requiring them would fail a conforming server; and there is no `PagedSkillsState` because every consumer of this list is a whole-catalog verdict — computed over page one of three, "this server's skills conform" is not merely partial but wrong. The fixture grows two skills, both for checks that were otherwise undemonstrable: `lying-listing` (listing and file disagree, digest still verifies) and `stale-manifest` (serves a file its manifest does not declare). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND46Ci64DC3tzBy9aG81Nc Signed-off-by: cliffhall <cliff@futurescale.com>
79 lines
3.6 KiB
TypeScript
79 lines
3.6 KiB
TypeScript
import { defineConfig } from "tsup";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(dirname, "../..");
|
|
|
|
export default defineConfig({
|
|
entry: ["src/index.ts"],
|
|
format: ["esm"],
|
|
outDir: "build",
|
|
clean: true,
|
|
// No source maps in the published bundle — they roughly double the on-disk
|
|
// size and aren't needed at runtime (debug via `npm run dev` on the source).
|
|
sourcemap: false,
|
|
target: "node22",
|
|
platform: "node",
|
|
noExternal: [/^@inspector\/core/],
|
|
external: [
|
|
// `undici` MUST stay external. It is CommonJS, so inlining it rewrites
|
|
// `import("undici")` to a relative chunk whose `require("assert")` hits
|
|
// esbuild's ESM `__require` shim and throws "Dynamic require of \"assert\"
|
|
// is not supported" — and because the specifier was rewritten at build time,
|
|
// no user-side install can ever satisfy it (#2067). It is declared in the
|
|
// ROOT manifest only, so tsup cannot infer this from a nearest-manifest
|
|
// lookup; the entry has to be explicit.
|
|
"undici",
|
|
"commander",
|
|
"open",
|
|
"pino",
|
|
"hono",
|
|
"@hono/node-server",
|
|
"vite",
|
|
"@vitejs/plugin-react",
|
|
"atomically",
|
|
"chokidar",
|
|
"@napi-rs/keyring",
|
|
// Root-declared (see the repo's dependency-placement rule) and CJS, which
|
|
// is the combination that bites: tsup externalizes what the *client's*
|
|
// package.json declares, so a root-only dependency is bundled unless named
|
|
// here — and inlining a CJS module into an ESM bundle leaves esbuild's
|
|
// `Dynamic require of "path" is not supported` shim, which throws at
|
|
// import time and takes the whole binary down before it parses a flag.
|
|
"proper-lockfile",
|
|
"@modelcontextprotocol/client",
|
|
"@modelcontextprotocol/core",
|
|
// Root-declared and reached through `core/mcp/apps.ts`, so it must be
|
|
// external here like every other root runtime dependency — which client
|
|
// actually reaches it is a function of what `core/` imports, not of this
|
|
// client's own code, so all three lists carry it (AGENTS.md). The CLI was
|
|
// inlining it; the #2067 guard surfaced that.
|
|
"@modelcontextprotocol/ext-apps",
|
|
// Consolidated to the ROOT manifest by #2195, along with every other
|
|
// runtime dependency `core/` imports. tsup externalizes only what the
|
|
// *nearest* package.json declares, so once a client stops declaring one it
|
|
// must be named here or esbuild inlines it — the #2067 failure class, now
|
|
// reached by a manifest edit rather than an omission.
|
|
"ajv",
|
|
"zod",
|
|
// Newly on `core/`'s runtime import graph as of #2248:
|
|
// `core/mcp/skillFile.ts` parses a served SKILL.md's YAML frontmatter to
|
|
// check it against the entry the listing advertised (SEP-2640). Already a
|
|
// root `dependency` — it was reached from `test-servers/src` — so this
|
|
// adds no package, but a root-declared dependency `core/` imports must be
|
|
// named in all three `external` lists or tsup inlines it here.
|
|
"yaml",
|
|
// Reached through `core/` but not through this client's own code today.
|
|
// AGENTS.md requires every root-declared package `core/` imports at runtime
|
|
// in ALL three lists regardless, because which client reaches one is a
|
|
// function of what `core/` imports rather than of what the client names —
|
|
// so the list must not depend on today's reachability (Copilot).
|
|
"react",
|
|
],
|
|
esbuildOptions(options) {
|
|
options.alias = {
|
|
"@inspector/core": path.join(repoRoot, "core"),
|
|
};
|
|
},
|
|
});
|