opedd-mcp 0.6.2 → 0.6.4
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/README.md +29 -2
- package/dist/index.js +563 -539
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,54 +5,63 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { initTelemetry, captureToolCall, shutdownTelemetry } from "./telemetry.js";
|
|
7
7
|
import { SERVER_VERSION } from "./version.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// B91 v0.4.0
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const BUYER_TOKEN = process.env.OPEDD_BUYER_TOKEN; // buyer API token (opedd_buyer_live_... or opedd_buyer_test_...)
|
|
24
|
-
const ACCESS_KEY = process.env.OPEDD_ACCESS_KEY; // enterprise access key (ent_*); for /enterprise-license GET feed
|
|
25
|
-
const BUYER_JWT = process.env.OPEDD_BUYER_JWT; // Supabase JWT; for /buyer-audit + /buyer-compliance-report
|
|
8
|
+
export function envCredentials() {
|
|
9
|
+
return {
|
|
10
|
+
apiBase: process.env.OPEDD_API_URL ?? "https://api.opedd.com",
|
|
11
|
+
buyerEmail: process.env.OPEDD_BUYER_EMAIL,
|
|
12
|
+
paymentMethodId: process.env.OPEDD_PAYMENT_METHOD_ID,
|
|
13
|
+
// B91 v0.4.0 (2026-05-26): OPEDD_PUB_BEARER canonical; OPEDD_API_KEY is
|
|
14
|
+
// the legacy op_ alias during the dual-mode transition window.
|
|
15
|
+
pubBearer: process.env.OPEDD_PUB_BEARER,
|
|
16
|
+
apiKey: process.env.OPEDD_API_KEY,
|
|
17
|
+
buyerToken: process.env.OPEDD_BUYER_TOKEN,
|
|
18
|
+
accessKey: process.env.OPEDD_ACCESS_KEY,
|
|
19
|
+
buyerJwt: process.env.OPEDD_BUYER_JWT,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const ENV_CREDS = envCredentials();
|
|
26
23
|
// ─── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// 30s cap (2026-07-10 hardening audit): without it a hung api.opedd.com
|
|
25
|
+
// call hangs the agent's tool call indefinitely — an MCP server must fail
|
|
26
|
+
// fast so the agent can recover.
|
|
27
|
+
const FETCH_TIMEOUT_MS = 30_000;
|
|
28
|
+
async function opeddFetch(creds, path, options = {}) {
|
|
29
|
+
const url = `${creds.apiBase}${path}`;
|
|
29
30
|
const headers = {
|
|
30
31
|
"Content-Type": "application/json",
|
|
31
32
|
// B91 v0.4.0 (2026-05-26): canonical Bearer preferred; legacy X-API-Key
|
|
32
33
|
// accepted only as fallback during transition window
|
|
33
|
-
...(
|
|
34
|
+
...(creds.pubBearer ? { Authorization: `Bearer ${creds.pubBearer}` } : (creds.apiKey ? { "X-API-Key": creds.apiKey } : {})),
|
|
34
35
|
...(options.headers ?? {}),
|
|
35
36
|
};
|
|
36
|
-
const res = await fetch(url, { ...options, headers });
|
|
37
|
-
|
|
37
|
+
const res = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
38
|
+
// Check ok BEFORE parsing (2026-07-10 hardening): an HTML 502/504 from the
|
|
39
|
+
// proxy must surface as "HTTP 502", not a JSON SyntaxError.
|
|
38
40
|
if (!res.ok) {
|
|
39
|
-
|
|
41
|
+
let msg = `HTTP ${res.status}`;
|
|
42
|
+
try {
|
|
43
|
+
const errBody = await res.json();
|
|
44
|
+
msg = errBody?.error || errBody?.message || msg;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// non-JSON error body — keep the HTTP-status default
|
|
48
|
+
}
|
|
40
49
|
throw new Error(msg);
|
|
41
50
|
}
|
|
42
|
-
return
|
|
51
|
+
return await res.json();
|
|
43
52
|
}
|
|
44
53
|
// Fetch an NDJSON endpoint and collect parsed lines into an array.
|
|
45
54
|
// Last line is typically `{"_meta": {...}}`; surfaced as `meta` field on the
|
|
46
55
|
// returned object so the MCP tool result has both shape pieces inline.
|
|
47
|
-
async function opeddFetchNdjson(path, options = {}) {
|
|
48
|
-
const url = `${
|
|
56
|
+
async function opeddFetchNdjson(creds, path, options = {}) {
|
|
57
|
+
const url = `${creds.apiBase}${path}`;
|
|
49
58
|
const headers = {
|
|
50
59
|
Accept: "application/x-ndjson",
|
|
51
60
|
// B91 v0.4.0 (2026-05-26): canonical Bearer preferred; legacy fallback
|
|
52
|
-
...(
|
|
61
|
+
...(creds.pubBearer ? { Authorization: `Bearer ${creds.pubBearer}` } : (creds.apiKey ? { "X-API-Key": creds.apiKey } : {})),
|
|
53
62
|
...(options.headers ?? {}),
|
|
54
63
|
};
|
|
55
|
-
const res = await fetch(url, { ...options, headers });
|
|
64
|
+
const res = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
56
65
|
if (!res.ok) {
|
|
57
66
|
let msg = `HTTP ${res.status}`;
|
|
58
67
|
try {
|
|
@@ -88,525 +97,540 @@ function err(msg) {
|
|
|
88
97
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
89
98
|
}
|
|
90
99
|
// ─── Tool definitions ─────────────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
function buildTools(has) {
|
|
101
|
+
const TOOLS = [
|
|
102
|
+
{
|
|
103
|
+
name: "lookup_content",
|
|
104
|
+
description: "Look up a piece of content on the Opedd registry by URL. " +
|
|
105
|
+
"Returns the article title, publisher, available license types, and pricing " +
|
|
106
|
+
"(human republication price and AI training/inference price). " +
|
|
107
|
+
"Always call this first to check if content is licensable and what it costs.",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
required: ["url"],
|
|
111
|
+
properties: {
|
|
112
|
+
url: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "The canonical URL of the article or content to look up",
|
|
115
|
+
},
|
|
105
116
|
},
|
|
106
117
|
},
|
|
107
118
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
119
|
+
{
|
|
120
|
+
name: "purchase_license",
|
|
121
|
+
description: "Purchase a content license from the Opedd protocol using a Stripe payment method. " +
|
|
122
|
+
"Returns a license key (format: OP-XXXX-XXXX) and a certificate URL. " +
|
|
123
|
+
"The buyer receives a Handshake Email with their license key. " +
|
|
124
|
+
"Set OPEDD_BUYER_EMAIL and OPEDD_PAYMENT_METHOD_ID env vars to avoid passing them on every call. " +
|
|
125
|
+
"License types: 'human' = republication rights, 'ai' = training dataset rights, 'ai_inference' = inference/RAG rights.",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
required: ["license_type"],
|
|
129
|
+
properties: {
|
|
130
|
+
article_url: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "URL of the article to license (use this OR article_id)",
|
|
133
|
+
},
|
|
134
|
+
article_id: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Opedd article UUID (use this OR article_url)",
|
|
137
|
+
},
|
|
138
|
+
license_type: {
|
|
139
|
+
type: "string",
|
|
140
|
+
enum: ["human", "ai", "ai_inference"],
|
|
141
|
+
description: "human = republication/editorial rights, ai = training dataset rights, ai_inference = inference/RAG rights",
|
|
142
|
+
},
|
|
143
|
+
buyer_email: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Email address for the license. Falls back to OPEDD_BUYER_EMAIL env var.",
|
|
146
|
+
},
|
|
147
|
+
buyer_name: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "Full name of the buyer (for the license record and certificate)",
|
|
150
|
+
},
|
|
151
|
+
buyer_organization: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Organization or company name (for enterprise/editorial licenses)",
|
|
154
|
+
},
|
|
155
|
+
intended_use: {
|
|
156
|
+
type: "string",
|
|
157
|
+
enum: ["personal", "editorial", "commercial", "ai_training", "corporate"],
|
|
158
|
+
description: "Intended use of the licensed content",
|
|
159
|
+
},
|
|
160
|
+
payment_method_id: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Stripe payment method ID (pm_...). Falls back to OPEDD_PAYMENT_METHOD_ID env var.",
|
|
163
|
+
},
|
|
153
164
|
},
|
|
154
165
|
},
|
|
155
166
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
{
|
|
168
|
+
name: "verify_license",
|
|
169
|
+
description: "Verify the authenticity of an Opedd license key. " +
|
|
170
|
+
"Returns license details including: article title, publisher, license type, " +
|
|
171
|
+
"issue date, amount paid, buyer info, and blockchain proof status. " +
|
|
172
|
+
"Use this to confirm a license is valid before using licensed content.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
required: ["license_key"],
|
|
176
|
+
properties: {
|
|
177
|
+
license_key: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "The license key to verify (format: OP-XXXX-XXXX)",
|
|
180
|
+
},
|
|
170
181
|
},
|
|
171
182
|
},
|
|
172
183
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
{
|
|
185
|
+
name: "browse_registry",
|
|
186
|
+
description: "Browse the public Opedd license registry. " +
|
|
187
|
+
"Returns recently issued licenses and licensable content. " +
|
|
188
|
+
"Filter by publisher_id to explore all content from a specific publisher. " +
|
|
189
|
+
"Filter by article_id to see all licenses issued for a specific article.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
publisher_id: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Filter results to a specific publisher (UUID)",
|
|
196
|
+
},
|
|
197
|
+
article_id: {
|
|
198
|
+
type: "string",
|
|
199
|
+
description: "Filter results to a specific article (UUID)",
|
|
200
|
+
},
|
|
201
|
+
limit: {
|
|
202
|
+
type: "number",
|
|
203
|
+
description: "Number of results to return (default: 10, max: 50)",
|
|
204
|
+
},
|
|
194
205
|
},
|
|
195
206
|
},
|
|
196
207
|
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
208
|
+
// ─── Public buyer-discovery — catalog browse ──────────────────────────────
|
|
209
|
+
{
|
|
210
|
+
name: "publisher_directory",
|
|
211
|
+
description: "Browse the public Opedd publisher catalog via GET /publisher-directory. " +
|
|
212
|
+
"Returns paginated publishers with article counts, pricing (per-article + annual + monthly-forward-feed), " +
|
|
213
|
+
"plan, and sample articles (RAG-extended metadata). " +
|
|
214
|
+
"**The primary discovery surface for AI labs to find Opedd-licensable publishers** — distinct from " +
|
|
215
|
+
"`browse_registry` (which lists issued LICENSES, not publishers). " +
|
|
216
|
+
"Filter by category (case-insensitive substring), min_articles, or verified status. " +
|
|
217
|
+
"Public no-auth — useful pre-purchase scoping before buyers commit to enterprise-license POST.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
category: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: "Case-insensitive substring filter on publisher category (e.g. 'finance', 'AI').",
|
|
224
|
+
},
|
|
225
|
+
min_articles: {
|
|
226
|
+
type: "number",
|
|
227
|
+
description: "Filter to publishers with at least this many licensable articles.",
|
|
228
|
+
},
|
|
229
|
+
verified: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "'true' to show only verified publishers (default), 'false' for unverified.",
|
|
232
|
+
},
|
|
233
|
+
limit: {
|
|
234
|
+
type: "number",
|
|
235
|
+
description: "Page size cap.",
|
|
236
|
+
},
|
|
237
|
+
offset: {
|
|
238
|
+
type: "number",
|
|
239
|
+
description: "Pagination offset.",
|
|
240
|
+
},
|
|
230
241
|
},
|
|
231
242
|
},
|
|
232
243
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
// ─── Phase 12 Wave 3 W3.1 — onboarding helpers ────────────────────────────
|
|
245
|
+
{
|
|
246
|
+
name: "detect_platform",
|
|
247
|
+
description: "Detect the content platform behind a URL via POST /detect-platform (Phase 12 Wave 3 W3.1). " +
|
|
248
|
+
"Public no-auth lookup. Given a URL, identifies what platform powers it " +
|
|
249
|
+
"(Substack / Beehiiv / Ghost / Medium / Brevo / custom) and returns the suggested onboarding workflow. " +
|
|
250
|
+
"Hostname-detectable platforms (Substack subdomain, Beehiiv suffix, etc.) resolve in milliseconds; " +
|
|
251
|
+
"custom domains may take ~few seconds while the detector probes well-known platform endpoints in parallel. " +
|
|
252
|
+
"Returns: {platform, confidence, archive_method, forward_method, required_credentials, instructions}. " +
|
|
253
|
+
"The archive_method + forward_method fields are the two onboarding-workflow inputs Opedd's setup wizard reads " +
|
|
254
|
+
"(one for historical content backfill, one for new-content forward stream). " +
|
|
255
|
+
"instructions is human-readable operator copy explaining the inferred path.",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
required: ["url"],
|
|
259
|
+
properties: {
|
|
260
|
+
url: {
|
|
261
|
+
type: "string",
|
|
262
|
+
description: "Publisher URL to inspect (any well-formed URL works; hostname-match short-circuits the probe path).",
|
|
263
|
+
},
|
|
253
264
|
},
|
|
254
265
|
},
|
|
255
266
|
},
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
// ─── Phase 12 Wave 1 W1.1 — public RSL Standard manifest ─────────────────
|
|
268
|
+
{
|
|
269
|
+
name: "rsl_get",
|
|
270
|
+
description: "Fetch a publisher's RSL Standard manifest via GET /rsl-manifest (Phase 12 Wave 1 W1.1). " +
|
|
271
|
+
"Public no-auth endpoint — discovery surface for AI agents/crawlers wanting to know what's licensable " +
|
|
272
|
+
"from a publisher BEFORE going through the buyer-account signup flow. " +
|
|
273
|
+
"Returns the 4 canonical license types (ai_retrieval, ai_training, human_per_article, human_full_archive) " +
|
|
274
|
+
"the publisher has opted into, plus the EU CDSM Article 4(3) opt-out posture (`tdm_reservation`). " +
|
|
275
|
+
"Set `jsonld: true` to request the JSON-LD shape with embedded HMAC-SHA256 signed receipt over the " +
|
|
276
|
+
"CDSM Article 4(3) reservation state + `tdm:reservationSignedAt` timestamp — regulators can post-hoc " +
|
|
277
|
+
"verify the reservation was the claimed value at the claimed time. Default `jsonld: false` returns " +
|
|
278
|
+
"the raw RSL Standard JSON manifest. " +
|
|
279
|
+
"Per INVARIANTS.md W1.6: this is the PUBLISHER-side CDSM Article 4(3) declaration surface. It is NOT " +
|
|
280
|
+
"an EU AI Act Article 53 attestation (which is buyer-side, JWT-auth, via article_53_attestation tool).",
|
|
281
|
+
inputSchema: {
|
|
282
|
+
type: "object",
|
|
283
|
+
required: ["publisher_id"],
|
|
284
|
+
properties: {
|
|
285
|
+
publisher_id: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "UUID of the publisher whose RSL manifest to fetch. Publisher must be verified.",
|
|
288
|
+
},
|
|
289
|
+
jsonld: {
|
|
290
|
+
type: "boolean",
|
|
291
|
+
description: "If true, request JSON-LD shape (Accept: application/ld+json) with embedded HMAC-SHA256 signed receipt. " +
|
|
292
|
+
"Default false returns raw RSL Standard JSON shape.",
|
|
293
|
+
},
|
|
283
294
|
},
|
|
284
295
|
},
|
|
285
296
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
297
|
+
// ─── Phase 10 + 11 buyer-side surfaces (M6.4) ─────────────────────────────
|
|
298
|
+
{
|
|
299
|
+
name: "purchase_enterprise_license",
|
|
300
|
+
description: "Purchase a bulk enterprise license covering multiple publishers (Phase 10). " +
|
|
301
|
+
"Returns a Stripe client_secret for payment completion + the enterprise_license_id. " +
|
|
302
|
+
"After payment, an ent_* access key is emailed to buyer_email. " +
|
|
303
|
+
"Scopes: 'custom' (pass-through publisher_ids), 'platform_wide' (auto-resolve all opted-in publishers), 'filtered' (Phase 10 filter_rules). " +
|
|
304
|
+
"License tiers: 'rag' (= ai_retrieval), 'training' (= ai_training, flat-fee not metered), 'inference' (= ai_retrieval), 'full_ai' (writes both retrieval + training records).",
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: "object",
|
|
307
|
+
required: ["publisher_ids", "buyer_email", "buyer_org"],
|
|
308
|
+
properties: {
|
|
309
|
+
publisher_ids: {
|
|
310
|
+
type: "array",
|
|
311
|
+
items: { type: "string" },
|
|
312
|
+
description: "Array of publisher UUIDs. Required for scope='custom'; ignored for platform_wide/filtered (resolved server-side).",
|
|
313
|
+
},
|
|
314
|
+
buyer_email: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "Email to deliver the access key after payment",
|
|
317
|
+
},
|
|
318
|
+
buyer_org: {
|
|
319
|
+
type: "string",
|
|
320
|
+
description: "Buyer organization name (for billing + audit ledger)",
|
|
321
|
+
},
|
|
322
|
+
billing_type: {
|
|
323
|
+
type: "string",
|
|
324
|
+
enum: ["annual", "monthly", "annual_plus_monthly"],
|
|
325
|
+
description: "Billing cadence (default: annual)",
|
|
326
|
+
},
|
|
327
|
+
license_tier: {
|
|
328
|
+
type: "string",
|
|
329
|
+
enum: ["rag", "training", "inference", "full_ai"],
|
|
330
|
+
description: "License tier (default: rag)",
|
|
331
|
+
},
|
|
332
|
+
duration_months: {
|
|
333
|
+
type: "number",
|
|
334
|
+
description: "License duration in months (default: 12)",
|
|
335
|
+
},
|
|
336
|
+
scope: {
|
|
337
|
+
type: "string",
|
|
338
|
+
enum: ["custom", "platform_wide", "filtered"],
|
|
339
|
+
description: "Coverage scope (default: custom)",
|
|
340
|
+
},
|
|
341
|
+
filter_rules: {
|
|
342
|
+
type: "object",
|
|
343
|
+
description: "Required when scope='filtered'. See Phase 10 docs for shape: excluded_publisher_ids / direct_license_carveouts / categories / max_price_per_event.",
|
|
344
|
+
},
|
|
345
|
+
buyer_webhook_url: {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "Optional HMAC-signed webhook for content.published events on covered publishers",
|
|
348
|
+
},
|
|
338
349
|
},
|
|
339
350
|
},
|
|
340
351
|
},
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
352
|
+
];
|
|
353
|
+
// If a buyer token is configured, expose content delivery tooling
|
|
354
|
+
if (has.buyerToken) {
|
|
355
|
+
TOOLS.push({
|
|
356
|
+
name: "get_content",
|
|
357
|
+
description: "Retrieve the full body of a licensed article using a buyer API token (opedd_buyer_live_* canonical; opedd_buyer_test_* for sandbox). " +
|
|
358
|
+
"Requires OPEDD_BUYER_TOKEN env var (create one at opedd.com/licenses after purchasing). " +
|
|
359
|
+
"Works for per-article licenses (token scoped to that article) and archive licenses (token covers all publisher content). " +
|
|
360
|
+
"The publisher must have content delivery enabled and must have pushed content for the article. " +
|
|
361
|
+
"Phase 11 M2 RAG-extended shape: response includes 7 RAG-essential metadata fields — author, language, word_count, content_hash, image_urls, canonical_url, tags. " +
|
|
362
|
+
"On pre-2026-05-14 historical articles, optional fields (author/language/image_urls/canonical_url/tags) may be NULL. " +
|
|
363
|
+
"NULL means 'data unavailable for this article', NOT 'explicitly empty' — treat as data-missing when filtering; do not interpret as anti-match.",
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
required: ["article_id"],
|
|
367
|
+
properties: {
|
|
368
|
+
article_id: {
|
|
369
|
+
type: "string",
|
|
370
|
+
description: "The Opedd article UUID to retrieve content for",
|
|
371
|
+
},
|
|
372
|
+
buyer_token: {
|
|
373
|
+
type: "string",
|
|
374
|
+
description: "Buyer API token (opedd_buyer_live_* or opedd_buyer_test_*). Falls back to OPEDD_BUYER_TOKEN env var.",
|
|
375
|
+
},
|
|
365
376
|
},
|
|
366
377
|
},
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// If an enterprise access key is configured, expose feed tools
|
|
381
|
+
if (has.accessKey) {
|
|
382
|
+
TOOLS.push({
|
|
383
|
+
name: "list_feed",
|
|
384
|
+
description: "List articles from a buyer's licensed catalog via GET /enterprise-license (Phase 10 + 11). " +
|
|
385
|
+
"Returns JSON-format response with paginated articles. " +
|
|
386
|
+
"Use `since` (ISO 8601) for delta-feed polling — only articles published after the timestamp. " +
|
|
387
|
+
"Use `cursor` for pagination across pages. " +
|
|
388
|
+
"Requires OPEDD_ACCESS_KEY (ent_* enterprise access key). " +
|
|
389
|
+
"For larger bulk corpus pulls, use stream_feed_ndjson (up to 1000 articles per call vs 200 here).",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
since: {
|
|
394
|
+
type: "string",
|
|
395
|
+
description: "ISO 8601 timestamp — return only articles with published_at > since",
|
|
396
|
+
},
|
|
397
|
+
cursor: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "Opaque cursor from prior response's _meta.next_cursor",
|
|
400
|
+
},
|
|
401
|
+
limit: {
|
|
402
|
+
type: "number",
|
|
403
|
+
description: "Max articles per response (default: 50, max: 200)",
|
|
404
|
+
},
|
|
394
405
|
},
|
|
395
406
|
},
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
407
|
+
});
|
|
408
|
+
TOOLS.push({
|
|
409
|
+
name: "stream_feed_ndjson",
|
|
410
|
+
description: "Bulk-export a buyer's licensed catalog via GET /enterprise-license?format=ndjson (Phase 11 M3). " +
|
|
411
|
+
"Returns up to 1000 articles per call (collected from line-delimited JSON wire format). " +
|
|
412
|
+
"Each article emits one usage_records row (analytics-only sentinel 'bulk-export:<request_id>:<article_id>' — not metered-billable per the revenue-model bifurcation invariant). " +
|
|
413
|
+
"Use `since` (ISO 8601) for delta-feed. Use `cursor` to paginate beyond 1000. " +
|
|
414
|
+
"Backend supports 5000 articles per call; the MCP cap is 1000 for transport reasonability. " +
|
|
415
|
+
"Real bulk-ingest pipelines should use the Python SDK (pip install opedd) directly — not via MCP. " +
|
|
416
|
+
"Requires OPEDD_ACCESS_KEY (ent_*).",
|
|
417
|
+
inputSchema: {
|
|
418
|
+
type: "object",
|
|
419
|
+
properties: {
|
|
420
|
+
since: {
|
|
421
|
+
type: "string",
|
|
422
|
+
description: "ISO 8601 timestamp — return only articles with published_at > since",
|
|
423
|
+
},
|
|
424
|
+
cursor: {
|
|
425
|
+
type: "string",
|
|
426
|
+
description: "Opaque cursor from prior response's _meta.next_cursor",
|
|
427
|
+
},
|
|
428
|
+
limit: {
|
|
429
|
+
type: "number",
|
|
430
|
+
description: "Max articles per response (default: 200, max: 1000)",
|
|
431
|
+
},
|
|
421
432
|
},
|
|
422
433
|
},
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
// If a Supabase buyer JWT is configured, expose audit + compliance tools
|
|
437
|
+
if (has.buyerJwt) {
|
|
438
|
+
TOOLS.push({
|
|
439
|
+
name: "get_audit_events",
|
|
440
|
+
description: "Browse per-event audit rows for the authenticated buyer via GET /buyer-audit (Phase 9.x). " +
|
|
441
|
+
"Each row carries license_terms + Tempo on-chain attestation (merkle_root + inclusion_proof when blockchain_status='confirmed'). " +
|
|
442
|
+
"Optional filter by event_type ('content_access', 'bulk_content_access', 'compliance_report_generated'). " +
|
|
443
|
+
"Window cap 30 days (vs 90-day cap on get_compliance_dossier). " +
|
|
444
|
+
"Attestation inclusion proof is included on every row by default — no separate flag needed (M6.4 consolidation per founder ratification: tools 4 + 6 merged into one cleaner mental model). " +
|
|
445
|
+
"Requires OPEDD_BUYER_JWT (Supabase session JWT from the buyer portal).",
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
from: {
|
|
450
|
+
type: "string",
|
|
451
|
+
description: "ISO 8601 timestamp lower bound (inclusive)",
|
|
452
|
+
},
|
|
453
|
+
to: {
|
|
454
|
+
type: "string",
|
|
455
|
+
description: "ISO 8601 timestamp upper bound (inclusive)",
|
|
456
|
+
},
|
|
457
|
+
event_type: {
|
|
458
|
+
type: "string",
|
|
459
|
+
enum: ["content_access", "bulk_content_access", "compliance_report_generated"],
|
|
460
|
+
description: "Optional event-class filter",
|
|
461
|
+
},
|
|
462
|
+
cursor: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "Opaque cursor for pagination",
|
|
465
|
+
},
|
|
466
|
+
limit: {
|
|
467
|
+
type: "number",
|
|
468
|
+
description: "Max events per response (default: 50, max: 200)",
|
|
469
|
+
},
|
|
459
470
|
},
|
|
460
471
|
},
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
properties: {},
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
TOOLS.push({
|
|
479
|
-
name: "article_53_attestation",
|
|
480
|
-
description: "Issue a signed JWT attesting to EU AI Act Article 53 compliance for a specific license via " +
|
|
481
|
-
"GET /eu-ai-act/article-53-attestation (Phase 12 Wave 1 W1.4). " +
|
|
482
|
-
"Returns a freshly-signed HS256 JWT regulators can verify offline against the canonical signing key. " +
|
|
483
|
-
"Embeds: license context, usage-count over the attestation window, the most-recent Tempo Merkle root, " +
|
|
484
|
-
"and canonical claims (iss/sub/iat/exp/jti/aud). " +
|
|
485
|
-
"**The artifact AI labs hand to legal/procurement for EU AI Act Article 53(1)(d) transparency-obligation evidence.** " +
|
|
486
|
-
"Per INVARIANTS.md W1.6: this attests to EU AI Act Article 53 ONLY (buyer-side GPAI-model-provider " +
|
|
487
|
-
"transparency obligation). It does NOT discharge a publisher's CDSM Article 4(3) reservation obligation — " +
|
|
488
|
-
"that lives on the rsl_get tool (jsonld=true variant). Never conflate. " +
|
|
489
|
-
"Optional `content_id` scopes the attestation to one article; default is license-wide. " +
|
|
490
|
-
"Window cap: 365 days. Requires OPEDD_BUYER_JWT.",
|
|
491
|
-
inputSchema: {
|
|
492
|
-
type: "object",
|
|
493
|
-
required: ["license_id"],
|
|
494
|
-
properties: {
|
|
495
|
-
license_id: {
|
|
496
|
-
type: "string",
|
|
497
|
-
description: "UUID of the enterprise_license OR legacy individual license to attest. Buyer must own it.",
|
|
498
|
-
},
|
|
499
|
-
content_id: {
|
|
500
|
-
type: "string",
|
|
501
|
-
description: "Optional UUID of a specific article to scope the attestation. Default: license-wide.",
|
|
502
|
-
},
|
|
503
|
-
window_start: {
|
|
504
|
-
type: "string",
|
|
505
|
-
description: "ISO 8601 lower bound of the attestation window. Default: now - 90 days.",
|
|
506
|
-
},
|
|
507
|
-
window_end: {
|
|
508
|
-
type: "string",
|
|
509
|
-
description: "ISO 8601 upper bound. Default: now. Window may not exceed 365 days (hard cap).",
|
|
510
|
-
},
|
|
472
|
+
});
|
|
473
|
+
TOOLS.push({
|
|
474
|
+
name: "get_buyer_account",
|
|
475
|
+
description: "Fetch the authenticated buyer's account profile + masked API key list via GET /buyer-account. " +
|
|
476
|
+
"Returns the enterprise_buyers row (contact_email, buyer_org, created_at, etc.) plus a list of all " +
|
|
477
|
+
"buyer-side API keys with masked prefixes (NEVER plaintext post-issuance — only the 12-char " +
|
|
478
|
+
"key_prefix is returned, e.g. 'opedd_buyer_'). " +
|
|
479
|
+
"Use cases: post-signup verification ('what was just issued to me?'), buyer dashboard mental model " +
|
|
480
|
+
"('what licenses do I currently hold?'), audit prep ('show me the key list before rotation'). " +
|
|
481
|
+
"For full mid-lifecycle license details (filter_rules, billing, payouts), buyers consult the buyer portal at opedd.com/buyer. " +
|
|
482
|
+
"Requires OPEDD_BUYER_JWT.",
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: "object",
|
|
485
|
+
properties: {},
|
|
511
486
|
},
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
487
|
+
});
|
|
488
|
+
TOOLS.push({
|
|
489
|
+
name: "article_53_attestation",
|
|
490
|
+
description: "Issue a signed JWT attesting to EU AI Act Article 53 compliance for a specific license via " +
|
|
491
|
+
"GET /eu-ai-act/article-53-attestation (Phase 12 Wave 1 W1.4). " +
|
|
492
|
+
"Returns a freshly-signed HS256 JWT regulators can verify offline against the canonical signing key. " +
|
|
493
|
+
"Embeds: license context, usage-count over the attestation window, the most-recent Tempo Merkle root, " +
|
|
494
|
+
"and canonical claims (iss/sub/iat/exp/jti/aud). " +
|
|
495
|
+
"**The artifact AI labs hand to legal/procurement for EU AI Act Article 53(1)(d) transparency-obligation evidence.** " +
|
|
496
|
+
"Per INVARIANTS.md W1.6: this attests to EU AI Act Article 53 ONLY (buyer-side GPAI-model-provider " +
|
|
497
|
+
"transparency obligation). It does NOT discharge a publisher's CDSM Article 4(3) reservation obligation — " +
|
|
498
|
+
"that lives on the rsl_get tool (jsonld=true variant). Never conflate. " +
|
|
499
|
+
"Optional `content_id` scopes the attestation to one article; default is license-wide. " +
|
|
500
|
+
"Window cap: 365 days. Requires OPEDD_BUYER_JWT.",
|
|
501
|
+
inputSchema: {
|
|
502
|
+
type: "object",
|
|
503
|
+
required: ["license_id"],
|
|
504
|
+
properties: {
|
|
505
|
+
license_id: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "UUID of the enterprise_license OR legacy individual license to attest. Buyer must own it.",
|
|
508
|
+
},
|
|
509
|
+
content_id: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Optional UUID of a specific article to scope the attestation. Default: license-wide.",
|
|
512
|
+
},
|
|
513
|
+
window_start: {
|
|
514
|
+
type: "string",
|
|
515
|
+
description: "ISO 8601 lower bound of the attestation window. Default: now - 90 days.",
|
|
516
|
+
},
|
|
517
|
+
window_end: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "ISO 8601 upper bound. Default: now. Window may not exceed 365 days (hard cap).",
|
|
520
|
+
},
|
|
538
521
|
},
|
|
539
522
|
},
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
523
|
+
});
|
|
524
|
+
TOOLS.push({
|
|
525
|
+
name: "get_compliance_dossier",
|
|
526
|
+
description: "Generate a procurement-defense compliance dossier via GET /buyer-compliance-report (Phase 11 M4). " +
|
|
527
|
+
"Per-row dossier shape: 25+ fields including 17 RAG-essential article fields + full license_terms + on_chain_attestation block. " +
|
|
528
|
+
"Bulk envelopes fan out into per-article rows by iterating metadata.article_ids[]. " +
|
|
529
|
+
"Self-audit invariant: every successful call writes one license_events row with event_type='compliance_report_generated' BEFORE returning. " +
|
|
530
|
+
"Window cap: 90 days per call (vs 30-day cap on get_audit_events). For annual audits, paginate via _meta.next_cursor across 4 quarterly windows. " +
|
|
531
|
+
"Compliance framework anchors (boolean flags) map to EU AI Act Article 53, CDSM Article 4(3), on-chain attestation, TDM reservation. " +
|
|
532
|
+
"Requires OPEDD_BUYER_JWT.",
|
|
533
|
+
inputSchema: {
|
|
534
|
+
type: "object",
|
|
535
|
+
required: ["from", "to"],
|
|
536
|
+
properties: {
|
|
537
|
+
from: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "ISO 8601 timestamp lower bound (inclusive)",
|
|
540
|
+
},
|
|
541
|
+
to: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "ISO 8601 timestamp upper bound (inclusive). Window cap 90 days.",
|
|
544
|
+
},
|
|
545
|
+
cursor: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "Opaque cursor for pagination",
|
|
548
|
+
},
|
|
561
549
|
},
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// If a publisher API key is configured, expose publisher-specific tooling
|
|
554
|
+
if (has.pubBearer) {
|
|
555
|
+
TOOLS.push({
|
|
556
|
+
name: "list_publisher_content",
|
|
557
|
+
description: "List all licensable articles for the authenticated publisher (requires OPEDD_PUB_BEARER, or legacy OPEDD_API_KEY). " +
|
|
558
|
+
"Returns articles with titles, descriptions, pricing, and sales statistics. " +
|
|
559
|
+
"Use article IDs from this list to purchase licenses via purchase_license.",
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: "object",
|
|
562
|
+
properties: {
|
|
563
|
+
limit: {
|
|
564
|
+
type: "number",
|
|
565
|
+
description: "Number of results (default: 20, max: 100)",
|
|
566
|
+
},
|
|
567
|
+
type: {
|
|
568
|
+
type: "string",
|
|
569
|
+
enum: ["human", "ai"],
|
|
570
|
+
description: "Filter by license type availability",
|
|
571
|
+
},
|
|
572
|
+
offset: {
|
|
573
|
+
type: "number",
|
|
574
|
+
description: "Pagination offset (default: 0)",
|
|
575
|
+
},
|
|
565
576
|
},
|
|
566
577
|
},
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
578
|
+
});
|
|
579
|
+
// Supply-side push: onboard a back-catalogue or send new posts directly from
|
|
580
|
+
// the AI assistant — the native companion to the /publishers-content API.
|
|
581
|
+
TOOLS.push({
|
|
582
|
+
name: "push_content",
|
|
583
|
+
description: "Push your published articles to Opedd so they can be licensed to AI buyers (requires OPEDD_PUB_BEARER — your opedd_pub_ publisher key). " +
|
|
584
|
+
"Send 1–100 articles per call; batch larger back-catalogues into multiple calls. Each article needs title, url, and html_body — everything else is optional (published_at defaults to now). " +
|
|
585
|
+
"This is the supply-side companion to list_publisher_content: use it to onboard your archive or push new content with no code, straight from your AI assistant.",
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: "object",
|
|
588
|
+
required: ["articles"],
|
|
589
|
+
properties: {
|
|
590
|
+
articles: {
|
|
591
|
+
type: "array",
|
|
592
|
+
minItems: 1,
|
|
593
|
+
maxItems: 100,
|
|
594
|
+
description: "1–100 articles to push in this call.",
|
|
595
|
+
items: {
|
|
596
|
+
type: "object",
|
|
597
|
+
required: ["title", "url", "html_body"],
|
|
598
|
+
properties: {
|
|
599
|
+
title: { type: "string", description: "Article title (required, ≤500 chars)." },
|
|
600
|
+
url: { type: "string", description: "The article's canonical web address (required)." },
|
|
601
|
+
html_body: { type: "string", description: "Full article content, HTML or plain text (required, ≤200,000 chars)." },
|
|
602
|
+
published_at: { type: "string", description: "ISO-8601 datetime (e.g. 2026-01-15T09:30:00Z). Defaults to now if omitted." },
|
|
603
|
+
description: { type: "string", description: "Short summary (≤500 chars)." },
|
|
604
|
+
author: { type: "string", description: "Author name (≤200 chars)." },
|
|
605
|
+
language: { type: "string", description: "Language code, e.g. \"en\"." },
|
|
606
|
+
tags: { type: "array", items: { type: "string" }, description: "Up to 20 tags." },
|
|
607
|
+
image_urls: { type: "array", items: { type: "string" }, description: "Up to 10 body-image URLs." },
|
|
608
|
+
thumbnail_url: { type: "string", description: "https cover-image URL for the catalog." },
|
|
609
|
+
canonical_url: { type: "string", description: "Canonical URL if different from url." },
|
|
610
|
+
category: { type: "string", description: "Content category." },
|
|
611
|
+
is_paid: { type: "boolean", description: "Whether the article is behind a paywall." },
|
|
612
|
+
audience: { type: "string", enum: ["everyone", "only_free", "only_paid"], description: "Intended audience." },
|
|
613
|
+
},
|
|
603
614
|
},
|
|
604
615
|
},
|
|
605
616
|
},
|
|
606
617
|
},
|
|
607
|
-
}
|
|
608
|
-
}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
return TOOLS;
|
|
609
621
|
}
|
|
622
|
+
// stdio: credential-gated list (only tools the local env can use).
|
|
623
|
+
export const TOOLS = buildTools({
|
|
624
|
+
buyerToken: !!ENV_CREDS.buyerToken,
|
|
625
|
+
accessKey: !!ENV_CREDS.accessKey,
|
|
626
|
+
buyerJwt: !!ENV_CREDS.buyerJwt,
|
|
627
|
+
pubBearer: !!(ENV_CREDS.pubBearer || ENV_CREDS.apiKey),
|
|
628
|
+
});
|
|
629
|
+
// hosted gateway: advertise ALL tools; per-request creds decide what
|
|
630
|
+
// actually succeeds (dispatchTool returns a clear error on missing auth).
|
|
631
|
+
export const ALL_TOOLS = buildTools({
|
|
632
|
+
buyerToken: true, accessKey: true, buyerJwt: true, pubBearer: true,
|
|
633
|
+
});
|
|
610
634
|
// ─── MCP Server ───────────────────────────────────────────────────────────────
|
|
611
635
|
const server = new Server({ name: "opedd-mcp", version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
612
636
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
@@ -626,7 +650,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
626
650
|
// is a thin delegation to dispatchTool. Exporting allows unit tests to mock
|
|
627
651
|
// global fetch + invoke each tool's handler directly without spawning a
|
|
628
652
|
// stdio subprocess.
|
|
629
|
-
export async function dispatchTool(name, args) {
|
|
653
|
+
export async function dispatchTool(name, args, creds = ENV_CREDS) {
|
|
630
654
|
try {
|
|
631
655
|
switch (name) {
|
|
632
656
|
// ── lookup_content ─────────────────────────────────────────────────────
|
|
@@ -634,7 +658,7 @@ export async function dispatchTool(name, args) {
|
|
|
634
658
|
const { url } = args;
|
|
635
659
|
if (!url)
|
|
636
660
|
return err("url is required");
|
|
637
|
-
const data = await opeddFetch(`/lookup-article?url=${encodeURIComponent(url)}`);
|
|
661
|
+
const data = await opeddFetch(creds, `/lookup-article?url=${encodeURIComponent(url)}`);
|
|
638
662
|
return ok(data);
|
|
639
663
|
}
|
|
640
664
|
// ── purchase_license ───────────────────────────────────────────────────
|
|
@@ -643,11 +667,11 @@ export async function dispatchTool(name, args) {
|
|
|
643
667
|
if (!article_url && !article_id) {
|
|
644
668
|
return err("Either article_url or article_id is required");
|
|
645
669
|
}
|
|
646
|
-
const buyerEmail = argEmail ||
|
|
670
|
+
const buyerEmail = argEmail || creds.buyerEmail;
|
|
647
671
|
if (!buyerEmail) {
|
|
648
672
|
return err("buyer_email is required (or set the OPEDD_BUYER_EMAIL env var)");
|
|
649
673
|
}
|
|
650
|
-
const paymentMethodId = argPm ||
|
|
674
|
+
const paymentMethodId = argPm || creds.paymentMethodId;
|
|
651
675
|
if (!paymentMethodId) {
|
|
652
676
|
return err("payment_method_id is required (or set the OPEDD_PAYMENT_METHOD_ID env var). " +
|
|
653
677
|
"Get a Stripe payment method ID by saving a card at stripe.com/docs/api/payment_methods.");
|
|
@@ -661,7 +685,7 @@ export async function dispatchTool(name, args) {
|
|
|
661
685
|
...(buyer_organization ? { buyer_organization } : {}),
|
|
662
686
|
...(intended_use ? { intended_use } : {}),
|
|
663
687
|
};
|
|
664
|
-
const data = await opeddFetch("/agent-purchase", {
|
|
688
|
+
const data = await opeddFetch(creds, "/agent-purchase", {
|
|
665
689
|
method: "POST",
|
|
666
690
|
body: JSON.stringify(body),
|
|
667
691
|
});
|
|
@@ -672,7 +696,7 @@ export async function dispatchTool(name, args) {
|
|
|
672
696
|
const { license_key } = args;
|
|
673
697
|
if (!license_key)
|
|
674
698
|
return err("license_key is required");
|
|
675
|
-
const data = await opeddFetch(`/verify-license?key=${encodeURIComponent(license_key)}`);
|
|
699
|
+
const data = await opeddFetch(creds, `/verify-license?key=${encodeURIComponent(license_key)}`);
|
|
676
700
|
return ok(data);
|
|
677
701
|
}
|
|
678
702
|
// ── browse_registry ────────────────────────────────────────────────────
|
|
@@ -684,7 +708,7 @@ export async function dispatchTool(name, args) {
|
|
|
684
708
|
if (article_id)
|
|
685
709
|
params.set("article_id", article_id);
|
|
686
710
|
params.set("limit", String(Math.min(Number(limit), 50)));
|
|
687
|
-
const data = await opeddFetch(`/registry?${params.toString()}`);
|
|
711
|
+
const data = await opeddFetch(creds, `/registry?${params.toString()}`);
|
|
688
712
|
return ok(data);
|
|
689
713
|
}
|
|
690
714
|
// ── get_content ────────────────────────────────────────────────────────
|
|
@@ -692,12 +716,12 @@ export async function dispatchTool(name, args) {
|
|
|
692
716
|
const { article_id, buyer_token: argToken } = args;
|
|
693
717
|
if (!article_id)
|
|
694
718
|
return err("article_id is required");
|
|
695
|
-
const token = argToken ||
|
|
719
|
+
const token = argToken || creds.buyerToken;
|
|
696
720
|
if (!token) {
|
|
697
721
|
return err("buyer_token is required (or set the OPEDD_BUYER_TOKEN env var). " +
|
|
698
722
|
"Create a token at opedd.com/licenses after purchasing a license.");
|
|
699
723
|
}
|
|
700
|
-
const data = await opeddFetch(`/content-delivery?article_id=${encodeURIComponent(article_id)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
724
|
+
const data = await opeddFetch(creds, `/content-delivery?article_id=${encodeURIComponent(article_id)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
701
725
|
return ok(data);
|
|
702
726
|
}
|
|
703
727
|
// ── publisher_directory (buyer-discovery catalog browse) ───────────────
|
|
@@ -715,7 +739,7 @@ export async function dispatchTool(name, args) {
|
|
|
715
739
|
if (offset !== undefined)
|
|
716
740
|
params.set("offset", String(offset));
|
|
717
741
|
const query = params.toString();
|
|
718
|
-
const data = await opeddFetch(`/publisher-directory${query ? `?${query}` : ""}`);
|
|
742
|
+
const data = await opeddFetch(creds, `/publisher-directory${query ? `?${query}` : ""}`);
|
|
719
743
|
return ok(data);
|
|
720
744
|
}
|
|
721
745
|
// ── detect_platform (Phase 12 Wave 3 W3.1) ─────────────────────────────
|
|
@@ -723,7 +747,7 @@ export async function dispatchTool(name, args) {
|
|
|
723
747
|
const { url } = args;
|
|
724
748
|
if (!url)
|
|
725
749
|
return err("url is required");
|
|
726
|
-
const data = await opeddFetch("/detect-platform", {
|
|
750
|
+
const data = await opeddFetch(creds, "/detect-platform", {
|
|
727
751
|
method: "POST",
|
|
728
752
|
body: JSON.stringify({ url }),
|
|
729
753
|
});
|
|
@@ -735,7 +759,7 @@ export async function dispatchTool(name, args) {
|
|
|
735
759
|
if (!publisher_id)
|
|
736
760
|
return err("publisher_id is required");
|
|
737
761
|
const accept = jsonld ? "application/ld+json" : "application/json";
|
|
738
|
-
const data = await opeddFetch(`/rsl-manifest?publisher_id=${encodeURIComponent(publisher_id)}`, { headers: { Accept: accept } });
|
|
762
|
+
const data = await opeddFetch(creds, `/rsl-manifest?publisher_id=${encodeURIComponent(publisher_id)}`, { headers: { Accept: accept } });
|
|
739
763
|
return ok(data);
|
|
740
764
|
}
|
|
741
765
|
// ── purchase_enterprise_license (Phase 10) ─────────────────────────────
|
|
@@ -761,7 +785,7 @@ export async function dispatchTool(name, args) {
|
|
|
761
785
|
...(filter_rules ? { filter_rules } : {}),
|
|
762
786
|
...(buyer_webhook_url ? { buyer_webhook_url } : {}),
|
|
763
787
|
};
|
|
764
|
-
const data = await opeddFetch("/enterprise-license", {
|
|
788
|
+
const data = await opeddFetch(creds, "/enterprise-license", {
|
|
765
789
|
method: "POST",
|
|
766
790
|
body: JSON.stringify(body),
|
|
767
791
|
});
|
|
@@ -769,12 +793,12 @@ export async function dispatchTool(name, args) {
|
|
|
769
793
|
}
|
|
770
794
|
// ── list_feed (Phase 10 + 11) ──────────────────────────────────────────
|
|
771
795
|
case "list_feed": {
|
|
772
|
-
if (!
|
|
796
|
+
if (!creds.accessKey) {
|
|
773
797
|
return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
|
|
774
798
|
}
|
|
775
799
|
const { since, cursor, limit = 50 } = args;
|
|
776
800
|
const params = new URLSearchParams({
|
|
777
|
-
access_key:
|
|
801
|
+
access_key: creds.accessKey,
|
|
778
802
|
format: "json",
|
|
779
803
|
limit: String(Math.min(Number(limit) || 50, 200)),
|
|
780
804
|
});
|
|
@@ -782,17 +806,17 @@ export async function dispatchTool(name, args) {
|
|
|
782
806
|
params.set("since", since);
|
|
783
807
|
if (cursor)
|
|
784
808
|
params.set("cursor", cursor);
|
|
785
|
-
const data = await opeddFetch(`/enterprise-license?${params.toString()}`);
|
|
809
|
+
const data = await opeddFetch(creds, `/enterprise-license?${params.toString()}`);
|
|
786
810
|
return ok(data);
|
|
787
811
|
}
|
|
788
812
|
// ── stream_feed_ndjson (Phase 11 M3) ───────────────────────────────────
|
|
789
813
|
case "stream_feed_ndjson": {
|
|
790
|
-
if (!
|
|
814
|
+
if (!creds.accessKey) {
|
|
791
815
|
return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
|
|
792
816
|
}
|
|
793
817
|
const { since, cursor, limit = 200 } = args;
|
|
794
818
|
const params = new URLSearchParams({
|
|
795
|
-
access_key:
|
|
819
|
+
access_key: creds.accessKey,
|
|
796
820
|
format: "ndjson",
|
|
797
821
|
limit: String(Math.min(Number(limit) || 200, 1000)),
|
|
798
822
|
});
|
|
@@ -800,12 +824,12 @@ export async function dispatchTool(name, args) {
|
|
|
800
824
|
params.set("since", since);
|
|
801
825
|
if (cursor)
|
|
802
826
|
params.set("cursor", cursor);
|
|
803
|
-
const data = await opeddFetchNdjson(`/enterprise-license?${params.toString()}`);
|
|
827
|
+
const data = await opeddFetchNdjson(creds, `/enterprise-license?${params.toString()}`);
|
|
804
828
|
return ok(data);
|
|
805
829
|
}
|
|
806
830
|
// ── get_audit_events (Phase 9.x + 10 M5 attestation) ───────────────────
|
|
807
831
|
case "get_audit_events": {
|
|
808
|
-
if (!
|
|
832
|
+
if (!creds.buyerJwt) {
|
|
809
833
|
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
810
834
|
}
|
|
811
835
|
const { from, to, event_type, cursor, limit = 50 } = args;
|
|
@@ -820,24 +844,24 @@ export async function dispatchTool(name, args) {
|
|
|
820
844
|
params.set("event_type", event_type);
|
|
821
845
|
if (cursor)
|
|
822
846
|
params.set("cursor", cursor);
|
|
823
|
-
const data = await opeddFetch(`/buyer-audit?${params.toString()}`, {
|
|
824
|
-
headers: { Authorization: `Bearer ${
|
|
847
|
+
const data = await opeddFetch(creds, `/buyer-audit?${params.toString()}`, {
|
|
848
|
+
headers: { Authorization: `Bearer ${creds.buyerJwt}` },
|
|
825
849
|
});
|
|
826
850
|
return ok(data);
|
|
827
851
|
}
|
|
828
852
|
// ── get_buyer_account (buyer profile + masked key list) ────────────────
|
|
829
853
|
case "get_buyer_account": {
|
|
830
|
-
if (!
|
|
854
|
+
if (!creds.buyerJwt) {
|
|
831
855
|
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
832
856
|
}
|
|
833
|
-
const data = await opeddFetch("/buyer-account", {
|
|
834
|
-
headers: { Authorization: `Bearer ${
|
|
857
|
+
const data = await opeddFetch(creds, "/buyer-account", {
|
|
858
|
+
headers: { Authorization: `Bearer ${creds.buyerJwt}` },
|
|
835
859
|
});
|
|
836
860
|
return ok(data);
|
|
837
861
|
}
|
|
838
862
|
// ── article_53_attestation (Phase 12 Wave 1 W1.4) ──────────────────────
|
|
839
863
|
case "article_53_attestation": {
|
|
840
|
-
if (!
|
|
864
|
+
if (!creds.buyerJwt) {
|
|
841
865
|
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
842
866
|
}
|
|
843
867
|
const { license_id, content_id, window_start, window_end } = args;
|
|
@@ -855,12 +879,12 @@ export async function dispatchTool(name, args) {
|
|
|
855
879
|
// slashed "/eu-ai-act/article-53-attestation" resolved to a
|
|
856
880
|
// nonexistent function named "eu-ai-act" and 404'd — this tool had
|
|
857
881
|
// NEVER worked in production until the 2026-07-10 fix (live-probed).
|
|
858
|
-
const data = await opeddFetch(`/eu-ai-act-article-53-attestation?${params.toString()}`, { headers: { Authorization: `Bearer ${
|
|
882
|
+
const data = await opeddFetch(creds, `/eu-ai-act-article-53-attestation?${params.toString()}`, { headers: { Authorization: `Bearer ${creds.buyerJwt}` } });
|
|
859
883
|
return ok(data);
|
|
860
884
|
}
|
|
861
885
|
// ── get_compliance_dossier (Phase 11 M4) ───────────────────────────────
|
|
862
886
|
case "get_compliance_dossier": {
|
|
863
|
-
if (!
|
|
887
|
+
if (!creds.buyerJwt) {
|
|
864
888
|
return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
|
|
865
889
|
}
|
|
866
890
|
const { from, to, cursor } = args;
|
|
@@ -875,14 +899,14 @@ export async function dispatchTool(name, args) {
|
|
|
875
899
|
});
|
|
876
900
|
if (cursor)
|
|
877
901
|
params.set("cursor", cursor);
|
|
878
|
-
const data = await opeddFetch(`/buyer-compliance-report?${params.toString()}`, {
|
|
879
|
-
headers: { Authorization: `Bearer ${
|
|
902
|
+
const data = await opeddFetch(creds, `/buyer-compliance-report?${params.toString()}`, {
|
|
903
|
+
headers: { Authorization: `Bearer ${creds.buyerJwt}` },
|
|
880
904
|
});
|
|
881
905
|
return ok(data);
|
|
882
906
|
}
|
|
883
907
|
// ── list_publisher_content ─────────────────────────────────────────────
|
|
884
908
|
case "list_publisher_content": {
|
|
885
|
-
if (!
|
|
909
|
+
if (!creds.pubBearer && !creds.apiKey) {
|
|
886
910
|
return err("OPEDD_PUB_BEARER env var is required for this tool (canonical Bearer; legacy OPEDD_API_KEY also accepted during transition)");
|
|
887
911
|
}
|
|
888
912
|
const { limit = 20, type, offset = 0 } = args;
|
|
@@ -891,12 +915,12 @@ export async function dispatchTool(name, args) {
|
|
|
891
915
|
params.set("offset", String(Number(offset)));
|
|
892
916
|
if (type)
|
|
893
917
|
params.set("type", type);
|
|
894
|
-
const data = await opeddFetch(`/api?${params.toString()}`);
|
|
918
|
+
const data = await opeddFetch(creds, `/api?${params.toString()}`);
|
|
895
919
|
return ok(data);
|
|
896
920
|
}
|
|
897
921
|
// ── push_content ───────────────────────────────────────────────────────
|
|
898
922
|
case "push_content": {
|
|
899
|
-
if (!
|
|
923
|
+
if (!creds.pubBearer && !creds.apiKey) {
|
|
900
924
|
return err("OPEDD_PUB_BEARER env var is required for this tool (your opedd_pub_ publisher key).");
|
|
901
925
|
}
|
|
902
926
|
const { articles } = args;
|
|
@@ -908,7 +932,7 @@ export async function dispatchTool(name, args) {
|
|
|
908
932
|
}
|
|
909
933
|
// Backend /publishers-content enforces the full strict schema + returns
|
|
910
934
|
// clear per-article errors; we pass through so the assistant can relay them.
|
|
911
|
-
const data = await opeddFetch("/publishers-content", {
|
|
935
|
+
const data = await opeddFetch(creds, "/publishers-content", {
|
|
912
936
|
method: "POST",
|
|
913
937
|
body: JSON.stringify({ articles }),
|
|
914
938
|
});
|