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.
Files changed (2) hide show
  1. package/index.mjs +176 -5
  2. 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.5.2" });
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
- if (poll.body.status === "done") return { kind: "brief", brief: poll.body.result };
94
- if (poll.body.status === "refused") return { kind: "brief", brief: poll.body.result || { status: "needs_review", note: "Identity could not be confidently resolved." } };
95
- if (poll.body.status === "failed") return { kind: "error", error: poll.body.error || "job_failed" };
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: "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." });
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniqueness-mcp",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Conservative human-in-the-loop MCP server for Uniqueness Engine personal context.",
5
5
  "type": "module",
6
6
  "bin": {