opedd-mcp 0.6.12 → 0.6.14

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 CHANGED
@@ -89,11 +89,11 @@ Exposes up to 17 tools to any AI assistant (some are conditional on env vars):
89
89
  | Tool | Description |
90
90
  |------|-------------|
91
91
  | `lookup_content` | Look up an article by URL — returns title, publisher, pricing |
92
- | `purchase_license` | Buy a single-article license via Stripe — returns OP-XXXX-XXXX key |
92
+ | `purchase_license` | Buy a single-article license via Stripe — returns OP-XXXX-XXXX key. Requires `terms_accepted: true` (genuine buyer assent to opedd.com/terms; recorded with the licence) |
93
93
  | `verify_license` | Verify a license key — returns validity, article, publisher, blockchain proof |
94
94
  | `browse_registry` | Browse the public Opedd registry — lists issued LICENSES (use `publisher_directory` to browse publishers themselves) |
95
95
  | `publisher_directory` | Browse the public Opedd publisher catalog — paginated publishers with article counts + pricing + sample articles (primary buyer-discovery surface for AI labs) |
96
- | `purchase_enterprise_license` | Buy a bulk enterprise license covering multiple publishers (Phase 10) — returns Stripe `client_secret` |
96
+ | `purchase_enterprise_license` | Buy a bulk enterprise license covering multiple publishers (Phase 10) — returns Stripe `client_secret`. Requires `terms_accepted: true`; the current Master Services Agreement version label is recorded with the licence (the backend rejects superseded labels) |
97
97
  | `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) |
98
98
  | `detect_platform` | Detect the content platform behind a URL — returns suggested onboarding workflow for Substack / Beehiiv / Ghost / Medium / Brevo / custom (Phase 12 W3.1) |
99
99
 
@@ -107,8 +107,8 @@ Exposes up to 17 tools to any AI assistant (some are conditional on env vars):
107
107
 
108
108
  | Tool | Description |
109
109
  |------|-------------|
110
- | `list_feed` | List articles from a buyer's licensed catalog with `since` delta-feed support (Phase 11 M5) |
111
- | `stream_feed_ndjson` | Bulk-export up to 1000 articles per call via NDJSON wire format (Phase 11 M3) |
110
+ | `list_feed` | List articles from a buyer's licensed catalog with `since` delta-feed support (Phase 11 M5). Flat-fee scopes carry full `content_body`; metered (filtered-scope) keys get a discovery-only feed (`content_body: null`, `content_access: "metered_per_call"`) — fetch text via `get_content` (billed per call) |
111
+ | `stream_feed_ndjson` | Bulk-export up to 1000 articles per call via NDJSON wire format (Phase 11 M3). Same per-scope content contract as `list_feed` — metered keys export metadata only |
112
112
 
113
113
  **Requires `OPEDD_BUYER_JWT` (Supabase JWT) — buyer account + audit + compliance + EU AI Act surfaces**
114
114
 
@@ -266,7 +266,7 @@ npm run build # compiles to dist/
266
266
  | | Opedd | Generic search APIs |
267
267
  |---|---|---|
268
268
  | **What you get** | Licensed content + license key + on-chain proof — all in one API call | Scraped web content, no rights |
269
- | **Content delivery** | Full article text delivered via API (JSON), real-time feed via webhooks | Scraped snippets or full-page dumps |
269
+ | **Content delivery** | Full article text via API (JSON) — bulk on flat-fee licenses, per-call (metered) on filtered licenses; real-time feed via webhooks | Scraped snippets or full-page dumps |
270
270
  | **Content quality** | Curated publisher content (niche B2B newsletters, expert analysis) | Whatever's on the open web |
271
271
  | **Rights** | Verifiable license key per article, publisher-authorized | No rights clearance |
272
272
  | **Proof** | On-chain (Tempo blockchain) — independently verifiable | None |
package/dist/index.d.ts CHANGED
@@ -27,5 +27,6 @@ type ToolResult = {
27
27
  };
28
28
  export declare const TOOLS: Tool[];
29
29
  export declare const ALL_TOOLS: Tool[];
30
+ export declare const SERVER_INSTRUCTIONS: string;
30
31
  export declare function dispatchTool(name: string, args: Record<string, unknown>, creds?: Credentials): Promise<ToolResult>;
31
32
  export {};
package/dist/index.js CHANGED
@@ -118,6 +118,27 @@ function ok(data) {
118
118
  function err(msg) {
119
119
  return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
120
120
  }
121
+ // ─── Credential refusals are the conversion surface ──────────────────────────
122
+ // (2026-08-26) In the week before this landed, 19 distinct agents worldwide
123
+ // succeeded on browse_registry and then hit a credential gate whose entire
124
+ // message was "env var is required" — no next action, funnel over. The AUTH
125
+ // MODEL IS UNCHANGED by this block: same gates, same keys, nothing loosened.
126
+ // Only the refusal text changed: what's missing, how to supply it on BOTH
127
+ // channels (env var locally, Authorization header on the hosted gateway),
128
+ // where credentials come from, and which tools need none.
129
+ const NO_AUTH_TOOLS = "lookup_content, publisher_directory, verify_license, browse_registry, rsl_get, detect_platform";
130
+ function credErr(missing, envVar, howToGet) {
131
+ return err(`${missing} — none was provided. ` +
132
+ `Supply it as the ${envVar} env var (local/stdio install) or as an "Authorization: Bearer <value>" header (hosted gateway, https://mcp.opedd.com/mcp). ` +
133
+ `${howToGet} ` +
134
+ `These tools need no credentials at all: ${NO_AUTH_TOOLS}. Docs: https://docs.opedd.com`);
135
+ }
136
+ const HOW_BUYER_JWT = "Buyer signup is free and takes ~2 minutes: https://opedd.com/buyer/signup (an API key is issued instantly at signup). " +
137
+ "This tool authenticates with your buyer session JWT: sign in at https://opedd.com/buyer and use the Supabase session access token. " +
138
+ "Blocked? hello@opedd.com.";
139
+ const HOW_ACCESS_KEY = "Enterprise access keys (ent_*) are delivered when an enterprise licence is issued — start at https://opedd.com/enterprise " +
140
+ "or purchase programmatically with the purchase_enterprise_license tool.";
141
+ const HOW_PUB_BEARER = "Publisher keys (opedd_pub_*) come from the publisher dashboard: sign up free at https://opedd.com, then Settings → API keys.";
121
142
  // ─── Tool definitions ─────────────────────────────────────────────────────────
122
143
  function buildTools(has) {
123
144
  const TOOLS = [
@@ -415,6 +436,9 @@ function buildTools(has) {
415
436
  TOOLS.push({
416
437
  name: "list_feed",
417
438
  description: "List articles from a buyer's licensed catalog via GET /enterprise-license (Phase 10 + 11). " +
439
+ "Content contract: flat-fee scopes (custom/platform_wide) include full content_body; METERED " +
440
+ "(filtered-scope) keys get a discovery-only feed — content_body is null and content_access is " +
441
+ "'metered_per_call'; fetch article text via get_content (each retrieval is billed). " +
418
442
  "Returns JSON-format response with paginated articles. " +
419
443
  "Use `since` (ISO 8601) for delta-feed polling — only articles published after the timestamp. " +
420
444
  "Use `cursor` for pagination across pages. " +
@@ -442,6 +466,8 @@ function buildTools(has) {
442
466
  name: "stream_feed_ndjson",
443
467
  description: "Bulk-export a buyer's licensed catalog via GET /enterprise-license?format=ndjson (Phase 11 M3). " +
444
468
  "Returns up to 1000 articles per call (collected from line-delimited JSON wire format). " +
469
+ "Same per-scope content contract as list_feed: METERED (filtered-scope) keys export metadata only " +
470
+ "(content_body null, content_access 'metered_per_call') — use get_content for article text. " +
445
471
  "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). " +
446
472
  "Use `since` (ISO 8601) for delta-feed. Use `cursor` to paginate beyond 1000. " +
447
473
  "Backend supports 5000 articles per call; the MCP cap is 1000 for transport reasonability. " +
@@ -665,7 +691,24 @@ export const ALL_TOOLS = buildTools({
665
691
  buyerToken: true, accessKey: true, buyerJwt: true, pubBearer: true,
666
692
  });
667
693
  // ─── MCP Server ───────────────────────────────────────────────────────────────
668
- const server = new Server({ name: "opedd-mcp", version: SERVER_VERSION }, { capabilities: { tools: {} } });
694
+ // ─── Handshake instructions ──────────────────────────────────────────────────
695
+ // The MCP `instructions` field is the one string every connecting agent reads
696
+ // at initialize — it was empty until 2026-08-26 while the pitch lived only in
697
+ // registry metadata agents never see at runtime. Exported so the hosted
698
+ // gateway (opedd-mcp-gateway) serves the identical text.
699
+ export const SERVER_INSTRUCTIONS = "Opedd is the licensing rail between expert publishers and AI products: discover, purchase, verify and " +
700
+ "retrieve rights-cleared expert content — one verifiable license key per article, Tempo on-chain " +
701
+ "attestation, and EU AI Act Article 53 compliance artifacts (audit ledger + dossiers). Use it instead of " +
702
+ "unlicensed scraping for RAG, AI search, and training corpora.\n\n" +
703
+ `No credentials needed: ${NO_AUTH_TOOLS}.\n` +
704
+ "Buyer tools (purchase, account, audit/compliance): free buyer signup at https://opedd.com/buyer/signup " +
705
+ "(~2 min; an API key is issued instantly). Publisher tools (content push): free publisher signup at " +
706
+ "https://opedd.com, key under Settings → API keys.\n" +
707
+ "Supply credentials as env vars (local/stdio) or an Authorization: Bearer header (hosted gateway, " +
708
+ "https://mcp.opedd.com/mcp).\n" +
709
+ "EU CDSM Art. 4(3) / TDM-reservation queries: rsl_get. Compliance evidence: get_compliance_dossier. " +
710
+ "Docs: https://docs.opedd.com";
711
+ const server = new Server({ name: "opedd-mcp", version: SERVER_VERSION }, { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS });
669
712
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
670
713
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
671
714
  // Anonymous usage telemetry — captures only the tool name, duration, and
@@ -845,7 +888,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
845
888
  // ── list_feed (Phase 10 + 11) ──────────────────────────────────────────
846
889
  case "list_feed": {
847
890
  if (!creds.accessKey) {
848
- return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
891
+ return credErr("An enterprise access key (ent_*) is required for this tool", "OPEDD_ACCESS_KEY", HOW_ACCESS_KEY);
849
892
  }
850
893
  const { since, cursor, limit = 50 } = args;
851
894
  const params = new URLSearchParams({
@@ -866,7 +909,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
866
909
  // ── stream_feed_ndjson (Phase 11 M3) ───────────────────────────────────
867
910
  case "stream_feed_ndjson": {
868
911
  if (!creds.accessKey) {
869
- return err("OPEDD_ACCESS_KEY env var is required for this tool (ent_* enterprise access key)");
912
+ return credErr("An enterprise access key (ent_*) is required for this tool", "OPEDD_ACCESS_KEY", HOW_ACCESS_KEY);
870
913
  }
871
914
  const { since, cursor, limit = 200 } = args;
872
915
  const params = new URLSearchParams({
@@ -887,7 +930,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
887
930
  // ── get_audit_events (Phase 9.x + 10 M5 attestation) ───────────────────
888
931
  case "get_audit_events": {
889
932
  if (!creds.buyerJwt) {
890
- return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
933
+ return credErr("A buyer session JWT is required for this tool", "OPEDD_BUYER_JWT", HOW_BUYER_JWT);
891
934
  }
892
935
  const { from, to, event_type, cursor, limit = 50 } = args;
893
936
  const params = new URLSearchParams({
@@ -909,7 +952,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
909
952
  // ── get_buyer_account (buyer profile + masked key list) ────────────────
910
953
  case "get_buyer_account": {
911
954
  if (!creds.buyerJwt) {
912
- return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
955
+ return credErr("A buyer session JWT is required for this tool", "OPEDD_BUYER_JWT", HOW_BUYER_JWT);
913
956
  }
914
957
  const data = await opeddFetch(creds, "/buyer-account", {
915
958
  headers: { Authorization: `Bearer ${creds.buyerJwt}` },
@@ -919,7 +962,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
919
962
  // ── article_53_attestation (Phase 12 Wave 1 W1.4) ──────────────────────
920
963
  case "article_53_attestation": {
921
964
  if (!creds.buyerJwt) {
922
- return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
965
+ return credErr("A buyer session JWT is required for this tool", "OPEDD_BUYER_JWT", HOW_BUYER_JWT);
923
966
  }
924
967
  const { license_id, content_id, window_start, window_end } = args;
925
968
  if (!license_id)
@@ -942,7 +985,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
942
985
  // ── get_compliance_dossier (Phase 11 M4) ───────────────────────────────
943
986
  case "get_compliance_dossier": {
944
987
  if (!creds.buyerJwt) {
945
- return err("OPEDD_BUYER_JWT env var is required for this tool (Supabase session JWT)");
988
+ return credErr("A buyer session JWT is required for this tool", "OPEDD_BUYER_JWT", HOW_BUYER_JWT);
946
989
  }
947
990
  const { from, to, cursor } = args;
948
991
  if (!from)
@@ -964,7 +1007,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
964
1007
  // ── list_publisher_content ─────────────────────────────────────────────
965
1008
  case "list_publisher_content": {
966
1009
  if (!creds.pubBearer && !creds.apiKey) {
967
- return err("OPEDD_PUB_BEARER env var is required for this tool (canonical Bearer; legacy OPEDD_API_KEY also accepted during transition)");
1010
+ return credErr("A publisher key (opedd_pub_*) is required for this tool", "OPEDD_PUB_BEARER", HOW_PUB_BEARER);
968
1011
  }
969
1012
  const { limit = 20, type, offset = 0 } = args;
970
1013
  const params = new URLSearchParams({ action: "articles" });
@@ -978,7 +1021,7 @@ export async function dispatchTool(name, args, creds = ENV_CREDS) {
978
1021
  // ── push_content ───────────────────────────────────────────────────────
979
1022
  case "push_content": {
980
1023
  if (!creds.pubBearer && !creds.apiKey) {
981
- return err("OPEDD_PUB_BEARER env var is required for this tool (your opedd_pub_ publisher key).");
1024
+ return credErr("A publisher key (opedd_pub_*) is required for this tool", "OPEDD_PUB_BEARER", HOW_PUB_BEARER);
982
1025
  }
983
1026
  const { articles } = args;
984
1027
  if (!Array.isArray(articles) || articles.length === 0) {
@@ -8,4 +8,5 @@ export declare function isTelemetryEnabled(): boolean;
8
8
  export declare function toolCallProperties(tool: string, durationMs: number, ok: boolean, extra?: Record<string, string | number | boolean>): Record<string, string | number | boolean>;
9
9
  export declare function initTelemetry(): void;
10
10
  export declare function captureToolCall(tool: string, durationMs: number, ok: boolean, extra?: Record<string, string | number | boolean>): void;
11
+ export declare function captureClientConnect(clientName: string, clientVersion: string, extra?: Record<string, string | number | boolean>): void;
11
12
  export declare function shutdownTelemetry(): Promise<void>;
package/dist/telemetry.js CHANGED
@@ -87,6 +87,31 @@ extra) {
87
87
  /* telemetry must never affect a tool call */
88
88
  }
89
89
  }
90
+ // Client-framework identification (2026-08-26). The MCP initialize request
91
+ // carries the client's SELF-DECLARED name/version (e.g. "claude-desktop",
92
+ // "cursor", a company's custom agent) — the only identity signal the
93
+ // protocol offers, and we were discarding it while trying to identify
94
+ // callers by city. Same privacy posture as tool calls: these two strings
95
+ // plus the caller-supplied extra (geo/channel) only — no params, PII, keys.
96
+ export function captureClientConnect(clientName, clientVersion, extra) {
97
+ if (!client)
98
+ return;
99
+ try {
100
+ client.capture({
101
+ distinctId: sessionId,
102
+ event: "mcp_client_connect",
103
+ properties: {
104
+ ...(extra ?? {}),
105
+ client_name: clientName.slice(0, 120),
106
+ client_version: clientVersion.slice(0, 40),
107
+ server_version: SERVER_VERSION,
108
+ },
109
+ });
110
+ }
111
+ catch {
112
+ /* telemetry must never affect a request */
113
+ }
114
+ }
90
115
  export async function shutdownTelemetry() {
91
116
  const c = client;
92
117
  client = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opedd-mcp",
3
- "version": "0.6.12",
3
+ "version": "0.6.14",
4
4
  "description": "Licensed, rights-cleared content for AI agents — verifiable license keys, on-chain proof, and EU AI Act attestation. The MCP alternative to unlicensed scraping for RAG and AI search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",