opedd-mcp 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/index.js +16 -1
- package/dist/telemetry.js +89 -0
- package/package.json +7 -3
- package/.github/workflows/ci.yml +0 -70
- package/src/index.ts +0 -1034
- package/tests/dispatcher.env-gated.test.ts +0 -283
- package/tests/dispatcher.test.ts +0 -419
- package/tsconfig.json +0 -15
package/README.md
CHANGED
|
@@ -87,9 +87,28 @@ Set environment variables to pre-configure the server:
|
|
|
87
87
|
| `OPEDD_PUB_BEARER` | Optional | Canonical Publisher API Bearer key (`opedd_pub_<env>_<32-hex>`; issued via `POST /publishers-api-keys action=create_api_key`) — enables `list_publisher_content`. **v0.4.0 canonical.** |
|
|
88
88
|
| `OPEDD_API_KEY` | Deprecated | Legacy Publisher API key (`op_...`) — fallback during the transition window; will stop working when opedd-backend Phase C deploys. Migrate to `OPEDD_PUB_BEARER`. |
|
|
89
89
|
| `OPEDD_API_URL` | Optional | Override the API base URL (default: Opedd production) |
|
|
90
|
+
| `OPEDD_MCP_TELEMETRY` | Optional | Set to `0` to disable anonymous usage telemetry (see below) |
|
|
90
91
|
|
|
91
92
|
> **Getting a Stripe payment method ID**: Save a card in your Stripe account and retrieve the `pm_...` ID via the [Stripe API](https://stripe.com/docs/api/payment_methods).
|
|
92
93
|
|
|
94
|
+
## Anonymous usage telemetry
|
|
95
|
+
|
|
96
|
+
To understand how AI assistants use this server, `opedd-mcp` sends anonymous
|
|
97
|
+
telemetry for each tool call: **only the tool name, its duration, whether it
|
|
98
|
+
succeeded, and the server version** — plus a random per-process id. It never
|
|
99
|
+
sends tool parameters, responses, your email, API keys, tokens, content, or
|
|
100
|
+
your IP. Data goes to Opedd's PostHog (EU region).
|
|
101
|
+
|
|
102
|
+
**To opt out**, set either environment variable before starting the server:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
OPEDD_MCP_TELEMETRY=0
|
|
106
|
+
# or the cross-tool standard:
|
|
107
|
+
DO_NOT_TRACK=1
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
When opted out, no telemetry client is created and nothing is sent.
|
|
111
|
+
|
|
93
112
|
## Claude Desktop setup
|
|
94
113
|
|
|
95
114
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { initTelemetry, captureToolCall, shutdownTelemetry } from "./telemetry.js";
|
|
6
7
|
// ─── Configuration ────────────────────────────────────────────────────────────
|
|
7
8
|
const API_BASE = process.env.OPEDD_API_URL ??
|
|
8
9
|
"https://api.opedd.com";
|
|
@@ -568,7 +569,15 @@ if (PUB_BEARER || API_KEY) {
|
|
|
568
569
|
// ─── MCP Server ───────────────────────────────────────────────────────────────
|
|
569
570
|
const server = new Server({ name: "opedd-mcp", version: "0.3.0" }, { capabilities: { tools: {} } });
|
|
570
571
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
571
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) =>
|
|
572
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
573
|
+
// Anonymous usage telemetry — captures only the tool name, duration, and
|
|
574
|
+
// success flag (see telemetry.ts). Wrapping the runtime handler (not
|
|
575
|
+
// dispatchTool) keeps unit tests, which call dispatchTool directly, silent.
|
|
576
|
+
const start = Date.now();
|
|
577
|
+
const result = await dispatchTool(request.params.name, (request.params.arguments ?? {}));
|
|
578
|
+
captureToolCall(request.params.name, Date.now() - start, result.isError !== true);
|
|
579
|
+
return result;
|
|
580
|
+
});
|
|
572
581
|
// ─── Tool dispatcher (exported for unit tests) ────────────────────────────────
|
|
573
582
|
//
|
|
574
583
|
// Extracted from the inline handler 2026-05-24 EEST as part of the opedd-mcp
|
|
@@ -856,6 +865,12 @@ export async function dispatchTool(name, args) {
|
|
|
856
865
|
if (typeof process !== "undefined" &&
|
|
857
866
|
process.argv?.[1] &&
|
|
858
867
|
import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
868
|
+
initTelemetry();
|
|
869
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
870
|
+
process.on(sig, () => {
|
|
871
|
+
void shutdownTelemetry().finally(() => process.exit(0));
|
|
872
|
+
});
|
|
873
|
+
}
|
|
859
874
|
const transport = new StdioServerTransport();
|
|
860
875
|
await server.connect(transport);
|
|
861
876
|
console.error("[opedd-mcp] Server running on stdio");
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Anonymous usage telemetry for the Opedd MCP server.
|
|
2
|
+
//
|
|
3
|
+
// Captures ONLY which tool an agent called, how long it took, and whether it
|
|
4
|
+
// succeeded — NEVER parameters, responses, PII, keys, or content. This module
|
|
5
|
+
// is only ever handed the tool name + duration + ok flag, so there is nothing
|
|
6
|
+
// sensitive to leak by construction. It reports to Opedd's PostHog (EU region)
|
|
7
|
+
// so we can understand how AI agents use the MCP distribution channel.
|
|
8
|
+
//
|
|
9
|
+
// Opt out: set OPEDD_MCP_TELEMETRY=0 (or "false"/"off"), or the widely-used
|
|
10
|
+
// DO_NOT_TRACK=1. When opted out no client is created and nothing is sent.
|
|
11
|
+
//
|
|
12
|
+
// stdio-safe: PostHog is reached over HTTPS only; this module NEVER writes to
|
|
13
|
+
// stdout (that would corrupt the MCP JSON-RPC stream). All errors are swallowed
|
|
14
|
+
// so telemetry can never affect a tool call or the server.
|
|
15
|
+
import { PostHog } from "posthog-node";
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
// Public, write-only PostHog project key (EU cloud, project 218295). Safe to
|
|
18
|
+
// embed — it can only send events, not read data.
|
|
19
|
+
const POSTHOG_KEY = "phc_zrKQzZLUZikynMWJWMHUiQBgRsJa2fLzDFMdh6HxGU6c";
|
|
20
|
+
const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
21
|
+
const SERVER_VERSION = "0.5.0";
|
|
22
|
+
/** Whether telemetry is allowed (respects OPEDD_MCP_TELEMETRY + DO_NOT_TRACK). Pure — exported for tests. */
|
|
23
|
+
export function isTelemetryEnabled() {
|
|
24
|
+
const t = process.env.OPEDD_MCP_TELEMETRY?.toLowerCase();
|
|
25
|
+
if (t === "0" || t === "false" || t === "off" || t === "no")
|
|
26
|
+
return false;
|
|
27
|
+
const dnt = process.env.DO_NOT_TRACK?.toLowerCase();
|
|
28
|
+
if (dnt === "1" || dnt === "true")
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The ONLY properties ever sent for a tool call — no parameters, responses,
|
|
34
|
+
* PII, keys, or content by construction. Pure; exported so a test can assert
|
|
35
|
+
* the privacy invariant directly.
|
|
36
|
+
*/
|
|
37
|
+
export function toolCallProperties(tool, durationMs, ok) {
|
|
38
|
+
return {
|
|
39
|
+
tool,
|
|
40
|
+
duration_ms: Math.round(durationMs),
|
|
41
|
+
ok,
|
|
42
|
+
server_version: SERVER_VERSION,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let client = null;
|
|
46
|
+
// Anonymous per-process session id — not tied to any user, buyer, key, or IP.
|
|
47
|
+
const sessionId = randomUUID();
|
|
48
|
+
export function initTelemetry() {
|
|
49
|
+
if (!isTelemetryEnabled())
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
client = new PostHog(POSTHOG_KEY, {
|
|
53
|
+
host: POSTHOG_HOST,
|
|
54
|
+
flushAt: 1, // low-volume telemetry; deliver each event promptly
|
|
55
|
+
flushInterval: 30000,
|
|
56
|
+
});
|
|
57
|
+
// Swallow any transport error so it can never surface or crash the server.
|
|
58
|
+
client.on?.("error", () => { });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
client = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function captureToolCall(tool, durationMs, ok) {
|
|
65
|
+
if (!client)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
client.capture({
|
|
69
|
+
distinctId: sessionId,
|
|
70
|
+
event: "mcp_tool_call",
|
|
71
|
+
properties: toolCallProperties(tool, durationMs, ok),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* telemetry must never affect a tool call */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function shutdownTelemetry() {
|
|
79
|
+
const c = client;
|
|
80
|
+
client = null;
|
|
81
|
+
if (!c)
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
await c.shutdown();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* ignore */
|
|
88
|
+
}
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opedd-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server for Opedd — discover, purchase, verify, and audit content licenses from AI assistants. Covers Phase 11 buyer-side surfaces + Phase 12 Wave 1 + 3 regulatory surfaces (CDSM Article 4(3) RSL + EU AI Act Article 53 attestation + platform onboarding helper).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"author": "Opedd",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
|
+
"posthog-node": "^4.0.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/node": "^22.19.19",
|
|
@@ -36,5 +37,8 @@
|
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=20"
|
|
39
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
]
|
|
40
44
|
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
strategy:
|
|
13
|
-
fail-fast: false
|
|
14
|
-
matrix:
|
|
15
|
-
# Node 18 dropped 2026-05-24 — vitest 4.x (rolldown dep) requires
|
|
16
|
-
# `node:util.styleText` which is only available on Node 21.7+.
|
|
17
|
-
# Node 18 reaches active LTS end April 2025; engines.node in
|
|
18
|
-
# package.json bumped to ">=20" in the same commit. Re-add Node 18
|
|
19
|
-
# only if vitest downgrade ratified (which is the alternative;
|
|
20
|
-
# founder picked engines bump).
|
|
21
|
-
node-version: ["20", "22"]
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
|
|
25
|
-
- name: Set up Node.js ${{ matrix.node-version }}
|
|
26
|
-
uses: actions/setup-node@v4
|
|
27
|
-
with:
|
|
28
|
-
node-version: ${{ matrix.node-version }}
|
|
29
|
-
cache: npm
|
|
30
|
-
|
|
31
|
-
- name: Install dependencies
|
|
32
|
-
# Using `npm install` rather than `npm ci` because the lockfile
|
|
33
|
-
# generated on macOS doesn't include Linux-conditional optional
|
|
34
|
-
# native deps (e.g. @emnapi/* used by vitest 4.x via napi-rs).
|
|
35
|
-
# `npm install` resolves transitively at install time per-platform;
|
|
36
|
-
# `npm ci` strictly validates lockfile-against-package.json and
|
|
37
|
-
# rejects when platform-specific transitive deps are missing.
|
|
38
|
-
# Trade-off: install is slower + slightly looser; acceptable for
|
|
39
|
-
# this repo's CI scale.
|
|
40
|
-
run: npm install
|
|
41
|
-
|
|
42
|
-
- name: TypeScript compile
|
|
43
|
-
run: npm run build
|
|
44
|
-
|
|
45
|
-
- name: Unit tests (vitest)
|
|
46
|
-
run: npm test
|
|
47
|
-
|
|
48
|
-
- name: Verify dist/index.js produced
|
|
49
|
-
run: |
|
|
50
|
-
test -f dist/index.js || (echo "::error::dist/index.js not produced by tsc"; exit 1)
|
|
51
|
-
echo "dist/index.js bytes: $(wc -c < dist/index.js)"
|
|
52
|
-
|
|
53
|
-
- name: Smoke-boot the MCP server
|
|
54
|
-
run: |
|
|
55
|
-
# Send initialize + tools/list, confirm v0.2.0 boots and lists tools
|
|
56
|
-
printf '%s\n%s\n' \
|
|
57
|
-
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \
|
|
58
|
-
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
|
59
|
-
| timeout 10 node dist/index.js > /tmp/mcp-output.txt 2> /tmp/mcp-stderr.txt || true
|
|
60
|
-
if ! grep -q '"serverInfo":{"name":"opedd-mcp"' /tmp/mcp-output.txt; then
|
|
61
|
-
echo "::error::MCP server did not respond with expected serverInfo"
|
|
62
|
-
cat /tmp/mcp-output.txt
|
|
63
|
-
cat /tmp/mcp-stderr.txt
|
|
64
|
-
exit 1
|
|
65
|
-
fi
|
|
66
|
-
if ! grep -q '"name":"lookup_content"' /tmp/mcp-output.txt; then
|
|
67
|
-
echo "::error::MCP server did not list lookup_content tool"
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
echo "MCP smoke boot OK"
|