uniqueness-mcp 0.5.2 → 0.6.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/index.mjs +176 -5
- package/package.json +1 -1
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.
|
|
14
|
+
const server = new McpServer({ name: "uniqueness", version: "0.6.0" });
|
|
15
15
|
|
|
16
16
|
const json = (value, isError = false) => ({
|
|
17
17
|
...(isError ? { isError: true } : {}),
|
|
@@ -76,6 +76,22 @@ function paymentAction(contract) {
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function batchPaymentAction(contract) {
|
|
80
|
+
return json({
|
|
81
|
+
status: "human_action_required",
|
|
82
|
+
action: "checkout",
|
|
83
|
+
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 another pack. This server stores no unpaid batch. After payment, call batch_connection_briefs again with the original requests, options, callbacks, and idempotency_key.",
|
|
84
|
+
payment_required: contract,
|
|
85
|
+
resume_tool: "batch_connection_briefs",
|
|
86
|
+
resume_requires: [
|
|
87
|
+
"original requests and options",
|
|
88
|
+
"original callback configuration, if any",
|
|
89
|
+
"payment_required.retry.idempotency_key",
|
|
90
|
+
],
|
|
91
|
+
fixture: contract.fixture,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
function authenticationRequired() {
|
|
80
96
|
return json({
|
|
81
97
|
status: "human_action_required",
|
|
@@ -90,9 +106,13 @@ async function pollBrief(jobId) {
|
|
|
90
106
|
while (Date.now() < deadline) {
|
|
91
107
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
92
108
|
const poll = await api(`/api/jobs/${jobId}`, { method: "GET" });
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
109
|
+
const status = poll.body.lifecycle_status || poll.body.status;
|
|
110
|
+
if (status === "completed" || status === "done")
|
|
111
|
+
return { kind: "brief", brief: poll.body.result };
|
|
112
|
+
if (status === "needs_review" || status === "refused")
|
|
113
|
+
return { kind: "brief", brief: poll.body.result || { status: "needs_review", note: "Identity could not be confidently resolved." } };
|
|
114
|
+
if (["failed", "cancelled", "expired"].includes(status))
|
|
115
|
+
return { kind: "error", error: poll.body.error || `job_${status}` };
|
|
96
116
|
}
|
|
97
117
|
return { kind: "pending", job_id: jobId };
|
|
98
118
|
}
|
|
@@ -274,7 +294,7 @@ function registerGetPersonalContext(toolName, description) {
|
|
|
274
294
|
return paymentAction(result.contract);
|
|
275
295
|
}
|
|
276
296
|
if (result.kind === "unauthorized") return json({ error: "unauthorized" }, true);
|
|
277
|
-
if (result.kind === "pending") return json({ status: "
|
|
297
|
+
if (result.kind === "pending") return json({ status: "running", job_id: result.job_id, message: "The job is still processing; call get_connection_brief_job with this job_id. Do not resubmit the person." });
|
|
278
298
|
if (result.kind === "retry_rejected") return json({ error: result.error, message: "Submit a new request with a new idempotency key." }, true);
|
|
279
299
|
if (result.kind === "error") return json({ error: result.error, message: result.message }, true);
|
|
280
300
|
return json(result.brief);
|
|
@@ -375,6 +395,157 @@ registerPreflightPersonalContexts(
|
|
|
375
395
|
"Deprecated alias for preflight_personal_contexts. Prefer preflight_personal_contexts.",
|
|
376
396
|
);
|
|
377
397
|
|
|
398
|
+
const profileSchema = z.object({
|
|
399
|
+
request_id: z.string().optional(),
|
|
400
|
+
linkedin_url: z.string().optional(),
|
|
401
|
+
name: z.string().optional(),
|
|
402
|
+
company: z.string().optional(),
|
|
403
|
+
email: z.string().optional(),
|
|
404
|
+
});
|
|
405
|
+
const batchOptionsSchema = z.object({
|
|
406
|
+
detail: z.enum(["compact", "full"]).optional(),
|
|
407
|
+
public_sources_only: z.boolean().optional(),
|
|
408
|
+
include_sensitive_topics: z.boolean().optional(),
|
|
409
|
+
store_raw_quotes: z.boolean().optional(),
|
|
410
|
+
include_outreach_angles: z.boolean().optional(),
|
|
411
|
+
}).optional();
|
|
412
|
+
|
|
413
|
+
server.tool(
|
|
414
|
+
"get_connection_brief_job",
|
|
415
|
+
"Retrieve one asynchronous job by job_id. Use this after a submit returns queued/running; never resubmit just to check progress.",
|
|
416
|
+
{ job_id: z.string(), detail: z.enum(["compact", "full"]).optional() },
|
|
417
|
+
async ({ job_id, detail }) => {
|
|
418
|
+
if (!KEY) return authenticationRequired();
|
|
419
|
+
const response = await api(`/api/jobs/${encodeURIComponent(job_id)}?detail=${detail || "full"}`, { method: "GET" });
|
|
420
|
+
if (response.status >= 400) return json(response.body, true);
|
|
421
|
+
return json({
|
|
422
|
+
...response.body,
|
|
423
|
+
legacy_status: response.body.status,
|
|
424
|
+
status: response.body.lifecycle_status || response.body.status,
|
|
425
|
+
});
|
|
426
|
+
},
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
server.tool(
|
|
430
|
+
"batch_connection_briefs",
|
|
431
|
+
"Submit up to 100 people as one durable batch. The backend queues work beyond its managed processing concurrency.",
|
|
432
|
+
{
|
|
433
|
+
requests: z.array(profileSchema).min(1).max(100),
|
|
434
|
+
idempotency_key: z.string().optional(),
|
|
435
|
+
callback_url: z.string().url().optional(),
|
|
436
|
+
callback_secret: z.string().max(512).optional(),
|
|
437
|
+
options: batchOptionsSchema,
|
|
438
|
+
},
|
|
439
|
+
async (args) => {
|
|
440
|
+
if (!KEY) return authenticationRequired();
|
|
441
|
+
const response = await api("/api/enrich/batch", { body: args });
|
|
442
|
+
if (response.status === 402 && isPaymentRequired(response.body))
|
|
443
|
+
return batchPaymentAction(response.body);
|
|
444
|
+
return json(response.body, response.status >= 400);
|
|
445
|
+
},
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
server.tool(
|
|
449
|
+
"estimate_batch_cost",
|
|
450
|
+
"Estimate batch credit cost without reserving credit or starting work.",
|
|
451
|
+
{ requests: z.array(profileSchema).min(1).max(100), options: batchOptionsSchema },
|
|
452
|
+
async ({ requests, options }) => {
|
|
453
|
+
if (!KEY) return authenticationRequired();
|
|
454
|
+
const response = await api("/api/enrich/batch", {
|
|
455
|
+
body: { requests, options, estimate_only: true },
|
|
456
|
+
});
|
|
457
|
+
return json(response.body, response.status >= 400);
|
|
458
|
+
},
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
server.tool(
|
|
462
|
+
"get_batch_status",
|
|
463
|
+
"Retrieve aggregate and per-job status for a durable batch.",
|
|
464
|
+
{ batch_id: z.string() },
|
|
465
|
+
async ({ batch_id }) => {
|
|
466
|
+
if (!KEY) return authenticationRequired();
|
|
467
|
+
const response = await api(`/api/enrich/batch/${encodeURIComponent(batch_id)}`, { method: "GET" });
|
|
468
|
+
return json(response.body, response.status >= 400);
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
server.tool(
|
|
473
|
+
"get_batch_results",
|
|
474
|
+
"Retrieve a stable page of batch result records, including explicit failures and needs-review outcomes.",
|
|
475
|
+
{
|
|
476
|
+
batch_id: z.string(),
|
|
477
|
+
cursor: z.string().optional(),
|
|
478
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
479
|
+
detail: z.enum(["compact", "full"]).optional(),
|
|
480
|
+
},
|
|
481
|
+
async ({ batch_id, cursor, limit, detail }) => {
|
|
482
|
+
if (!KEY) return authenticationRequired();
|
|
483
|
+
const query = new URLSearchParams({
|
|
484
|
+
cursor: cursor || "0",
|
|
485
|
+
limit: String(limit || 50),
|
|
486
|
+
detail: detail || "full",
|
|
487
|
+
});
|
|
488
|
+
const response = await api(`/api/enrich/batch/${encodeURIComponent(batch_id)}/results?${query}`, { method: "GET" });
|
|
489
|
+
return json(response.body, response.status >= 400);
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
server.tool(
|
|
494
|
+
"export_batch",
|
|
495
|
+
"Export one terminal batch as JSON, JSONL, CSV, or Markdown.",
|
|
496
|
+
{
|
|
497
|
+
batch_id: z.string(),
|
|
498
|
+
format: z.enum(["json", "jsonl", "csv", "markdown"]).optional(),
|
|
499
|
+
},
|
|
500
|
+
async ({ batch_id, format }) => {
|
|
501
|
+
if (!KEY) return authenticationRequired();
|
|
502
|
+
const response = await fetch(
|
|
503
|
+
`${BASE}/api/enrich/batch/${encodeURIComponent(batch_id)}/export?format=${format || "jsonl"}`,
|
|
504
|
+
{ headers: keyHeaders(undefined, true) },
|
|
505
|
+
);
|
|
506
|
+
const text = await response.text();
|
|
507
|
+
return {
|
|
508
|
+
...(response.ok ? {} : { isError: true }),
|
|
509
|
+
content: [{ type: "text", text }],
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
server.tool(
|
|
515
|
+
"cancel_job",
|
|
516
|
+
"Cancel one asynchronous job.",
|
|
517
|
+
{ job_id: z.string() },
|
|
518
|
+
async ({ job_id }) => {
|
|
519
|
+
if (!KEY) return authenticationRequired();
|
|
520
|
+
const response = await api(`/api/jobs/${encodeURIComponent(job_id)}/cancel`, { body: {} });
|
|
521
|
+
return json(response.body, response.status >= 400);
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
server.tool(
|
|
526
|
+
"retry_job",
|
|
527
|
+
"Retry one asynchronous job.",
|
|
528
|
+
{ job_id: z.string() },
|
|
529
|
+
async ({ job_id }) => {
|
|
530
|
+
if (!KEY) return authenticationRequired();
|
|
531
|
+
const response = await api(`/api/jobs/${encodeURIComponent(job_id)}/retry`, { body: {} });
|
|
532
|
+
return json(response.body, response.status >= 400);
|
|
533
|
+
},
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
server.tool(
|
|
537
|
+
"retry_failed_batch",
|
|
538
|
+
"Submit a new batch containing only terminal system failures. Identity needs-review outcomes are not retried.",
|
|
539
|
+
{ batch_id: z.string(), idempotency_key: z.string().optional() },
|
|
540
|
+
async ({ batch_id, idempotency_key }) => {
|
|
541
|
+
if (!KEY) return authenticationRequired();
|
|
542
|
+
const response = await api(`/api/enrich/batch/${encodeURIComponent(batch_id)}/retry`, {
|
|
543
|
+
body: { idempotency_key },
|
|
544
|
+
});
|
|
545
|
+
return json(response.body, response.status >= 400);
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
|
|
378
549
|
server.tool(
|
|
379
550
|
"submit_feedback",
|
|
380
551
|
"Rate a delivered personal context. Feedback never changes billing.",
|