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.
- package/README.md +67 -0
- package/index.mjs +90 -53
- 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.
|
|
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: "
|
|
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.
|
|
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
|
|
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
|
-
|
|
258
|
-
|
|
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
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
330
|
-
"
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
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.
|
|
4
|
-
"description": "Conservative human-in-the-loop MCP server for Uniqueness Engine
|
|
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"
|