opedd-mcp 0.2.0 → 0.4.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.
- package/.github/workflows/ci.yml +19 -2
- package/README.md +39 -9
- package/dist/index.js +239 -17
- package/package.json +9 -6
- package/src/index.ts +291 -24
- package/tests/dispatcher.env-gated.test.ts +283 -0
- package/tests/dispatcher.test.ts +419 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -12,7 +12,13 @@ jobs:
|
|
|
12
12
|
strategy:
|
|
13
13
|
fail-fast: false
|
|
14
14
|
matrix:
|
|
15
|
-
|
|
15
|
+
# Node 18 dropped 2026-05-24 — vitest 4.x (rolldown dep) requires
|
|
16
|
+
# `node:util.styleText` which is only available on Node 21.7+.
|
|
17
|
+
# Node 18 reaches active LTS end April 2025; engines.node in
|
|
18
|
+
# package.json bumped to ">=20" in the same commit. Re-add Node 18
|
|
19
|
+
# only if vitest downgrade ratified (which is the alternative;
|
|
20
|
+
# founder picked engines bump).
|
|
21
|
+
node-version: ["20", "22"]
|
|
16
22
|
steps:
|
|
17
23
|
- uses: actions/checkout@v4
|
|
18
24
|
|
|
@@ -23,11 +29,22 @@ jobs:
|
|
|
23
29
|
cache: npm
|
|
24
30
|
|
|
25
31
|
- name: Install dependencies
|
|
26
|
-
|
|
32
|
+
# Using `npm install` rather than `npm ci` because the lockfile
|
|
33
|
+
# generated on macOS doesn't include Linux-conditional optional
|
|
34
|
+
# native deps (e.g. @emnapi/* used by vitest 4.x via napi-rs).
|
|
35
|
+
# `npm install` resolves transitively at install time per-platform;
|
|
36
|
+
# `npm ci` strictly validates lockfile-against-package.json and
|
|
37
|
+
# rejects when platform-specific transitive deps are missing.
|
|
38
|
+
# Trade-off: install is slower + slightly looser; acceptable for
|
|
39
|
+
# this repo's CI scale.
|
|
40
|
+
run: npm install
|
|
27
41
|
|
|
28
42
|
- name: TypeScript compile
|
|
29
43
|
run: npm run build
|
|
30
44
|
|
|
45
|
+
- name: Unit tests (vitest)
|
|
46
|
+
run: npm test
|
|
47
|
+
|
|
31
48
|
- name: Verify dist/index.js produced
|
|
32
49
|
run: |
|
|
33
50
|
test -f dist/index.js || (echo "::error::dist/index.js not produced by tsc"; exit 1)
|
package/README.md
CHANGED
|
@@ -8,17 +8,20 @@ Lets AI assistants (Claude Desktop, Cursor, Windsurf, or any MCP-compatible host
|
|
|
8
8
|
|
|
9
9
|
## What it does
|
|
10
10
|
|
|
11
|
-
Exposes up to
|
|
11
|
+
Exposes up to 16 tools to any AI assistant (some are conditional on env vars):
|
|
12
12
|
|
|
13
|
-
**Always available — discovery + per-article purchase**
|
|
13
|
+
**Always available — discovery + per-article purchase + onboarding + rights signaling**
|
|
14
14
|
|
|
15
15
|
| Tool | Description |
|
|
16
16
|
|------|-------------|
|
|
17
17
|
| `lookup_content` | Look up an article by URL — returns title, publisher, pricing |
|
|
18
18
|
| `purchase_license` | Buy a single-article license via Stripe — returns OP-XXXX-XXXX key |
|
|
19
19
|
| `verify_license` | Verify a license key — returns validity, article, publisher, blockchain proof |
|
|
20
|
-
| `browse_registry` | Browse the public Opedd registry (
|
|
20
|
+
| `browse_registry` | Browse the public Opedd registry — lists issued LICENSES (use `publisher_directory` to browse publishers themselves) |
|
|
21
|
+
| `publisher_directory` | Browse the public Opedd publisher catalog — paginated publishers with article counts + pricing + sample articles (primary buyer-discovery surface for AI labs) |
|
|
21
22
|
| `purchase_enterprise_license` | Buy a bulk enterprise license covering multiple publishers (Phase 10) — returns Stripe `client_secret` |
|
|
23
|
+
| `rsl_get` | Fetch a publisher's RSL Standard manifest — public discovery surface; `jsonld: true` returns CDSM Article 4(3) signed receipt (Phase 12 W1.1) |
|
|
24
|
+
| `detect_platform` | Detect the content platform behind a URL — returns suggested onboarding workflow for Substack / Beehiiv / Ghost / Medium / Brevo / custom (Phase 12 W3.1) |
|
|
22
25
|
|
|
23
26
|
**Requires `OPEDD_BUYER_TOKEN` (opedd_buyer_live_*)**
|
|
24
27
|
|
|
@@ -26,26 +29,38 @@ Exposes up to 11 tools to any AI assistant (some are conditional on env vars):
|
|
|
26
29
|
|------|-------------|
|
|
27
30
|
| `get_content` | Retrieve a licensed article — includes 7 Phase 11 M2 RAG metadata fields (author, language, word_count, content_hash, image_urls, canonical_url, tags) |
|
|
28
31
|
|
|
29
|
-
**Requires `OPEDD_ACCESS_KEY` (
|
|
32
|
+
**Requires `OPEDD_ACCESS_KEY` (ent_*) — buyer-side feed surfaces**
|
|
30
33
|
|
|
31
34
|
| Tool | Description |
|
|
32
35
|
|------|-------------|
|
|
33
36
|
| `list_feed` | List articles from a buyer's licensed catalog with `since` delta-feed support (Phase 11 M5) |
|
|
34
37
|
| `stream_feed_ndjson` | Bulk-export up to 1000 articles per call via NDJSON wire format (Phase 11 M3) |
|
|
35
38
|
|
|
36
|
-
**Requires `OPEDD_BUYER_JWT` (Supabase JWT) — audit + compliance surfaces**
|
|
39
|
+
**Requires `OPEDD_BUYER_JWT` (Supabase JWT) — buyer account + audit + compliance + EU AI Act surfaces**
|
|
37
40
|
|
|
38
41
|
| Tool | Description |
|
|
39
42
|
|------|-------------|
|
|
43
|
+
| `get_buyer_account` | Fetch buyer profile + masked API key list — buyer-dashboard mental model |
|
|
40
44
|
| `get_audit_events` | Per-event audit ledger with Tempo on-chain attestation inclusion proofs inline (Phase 9.x + 10 M5) |
|
|
41
45
|
| `get_compliance_dossier` | Procurement-defense compliance dossier mapping retrievals to license terms (Phase 11 M4) |
|
|
46
|
+
| `article_53_attestation` | Signed JWT attesting EU AI Act Article 53(1)(d) compliance for a license — the artifact AI labs hand to legal/procurement (Phase 12 W1.4) |
|
|
42
47
|
|
|
43
|
-
**Requires `
|
|
48
|
+
**Requires `OPEDD_PUB_BEARER` (opedd_pub_*) — publisher-side**
|
|
44
49
|
|
|
45
50
|
| Tool | Description |
|
|
46
51
|
|------|-------------|
|
|
47
52
|
| `list_publisher_content` | List your own articles with pricing and stats |
|
|
48
53
|
|
|
54
|
+
### Regulatory framing (CDSM Article 4 vs EU AI Act Article 53 — never conflated)
|
|
55
|
+
|
|
56
|
+
Per the [opedd-backend INVARIANTS.md W1.6 amendment](https://github.com/Opedd/opedd-backend/blob/main/INVARIANTS.md):
|
|
57
|
+
|
|
58
|
+
- **`rsl_get(publisher_id, jsonld=true)`** → publisher-side **CDSM Article 4(3)** opt-out declaration (signed JSON-LD receipt over the reservation state).
|
|
59
|
+
- **`article_53_attestation(license_id)`** → buyer-side **EU AI Act Article 53** attestation (signed HS256 JWT scoped to one license).
|
|
60
|
+
- **`get_compliance_dossier(from, to)`** → comprehensive procurement-defense dossier covering BOTH frameworks (publisher CDSM reservation honored + buyer Article 53 evidence chain).
|
|
61
|
+
|
|
62
|
+
These serve different audit-defensibility modes and never share wire format. Tool descriptions cite the W1.6 invariant inline.
|
|
63
|
+
|
|
49
64
|
## Install
|
|
50
65
|
|
|
51
66
|
```bash
|
|
@@ -66,10 +81,11 @@ Set environment variables to pre-configure the server:
|
|
|
66
81
|
|----------|----------|-------------|
|
|
67
82
|
| `OPEDD_BUYER_EMAIL` | Recommended | Your email — used as default for all purchases |
|
|
68
83
|
| `OPEDD_PAYMENT_METHOD_ID` | Recommended | Stripe `pm_...` ID — used for autonomous per-article purchasing |
|
|
69
|
-
| `OPEDD_BUYER_TOKEN` | Optional | Buyer API token (`opedd_buyer_live_*` canonical
|
|
70
|
-
| `OPEDD_ACCESS_KEY` | Optional | Enterprise access key (`
|
|
84
|
+
| `OPEDD_BUYER_TOKEN` | Optional | Buyer API token (`opedd_buyer_live_*` canonical; `opedd_buyer_test_*` for sandbox) — enables `get_content` |
|
|
85
|
+
| `OPEDD_ACCESS_KEY` | Optional | Enterprise access key (`ent_*`) — enables `list_feed` + `stream_feed_ndjson` |
|
|
71
86
|
| `OPEDD_BUYER_JWT` | Optional | Supabase session JWT from the buyer portal — enables `get_audit_events` + `get_compliance_dossier` |
|
|
72
|
-
| `
|
|
87
|
+
| `OPEDD_PUB_BEARER` | Optional | Canonical Publisher API Bearer key (`opedd_pub_<env>_<32-hex>`; issued via `POST /publishers-api-keys action=create_api_key`) — enables `list_publisher_content`. **v0.4.0 canonical.** |
|
|
88
|
+
| `OPEDD_API_KEY` | Deprecated | Legacy Publisher API key (`op_...`) — fallback during the transition window; will stop working when opedd-backend Phase C deploys. Migrate to `OPEDD_PUB_BEARER`. |
|
|
73
89
|
| `OPEDD_API_URL` | Optional | Override the API base URL (default: Opedd production) |
|
|
74
90
|
|
|
75
91
|
> **Getting a Stripe payment method ID**: Save a card in your Stripe account and retrieve the `pm_...` ID via the [Stripe API](https://stripe.com/docs/api/payment_methods).
|
|
@@ -121,6 +137,20 @@ Once configured, you can ask your AI assistant:
|
|
|
121
137
|
|
|
122
138
|
> "Browse the latest licenses from The Information publisher."
|
|
123
139
|
|
|
140
|
+
### Phase 12 Wave 1 + 3 surfaces (v0.3.0)
|
|
141
|
+
|
|
142
|
+
> "What does publisher 8268c353-ffa3-4db3-bbb2-90ddbbb43e41 license? Pull the RSL manifest." — uses `rsl_get`
|
|
143
|
+
|
|
144
|
+
> "Get me a CDSM Article 4(3) signed receipt for that publisher's reservation state — set `jsonld: true`." — uses `rsl_get` with content negotiation
|
|
145
|
+
|
|
146
|
+
> "Generate an EU AI Act Article 53 attestation JWT for our license `11111111-...` covering the last 90 days. I need the artifact for our procurement audit committee." — uses `article_53_attestation` (requires `OPEDD_BUYER_JWT`)
|
|
147
|
+
|
|
148
|
+
> "This publisher just signed up: `https://noahpinion.substack.com`. Which onboarding workflow should we use?" — uses `detect_platform`
|
|
149
|
+
|
|
150
|
+
> "Browse the finance category in the Opedd catalog — show me publishers with at least 50 articles." — uses `publisher_directory`
|
|
151
|
+
|
|
152
|
+
> "What's in my buyer account? Show me my active API keys." — uses `get_buyer_account` (requires `OPEDD_BUYER_JWT`)
|
|
153
|
+
|
|
124
154
|
The assistant will call the appropriate Opedd tools, show you the results, and—if you've pre-configured a payment method—can complete a purchase autonomously.
|
|
125
155
|
|
|
126
156
|
## Payment methods
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
// ─── Configuration ────────────────────────────────────────────────────────────
|
|
6
7
|
const API_BASE = process.env.OPEDD_API_URL ??
|
|
7
8
|
"https://api.opedd.com";
|
|
8
9
|
const BUYER_EMAIL = process.env.OPEDD_BUYER_EMAIL;
|
|
9
10
|
const PAYMENT_METHOD_ID = process.env.OPEDD_PAYMENT_METHOD_ID;
|
|
10
11
|
const API_KEY = process.env.OPEDD_API_KEY; // publisher API key (op_...)
|
|
11
|
-
const BUYER_TOKEN = process.env.OPEDD_BUYER_TOKEN; // buyer API token (opedd_buyer_live_... or
|
|
12
|
-
const ACCESS_KEY = process.env.OPEDD_ACCESS_KEY; // enterprise access key (
|
|
12
|
+
const BUYER_TOKEN = process.env.OPEDD_BUYER_TOKEN; // buyer API token (opedd_buyer_live_... or opedd_buyer_test_...)
|
|
13
|
+
const ACCESS_KEY = process.env.OPEDD_ACCESS_KEY; // enterprise access key (ent_*); for /enterprise-license GET feed
|
|
13
14
|
const BUYER_JWT = process.env.OPEDD_BUYER_JWT; // Supabase JWT; for /buyer-audit + /buyer-compliance-report
|
|
14
15
|
// ─── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
15
16
|
async function opeddFetch(path, options = {}) {
|
|
@@ -73,7 +74,7 @@ function err(msg) {
|
|
|
73
74
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
74
75
|
}
|
|
75
76
|
// ─── Tool definitions ─────────────────────────────────────────────────────────
|
|
76
|
-
const TOOLS = [
|
|
77
|
+
export const TOOLS = [
|
|
77
78
|
{
|
|
78
79
|
name: "lookup_content",
|
|
79
80
|
description: "Look up a piece of content on the Opedd registry by URL. " +
|
|
@@ -180,12 +181,101 @@ const TOOLS = [
|
|
|
180
181
|
},
|
|
181
182
|
},
|
|
182
183
|
},
|
|
184
|
+
// ─── Public buyer-discovery — catalog browse ──────────────────────────────
|
|
185
|
+
{
|
|
186
|
+
name: "publisher_directory",
|
|
187
|
+
description: "Browse the public Opedd publisher catalog via GET /publisher-directory. " +
|
|
188
|
+
"Returns paginated publishers with article counts, pricing (per-article + annual + monthly-forward-feed), " +
|
|
189
|
+
"plan, and sample articles (RAG-extended metadata). " +
|
|
190
|
+
"**The primary discovery surface for AI labs to find Opedd-licensable publishers** — distinct from " +
|
|
191
|
+
"`browse_registry` (which lists issued LICENSES, not publishers). " +
|
|
192
|
+
"Filter by category (case-insensitive substring), min_articles, or verified status. " +
|
|
193
|
+
"Public no-auth — useful pre-purchase scoping before buyers commit to enterprise-license POST.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {
|
|
197
|
+
category: {
|
|
198
|
+
type: "string",
|
|
199
|
+
description: "Case-insensitive substring filter on publisher category (e.g. 'finance', 'AI').",
|
|
200
|
+
},
|
|
201
|
+
min_articles: {
|
|
202
|
+
type: "number",
|
|
203
|
+
description: "Filter to publishers with at least this many licensable articles.",
|
|
204
|
+
},
|
|
205
|
+
verified: {
|
|
206
|
+
type: "string",
|
|
207
|
+
description: "'true' to show only verified publishers (default), 'false' for unverified.",
|
|
208
|
+
},
|
|
209
|
+
limit: {
|
|
210
|
+
type: "number",
|
|
211
|
+
description: "Page size cap.",
|
|
212
|
+
},
|
|
213
|
+
offset: {
|
|
214
|
+
type: "number",
|
|
215
|
+
description: "Pagination offset.",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
// ─── Phase 12 Wave 3 W3.1 — onboarding helpers ────────────────────────────
|
|
221
|
+
{
|
|
222
|
+
name: "detect_platform",
|
|
223
|
+
description: "Detect the content platform behind a URL via POST /detect-platform (Phase 12 Wave 3 W3.1). " +
|
|
224
|
+
"Public no-auth lookup. Given a URL, identifies what platform powers it " +
|
|
225
|
+
"(Substack / Beehiiv / Ghost / Medium / Brevo / custom) and returns the suggested onboarding workflow. " +
|
|
226
|
+
"Hostname-detectable platforms (Substack subdomain, Beehiiv suffix, etc.) resolve in milliseconds; " +
|
|
227
|
+
"custom domains may take ~few seconds while the detector probes well-known platform endpoints in parallel. " +
|
|
228
|
+
"Returns: {platform, confidence, archive_method, forward_method, required_credentials, instructions}. " +
|
|
229
|
+
"The archive_method + forward_method fields are the two onboarding-workflow inputs Opedd's setup wizard reads " +
|
|
230
|
+
"(one for historical content backfill, one for new-content forward stream). " +
|
|
231
|
+
"instructions is human-readable operator copy explaining the inferred path.",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: "object",
|
|
234
|
+
required: ["url"],
|
|
235
|
+
properties: {
|
|
236
|
+
url: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Publisher URL to inspect (any well-formed URL works; hostname-match short-circuits the probe path).",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
// ─── Phase 12 Wave 1 W1.1 — public RSL Standard manifest ─────────────────
|
|
244
|
+
{
|
|
245
|
+
name: "rsl_get",
|
|
246
|
+
description: "Fetch a publisher's RSL Standard manifest via GET /rsl-manifest (Phase 12 Wave 1 W1.1). " +
|
|
247
|
+
"Public no-auth endpoint — discovery surface for AI agents/crawlers wanting to know what's licensable " +
|
|
248
|
+
"from a publisher BEFORE going through the buyer-account signup flow. " +
|
|
249
|
+
"Returns the 4 canonical license types (ai_retrieval, ai_training, human_per_article, human_full_archive) " +
|
|
250
|
+
"the publisher has opted into, plus the EU CDSM Article 4(3) opt-out posture (`tdm_reservation`). " +
|
|
251
|
+
"Set `jsonld: true` to request the JSON-LD shape with embedded HMAC-SHA256 signed receipt over the " +
|
|
252
|
+
"CDSM Article 4(3) reservation state + `tdm:reservationSignedAt` timestamp — regulators can post-hoc " +
|
|
253
|
+
"verify the reservation was the claimed value at the claimed time. Default `jsonld: false` returns " +
|
|
254
|
+
"the raw RSL Standard JSON manifest. " +
|
|
255
|
+
"Per INVARIANTS.md W1.6: this is the PUBLISHER-side CDSM Article 4(3) declaration surface. It is NOT " +
|
|
256
|
+
"an EU AI Act Article 53 attestation (which is buyer-side, JWT-auth, via article_53_attestation tool).",
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: "object",
|
|
259
|
+
required: ["publisher_id"],
|
|
260
|
+
properties: {
|
|
261
|
+
publisher_id: {
|
|
262
|
+
type: "string",
|
|
263
|
+
description: "UUID of the publisher whose RSL manifest to fetch. Publisher must be verified.",
|
|
264
|
+
},
|
|
265
|
+
jsonld: {
|
|
266
|
+
type: "boolean",
|
|
267
|
+
description: "If true, request JSON-LD shape (Accept: application/ld+json) with embedded HMAC-SHA256 signed receipt. " +
|
|
268
|
+
"Default false returns raw RSL Standard JSON shape.",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
183
273
|
// ─── Phase 10 + 11 buyer-side surfaces (M6.4) ─────────────────────────────
|
|
184
274
|
{
|
|
185
275
|
name: "purchase_enterprise_license",
|
|
186
276
|
description: "Purchase a bulk enterprise license covering multiple publishers (Phase 10). " +
|
|
187
277
|
"Returns a Stripe client_secret for payment completion + the enterprise_license_id. " +
|
|
188
|
-
"After payment, an
|
|
278
|
+
"After payment, an ent_* access key is emailed to buyer_email. " +
|
|
189
279
|
"Scopes: 'custom' (pass-through publisher_ids), 'platform_wide' (auto-resolve all opted-in publishers), 'filtered' (Phase 10 filter_rules). " +
|
|
190
280
|
"License tiers: 'rag' (= ai_retrieval), 'training' (= ai_training, flat-fee not metered), 'inference' (= ai_retrieval), 'full_ai' (writes both retrieval + training records).",
|
|
191
281
|
inputSchema: {
|
|
@@ -240,7 +330,7 @@ const TOOLS = [
|
|
|
240
330
|
if (BUYER_TOKEN) {
|
|
241
331
|
TOOLS.push({
|
|
242
332
|
name: "get_content",
|
|
243
|
-
description: "Retrieve the full body of a licensed article using a buyer API token (opedd_buyer_live_* canonical
|
|
333
|
+
description: "Retrieve the full body of a licensed article using a buyer API token (opedd_buyer_live_* canonical; opedd_buyer_test_* for sandbox). " +
|
|
244
334
|
"Requires OPEDD_BUYER_TOKEN env var (create one at opedd.com/licenses after purchasing). " +
|
|
245
335
|
"Works for per-article licenses (token scoped to that article) and archive licenses (token covers all publisher content). " +
|
|
246
336
|
"The publisher must have content delivery enabled and must have pushed content for the article. " +
|
|
@@ -257,7 +347,7 @@ if (BUYER_TOKEN) {
|
|
|
257
347
|
},
|
|
258
348
|
buyer_token: {
|
|
259
349
|
type: "string",
|
|
260
|
-
description: "Buyer API token (opedd_buyer_live_* or
|
|
350
|
+
description: "Buyer API token (opedd_buyer_live_* or opedd_buyer_test_*). Falls back to OPEDD_BUYER_TOKEN env var.",
|
|
261
351
|
},
|
|
262
352
|
},
|
|
263
353
|
},
|
|
@@ -271,7 +361,7 @@ if (ACCESS_KEY) {
|
|
|
271
361
|
"Returns JSON-format response with paginated articles. " +
|
|
272
362
|
"Use `since` (ISO 8601) for delta-feed polling — only articles published after the timestamp. " +
|
|
273
363
|
"Use `cursor` for pagination across pages. " +
|
|
274
|
-
"Requires OPEDD_ACCESS_KEY (
|
|
364
|
+
"Requires OPEDD_ACCESS_KEY (ent_* enterprise access key). " +
|
|
275
365
|
"For larger bulk corpus pulls, use stream_feed_ndjson (up to 1000 articles per call vs 200 here).",
|
|
276
366
|
inputSchema: {
|
|
277
367
|
type: "object",
|
|
@@ -299,7 +389,7 @@ if (ACCESS_KEY) {
|
|
|
299
389
|
"Use `since` (ISO 8601) for delta-feed. Use `cursor` to paginate beyond 1000. " +
|
|
300
390
|
"Backend supports 5000 articles per call; the MCP cap is 1000 for transport reasonability. " +
|
|
301
391
|
"Real bulk-ingest pipelines should use the Python SDK (pip install opedd) directly — not via MCP. " +
|
|
302
|
-
"Requires OPEDD_ACCESS_KEY (
|
|
392
|
+
"Requires OPEDD_ACCESS_KEY (ent_*).",
|
|
303
393
|
inputSchema: {
|
|
304
394
|
type: "object",
|
|
305
395
|
properties: {
|
|
@@ -356,6 +446,57 @@ if (BUYER_JWT) {
|
|
|
356
446
|
},
|
|
357
447
|
},
|
|
358
448
|
});
|
|
449
|
+
TOOLS.push({
|
|
450
|
+
name: "get_buyer_account",
|
|
451
|
+
description: "Fetch the authenticated buyer's account profile + masked API key list via GET /buyer-account. " +
|
|
452
|
+
"Returns the enterprise_buyers row (contact_email, buyer_org, created_at, etc.) plus a list of all " +
|
|
453
|
+
"buyer-side API keys with masked prefixes (NEVER plaintext post-issuance — only the 12-char " +
|
|
454
|
+
"key_prefix is returned, e.g. 'opedd_buyer_'). " +
|
|
455
|
+
"Use cases: post-signup verification ('what was just issued to me?'), buyer dashboard mental model " +
|
|
456
|
+
"('what licenses do I currently hold?'), audit prep ('show me the key list before rotation'). " +
|
|
457
|
+
"For full mid-lifecycle license details (filter_rules, billing, payouts), buyers consult the buyer portal at opedd.com/buyer. " +
|
|
458
|
+
"Requires OPEDD_BUYER_JWT.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {},
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
TOOLS.push({
|
|
465
|
+
name: "article_53_attestation",
|
|
466
|
+
description: "Issue a signed JWT attesting to EU AI Act Article 53 compliance for a specific license via " +
|
|
467
|
+
"GET /eu-ai-act/article-53-attestation (Phase 12 Wave 1 W1.4). " +
|
|
468
|
+
"Returns a freshly-signed HS256 JWT regulators can verify offline against the canonical signing key. " +
|
|
469
|
+
"Embeds: license context, usage-count over the attestation window, the most-recent Tempo Merkle root, " +
|
|
470
|
+
"and canonical claims (iss/sub/iat/exp/jti/aud). " +
|
|
471
|
+
"**The artifact AI labs hand to legal/procurement for EU AI Act Article 53(1)(d) transparency-obligation evidence.** " +
|
|
472
|
+
"Per INVARIANTS.md W1.6: this attests to EU AI Act Article 53 ONLY (buyer-side GPAI-model-provider " +
|
|
473
|
+
"transparency obligation). It does NOT discharge a publisher's CDSM Article 4(3) reservation obligation — " +
|
|
474
|
+
"that lives on the rsl_get tool (jsonld=true variant). Never conflate. " +
|
|
475
|
+
"Optional `content_id` scopes the attestation to one article; default is license-wide. " +
|
|
476
|
+
"Window cap: 365 days. Requires OPEDD_BUYER_JWT.",
|
|
477
|
+
inputSchema: {
|
|
478
|
+
type: "object",
|
|
479
|
+
required: ["license_id"],
|
|
480
|
+
properties: {
|
|
481
|
+
license_id: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "UUID of the enterprise_license OR legacy individual license to attest. Buyer must own it.",
|
|
484
|
+
},
|
|
485
|
+
content_id: {
|
|
486
|
+
type: "string",
|
|
487
|
+
description: "Optional UUID of a specific article to scope the attestation. Default: license-wide.",
|
|
488
|
+
},
|
|
489
|
+
window_start: {
|
|
490
|
+
type: "string",
|
|
491
|
+
description: "ISO 8601 lower bound of the attestation window. Default: now - 90 days.",
|
|
492
|
+
},
|
|
493
|
+
window_end: {
|
|
494
|
+
type: "string",
|
|
495
|
+
description: "ISO 8601 upper bound. Default: now. Window may not exceed 365 days (hard cap).",
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
});
|
|
359
500
|
TOOLS.push({
|
|
360
501
|
name: "get_compliance_dossier",
|
|
361
502
|
description: "Generate a procurement-defense compliance dossier via GET /buyer-compliance-report (Phase 11 M4). " +
|
|
@@ -413,10 +554,17 @@ if (API_KEY) {
|
|
|
413
554
|
});
|
|
414
555
|
}
|
|
415
556
|
// ─── MCP Server ───────────────────────────────────────────────────────────────
|
|
416
|
-
const server = new Server({ name: "opedd-mcp", version: "0.
|
|
557
|
+
const server = new Server({ name: "opedd-mcp", version: "0.3.0" }, { capabilities: { tools: {} } });
|
|
417
558
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
418
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
419
|
-
|
|
559
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => dispatchTool(request.params.name, (request.params.arguments ?? {})));
|
|
560
|
+
// ─── Tool dispatcher (exported for unit tests) ────────────────────────────────
|
|
561
|
+
//
|
|
562
|
+
// Extracted from the inline handler 2026-05-24 EEST as part of the opedd-mcp
|
|
563
|
+
// test cohort ship. Behavior-preserving: the server.setRequestHandler above
|
|
564
|
+
// is a thin delegation to dispatchTool. Exporting allows unit tests to mock
|
|
565
|
+
// global fetch + invoke each tool's handler directly without spawning a
|
|
566
|
+
// stdio subprocess.
|
|
567
|
+
export async function dispatchTool(name, args) {
|
|
420
568
|
try {
|
|
421
569
|
switch (name) {
|
|
422
570
|
// ── lookup_content ─────────────────────────────────────────────────────
|
|
@@ -490,6 +638,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
490
638
|
const data = await opeddFetch(`/content-delivery?article_id=${encodeURIComponent(article_id)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
491
639
|
return ok(data);
|
|
492
640
|
}
|
|
641
|
+
// ── publisher_directory (buyer-discovery catalog browse) ───────────────
|
|
642
|
+
case "publisher_directory": {
|
|
643
|
+
const { category, min_articles, verified, limit, offset } = args;
|
|
644
|
+
const params = new URLSearchParams();
|
|
645
|
+
if (category)
|
|
646
|
+
params.set("category", category);
|
|
647
|
+
if (min_articles !== undefined)
|
|
648
|
+
params.set("min_articles", String(min_articles));
|
|
649
|
+
if (verified !== undefined)
|
|
650
|
+
params.set("verified", verified);
|
|
651
|
+
if (limit !== undefined)
|
|
652
|
+
params.set("limit", String(limit));
|
|
653
|
+
if (offset !== undefined)
|
|
654
|
+
params.set("offset", String(offset));
|
|
655
|
+
const query = params.toString();
|
|
656
|
+
const data = await opeddFetch(`/publisher-directory${query ? `?${query}` : ""}`);
|
|
657
|
+
return ok(data);
|
|
658
|
+
}
|
|
659
|
+
// ── detect_platform (Phase 12 Wave 3 W3.1) ─────────────────────────────
|
|
660
|
+
case "detect_platform": {
|
|
661
|
+
const { url } = args;
|
|
662
|
+
if (!url)
|
|
663
|
+
return err("url is required");
|
|
664
|
+
const data = await opeddFetch("/detect-platform", {
|
|
665
|
+
method: "POST",
|
|
666
|
+
body: JSON.stringify({ url }),
|
|
667
|
+
});
|
|
668
|
+
return ok(data);
|
|
669
|
+
}
|
|
670
|
+
// ── rsl_get (Phase 12 Wave 1 W1.1) ─────────────────────────────────────
|
|
671
|
+
case "rsl_get": {
|
|
672
|
+
const { publisher_id, jsonld = false } = args;
|
|
673
|
+
if (!publisher_id)
|
|
674
|
+
return err("publisher_id is required");
|
|
675
|
+
const accept = jsonld ? "application/ld+json" : "application/json";
|
|
676
|
+
const data = await opeddFetch(`/rsl-manifest?publisher_id=${encodeURIComponent(publisher_id)}`, { headers: { Accept: accept } });
|
|
677
|
+
return ok(data);
|
|
678
|
+
}
|
|
493
679
|
// ── purchase_enterprise_license (Phase 10) ─────────────────────────────
|
|
494
680
|
case "purchase_enterprise_license": {
|
|
495
681
|
const { publisher_ids, buyer_email: pelEmail, buyer_org, billing_type = "annual", license_tier = "rag", duration_months = 12, scope = "custom", filter_rules, buyer_webhook_url, } = args;
|
|
@@ -522,7 +708,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
522
708
|
// ── list_feed (Phase 10 + 11) ──────────────────────────────────────────
|
|
523
709
|
case "list_feed": {
|
|
524
710
|
if (!ACCESS_KEY) {
|
|
525
|
-
return err("OPEDD_ACCESS_KEY env var is required for this tool (
|
|
711
|
+
return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
|
|
526
712
|
}
|
|
527
713
|
const { since, cursor, limit = 50 } = args;
|
|
528
714
|
const params = new URLSearchParams({
|
|
@@ -540,7 +726,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
540
726
|
// ── stream_feed_ndjson (Phase 11 M3) ───────────────────────────────────
|
|
541
727
|
case "stream_feed_ndjson": {
|
|
542
728
|
if (!ACCESS_KEY) {
|
|
543
|
-
return err("OPEDD_ACCESS_KEY env var is required for this tool (
|
|
729
|
+
return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
|
|
544
730
|
}
|
|
545
731
|
const { since, cursor, limit = 200 } = args;
|
|
546
732
|
const params = new URLSearchParams({
|
|
@@ -577,6 +763,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
577
763
|
});
|
|
578
764
|
return ok(data);
|
|
579
765
|
}
|
|
766
|
+
// ── get_buyer_account (buyer profile + masked key list) ────────────────
|
|
767
|
+
case "get_buyer_account": {
|
|
768
|
+
if (!BUYER_JWT) {
|
|
769
|
+
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
770
|
+
}
|
|
771
|
+
const data = await opeddFetch("/buyer-account", {
|
|
772
|
+
headers: { Authorization: `Bearer ${BUYER_JWT}` },
|
|
773
|
+
});
|
|
774
|
+
return ok(data);
|
|
775
|
+
}
|
|
776
|
+
// ── article_53_attestation (Phase 12 Wave 1 W1.4) ──────────────────────
|
|
777
|
+
case "article_53_attestation": {
|
|
778
|
+
if (!BUYER_JWT) {
|
|
779
|
+
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
780
|
+
}
|
|
781
|
+
const { license_id, content_id, window_start, window_end } = args;
|
|
782
|
+
if (!license_id)
|
|
783
|
+
return err("license_id is required");
|
|
784
|
+
const params = new URLSearchParams({ license_id });
|
|
785
|
+
if (content_id)
|
|
786
|
+
params.set("content_id", content_id);
|
|
787
|
+
if (window_start)
|
|
788
|
+
params.set("window_start", window_start);
|
|
789
|
+
if (window_end)
|
|
790
|
+
params.set("window_end", window_end);
|
|
791
|
+
const data = await opeddFetch(`/eu-ai-act/article-53-attestation?${params.toString()}`, { headers: { Authorization: `Bearer ${BUYER_JWT}` } });
|
|
792
|
+
return ok(data);
|
|
793
|
+
}
|
|
580
794
|
// ── get_compliance_dossier (Phase 11 M4) ───────────────────────────────
|
|
581
795
|
case "get_compliance_dossier": {
|
|
582
796
|
if (!BUYER_JWT) {
|
|
@@ -621,8 +835,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
621
835
|
const msg = e instanceof Error ? e.message : String(e);
|
|
622
836
|
return err(`Request failed: ${msg}`);
|
|
623
837
|
}
|
|
624
|
-
}
|
|
838
|
+
}
|
|
625
839
|
// ─── Start ────────────────────────────────────────────────────────────────────
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
840
|
+
// Skip stdio bootstrap when imported as a module (unit tests need TOOLS +
|
|
841
|
+
// dispatchTool without the server.connect side-effect). Detect via
|
|
842
|
+
// `import.meta.url === pathToFileURL(process.argv[1]).href` — the canonical
|
|
843
|
+
// "am I the entry point" check for ESM Node modules.
|
|
844
|
+
if (typeof process !== "undefined" &&
|
|
845
|
+
process.argv?.[1] &&
|
|
846
|
+
import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
847
|
+
const transport = new StdioServerTransport();
|
|
848
|
+
await server.connect(transport);
|
|
849
|
+
console.error("[opedd-mcp] Server running on stdio");
|
|
850
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opedd-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Opedd — discover, purchase, verify, and audit content licenses from AI assistants. Covers Phase 11 buyer-side surfaces.",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "MCP server for Opedd — discover, purchase, verify, and audit content licenses from AI assistants. Covers Phase 11 buyer-side surfaces + Phase 12 Wave 1 + 3 regulatory surfaces (CDSM Article 4(3) RSL + EU AI Act Article 53 attestation + platform onboarding helper).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
|
-
"dev": "tsx src/index.ts"
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
14
16
|
},
|
|
15
17
|
"keywords": [
|
|
16
18
|
"mcp",
|
|
@@ -26,11 +28,12 @@
|
|
|
26
28
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^22.
|
|
31
|
+
"@types/node": "^22.19.19",
|
|
30
32
|
"tsx": "^4.0.0",
|
|
31
|
-
"typescript": "^5.0.0"
|
|
33
|
+
"typescript": "^5.0.0",
|
|
34
|
+
"vitest": "^4.1.7"
|
|
32
35
|
},
|
|
33
36
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
37
|
+
"node": ">=20"
|
|
35
38
|
}
|
|
36
39
|
}
|