opedd-mcp 0.12.8 → 0.12.10
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/dist/index.js +35 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ async function opeddFetch(creds, path, options = {}) {
|
|
|
53
53
|
// proxy must surface as "HTTP 502", not a JSON SyntaxError.
|
|
54
54
|
if (!res.ok) {
|
|
55
55
|
let msg = `HTTP ${res.status}`;
|
|
56
|
+
let details;
|
|
56
57
|
try {
|
|
57
58
|
const errBody = await res.json();
|
|
58
59
|
// Backend envelope upgraded to structured errors ({ error: { code,
|
|
@@ -60,6 +61,8 @@ async function opeddFetch(creds, path, options = {}) {
|
|
|
60
61
|
// object into Error() rendered "[object Object]" — every agent-facing
|
|
61
62
|
// failure was unreadable (launch-gaps follow-on, 2026-07-17).
|
|
62
63
|
const e = errBody?.error;
|
|
64
|
+
if (typeof e === "object" && e !== null)
|
|
65
|
+
details = e.details;
|
|
63
66
|
msg =
|
|
64
67
|
(typeof e === "object" && e !== null ? [e.code, e.message].filter(Boolean).join(": ") : e) ||
|
|
65
68
|
errBody?.message ||
|
|
@@ -69,8 +72,9 @@ async function opeddFetch(creds, path, options = {}) {
|
|
|
69
72
|
// non-JSON error body — keep the HTTP-status default
|
|
70
73
|
}
|
|
71
74
|
// The status rides on the error so a caller can treat one status as an
|
|
72
|
-
// answer (lookup_content: 404 = not on Opedd yet) without parsing text
|
|
73
|
-
|
|
75
|
+
// answer (lookup_content: 404 = not on Opedd yet) without parsing text;
|
|
76
|
+
// error.details rides too (lookup_content: the publication at that site).
|
|
77
|
+
throw Object.assign(new Error(msg), { status: res.status, details });
|
|
74
78
|
}
|
|
75
79
|
return await res.json();
|
|
76
80
|
}
|
|
@@ -184,7 +188,7 @@ function buildTools(has) {
|
|
|
184
188
|
const TOOLS = [
|
|
185
189
|
{
|
|
186
190
|
name: "lookup_content",
|
|
187
|
-
description: "Looks up an article on Opedd by its web address. Returns the title, publication, available licence types and prices, or that it is not on Opedd yet (then request_publication records the request).",
|
|
191
|
+
description: "Looks up an article on Opedd by its web address. Returns the title, publication, available licence types and prices; for a publication's homepage, the publication and its licensing page; or that it is not on Opedd yet (then request_publication records the request).",
|
|
188
192
|
inputSchema: {
|
|
189
193
|
type: "object",
|
|
190
194
|
required: ["url"],
|
|
@@ -346,7 +350,7 @@ function buildTools(has) {
|
|
|
346
350
|
// ─── Phase 12 Wave 3 W3.1 — onboarding helpers ────────────────────────────
|
|
347
351
|
{
|
|
348
352
|
name: "detect_platform",
|
|
349
|
-
description: "
|
|
353
|
+
description: "For publishers: detect the content platform behind a URL. " +
|
|
350
354
|
"Public no-auth lookup. Given a URL, identifies what platform powers it " +
|
|
351
355
|
"(Substack / Beehiiv / Ghost / Medium / Brevo / custom) and returns the suggested onboarding workflow. " +
|
|
352
356
|
"Hostname-detectable platforms (Substack subdomain, Beehiiv suffix, etc.) resolve in milliseconds; " +
|
|
@@ -369,7 +373,7 @@ function buildTools(has) {
|
|
|
369
373
|
// ─── Phase 12 Wave 1 W1.1 — public RSL Standard manifest ─────────────────
|
|
370
374
|
{
|
|
371
375
|
name: "rsl_get",
|
|
372
|
-
description: "Fetch a publisher's RSL Standard manifest
|
|
376
|
+
description: "Fetch a publisher's RSL Standard manifest (machine-readable licensing terms). " +
|
|
373
377
|
"Public no-auth endpoint — discovery surface for AI agents/crawlers wanting to know what's licensable " +
|
|
374
378
|
"from a publisher BEFORE going through the buyer-account signup flow. " +
|
|
375
379
|
"Returns the 4 canonical license types (ai_retrieval, ai_training, human_per_article, human_full_archive) " +
|
|
@@ -378,8 +382,8 @@ function buildTools(has) {
|
|
|
378
382
|
"CDSM Article 4(3) reservation state + `tdm:reservationSignedAt` timestamp — regulators can post-hoc " +
|
|
379
383
|
"verify the reservation was the claimed value at the claimed time. Default `jsonld: false` returns " +
|
|
380
384
|
"the raw RSL Standard JSON manifest. " +
|
|
381
|
-
"
|
|
382
|
-
"
|
|
385
|
+
"This is the publisher's own declaration. It is not a compliance attestation for a buyer " +
|
|
386
|
+
"(use article_53_attestation for that).",
|
|
383
387
|
inputSchema: {
|
|
384
388
|
type: "object",
|
|
385
389
|
required: ["publisher_id"],
|
|
@@ -478,7 +482,7 @@ function buildTools(has) {
|
|
|
478
482
|
"retrieve articles: it answers 403 SEARCH_ONLY_LICENCE \u2014 ask a question with search_passages instead. " +
|
|
479
483
|
"Articles the publisher stopped licensing answer 403 ARTICLE_EXCLUDED. " +
|
|
480
484
|
"The publisher must have content delivery enabled and must have pushed content for the article. " +
|
|
481
|
-
"
|
|
485
|
+
"The response includes author, language, word_count, content_hash, image_urls, canonical_url and tags. " +
|
|
482
486
|
"On pre-2026-05-14 historical articles, optional fields (author/language/image_urls/canonical_url/tags) may be NULL. " +
|
|
483
487
|
"NULL means 'data unavailable for this article', NOT 'explicitly empty' — treat as data-missing when filtering; do not interpret as anti-match.",
|
|
484
488
|
inputSchema: {
|
|
@@ -502,7 +506,7 @@ function buildTools(has) {
|
|
|
502
506
|
if (has.accessKey || has.buyerToken) {
|
|
503
507
|
TOOLS.push({
|
|
504
508
|
name: "list_feed",
|
|
505
|
-
description: "List articles
|
|
509
|
+
description: "List the articles in a licence order you hold. " +
|
|
506
510
|
"Content contract: AI training orders include full content_body for the back catalogue up to the order date " +
|
|
507
511
|
"(content_access 'included'). Every other order is discovery-only (content_body null): AI answers monthly and " +
|
|
508
512
|
"client display fetch text per article via get_content (content_access 'retrieval_per_article'); pay-per-request " +
|
|
@@ -538,11 +542,11 @@ function buildTools(has) {
|
|
|
538
542
|
});
|
|
539
543
|
TOOLS.push({
|
|
540
544
|
name: "stream_feed_ndjson",
|
|
541
|
-
description: "Bulk-export
|
|
545
|
+
description: "Bulk-export the articles in a licence order you hold. " +
|
|
542
546
|
"Returns up to 1000 articles per call (collected from line-delimited JSON wire format). " +
|
|
543
547
|
"Same content contract as list_feed: full text for AI training orders only; every other order exports " +
|
|
544
548
|
"metadata (content_body null) — use get_content for article text. " +
|
|
545
|
-
"Each article
|
|
549
|
+
"Each exported article is recorded for analytics; a bulk export is not billed per article. " +
|
|
546
550
|
"Use `since` (ISO 8601) for delta-feed. Use `cursor` to paginate beyond 1000. " +
|
|
547
551
|
"Backend supports 5000 articles per call; the MCP cap is 1000 for transport reasonability. " +
|
|
548
552
|
"Real bulk-ingest pipelines should use the Python SDK (pip install opedd) directly — not via MCP. " +
|
|
@@ -634,15 +638,12 @@ function buildTools(has) {
|
|
|
634
638
|
});
|
|
635
639
|
TOOLS.push({
|
|
636
640
|
name: "article_53_attestation",
|
|
637
|
-
description: "Issue a signed compliance attestation (a JWT) for a
|
|
638
|
-
"GET /eu-ai-act/article-53-attestation (Phase 12 Wave 1 W1.4). " +
|
|
641
|
+
description: "Issue a signed compliance attestation (a JWT) for a licence you hold. " +
|
|
639
642
|
"Returns a freshly-signed HS256 JWT regulators can verify offline against the canonical signing key. " +
|
|
640
643
|
"Embeds: license context, usage-count over the attestation window, the most-recent Tempo Merkle root, " +
|
|
641
644
|
"and canonical claims (iss/sub/iat/exp/jti/aud). " +
|
|
642
645
|
"**The artifact a buyer hands to legal/procurement as evidence of where its content came from.** " +
|
|
643
|
-
"
|
|
644
|
-
"It does NOT discharge a publisher's CDSM Article 4(3) reservation obligation — " +
|
|
645
|
-
"that lives on the rsl_get tool (jsonld=true variant). Never conflate. " +
|
|
646
|
+
"It attests how your licence was used; the publisher's own licensing declaration is rsl_get. " +
|
|
646
647
|
"Optional `content_id` scopes the attestation to one article; default is license-wide. " +
|
|
647
648
|
"Window cap: 365 days. Auth: an audit-scoped buyer API key (OPEDD_BUYER_TOKEN) or OPEDD_BUYER_JWT.",
|
|
648
649
|
inputSchema: {
|
|
@@ -878,6 +879,20 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
|
|
|
878
879
|
// servers on error rates, and the honest reply is "not on Opedd yet"
|
|
879
880
|
// with a way to ask for it, never "coming soon".
|
|
880
881
|
if (e.status === 404) {
|
|
882
|
+
// No article at this address, but the address is a publication on
|
|
883
|
+
// Opedd (its homepage, or a piece not imported yet): say so, and
|
|
884
|
+
// where to license it (2026-10-24). Agents that looked up a
|
|
885
|
+
// publication's homepage were told it was not on Opedd.
|
|
886
|
+
const details = e.details;
|
|
887
|
+
if (details?.on_opedd === true && details.publication) {
|
|
888
|
+
return ok({
|
|
889
|
+
on_opedd: true,
|
|
890
|
+
url,
|
|
891
|
+
publication: details.publication,
|
|
892
|
+
message: `There is no single article at this address, but ${details.publication.name ?? "this publication"} is on Opedd. ` +
|
|
893
|
+
"Its licences and prices are on its licensing page; use search_content or search_passages to find its articles.",
|
|
894
|
+
});
|
|
895
|
+
}
|
|
881
896
|
return ok({
|
|
882
897
|
on_opedd: false,
|
|
883
898
|
url,
|
|
@@ -988,10 +1003,10 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
|
|
|
988
1003
|
return err("query must be at most 1000 characters");
|
|
989
1004
|
const token = argToken || creds.buyerToken;
|
|
990
1005
|
if (!token) {
|
|
991
|
-
return err("
|
|
992
|
-
"
|
|
993
|
-
"
|
|
994
|
-
"use search_content
|
|
1006
|
+
return err("This needs an Opedd buyer key. Sign up at https://opedd.com/buyer/signup (about two minutes): " +
|
|
1007
|
+
"new accounts start with up to 3 free answers, then each answer is billed at the publication's own price. " +
|
|
1008
|
+
"Then pass the key as Authorization: Bearer <key> (hosted server https://mcp.opedd.com/mcp) or " +
|
|
1009
|
+
"OPEDD_BUYER_TOKEN (local). To see what Opedd covers first, use search_content or publisher_directory (free, no key).");
|
|
995
1010
|
}
|
|
996
1011
|
const envelope = (await opeddFetch(creds, "/search", {
|
|
997
1012
|
method: "POST",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opedd-mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
4
4
|
"description": "Content licensing for the AI era. Licensed, rights-cleared content for AI agents, with a verifiable license key for every article. The MCP alternative to unlicensed scraping for RAG and AI search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|