rentahuman-mcp 3.2.0 → 3.2.1
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/dist/serve.js +106 -11
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -3175,7 +3175,12 @@ var common = {
|
|
|
3175
3175
|
invalidTimeFormat: "Invalid time format. Use HH:mm (e.g., 09:00, 14:30)."
|
|
3176
3176
|
};
|
|
3177
3177
|
var bounties = {
|
|
3178
|
-
dryRunPreview: "This is a preview \u2014 the bounty has NOT been created yet. preview.fundingTotal is the total funding requirement (including the platform fee) before any existing wallet balance is applied, so it may be higher than the hosted checkout charge. Show this estimate to the operator and ask if they'd like to edit anything before posting. To create the bounty, call create_bounty again with the same parameters and dryRun omitted or set to false.",
|
|
3178
|
+
dryRunPreview: "This is a preview \u2014 the bounty has NOT been created yet, no funds were reserved, and no checkout was started. preview.fundingTotal is the total funding requirement (including the platform fee) before any existing wallet balance is applied, so it may be higher than the hosted checkout charge. Show this estimate to the operator and ask if they'd like to edit anything before posting. To create the bounty, call create_bounty again with the same parameters and dryRun omitted or set to false.",
|
|
3179
|
+
dryRunServerValidated: "serverValidation=passed: the RentAHuman API validated and normalized these parameters with the same rules as a real create (country targeting, pricing, screening fields, content moderation). The AI content review runs only on the real create, so a live bounty can additionally land in pending_review.",
|
|
3180
|
+
dryRunServerUnavailable: "serverValidation=unavailable: the RentAHuman API could not be reached, so this preview was computed locally and has NOT been validated by the server (country targeting, pricing rules and screening fields are only checked there). Tell the operator the preview is unverified and retry the preview before creating.",
|
|
3181
|
+
dryRunMockPreview: "serverValidation=skipped: mock mode computes the preview locally without contacting the API.",
|
|
3182
|
+
retryWithSameKey: (idempotencyKey) => `The API did not confirm whether the bounty was created. Retry create_bounty with the same parameters and the same idempotencyKey ("${idempotencyKey}") after about 30 seconds: a replay returns the original bounty (including its deposit_url) instead of creating a duplicate, and a 409 idempotency_in_progress means the first attempt is still running \u2014 wait and retry again. Do not change the idempotencyKey.`,
|
|
3183
|
+
retryWithoutKey: "The API did not confirm whether the bounty was created, and this call had no idempotencyKey, so a plain retry could create a duplicate. First call list_bounties (mine=true) to check for a bounty with this title (an unfunded one is in pending_deposit status and is not publicly visible). If it is missing, retry with an idempotencyKey so further retries are safe.",
|
|
3179
3184
|
createdMultiPerson: (spotsAvailable) => `Multi-person bounty created successfully. Looking for ${spotsAvailable} humans.`,
|
|
3180
3185
|
createdSingle: "Bounty created successfully. Humans can now view and apply.",
|
|
3181
3186
|
depositRequired: "DEPOSIT REQUIRED: The bounty has been created but is NOT visible yet. Tell the operator checkout_total (it includes a platform fee) before they pay. They must complete checkout at the deposit_url to make the bounty live. Share this URL with the human who owns this account.",
|
|
@@ -3300,6 +3305,12 @@ var MOCK_HIRING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3300
3305
|
"pending_funding",
|
|
3301
3306
|
"partially_filled"
|
|
3302
3307
|
]);
|
|
3308
|
+
var asRecord = (value) => {
|
|
3309
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3310
|
+
return {};
|
|
3311
|
+
}
|
|
3312
|
+
return Object.fromEntries(Object.entries(value));
|
|
3313
|
+
};
|
|
3303
3314
|
var mockHiringSpotsRemaining = (bounty) => MOCK_HIRING_STATUSES.has(bounty.status) ? Math.max(0, (bounty.spotsAvailable || 1) - (bounty.spotsFilled || 0)) : 0;
|
|
3304
3315
|
var handleCreateBounty = (args) => Effect9.gen(function* () {
|
|
3305
3316
|
const params = yield* decode(CreateBountyRequest)(args);
|
|
@@ -3317,6 +3328,11 @@ var handleCreateBounty = (args) => Effect9.gen(function* () {
|
|
|
3317
3328
|
const screeningFields = screeningFieldsResult.fields;
|
|
3318
3329
|
const excludedParticipantSourceBountyIds = params.excludedParticipantSourceBountyIds?.length ? params.excludedParticipantSourceBountyIds : void 0;
|
|
3319
3330
|
const exclusionFields = excludedParticipantSourceBountyIds ? { excludedParticipantSourceBountyIds } : {};
|
|
3331
|
+
const {
|
|
3332
|
+
dryRun: _dryRun,
|
|
3333
|
+
excludedParticipantSourceBountyIds: _rawExclusion,
|
|
3334
|
+
...createParams
|
|
3335
|
+
} = params;
|
|
3320
3336
|
if (params.dryRun) {
|
|
3321
3337
|
const spotsAvailable = params.spotsAvailable || 1;
|
|
3322
3338
|
const currency = params.currency || "USD";
|
|
@@ -3365,11 +3381,66 @@ var handleCreateBounty = (args) => Effect9.gen(function* () {
|
|
|
3365
3381
|
spotsAvailable
|
|
3366
3382
|
})
|
|
3367
3383
|
};
|
|
3384
|
+
if (config.mockMode) {
|
|
3385
|
+
return ok({
|
|
3386
|
+
success: true,
|
|
3387
|
+
dryRun: true,
|
|
3388
|
+
serverValidation: "skipped",
|
|
3389
|
+
preview,
|
|
3390
|
+
message: `${messages.bounties.dryRunPreview} ${messages.bounties.dryRunMockPreview}`
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
const verification2 = yield* identity2.createVerification("create_bounty");
|
|
3394
|
+
const { idempotencyKey: _previewKey, ...previewParams } = createParams;
|
|
3395
|
+
const outcome = yield* api.post("/bounties", {
|
|
3396
|
+
...previewParams,
|
|
3397
|
+
...exclusionFields,
|
|
3398
|
+
applicationDetails: screeningFields,
|
|
3399
|
+
dryRun: true,
|
|
3400
|
+
...verification2
|
|
3401
|
+
}).pipe(
|
|
3402
|
+
Effect9.map((result2) => ({
|
|
3403
|
+
kind: "server",
|
|
3404
|
+
result: asRecord(result2)
|
|
3405
|
+
})),
|
|
3406
|
+
Effect9.catchTag(
|
|
3407
|
+
"TimeoutError",
|
|
3408
|
+
() => Effect9.succeed({ kind: "unreachable" })
|
|
3409
|
+
),
|
|
3410
|
+
Effect9.catchTag(
|
|
3411
|
+
"ApiError",
|
|
3412
|
+
(error) => Effect9.succeed(
|
|
3413
|
+
error.statusCode === 0 ? { kind: "unreachable" } : { kind: "rejected", error }
|
|
3414
|
+
)
|
|
3415
|
+
)
|
|
3416
|
+
);
|
|
3417
|
+
if (outcome.kind === "rejected") {
|
|
3418
|
+
return err({
|
|
3419
|
+
success: false,
|
|
3420
|
+
dryRun: true,
|
|
3421
|
+
error: outcome.error.body,
|
|
3422
|
+
errorType: "ApiError",
|
|
3423
|
+
statusCode: outcome.error.statusCode,
|
|
3424
|
+
endpoint: outcome.error.endpoint
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
if (outcome.kind === "unreachable") {
|
|
3428
|
+
return ok({
|
|
3429
|
+
success: true,
|
|
3430
|
+
dryRun: true,
|
|
3431
|
+
serverValidation: "unavailable",
|
|
3432
|
+
preview,
|
|
3433
|
+
message: `${messages.bounties.dryRunPreview} ${messages.bounties.dryRunServerUnavailable}`
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
const serverPreview = asRecord(outcome.result.preview);
|
|
3437
|
+
const fundingTotal = typeof outcome.result.fundingTotal === "number" ? outcome.result.fundingTotal : preview.fundingTotal;
|
|
3368
3438
|
return ok({
|
|
3369
3439
|
success: true,
|
|
3370
3440
|
dryRun: true,
|
|
3371
|
-
|
|
3372
|
-
|
|
3441
|
+
serverValidation: "passed",
|
|
3442
|
+
preview: { ...serverPreview, fundingTotal },
|
|
3443
|
+
message: `${messages.bounties.dryRunPreview} ${messages.bounties.dryRunServerValidated}`
|
|
3373
3444
|
});
|
|
3374
3445
|
}
|
|
3375
3446
|
yield* rateLimit.check("create_bounty");
|
|
@@ -3428,18 +3499,42 @@ var handleCreateBounty = (args) => Effect9.gen(function* () {
|
|
|
3428
3499
|
})
|
|
3429
3500
|
);
|
|
3430
3501
|
}
|
|
3431
|
-
const {
|
|
3432
|
-
dryRun: _,
|
|
3433
|
-
excludedParticipantSourceBountyIds: _rawExclusion,
|
|
3434
|
-
...createParams
|
|
3435
|
-
} = params;
|
|
3436
3502
|
const verification = yield* identity2.createVerification("create_bounty");
|
|
3437
|
-
const
|
|
3503
|
+
const created = yield* api.post("/bounties", {
|
|
3438
3504
|
...createParams,
|
|
3439
3505
|
...exclusionFields,
|
|
3440
3506
|
applicationDetails: screeningFields,
|
|
3441
3507
|
...verification
|
|
3442
|
-
})
|
|
3508
|
+
}).pipe(
|
|
3509
|
+
Effect9.map((result2) => ({
|
|
3510
|
+
ok: true,
|
|
3511
|
+
result: asRecord(result2)
|
|
3512
|
+
})),
|
|
3513
|
+
// Upstream failures (gateway errors, timeouts, no response) leave the
|
|
3514
|
+
// agent unsure whether the bounty exists. Client errors keep their
|
|
3515
|
+
// normal error path — they are answers, not uncertainty.
|
|
3516
|
+
Effect9.catchTag(
|
|
3517
|
+
"TimeoutError",
|
|
3518
|
+
(error) => Effect9.succeed({ ok: false, error })
|
|
3519
|
+
),
|
|
3520
|
+
Effect9.catchTag(
|
|
3521
|
+
"ApiError",
|
|
3522
|
+
(error) => error.statusCode === 0 || error.statusCode >= 500 ? Effect9.succeed({ ok: false, error }) : Effect9.fail(error)
|
|
3523
|
+
)
|
|
3524
|
+
);
|
|
3525
|
+
if (!created.ok) {
|
|
3526
|
+
const failure = created.error;
|
|
3527
|
+
const isApiError = "statusCode" in failure;
|
|
3528
|
+
return err({
|
|
3529
|
+
success: false,
|
|
3530
|
+
error: failure.message,
|
|
3531
|
+
errorType: isApiError ? "ApiError" : "TimeoutError",
|
|
3532
|
+
...isApiError ? { statusCode: failure.statusCode } : {},
|
|
3533
|
+
endpoint: failure.endpoint,
|
|
3534
|
+
retry_guidance: params.idempotencyKey ? messages.bounties.retryWithSameKey(params.idempotencyKey) : messages.bounties.retryWithoutKey
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
const result = created.result;
|
|
3443
3538
|
if (result.deposit_url) {
|
|
3444
3539
|
return ok({
|
|
3445
3540
|
...result,
|
|
@@ -3908,7 +4003,7 @@ var payEnterpriseBountyTool = toMcpTool(
|
|
|
3908
4003
|
var bountyTools = [
|
|
3909
4004
|
{
|
|
3910
4005
|
name: "create_bounty",
|
|
3911
|
-
description: `Create a one-shot task bounty for humans to apply to. To target an entire country, pass location with an ISO country code and isRemoteAllowed=false while omitting city and state. **IMPORTANT: Always call with dryRun=true first** to preview the bounty. Dry-run \`preview.fundingTotal\` is the total funding requirement before any existing wallet balance is applied. Show that estimate to the operator before posting. Show the preview to the user and ask 'Here's your bounty \u2014 would you like to edit anything before posting?' Only call again with dryRun=false (or omitted) after the user confirms. **You MUST help the user define completionCriteria and evidenceTypes** \u2014 ask what 'done' looks like and what proof they need (text/data, photos, video, or links). For specialized standard-application tasks, configure applicationDetails so applicants provide application details before review. Blank rows are ignored; application upload fields are one file each and capped at 3 total; acknowledgment checkboxes are capped at 5 total and can be optional or required; and one required live_video field may contain a script applicants must record with the in-browser camera. For appearance-based bounties where the worker appears on camera or their presence is part of the deliverable (sign holding, UGC, sponsored posts, on-camera video, promo/GTM appearances), always include one required live_video field with a label asking the applicant to explain in two sentences why they are a good fit \u2014 this gates applications on a live self-recorded video so the poster sees each applicant before accepting. Do not ask for passwords, OTP/2FA codes, API keys, private keys, seed phrases, government IDs, bank/card details, exact home addresses, dates of birth, or other sensitive personal information. For direct-review collection bounties, use submissionMode='photo_upload', 'video_upload', or 'document_upload' with matching submission settings instead of applicationDetails. lifecycleMessages can define auto-message templates for acceptance, rejection, and submission review transitions. You can require applicants to provide specific links (LinkedIn, GitHub, resume, etc.) using the requiredLinks parameter. Requires RENTAHUMAN_API_KEY from the account owner. Standard accounts use available wallet balance first; if the wallet cannot cover the bounty, the response includes deposit_url and checkout_total (the hosted charge after wallet balance is applied) and the account owner must complete checkout before the bounty is visible. Supports multi-person bounties by setting spotsAvailable > 1. Set completionWindowHours (any whole number of hours from 1 to 720, e.g. 36) to give confirmed workers a completion deadline: overdue seats auto-release and reopen for other applicants; workers may request extensions you answer with decide_extension_request. Pass optional \`idempotencyKey\` to make this safe to retry (a replayed key returns the original result instead of duplicating). Applicants are auto-reviewed by default (autoAccept, default true): deterministic checks always run and an AI review runs only for free-text screening answers \u2014 qualified applicants are accepted, clear mismatches rejected with a reason, uncertain cases left pending for manual review; pass autoAccept: false to review and accept every application yourself; on micCheckRequired bounties, set autoAcceptMinMicScore (1-5) to make the mic score a hard cutoff: applications below it are auto-rejected, at/above proceed toward acceptance, and unscored recordings stay pending. When omitted, the platform default 3.0 applies and lower scores only stay pending for manual review. To keep previous participants out, pass excludedParticipantSourceBountyIds with up to ${MAX_EXCLUDED_PARTICIPANT_SOURCE_BOUNTIES} ids of your OWN earlier bounties: any worker who was ever accepted on one of them cannot see or apply to the new bounty, and you can change the list later with update_bounty. AI-managed, QA, and taste run bounties, and never-funded drafts are rejected as sources. BETA: pass aiManaged: true to have the platform fully manage the bounty \u2014 automated recruiting, vetting, submission review, payment release, and a final report. Fixed price USD only, 1-50 spots. Poll the bounty's managed run or subscribe to run.report_ready for the finished work.`,
|
|
4006
|
+
description: `Create a one-shot task bounty for humans to apply to. To target an entire country, pass location with an ISO country code and isRemoteAllowed=false while omitting city and state. **IMPORTANT: Always call with dryRun=true first** to preview the bounty. A preview never creates a bounty, reserves funds, or starts a checkout. When the API is reachable it validates and normalizes the parameters with the same rules as a real create and the response carries serverValidation=passed; if the API cannot be reached the preview is computed locally and carries serverValidation=unavailable \u2014 tell the operator it is unverified. Dry-run \`preview.fundingTotal\` is the total funding requirement before any existing wallet balance is applied. Show that estimate to the operator before posting. Show the preview to the user and ask 'Here's your bounty \u2014 would you like to edit anything before posting?' Only call again with dryRun=false (or omitted) after the user confirms. **You MUST help the user define completionCriteria and evidenceTypes** \u2014 ask what 'done' looks like and what proof they need (text/data, photos, video, or links). For specialized standard-application tasks, configure applicationDetails so applicants provide application details before review. Blank rows are ignored; application upload fields are one file each and capped at 3 total; acknowledgment checkboxes are capped at 5 total and can be optional or required; and one required live_video field may contain a script applicants must record with the in-browser camera. For appearance-based bounties where the worker appears on camera or their presence is part of the deliverable (sign holding, UGC, sponsored posts, on-camera video, promo/GTM appearances), always include one required live_video field with a label asking the applicant to explain in two sentences why they are a good fit \u2014 this gates applications on a live self-recorded video so the poster sees each applicant before accepting. Do not ask for passwords, OTP/2FA codes, API keys, private keys, seed phrases, government IDs, bank/card details, exact home addresses, dates of birth, or other sensitive personal information. For direct-review collection bounties, use submissionMode='photo_upload', 'video_upload', or 'document_upload' with matching submission settings instead of applicationDetails. lifecycleMessages can define auto-message templates for acceptance, rejection, and submission review transitions. You can require applicants to provide specific links (LinkedIn, GitHub, resume, etc.) using the requiredLinks parameter. Requires RENTAHUMAN_API_KEY from the account owner. Standard accounts use available wallet balance first; if the wallet cannot cover the bounty, the response includes deposit_url and checkout_total (the hosted charge after wallet balance is applied) and the account owner must complete checkout before the bounty is visible. Supports multi-person bounties by setting spotsAvailable > 1. Set completionWindowHours (any whole number of hours from 1 to 720, e.g. 36) to give confirmed workers a completion deadline: overdue seats auto-release and reopen for other applicants; workers may request extensions you answer with decide_extension_request. Pass optional \`idempotencyKey\` to make this safe to retry (a replayed key returns the original result instead of duplicating). Applicants are auto-reviewed by default (autoAccept, default true): deterministic checks always run and an AI review runs only for free-text screening answers \u2014 qualified applicants are accepted, clear mismatches rejected with a reason, uncertain cases left pending for manual review; pass autoAccept: false to review and accept every application yourself; on micCheckRequired bounties, set autoAcceptMinMicScore (1-5) to make the mic score a hard cutoff: applications below it are auto-rejected, at/above proceed toward acceptance, and unscored recordings stay pending. When omitted, the platform default 3.0 applies and lower scores only stay pending for manual review. To keep previous participants out, pass excludedParticipantSourceBountyIds with up to ${MAX_EXCLUDED_PARTICIPANT_SOURCE_BOUNTIES} ids of your OWN earlier bounties: any worker who was ever accepted on one of them cannot see or apply to the new bounty, and you can change the list later with update_bounty. AI-managed, QA, and taste run bounties, and never-funded drafts are rejected as sources. BETA: pass aiManaged: true to have the platform fully manage the bounty \u2014 automated recruiting, vetting, submission review, payment release, and a final report. Fixed price USD only, 1-50 spots. Poll the bounty's managed run or subscribe to run.report_ready for the finished work.`,
|
|
3912
4007
|
inputSchema: toInputSchema(CreateBountyRequest),
|
|
3913
4008
|
handler: handleCreateBounty
|
|
3914
4009
|
},
|