open-museum-mcp 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +35 -5
  2. package/dist/clearanceTool.d.ts +25 -0
  3. package/dist/clearanceTool.js +24 -0
  4. package/dist/clearanceTool.js.map +1 -0
  5. package/dist/core/cache.d.ts +20 -0
  6. package/dist/core/cache.js +2 -0
  7. package/dist/core/cache.js.map +1 -0
  8. package/dist/core/clearance/envelope.d.ts +26 -0
  9. package/dist/core/clearance/envelope.js +15 -0
  10. package/dist/core/clearance/envelope.js.map +1 -0
  11. package/dist/core/clearance/jcs.d.ts +8 -0
  12. package/dist/core/clearance/jcs.js +65 -0
  13. package/dist/core/clearance/jcs.js.map +1 -0
  14. package/dist/core/clearance/licenseMap.d.ts +38 -0
  15. package/dist/core/clearance/licenseMap.js +68 -0
  16. package/dist/core/clearance/licenseMap.js.map +1 -0
  17. package/dist/core/clearance/manifest.d.ts +104 -0
  18. package/dist/core/clearance/manifest.js +112 -0
  19. package/dist/core/clearance/manifest.js.map +1 -0
  20. package/dist/core/federation.d.ts +98 -0
  21. package/dist/core/federation.js +165 -0
  22. package/dist/core/federation.js.map +1 -0
  23. package/dist/core/index.d.ts +21 -0
  24. package/dist/core/index.js +24 -0
  25. package/dist/core/index.js.map +1 -0
  26. package/dist/dateParser.js +8 -2
  27. package/dist/dateParser.js.map +1 -1
  28. package/dist/fetchers/aic.js +11 -6
  29. package/dist/fetchers/aic.js.map +1 -1
  30. package/dist/fetchers/met.js +8 -1
  31. package/dist/fetchers/met.js.map +1 -1
  32. package/dist/server.js +50 -110
  33. package/dist/server.js.map +1 -1
  34. package/package.json +14 -4
  35. package/spec/clearance/VERSIONING.md +77 -0
  36. package/spec/clearance/v0.1/advisory-entry.schema.json +30 -0
  37. package/spec/clearance/v0.1/clearance-manifest.schema.json +213 -0
  38. package/spec/clearance/v0.1/context.jsonld +88 -0
  39. package/spec/clearance/v0.1/examples/cc0-accepted.json +107 -0
  40. package/spec/clearance/v0.1/examples/deny-unrecognized.json +78 -0
  41. package/spec/clearance/v0.1/rules.md +106 -0
  42. package/spec/clearance/v0.1/spec.md +212 -0
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/cfpramod/open-museum-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/cfpramod/open-museum-mcp/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/open-museum-mcp.svg)](https://www.npmjs.com/package/open-museum-mcp)
5
+ [![open-museum-mcp MCP server](https://glama.ai/mcp/servers/cfpramod/open-museum-mcp/badges/score.svg)](https://glama.ai/mcp/servers/cfpramod/open-museum-mcp)
5
6
 
6
7
  > One search across five open-access museum collections, with strict per-museum rights verification and ready-to-use citations.
7
8
 
@@ -87,7 +88,17 @@ A search call returns license-verified results in one normalized shape:
87
88
 
88
89
  > **Want to see it work before installing?** [pramod.ch/open-museum-mcp](https://pramod.ch/open-museum-mcp) walks through the three core workflows with real records and citations.
89
90
 
90
- The package is on npm. The simplest setup is to add it directly to your MCP client config; `npx` will fetch and run it on first launch:
91
+ ### One-click
92
+
93
+ For supported clients, no JSON, no Node, no npm — pick your client:
94
+
95
+ - **Claude Desktop:** [Download `open-museum-mcp.mcpb`](https://github.com/cfpramod/open-museum-mcp/releases/latest/download/open-museum-mcp.mcpb) from the latest release and double-click it. Claude Desktop opens the bundle and installs the server in one step.
96
+ - **VS Code:** [Install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22open-museum-mcp%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22open-museum-mcp%22%5D%7D) — opens VS Code with the install dialog prefilled (requires the GitHub Copilot Chat extension).
97
+ - **Cursor:** [Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=open-museum-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm9wZW4tbXVzZXVtLW1jcCJdfQ==) — same idea, Cursor-flavoured (MCP support is built in).
98
+
99
+ ### JSON config (any MCP client)
100
+
101
+ For ChatGPT with MCP, Cline, Goose, Continue, Zed, or anything else: paste this into your client's MCP config and restart. `npx` fetches and runs the server on first launch.
91
102
 
92
103
  ```json
93
104
  {
@@ -100,8 +111,6 @@ The package is on npm. The simplest setup is to add it directly to your MCP clie
100
111
  }
101
112
  ```
102
113
 
103
- That's it. Restart your MCP client and the tools below become available.
104
-
105
114
  ### From source (for contributors)
106
115
 
107
116
  ```bash
@@ -246,6 +255,14 @@ Highlights:
246
255
  - `artist.attributionType` distinguishes `named` / `anonymous` / `workshop` / `after` / `attributed` / `circle` / `follower`.
247
256
  - `imageOpenAccess` is held distinct from `metadataOpenAccess` because museums frequently publish open metadata for objects whose images are not openly licensed.
248
257
 
258
+ ### Compatibility
259
+
260
+ `Artwork` field semantics are stable. New versions may add fields or tools, but won't repurpose or remove existing ones within a major version.
261
+
262
+ - **Backward-compatible.** New server versions may add fields to `Artwork`, add new tools, or add new optional arguments. A v0.5 client calling a v0.6 server will continue to work; it just ignores fields it doesn't know.
263
+ - **Forward-compatible.** A v0.6 client calling an older v0.5 server will get records that lack v0.6-only fields, but the records it does get will validate against the older client's schema.
264
+ - **SemVer.** While in v0.x, MINOR bumps may include backward-incompatible changes (per the [SemVer spec](https://semver.org/) for pre-1.0 versions). From v1.0 onwards, only MAJOR bumps may break compatibility.
265
+
249
266
  ## Non-goals
250
267
 
251
268
  - **Not a full art-history ontology.** The dynasty and region tables cover the most-encountered cases; they are not exhaustive iconographic taxonomies.
@@ -257,13 +274,26 @@ Highlights:
257
274
 
258
275
  - v0.1: Met adapter, dynasty-aware date parser, license gate, `cite` tool, MCP resources.
259
276
  - v0.2: Cleveland and AIC adapters, `discover_random` with constraints, `list_traditions`.
260
- - v0.3: Wikimedia Commons adapter (per-record rights model; unlocks works whose home museums lack public APIs).
261
- - v0.4: Europeana adapter (federated European institutions, opt-in via API key); `year_min`/`year_max` date-range filter on `search_artworks`. **(here)**
277
+ - v0.3: Wikimedia Commons adapter (per-record rights model; covers works whose home museums lack public APIs).
278
+ - v0.4: Europeana adapter (federated European institutions, opt-in via API key); `year_min`/`year_max` date-range filter on `search_artworks`.
279
+ - v0.5: `.mcpb` Desktop Extension bundle (one-click Claude Desktop install); migration to Node 22+ `node:sqlite` (no native compile required). **(here)**
262
280
  - v0.7: Wikidata enrichment (artist QIDs, movement, country, dedup across cache).
263
281
  - v0.8: Dominant-colour extraction across museums (`color: "#3a5f7d"` discovery via `sharp`).
264
282
  - v1.0: Artist-obscurity scoring (`object_count_total`, `museum_count`) for deliberate exploration of less-canonical work.
265
283
  - v2.0: Smithsonian, Rijksmuseum direct integration, Walters Art Museum, more European institutions.
266
284
 
285
+ ### Release cadence
286
+
287
+ This is a side project. New releases ship when one of three things happens: my own work needs a feature, a new museum is wired in, or a contributor's PR is merged. There is no fixed cadence. I do commit to reviewing and merging contributor PRs promptly.
288
+
289
+ ### Project status
290
+
291
+ This is actively maintained as of the latest release. Because it is a side project, if active maintenance ever stops I will archive the repository and say so here, rather than leave it looking live. The published npm package, the adapters, and the `Artwork` schema remain usable either way, and the code is MIT-licensed, so anyone is free to fork and continue it. Governance and the path for new maintainers are described in [GOVERNANCE.md](GOVERNANCE.md).
292
+
293
+ ### Changelog
294
+
295
+ [Releases](https://github.com/cfpramod/open-museum-mcp/releases) lists what shipped in each version, with notes.
296
+
267
297
  ## Contributing a museum adapter
268
298
 
269
299
  See [CONTRIBUTING.md](CONTRIBUTING.md). The short version:
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import type { Federation } from './core/index.js';
3
+ /**
4
+ * Input schema for the `clearance_record` MCP tool.
5
+ *
6
+ * Deliberately does NOT regex-validate the id against ID_REGEX, unlike
7
+ * `get_artwork` and `cite`. The clearance tool's contract is that a non-cleared
8
+ * work — including a malformed id — returns a definitive DENY manifest, not an
9
+ * error. `federation.clearanceManifest` emits that deny for an invalid id, so
10
+ * gating the id here would wrongly convert a contractual deny into a ZodError.
11
+ */
12
+ export declare const ClearanceInput: z.ZodObject<{
13
+ id: z.ZodString;
14
+ }, z.core.$strip>;
15
+ /**
16
+ * Handle a `clearance_record` call. Returns a normal (non-error) tool result
17
+ * for every id: a cleared work yields a permitted manifest, a non-cleared or
18
+ * malformed id yields a deny manifest. Both are valid answers.
19
+ */
20
+ export declare function handleClearanceRecord(federation: Federation, args: unknown): Promise<{
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ }>;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Input schema for the `clearance_record` MCP tool.
4
+ *
5
+ * Deliberately does NOT regex-validate the id against ID_REGEX, unlike
6
+ * `get_artwork` and `cite`. The clearance tool's contract is that a non-cleared
7
+ * work — including a malformed id — returns a definitive DENY manifest, not an
8
+ * error. `federation.clearanceManifest` emits that deny for an invalid id, so
9
+ * gating the id here would wrongly convert a contractual deny into a ZodError.
10
+ */
11
+ export const ClearanceInput = z.object({
12
+ id: z.string(),
13
+ });
14
+ /**
15
+ * Handle a `clearance_record` call. Returns a normal (non-error) tool result
16
+ * for every id: a cleared work yields a permitted manifest, a non-cleared or
17
+ * malformed id yields a deny manifest. Both are valid answers.
18
+ */
19
+ export async function handleClearanceRecord(federation, args) {
20
+ const input = ClearanceInput.parse(args);
21
+ const env = await federation.clearanceManifest(input.id);
22
+ return { content: [{ type: 'text', text: JSON.stringify(env, null, 2) }] };
23
+ }
24
+ //# sourceMappingURL=clearanceTool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clearanceTool.js","sourceRoot":"","sources":["../src/clearanceTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;CACf,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAAsB,EAAE,IAAa;IAC/E,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACtF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { Artwork } from '../types.js';
2
+ /** A value that may be returned synchronously or as a promise. */
3
+ export type Awaitable<T> = T | Promise<T>;
4
+ /**
5
+ * The minimal cache surface the federation engine depends on. Two methods for
6
+ * normalized objects, two for search-result ID lists.
7
+ *
8
+ * Implementations may be synchronous or asynchronous: the MCP server's
9
+ * `node:sqlite` cache returns values directly, while an edge deployment
10
+ * (e.g. Cloudflare KV) returns promises. The federation `await`s every call,
11
+ * so both satisfy this interface. Keeping the surface this small is what lets
12
+ * the same engine run on a stdio server and on a Workers runtime that cannot
13
+ * load `node:sqlite`.
14
+ */
15
+ export interface CacheStore {
16
+ getObject(id: string): Awaitable<Artwork | null>;
17
+ upsertObject(art: Artwork): Awaitable<void>;
18
+ getQuery(cacheKey: string): Awaitable<string[] | null>;
19
+ putQuery(cacheKey: string, ids: string[]): Awaitable<void>;
20
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/core/cache.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Tier-0 envelope: integrity, not authenticity. It wraps a pure Clearance
3
+ * Manifest payload with a hash computed over the payload's RFC 8785 (JCS)
4
+ * canonical bytes. The hash lives HERE, never inside the payload — payload
5
+ * purity is a design invariant (the payload never carries its own hash, its
6
+ * signature, or any commercial data).
7
+ *
8
+ * This is what the distributed OSS MCP emits by default; it ships no key.
9
+ * Tiers 1/2 (C2PA signing) wrap the same payload with a signature — the payload
10
+ * shape is unchanged across tiers.
11
+ */
12
+ export interface Tier0Envelope<T = unknown> {
13
+ tier: 0;
14
+ payload: T;
15
+ integrity: {
16
+ alg: 'sha-256';
17
+ jcs: true;
18
+ hash: string;
19
+ };
20
+ }
21
+ /**
22
+ * Wrap a payload in a Tier-0 envelope. Canonicalizes FIRST (RFC 8785), then
23
+ * hashes the canonical bytes with Web Crypto (`crypto.subtle`, Workers-safe).
24
+ * Throws if the payload is not canonicalizable JSON.
25
+ */
26
+ export declare function wrapTier0<T>(payload: T): Promise<Tier0Envelope<T>>;
@@ -0,0 +1,15 @@
1
+ import { canonicalizeToBytes } from './jcs.js';
2
+ /**
3
+ * Wrap a payload in a Tier-0 envelope. Canonicalizes FIRST (RFC 8785), then
4
+ * hashes the canonical bytes with Web Crypto (`crypto.subtle`, Workers-safe).
5
+ * Throws if the payload is not canonicalizable JSON.
6
+ */
7
+ export async function wrapTier0(payload) {
8
+ const bytes = canonicalizeToBytes(payload);
9
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
10
+ const hash = Array.from(new Uint8Array(digest))
11
+ .map((b) => b.toString(16).padStart(2, '0'))
12
+ .join('');
13
+ return { tier: 0, payload, integrity: { alg: 'sha-256', jcs: true, hash } };
14
+ }
15
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/core/clearance/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAmB/C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,OAAU;IAC3C,MAAM,KAAK,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;SAC5C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Canonicalize a JSON-compatible value to its RFC 8785 string form.
3
+ * Throws on values JSON cannot represent (functions, symbols, undefined,
4
+ * non-finite numbers) — a Clearance Manifest payload must be canonicalizable.
5
+ */
6
+ export declare function canonicalize(value: unknown): string;
7
+ /** Canonicalize and encode to UTF-8 bytes (ArrayBuffer-backed, for Web Crypto). */
8
+ export declare function canonicalizeToBytes(value: unknown): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,65 @@
1
+ // RFC 8785 JSON Canonicalization Scheme (JCS).
2
+ //
3
+ // Produces the canonical UTF-8 serialization of a JSON value: object keys sorted
4
+ // by UTF-16 code unit, ECMAScript number serialization, minimal string escaping,
5
+ // no insignificant whitespace. This is the byte sequence a Clearance Manifest's
6
+ // Tier-0 envelope hashes over.
7
+ //
8
+ // Lifted verbatim (ported to TypeScript) from the PIF sibling standard's vetted
9
+ // dependency-free verifier (pif-spec/verifier/jcs.mjs). Runs unchanged in Node,
10
+ // the browser, and Cloudflare Workers — no dependencies, no `node:` imports.
11
+ //
12
+ // IMPORTANT (the contract the spec must warn implementers about): canonicalize
13
+ // FIRST, then hash the canonical bytes. Never hash the raw serialized string —
14
+ // JSON.stringify on the same object can produce different bytes (key order,
15
+ // whitespace) and therefore a different, non-interoperable hash.
16
+ /**
17
+ * Canonicalize a JSON-compatible value to its RFC 8785 string form.
18
+ * Throws on values JSON cannot represent (functions, symbols, undefined,
19
+ * non-finite numbers) — a Clearance Manifest payload must be canonicalizable.
20
+ */
21
+ export function canonicalize(value) {
22
+ if (value === null)
23
+ return 'null';
24
+ const t = typeof value;
25
+ if (t === 'boolean')
26
+ return value ? 'true' : 'false';
27
+ if (t === 'number') {
28
+ if (!Number.isFinite(value)) {
29
+ throw new Error('JCS: non-finite numbers are not permitted in JSON');
30
+ }
31
+ // JSON.stringify uses ECMAScript Number-to-string, which is what JCS mandates.
32
+ return JSON.stringify(value);
33
+ }
34
+ if (t === 'string') {
35
+ // ECMAScript JSON string escaping (control chars as \u00XX, correct surrogate
36
+ // handling) is exactly the escaping RFC 8785 specifies.
37
+ //
38
+ // PORTABILITY NOTE for non-JS implementers (RFC 8785 §3.2.2.2): escape ONLY the
39
+ // two mandatory characters (" -> \", \ -> \\) and the C0 control range U+0000..U+001F,
40
+ // using the short forms \b \t \n \f \r where they exist and \u00XX (lowercase hex)
41
+ // otherwise. Do NOT escape the forward solidus "/", and do NOT escape any non-ASCII
42
+ // character; codepoints >= U+0080 are emitted as raw UTF-8. Over-escaping (e.g.
43
+ // \uXXXX for every non-ASCII char, as some language JSON encoders do by default) is
44
+ // the most common interop bug and will produce a different byte string, hence a
45
+ // different hash. V8's JSON.stringify already implements exactly this.
46
+ return JSON.stringify(value);
47
+ }
48
+ if (Array.isArray(value)) {
49
+ return '[' + value.map(canonicalize).join(',') + ']';
50
+ }
51
+ if (t === 'object') {
52
+ // Object.keys() then default Array sort: lexicographic by UTF-16 code unit,
53
+ // which is the ordering RFC 8785 requires.
54
+ const obj = value;
55
+ const keys = Object.keys(obj).sort();
56
+ const members = keys.map((k) => JSON.stringify(k) + ':' + canonicalize(obj[k]));
57
+ return '{' + members.join(',') + '}';
58
+ }
59
+ throw new Error(`JCS: unsupported value of type ${t}`);
60
+ }
61
+ /** Canonicalize and encode to UTF-8 bytes (ArrayBuffer-backed, for Web Crypto). */
62
+ export function canonicalizeToBytes(value) {
63
+ return new TextEncoder().encode(canonicalize(value));
64
+ }
65
+ //# sourceMappingURL=jcs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jcs.js","sourceRoot":"","sources":["../../../src/core/clearance/jcs.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,+BAA+B;AAC/B,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,6EAA6E;AAC7E,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,iEAAiE;AAEjE;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAElC,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC;IAEvB,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAErD,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,+EAA+E;QAC/E,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,8EAA8E;QAC9E,wDAAwD;QACxD,EAAE;QACF,gFAAgF;QAChF,uFAAuF;QACvF,mFAAmF;QACnF,oFAAoF;QACpF,gFAAgF;QAChF,oFAAoF;QACpF,gFAAgF;QAChF,uEAAuE;QACvE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,4EAA4E;QAC5E,2CAA2C;QAC3C,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChF,OAAO,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACvC,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { LicenseType, RightsConfidence } from '../../types.js';
2
+ /**
3
+ * The structured `basis` for a single clearance determination. Shape is normative
4
+ * (schema-enforced): `rule`, `inputs`, and `summary` are all required —
5
+ * honesty-by-architecture. A determination that cannot name its rule, cite its
6
+ * inputs, and state its reasoning is not a valid determination.
7
+ *
8
+ * `rule` is a stable id that resolves as a fragment under the rule registry:
9
+ * `https://openclearance.org/v0.1/rules#<rule>`. The set of ids is OPEN, not a
10
+ * closed enum — an unrecognised id yields a non-fatal `unrecognised_rule`
11
+ * advisory at verification time, never a schema rejection. See
12
+ * `spec/clearance/v0.1/rules.md`.
13
+ */
14
+ export interface ClearanceBasis {
15
+ rule: string;
16
+ inputs: {
17
+ field: string;
18
+ value: unknown;
19
+ }[];
20
+ summary: string;
21
+ }
22
+ export interface ClearanceDecision {
23
+ statement: string | null;
24
+ commercialReproduction: {
25
+ permitted: boolean;
26
+ basis: ClearanceBasis;
27
+ };
28
+ derivatives: {
29
+ permitted: boolean;
30
+ basis: ClearanceBasis;
31
+ };
32
+ attributionRequired: {
33
+ required: boolean;
34
+ basis: ClearanceBasis;
35
+ };
36
+ confidence: RightsConfidence;
37
+ }
38
+ export declare function clearanceForLicense(type: LicenseType): ClearanceDecision;
@@ -0,0 +1,68 @@
1
+ // The ONLY place clearance determinations live. Fail-closed: a license type not
2
+ // listed here resolves to all-false / default-deny. Adding a recognised
3
+ // permissive license later is a single entry here + a rule-registry row + a test.
4
+ const PERMISSIVE = {
5
+ CC0: {
6
+ statement: 'https://creativecommons.org/publicdomain/zero/1.0/',
7
+ rulePrefix: 'cc0',
8
+ summaryNoun: 'CC0 public-domain dedication',
9
+ },
10
+ // The gate only emits type=PD for the worldwide Creative Commons Public
11
+ // Domain Mark (Europeana `rights` = .../publicdomain/mark/1.0/) and the
12
+ // Wikimedia PD/PDM templates — never a jurisdiction-scoped rightsstatements.org
13
+ // value. The Public Domain Mark URI is therefore the accurate, gate-aligned
14
+ // statement; the old US-scoped NoC-US URI claimed a narrower (and incorrect)
15
+ // scope the gate never asserts.
16
+ PD: {
17
+ statement: 'https://creativecommons.org/publicdomain/mark/1.0/',
18
+ rulePrefix: 'pd',
19
+ summaryNoun: 'Public Domain Mark status',
20
+ },
21
+ };
22
+ export function clearanceForLicense(type) {
23
+ const ok = PERMISSIVE[type];
24
+ const inputs = [{ field: 'license.type', value: type }];
25
+ if (ok) {
26
+ return {
27
+ statement: ok.statement,
28
+ commercialReproduction: {
29
+ permitted: true,
30
+ basis: {
31
+ rule: `${ok.rulePrefix}-grants-commercial`,
32
+ inputs,
33
+ summary: `${ok.summaryNoun} permits all uses, including commercial.`,
34
+ },
35
+ },
36
+ derivatives: {
37
+ permitted: true,
38
+ basis: {
39
+ rule: `${ok.rulePrefix}-grants-derivatives`,
40
+ inputs,
41
+ summary: `${ok.summaryNoun} permits modification and derivative works.`,
42
+ },
43
+ },
44
+ attributionRequired: {
45
+ required: false,
46
+ basis: {
47
+ rule: `${ok.rulePrefix}-waives-attribution`,
48
+ inputs,
49
+ summary: `${ok.summaryNoun} requires no attribution as a condition of reuse.`,
50
+ },
51
+ },
52
+ confidence: 'high',
53
+ };
54
+ }
55
+ const denyBasis = {
56
+ rule: 'default-deny',
57
+ inputs,
58
+ summary: `unrecognized or non-permissive license type '${type}' ⇒ default deny`,
59
+ };
60
+ return {
61
+ statement: null,
62
+ commercialReproduction: { permitted: false, basis: denyBasis },
63
+ derivatives: { permitted: false, basis: denyBasis },
64
+ attributionRequired: { required: false, basis: denyBasis },
65
+ confidence: 'low',
66
+ };
67
+ }
68
+ //# sourceMappingURL=licenseMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"licenseMap.js","sourceRoot":"","sources":["../../../src/core/clearance/licenseMap.ts"],"names":[],"mappings":"AA4BA,gFAAgF;AAChF,wEAAwE;AACxE,kFAAkF;AAClF,MAAM,UAAU,GAEZ;IACF,GAAG,EAAE;QACH,SAAS,EAAE,oDAAoD;QAC/D,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,8BAA8B;KAC5C;IACD,wEAAwE;IACxE,wEAAwE;IACxE,gFAAgF;IAChF,4EAA4E;IAC5E,6EAA6E;IAC7E,gCAAgC;IAChC,EAAE,EAAE;QACF,SAAS,EAAE,oDAAoD;QAC/D,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,2BAA2B;KACzC;CACF,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,IAAiB;IACnD,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAExD,IAAI,EAAE,EAAE,CAAC;QACP,OAAO;YACL,SAAS,EAAE,EAAE,CAAC,SAAS;YACvB,sBAAsB,EAAE;gBACtB,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,oBAAoB;oBAC1C,MAAM;oBACN,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,0CAA0C;iBACrE;aACF;YACD,WAAW,EAAE;gBACX,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,qBAAqB;oBAC3C,MAAM;oBACN,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,6CAA6C;iBACxE;aACF;YACD,mBAAmB,EAAE;gBACnB,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,qBAAqB;oBAC3C,MAAM;oBACN,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,mDAAmD;iBAC9E;aACF;YACD,UAAU,EAAE,MAAM;SACnB,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAmB;QAChC,IAAI,EAAE,cAAc;QACpB,MAAM;QACN,OAAO,EAAE,gDAAgD,IAAI,kBAAkB;KAChF,CAAC;IACF,OAAO;QACL,SAAS,EAAE,IAAI;QACf,sBAAsB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;QAC9D,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;QACnD,mBAAmB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;QAC1D,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,104 @@
1
+ import type { Artist, ArtworkImages, RightsConfidence, ValidationResult } from '../../types.js';
2
+ import { type ClearanceBasis } from './licenseMap.js';
3
+ /**
4
+ * The pure Clearance Manifest payload (JSON-LD). It never contains its own hash,
5
+ * its signature, or any commercial data — integrity lives in the Tier-0 envelope
6
+ * (see envelope.ts), commerce in a sibling vendor assertion that references this
7
+ * payload by content hash. The payload is byte-identical to what the engine
8
+ * emits and independently verifiable.
9
+ *
10
+ * Field names mirror the engine's canonical Artwork vocabulary wherever they map
11
+ * (`artist`, `displayDate`, `imageUrls`, `imageOpenAccess`, `metadataOpenAccess`,
12
+ * `museum`) so the open-museum.art data-model reconciliation is a thin mapping,
13
+ * not a translation. JSON-LD semantics are supplied by aliasing these terms to
14
+ * schema.org / Dublin Core IRIs in context.jsonld — the `@context` is the sole
15
+ * normative authority; `specVersion` is human-readable convenience only.
16
+ */
17
+ export interface ClearanceManifestPayload {
18
+ '@context': string[];
19
+ type: 'ClearanceManifest';
20
+ specVersion: string;
21
+ work: ClearanceWork;
22
+ source: ClearanceSource;
23
+ rights: ClearanceRights;
24
+ clearance: ClearanceBlock;
25
+ verification: ClearanceVerification;
26
+ /** Omitted for rejected records, which carry no identified Artwork to cite. */
27
+ citation?: ClearanceCitation;
28
+ }
29
+ export interface ClearanceWork {
30
+ id: string;
31
+ title?: string;
32
+ artist?: Artist;
33
+ displayDate?: string;
34
+ yearStart?: number | null;
35
+ yearEnd?: number | null;
36
+ medium?: string;
37
+ }
38
+ export interface ClearanceMuseum {
39
+ code: string;
40
+ name?: string;
41
+ url?: string;
42
+ }
43
+ export interface ClearanceSource {
44
+ museum: ClearanceMuseum;
45
+ apiUrl?: string;
46
+ pageUrl?: string;
47
+ originalUrl?: string;
48
+ imageUrls?: ArtworkImages;
49
+ }
50
+ export interface ClearanceRights {
51
+ statement: string | null;
52
+ sourceApiValue: {
53
+ field: string;
54
+ value: unknown;
55
+ } | null;
56
+ imageOpenAccess: boolean;
57
+ metadataOpenAccess: boolean;
58
+ confidence: RightsConfidence;
59
+ }
60
+ export interface ClearanceBlock {
61
+ commercialReproduction: {
62
+ permitted: boolean;
63
+ basis: ClearanceBasis;
64
+ };
65
+ derivatives: {
66
+ permitted: boolean;
67
+ basis: ClearanceBasis;
68
+ };
69
+ attributionRequired: {
70
+ required: boolean;
71
+ basis: ClearanceBasis;
72
+ };
73
+ }
74
+ export interface ClearanceVerification {
75
+ determinedBy: {
76
+ actor: string;
77
+ role: string;
78
+ };
79
+ tool: string;
80
+ determinedAt: string;
81
+ ruleContext: string;
82
+ determinationSource: {
83
+ type: string;
84
+ field?: string;
85
+ url?: string;
86
+ retrievedAt: string;
87
+ };
88
+ }
89
+ export interface ClearanceCitation {
90
+ full: string;
91
+ caption: string;
92
+ short: string;
93
+ }
94
+ export interface BuildOptions {
95
+ /** Engine version string for the `verification.tool` provenance field. */
96
+ engineVersion: string;
97
+ /**
98
+ * Generation timestamp, used only where no determination timestamp exists in
99
+ * the data (the deny path has no `license.verifiedAt`). Injected so manifests
100
+ * are deterministic and reproducible as conformance fixtures.
101
+ */
102
+ now: string;
103
+ }
104
+ export declare function buildClearancePayload(result: ValidationResult, opts: BuildOptions): ClearanceManifestPayload;
@@ -0,0 +1,112 @@
1
+ import { cite } from '../../cite.js';
2
+ import { clearanceForLicense } from './licenseMap.js';
3
+ const CONTEXT = [
4
+ 'https://schema.org/',
5
+ 'http://purl.org/dc/terms/',
6
+ 'https://openclearance.org/v0.1/context.jsonld',
7
+ ];
8
+ const TYPE = 'ClearanceManifest';
9
+ const SPEC_VERSION = '0.1';
10
+ const TOOL_NAME = 'open-museum-mcp';
11
+ /** Split `<museum>.<field path>` on the first dot. `met.isPublicDomain` → field `isPublicDomain`. */
12
+ function apiFieldOf(verificationSource) {
13
+ const i = verificationSource.indexOf('.');
14
+ return i < 0 ? verificationSource : verificationSource.slice(i + 1);
15
+ }
16
+ export function buildClearancePayload(result, opts) {
17
+ return result.status === 'accepted'
18
+ ? buildAccepted(result.artwork, opts)
19
+ : buildRejected(result.rejection, opts);
20
+ }
21
+ function buildAccepted(art, opts) {
22
+ const decision = clearanceForLicense(art.license.type);
23
+ const field = apiFieldOf(art.license.verificationSource);
24
+ return {
25
+ '@context': CONTEXT,
26
+ type: TYPE,
27
+ specVersion: SPEC_VERSION,
28
+ work: {
29
+ id: art.id,
30
+ title: art.title,
31
+ artist: art.artist,
32
+ displayDate: art.displayDate,
33
+ yearStart: art.yearStart,
34
+ yearEnd: art.yearEnd,
35
+ medium: art.medium,
36
+ },
37
+ source: {
38
+ museum: { code: art.museum.code, name: art.museum.name, url: art.museum.url },
39
+ apiUrl: art.source.apiUrl,
40
+ pageUrl: art.source.pageUrl,
41
+ ...(art.source.originalUrl ? { originalUrl: art.source.originalUrl } : {}),
42
+ imageUrls: art.imageUrls,
43
+ },
44
+ rights: {
45
+ statement: decision.statement,
46
+ sourceApiValue: { field, value: art.license.rawValue },
47
+ imageOpenAccess: art.imageOpenAccess,
48
+ metadataOpenAccess: art.metadataOpenAccess,
49
+ confidence: decision.confidence,
50
+ },
51
+ clearance: {
52
+ commercialReproduction: decision.commercialReproduction,
53
+ derivatives: decision.derivatives,
54
+ attributionRequired: decision.attributionRequired,
55
+ },
56
+ verification: {
57
+ determinedBy: { actor: `museum:${art.museum.code}`, role: 'rights-source' },
58
+ tool: `${TOOL_NAME}@${opts.engineVersion} · ${art.license.verificationSource}`,
59
+ determinedAt: art.license.verifiedAt,
60
+ ruleContext: `${art.license.verificationSource}='${art.license.rawValue}' ⇒ ${art.license.type}`,
61
+ determinationSource: {
62
+ type: 'api-field',
63
+ field,
64
+ url: art.source.apiUrl,
65
+ retrievedAt: art.license.verifiedAt,
66
+ },
67
+ },
68
+ citation: {
69
+ full: cite(art, 'full'),
70
+ caption: cite(art, 'caption'),
71
+ short: cite(art, 'short'),
72
+ },
73
+ };
74
+ }
75
+ function buildRejected(rej, opts) {
76
+ // The deny determination (all-false booleans, default-deny rule, low
77
+ // confidence) comes from the single source of truth; the rejection supplies
78
+ // the case-specific evidence (the gate's verbatim reason) into each basis.
79
+ const decision = clearanceForLicense('UNKNOWN');
80
+ const basis = {
81
+ rule: 'default-deny',
82
+ inputs: [{ field: 'rejection.reason', value: rej.reason }],
83
+ summary: `rights gate rejected '${rej.id}': ${rej.reason} ⇒ default deny`,
84
+ };
85
+ return {
86
+ '@context': CONTEXT,
87
+ type: TYPE,
88
+ specVersion: SPEC_VERSION,
89
+ work: { id: rej.id },
90
+ source: { museum: { code: rej.museumCode } },
91
+ rights: {
92
+ statement: decision.statement,
93
+ sourceApiValue: null,
94
+ imageOpenAccess: false,
95
+ metadataOpenAccess: false,
96
+ confidence: decision.confidence,
97
+ },
98
+ clearance: {
99
+ commercialReproduction: { permitted: false, basis },
100
+ derivatives: { permitted: false, basis },
101
+ attributionRequired: { required: false, basis },
102
+ },
103
+ verification: {
104
+ determinedBy: { actor: 'engine:open-museum-mcp', role: 'rights-gate' },
105
+ tool: `${TOOL_NAME}@${opts.engineVersion} · rights-gate`,
106
+ determinedAt: opts.now,
107
+ ruleContext: `strict default deny: ${rej.reason}`,
108
+ determinationSource: { type: 'rights-gate-default', retrievedAt: opts.now },
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/core/clearance/manifest.ts"],"names":[],"mappings":"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"}