opedd-mcp 0.12.7 → 0.12.8
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 +4 -3
- package/dist/index.js +53 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Then add it to your MCP host (see [Claude Desktop / Cursor / Windsurf setup](#cl
|
|
|
35
35
|
|
|
36
36
|
## Hosted endpoint — no install (mcp.opedd.com)
|
|
37
37
|
|
|
38
|
-
Prefer zero-install? The same
|
|
38
|
+
Prefer zero-install? The same 21 tools are served hosted at **`https://mcp.opedd.com/mcp`** (Streamable HTTP). Auth is an `Authorization: Bearer` header with any Opedd key — public discovery tools need no auth at all.
|
|
39
39
|
|
|
40
40
|
**Claude Messages API** (production agents):
|
|
41
41
|
|
|
@@ -82,7 +82,7 @@ Key routing: `opedd_sk_*` (legacy `opedd_pub_*`) → publisher tools · `opedd_b
|
|
|
82
82
|
|
|
83
83
|
## What it does
|
|
84
84
|
|
|
85
|
-
Exposes up to
|
|
85
|
+
Exposes up to 21 tools to any AI assistant (some are conditional on env vars):
|
|
86
86
|
|
|
87
87
|
Every tool carries a `title` and MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so an assistant can tell which tools only read and which spend money or write (v0.12.0).
|
|
88
88
|
|
|
@@ -92,7 +92,8 @@ Every tool carries a `title` and MCP annotations (`readOnlyHint`, `destructiveHi
|
|
|
92
92
|
|------|-------------|
|
|
93
93
|
| `search_content` | **Free discovery.** Full-text search across everything licensable (v0.8.0) — ranked matches with publisher, prices, word count and a description snippet; quotes for phrases, `-word` to exclude. Returns **no article text** — use `search_passages` to actually read |
|
|
94
94
|
| `search_passages` | **Paid per answer, or included in a monthly subscription.** Ask a question, get licensed passages that answer it, with citations. Per-question licences: charged once per publisher per question at that publisher's own price, capped at 300 words or 25% of the article, counted cumulatively. Monthly AI answers subscriptions (v0.12.0): included, not capped, from the subscription's start. Narrow with `publishers`, `published_after`, `published_before`, `max_results` (1–25). Needs a buyer key with the `search` scope. Re-asking within 24h is free |
|
|
95
|
-
| `lookup_content` | Look up an article by URL — returns title, publisher, pricing |
|
|
95
|
+
| `lookup_content` | Look up an article by URL — returns title, publisher, pricing, or that it is not on Opedd yet |
|
|
96
|
+
| `request_publication` | Ask to be told when a publication Opedd does not carry yet becomes available (url + email; no key) |
|
|
96
97
|
| `purchase_license` | Buy a Human republication licence for one article via Stripe — returns OP-XXXX-XXXX key. Requires `terms_accepted: true` (genuine buyer assent to opedd.com/terms; recorded with the licence) |
|
|
97
98
|
| `verify_license` | Verify a license key — returns validity, article, publisher, public-record proof |
|
|
98
99
|
| `browse_registry` | Browse the public Opedd registry — lists issued LICENSES (use `publisher_directory` to browse publishers themselves) |
|
package/dist/index.js
CHANGED
|
@@ -68,7 +68,9 @@ async function opeddFetch(creds, path, options = {}) {
|
|
|
68
68
|
catch {
|
|
69
69
|
// non-JSON error body — keep the HTTP-status default
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
// 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
|
+
throw Object.assign(new Error(msg), { status: res.status });
|
|
72
74
|
}
|
|
73
75
|
return await res.json();
|
|
74
76
|
}
|
|
@@ -134,7 +136,7 @@ function err(msg) {
|
|
|
134
136
|
// Only the refusal text changed: what's missing, how to supply it on BOTH
|
|
135
137
|
// channels (env var locally, Authorization header on the hosted gateway),
|
|
136
138
|
// where credentials come from, and which tools need none.
|
|
137
|
-
const NO_AUTH_TOOLS = "lookup_content, publisher_directory, verify_license, browse_registry, rsl_get, detect_platform";
|
|
139
|
+
const NO_AUTH_TOOLS = "lookup_content, publisher_directory, verify_license, browse_registry, rsl_get, detect_platform, request_publication";
|
|
138
140
|
function credErr(missing, envVar, howToGet) {
|
|
139
141
|
return err(`${missing} — none was provided. ` +
|
|
140
142
|
`Supply it as the ${envVar} env var (local/stdio install) or as an "Authorization: Bearer <value>" header (hosted gateway, https://mcp.opedd.com/mcp). ` +
|
|
@@ -182,7 +184,7 @@ function buildTools(has) {
|
|
|
182
184
|
const TOOLS = [
|
|
183
185
|
{
|
|
184
186
|
name: "lookup_content",
|
|
185
|
-
description: "Looks up an article on Opedd by its web address. Returns the title, publication, available licence types and prices, or that
|
|
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).",
|
|
186
188
|
inputSchema: {
|
|
187
189
|
type: "object",
|
|
188
190
|
required: ["url"],
|
|
@@ -194,6 +196,21 @@ function buildTools(has) {
|
|
|
194
196
|
},
|
|
195
197
|
},
|
|
196
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: "request_publication",
|
|
201
|
+
description: "Asks Opedd to tell you when a publication it does not carry yet becomes available to license. Use it after lookup_content says an article is not on Opedd. Records the request against the email given; nothing is bought. If the publication is already on Opedd, returns its licensing page instead. Free; no key needed.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
required: ["url", "email"],
|
|
205
|
+
properties: {
|
|
206
|
+
url: { type: "string", description: "The web address of the publication or of one of its articles." },
|
|
207
|
+
email: { type: "string", description: "Where Opedd should write when the publication is available (the user's email, with their consent)." },
|
|
208
|
+
name: { type: "string", description: "Optional: the requester's name." },
|
|
209
|
+
organization: { type: "string", description: "Optional: the requester's company or fund." },
|
|
210
|
+
note: { type: "string", description: "Optional: what the content is needed for (max 1000 characters)." },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
197
214
|
{
|
|
198
215
|
name: "search_content",
|
|
199
216
|
description: "Full-text search of the articles licensable on Opedd. Returns matching articles with the publication, prices, word count and a short description; never article text. Supports quoted phrases and -word to exclude a word. Free; no key needed.",
|
|
@@ -760,6 +777,8 @@ function buildTools(has) {
|
|
|
760
777
|
const read = (title) => ({ title, readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false });
|
|
761
778
|
export const TOOL_ANNOTATIONS = {
|
|
762
779
|
lookup_content: read("Look up an article's licence and price"),
|
|
780
|
+
// Writes one request row (a repeat for the same email writes nothing): not read-only, idempotent.
|
|
781
|
+
request_publication: { title: "Ask to be told when a publication is available", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
763
782
|
search_content: read("Find who covers a topic (free, no text)"),
|
|
764
783
|
purchase_license: { title: "Buy an article licence", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
765
784
|
verify_license: read("Verify a licence key"),
|
|
@@ -850,8 +869,37 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
|
|
|
850
869
|
const { url } = args;
|
|
851
870
|
if (!url)
|
|
852
871
|
return err("url is required");
|
|
853
|
-
|
|
854
|
-
|
|
872
|
+
try {
|
|
873
|
+
const data = await opeddFetch(creds, `/lookup-article?url=${encodeURIComponent(url)}`);
|
|
874
|
+
return ok(data);
|
|
875
|
+
}
|
|
876
|
+
catch (e) {
|
|
877
|
+
// A miss is an answer, not a failure (2026-10-22). Directories score
|
|
878
|
+
// servers on error rates, and the honest reply is "not on Opedd yet"
|
|
879
|
+
// with a way to ask for it, never "coming soon".
|
|
880
|
+
if (e.status === 404) {
|
|
881
|
+
return ok({
|
|
882
|
+
on_opedd: false,
|
|
883
|
+
url,
|
|
884
|
+
message: "This article is not on Opedd yet. If you want to license this publication, call request_publication " +
|
|
885
|
+
"with this url and an email, and Opedd will write when it becomes available.",
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
throw e;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
// ── request_publication ────────────────────────────────────────────────
|
|
892
|
+
case "request_publication": {
|
|
893
|
+
const { url, email, name, organization, note } = args;
|
|
894
|
+
if (!url)
|
|
895
|
+
return err("url is required");
|
|
896
|
+
if (!email)
|
|
897
|
+
return err("email is required: Opedd writes to it when the publication is available");
|
|
898
|
+
const envelope = (await opeddFetch(creds, "/publication-request", {
|
|
899
|
+
method: "POST",
|
|
900
|
+
body: JSON.stringify({ url, email, name, organization, note }),
|
|
901
|
+
}));
|
|
902
|
+
return ok(envelope.data ?? envelope);
|
|
855
903
|
}
|
|
856
904
|
// ── search_content ─────────────────────────────────────────────────────
|
|
857
905
|
case "search_content": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opedd-mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
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",
|