uniqueness-mcp 0.5.0 → 0.5.2

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.
Files changed (3) hide show
  1. package/README.md +67 -0
  2. package/index.mjs +90 -53
  3. package/package.json +4 -3
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # uniqueness-mcp
2
+
3
+ [MCP](https://modelcontextprotocol.io) server for [Uniqueness Engine](https://uniquenessengine.com) — conservative, human-in-the-loop access to fact-checked **personal context**.
4
+
5
+ Commercial contract: `hard-paid-v1`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npx -y uniqueness-mcp
11
+ ```
12
+
13
+ Requires Node **18+**.
14
+
15
+ ## Cursor / Claude Desktop config
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "uniqueness": {
21
+ "command": "npx",
22
+ "args": ["-y", "uniqueness-mcp"],
23
+ "env": {
24
+ "UNIQUENESS_API_KEY": "uq_live_..."
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ Without `UNIQUENESS_API_KEY`, use `begin_programmatic_signup` and `complete_programmatic_signup` after human email approval.
32
+
33
+ ## Tools
34
+
35
+ | Tool | Purpose |
36
+ |------|---------|
37
+ | `get_personal_context` | Submit one enrichment (`detail=compact` default). Alias: `get_connection_brief` |
38
+ | `resume_personal_context` | After human Checkout, retry with 402 idempotency key. Alias: `resume_connection_brief` |
39
+ | `get_billing_status` | Read spendable balance + auto-recharge state (read-only) |
40
+ | `begin_programmatic_signup` / `complete_programmatic_signup` | Named-device verification flow |
41
+ | `preflight_personal_contexts` | Batch estimate before spend. Alias: `preflight_connection_briefs` |
42
+ | `submit_feedback` | Rate a delivered brief |
43
+
44
+ ## Safety (by design)
45
+
46
+ - **No purchase tools** — MCP never buys a pack, saves a payment method, enables auto-recharge, or subscribes.
47
+ - **`402 payment_required`** returns structured `human_action_required`; present `recommended_offer.checkout_url` or `billing_url` only after explicit human consent.
48
+ - **No unpaid target storage** — after Checkout, the host must call `resume_personal_context` with the original target fields, `payment_required.retry.idempotency_key`, and the previously observed available balance.
49
+
50
+ Jordan Ellis is a **fictional static fixture** and the only free experience (`GET /api/examples/kitchen-sink`). Do not smoke-test real people without explicit human approval and paid credit.
51
+
52
+ ## Environment
53
+
54
+ ```bash
55
+ export UNIQUENESS_API_KEY=uq_live_...
56
+ export UNIQUENESS_API_URL=https://uniquenessengine.com # optional override
57
+ ```
58
+
59
+ ## Docs
60
+
61
+ - [MCP quickstart](https://uniquenessengine.com/documentation/mcp/quickstart)
62
+ - [Agent setup](https://uniquenessengine.com/agents)
63
+ - [llms.txt](https://uniquenessengine.com/llms.txt)
64
+ - [OpenAPI](https://uniquenessengine.com/openapi.json)
65
+ - [CLI](https://www.npmjs.com/package/uniqueness)
66
+
67
+ Support: support@uniquenessengine.com
package/index.mjs CHANGED
@@ -11,7 +11,7 @@ const BASE = process.env.UNIQUENESS_API_URL || "https://uniquenessengine.com";
11
11
  let KEY = process.env.UNIQUENESS_API_KEY || "";
12
12
  const signupContinuations = new Map();
13
13
 
14
- const server = new McpServer({ name: "uniqueness", version: "0.5.0" });
14
+ const server = new McpServer({ name: "uniqueness", version: "0.5.2" });
15
15
 
16
16
  const json = (value, isError = false) => ({
17
17
  ...(isError ? { isError: true } : {}),
@@ -63,9 +63,10 @@ function paymentAction(contract) {
63
63
  return json({
64
64
  status: "human_action_required",
65
65
  action: "checkout",
66
- message: "Ask a human before surfacing a purchase action. After explicit consent, use payment_required.recommended_offer.checkout_url for the offered pack, or payment_required.billing_url if the human wants to choose another pack. This MCP server has not bought anything, saved a payment method, enabled auto-recharge, or started a subscription. It stores no unpaid target. After payment, the host may call resume_connection_brief with the original target, the returned retry.idempotency_key, and the previously observed available balance; the service verifies both the new balance and retry identity.",
66
+ message: "Ask a human before surfacing a purchase action. After explicit consent, use payment_required.recommended_offer.checkout_url for the offered pack, or payment_required.billing_url if the human wants to choose another pack. This MCP server has not bought anything, saved a payment method, enabled auto-recharge, or started a subscription. It stores no unpaid target. After payment, the host may call resume_personal_context (alias: resume_connection_brief) with the original target, the returned retry.idempotency_key, and the previously observed available balance; the service verifies both the new balance and retry identity.",
67
67
  payment_required: contract,
68
- resume_tool: "resume_connection_brief",
68
+ resume_tool: "resume_personal_context",
69
+ resume_tool_alias: "resume_connection_brief",
69
70
  resume_requires: [
70
71
  "original target fields",
71
72
  "payment_required.retry.idempotency_key",
@@ -104,7 +105,8 @@ async function submitBrief(args, idempotencyKey) {
104
105
  if (submit.status === 409 && (submit.body.error === "idempotency_key_expired" || submit.body.error === "idempotency_key_mismatch"))
105
106
  return { kind: "retry_rejected", error: submit.body.error };
106
107
  if (submit.status >= 400) return { kind: "error", error: submit.body.error || `http_${submit.status}`, message: submit.body.message };
107
- if (submit.body.brief) return { kind: "brief", brief: submit.body.brief };
108
+ if (submit.body.personal_context || submit.body.brief)
109
+ return { kind: "brief", brief: submit.body.personal_context || submit.body.brief };
108
110
  if (!submit.body.job_id) return { kind: "error", error: "invalid_enrich_response" };
109
111
  return pollBrief(submit.body.job_id);
110
112
  }
@@ -115,7 +117,12 @@ function briefInputSchema() {
115
117
  name: z.string().optional().describe("Full name (use with company)"),
116
118
  company: z.string().optional().describe("Company name (use with name)"),
117
119
  email: z.string().optional().describe("Work email (optional disambiguator)"),
118
- detail: z.enum(["compact", "full"]).optional().describe("Response shape"),
120
+ detail: z
121
+ .enum(["compact", "full"])
122
+ .optional()
123
+ .describe(
124
+ 'Response shape. Defaults to "compact" (recommended for agents). Pass "full" for provenance sub-scores (debugging/eval/trust UI).',
125
+ ),
119
126
  };
120
127
  }
121
128
 
@@ -123,6 +130,9 @@ function cleanBriefArgs(args) {
123
130
  const body = {};
124
131
  for (const key of ["linkedin_url", "name", "company", "email", "detail"])
125
132
  if (args[key]) body[key] = args[key];
133
+ // Agents should not inherit the HTTP production default (full). Compact is the
134
+ // recommended agent path; omit only means "use MCP default", not "use server default".
135
+ if (!body.detail) body.detail = "compact";
126
136
  return body;
127
137
  }
128
138
 
@@ -254,11 +264,8 @@ server.tool(
254
264
  },
255
265
  );
256
266
 
257
- server.tool(
258
- "get_connection_brief",
259
- "Submit one full-quality written enrichment. A real target requires spendable credit. On canonical 402 this returns structured human_action_required; it never buys, saves a method, enables auto-recharge, or subscribes. Refusal, wrong-person, no-result, and system-failure outcomes release the exact credit. Support: support@uniquenessengine.com.",
260
- briefInputSchema(),
261
- async (args) => {
267
+ function registerGetPersonalContext(toolName, description) {
268
+ server.tool(toolName, description, briefInputSchema(), async (args) => {
262
269
  if (!KEY) return authenticationRequired();
263
270
  if (!args.linkedin_url && !args.name) return json({ error: "invalid_input", message: "Provide linkedin_url, or name (+ company)." }, true);
264
271
  const body = cleanBriefArgs(args);
@@ -267,44 +274,77 @@ server.tool(
267
274
  return paymentAction(result.contract);
268
275
  }
269
276
  if (result.kind === "unauthorized") return json({ error: "unauthorized" }, true);
270
- if (result.kind === "pending") return json({ status: "pending", job_id: result.job_id, message: "The job is still processing; call get_connection_brief again with the same input to poll a fresh server request." });
277
+ if (result.kind === "pending") return json({ status: "pending", job_id: result.job_id, message: "The job is still processing; call get_personal_context again with the same input to poll a fresh server request." });
271
278
  if (result.kind === "retry_rejected") return json({ error: result.error, message: "Submit a new request with a new idempotency key." }, true);
272
279
  if (result.kind === "error") return json({ error: result.error, message: result.message }, true);
273
280
  return json(result.brief);
274
- },
275
- );
281
+ });
282
+ }
276
283
 
277
- server.tool(
278
- "resume_connection_brief",
284
+ function registerResumePersonalContext(toolName, description) {
285
+ server.tool(
286
+ toolName,
287
+ description,
288
+ {
289
+ ...briefInputSchema(),
290
+ idempotency_key: z.string().min(1).max(200).describe("payment_required.retry.idempotency_key from the original 402"),
291
+ previous_available: z.number().int().nonnegative().describe("payment_required.credits.available from the original 402"),
292
+ },
293
+ async (args) => {
294
+ if (!KEY) return authenticationRequired();
295
+ const body = cleanBriefArgs(args);
296
+ if (!body.linkedin_url && !body.name)
297
+ return json({ error: "invalid_input", message: "Resupply the original linkedin_url, or name (+ company). No unpaid target is stored by this MCP server." }, true);
298
+ const account = await api("/api/account", { method: "GET" });
299
+ const balance = spendableBalance(account.body);
300
+ if (account.status !== 200 || balance === null || balance <= args.previous_available) {
301
+ return json({
302
+ status: "human_action_required",
303
+ action: "verify_checkout",
304
+ message: "The service does not yet show more spendable credit than the original 402, so the caller-supplied target was not retried. Complete/check Checkout and call this tool again; do not claim payment as proof.",
305
+ observed_balance: balance,
306
+ });
307
+ }
308
+ const result = await submitBrief(body, args.idempotency_key);
309
+ if (result.kind === "payment_required") return paymentAction(result.contract);
310
+ if (result.kind === "pending") return json({ status: "pending", job_id: result.job_id });
311
+ if (result.kind === "retry_rejected")
312
+ return json({ error: result.error, message: "Retry metadata is no longer valid. Submit a new request with a new idempotency key." }, true);
313
+ if (result.kind === "error" || result.kind === "unauthorized") return json({ error: result.error || "unauthorized", message: result.message }, true);
314
+ return json(result.brief);
315
+ },
316
+ );
317
+ }
318
+
319
+ function registerPreflightPersonalContexts(toolName, description) {
320
+ server.tool(
321
+ toolName,
322
+ description,
323
+ { profiles: z.array(z.object({ linkedin_url: z.string().optional(), name: z.string().optional(), company: z.string().optional(), email: z.string().optional() })).min(1).max(100) },
324
+ async ({ profiles }) => {
325
+ if (!KEY) return authenticationRequired();
326
+ const response = await api("/api/enrich/batch", { body: { profiles, estimate_only: true } });
327
+ if (response.status !== 200) return json({ error: response.body.error || `http_${response.status}`, message: response.body.message }, true);
328
+ return json({ status: "preflight", ...response.body, all_or_nothing: true });
329
+ },
330
+ );
331
+ }
332
+
333
+ registerGetPersonalContext(
334
+ "get_personal_context",
335
+ "Submit one full-quality written enrichment (personal context). A real target requires spendable credit. Defaults to detail=compact (recommended for agents); pass detail=full for provenance sub-scores. On canonical 402 this returns structured human_action_required; it never buys, saves a method, enables auto-recharge, or subscribes. Refusal, wrong-person, no-result, and system-failure outcomes release the exact credit. Support: support@uniquenessengine.com.",
336
+ );
337
+ registerGetPersonalContext(
338
+ "get_connection_brief",
339
+ "Deprecated alias for get_personal_context. Prefer get_personal_context.",
340
+ );
341
+ registerResumePersonalContext(
342
+ "resume_personal_context",
279
343
  "After a human has completed Checkout, verify spendable credit with the service and only then resubmit caller-supplied original target fields with the 402 retry idempotency key. The MCP server stores no unpaid target and never trusts a payment assertion alone.",
280
- {
281
- ...briefInputSchema(),
282
- idempotency_key: z.string().min(1).max(200).describe("payment_required.retry.idempotency_key from the original 402"),
283
- previous_available: z.number().int().nonnegative().describe("payment_required.credits.available from the original 402"),
284
- },
285
- async (args) => {
286
- if (!KEY) return authenticationRequired();
287
- const body = cleanBriefArgs(args);
288
- if (!body.linkedin_url && !body.name)
289
- return json({ error: "invalid_input", message: "Resupply the original linkedin_url, or name (+ company). No unpaid target is stored by this MCP server." }, true);
290
- const account = await api("/api/account", { method: "GET" });
291
- const balance = spendableBalance(account.body);
292
- if (account.status !== 200 || balance === null || balance <= args.previous_available) {
293
- return json({
294
- status: "human_action_required",
295
- action: "verify_checkout",
296
- message: "The service does not yet show more spendable credit than the original 402, so the caller-supplied target was not retried. Complete/check Checkout and call this tool again; do not claim payment as proof.",
297
- observed_balance: balance,
298
- });
299
- }
300
- const result = await submitBrief(body, args.idempotency_key);
301
- if (result.kind === "payment_required") return paymentAction(result.contract);
302
- if (result.kind === "pending") return json({ status: "pending", job_id: result.job_id });
303
- if (result.kind === "retry_rejected")
304
- return json({ error: result.error, message: "Retry metadata is no longer valid. Submit a new request with a new idempotency key." }, true);
305
- if (result.kind === "error" || result.kind === "unauthorized") return json({ error: result.error || "unauthorized", message: result.message }, true);
306
- return json(result.brief);
307
- },
344
+ );
345
+ registerResumePersonalContext(
346
+ "resume_connection_brief",
347
+ "Deprecated alias for resume_personal_context. Prefer resume_personal_context.",
308
348
  );
309
349
 
310
350
  server.tool(
@@ -326,21 +366,18 @@ server.tool(
326
366
  },
327
367
  );
328
368
 
329
- server.tool(
330
- "preflight_connection_briefs",
369
+ registerPreflightPersonalContexts(
370
+ "preflight_personal_contexts",
331
371
  "Read-only batch estimate. It never purchases credit, triggers auto-recharge, reserves credit, or starts a partial batch. Use the API directly with explicit human approval to submit an all-or-nothing batch.",
332
- { profiles: z.array(z.object({ linkedin_url: z.string().optional(), name: z.string().optional(), company: z.string().optional(), email: z.string().optional() })).min(1).max(100) },
333
- async ({ profiles }) => {
334
- if (!KEY) return authenticationRequired();
335
- const response = await api("/api/enrich/batch", { body: { profiles, estimate_only: true } });
336
- if (response.status !== 200) return json({ error: response.body.error || `http_${response.status}`, message: response.body.message }, true);
337
- return json({ status: "preflight", ...response.body, all_or_nothing: true });
338
- },
372
+ );
373
+ registerPreflightPersonalContexts(
374
+ "preflight_connection_briefs",
375
+ "Deprecated alias for preflight_personal_contexts. Prefer preflight_personal_contexts.",
339
376
  );
340
377
 
341
378
  server.tool(
342
379
  "submit_feedback",
343
- "Rate a delivered Connection Brief. Feedback never changes billing.",
380
+ "Rate a delivered personal context. Feedback never changes billing.",
344
381
  {
345
382
  rating: z.enum(["up", "down"]),
346
383
  canonical_id: z.string().optional(),
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "uniqueness-mcp",
3
- "version": "0.5.0",
4
- "description": "Conservative human-in-the-loop MCP server for Uniqueness Engine fact-checked connection briefs.",
3
+ "version": "0.5.2",
4
+ "description": "Conservative human-in-the-loop MCP server for Uniqueness Engine personal context.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "uniqueness-mcp": "index.mjs"
8
8
  },
9
9
  "files": [
10
- "index.mjs"
10
+ "index.mjs",
11
+ "README.md"
11
12
  ],
12
13
  "engines": {
13
14
  "node": ">=18"