@humanops/mcp-server 0.3.2 → 0.3.3

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/dist/index.js +191 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -522,7 +522,10 @@ const PostTaskInputSchema = z.object({
522
522
  lng: z.number(),
523
523
  address: z.string().min(1),
524
524
  }),
525
- reward_usd: z.number().min(MIN_TASK_VALUE_USD).max(MAX_TASK_VALUE_USD),
525
+ urgency: z.enum(["standard", "priority", "urgent"]).optional(),
526
+ reward_usd: z.number().min(MIN_TASK_VALUE_USD).max(MAX_TASK_VALUE_USD).optional(),
527
+ max_budget_usd: z.number().min(MIN_TASK_VALUE_USD).max(MAX_TASK_VALUE_USD).optional(),
528
+ proposal_deadline_minutes: z.number().int().min(15).max(10080).optional(),
526
529
  deadline: z.string().datetime(),
527
530
  proof_requirements: z.array(z.string().min(1).max(500)).min(1).max(10),
528
531
  task_type: TaskTypeEnum,
@@ -535,10 +538,37 @@ const PostTaskInputSchema = z.object({
535
538
  .optional(),
536
539
  callback_secret: z.string().min(16).max(128).optional(),
537
540
  idempotency_key: z.string().min(1).max(200).optional(),
541
+ }).refine((d) => (d.reward_usd !== undefined || d.max_budget_usd !== undefined) &&
542
+ !(d.reward_usd !== undefined && d.max_budget_usd !== undefined), {
543
+ message: "Provide either reward_usd (fixed price) or max_budget_usd (proposal mode), not both",
544
+ path: ["reward_usd"],
538
545
  });
539
546
  const GetTaskResultInputSchema = z.object({
540
547
  task_id: z.string().min(1),
541
548
  });
549
+ const SubmitReviewInputSchema = z.object({
550
+ task_id: z.string().min(1),
551
+ rating: z.number().int().min(1).max(10),
552
+ comment: z.string().min(1).max(1000).optional(),
553
+ });
554
+ const GetOperatorReviewsInputSchema = z.object({
555
+ operator_id: z.string().min(1),
556
+ });
557
+ const ListProposalsInputSchema = z.object({
558
+ task_id: z.string().min(1),
559
+ });
560
+ const AwardProposalToolInputSchema = z.object({
561
+ task_id: z.string().min(1),
562
+ proposal_id: z.string().min(1),
563
+ });
564
+ const RejectProposalToolInputSchema = z.object({
565
+ task_id: z.string().min(1),
566
+ proposal_id: z.string().min(1),
567
+ reason: z.string().max(200).optional(),
568
+ });
569
+ const CancelProposalWindowInputSchema = z.object({
570
+ task_id: z.string().min(1),
571
+ });
542
572
  const FundAccountInputSchema = z.object({
543
573
  amount_usd: z.number().min(MIN_DEPOSIT_USD).max(MAX_DEPOSIT_USD),
544
574
  payment_method: z.enum(["usdc", "card", "bank_transfer"]).default("usdc"),
@@ -612,7 +642,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
612
642
  },
613
643
  {
614
644
  name: "post_task",
615
- description: "Create a new task for a human operator. Supports physical tasks (VERIFICATION, PHOTO, DELIVERY, INSPECTION), digital tasks (CAPTCHA_SOLVING, FORM_FILLING, etc.), and credential tasks (ACCOUNT_CREATION, API_KEY_PROCUREMENT, etc.). For digital tasks prefer dispatch_digital_task; for credential tasks prefer dispatch_credential_task. Funds will be escrowed from your account (reward + platform fee).",
645
+ description: "Create a new task for a human operator. Supports physical tasks (VERIFICATION, PHOTO, DELIVERY, INSPECTION), digital tasks (CAPTCHA_SOLVING, FORM_FILLING, etc.), and credential tasks (ACCOUNT_CREATION, API_KEY_PROCUREMENT, etc.). For digital tasks prefer dispatch_digital_task; for credential tasks prefer dispatch_credential_task. Provide either reward_usd (fixed price; escrowed at creation) OR max_budget_usd (proposal mode; escrow created when you award a proposal).",
616
646
  inputSchema: {
617
647
  type: "object",
618
648
  properties: {
@@ -623,7 +653,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
623
653
  properties: { lat: { type: "number" }, lng: { type: "number" }, address: { type: "string" } },
624
654
  required: ["lat", "lng", "address"],
625
655
  },
626
- reward_usd: { type: "number" },
656
+ urgency: {
657
+ type: "string",
658
+ enum: ["standard", "priority", "urgent"],
659
+ description: "Urgency level (standard=24h pickup, priority=4h pickup, urgent=1h pickup + 4h completion SLA).",
660
+ },
661
+ reward_usd: { type: "number", description: "Fixed-price mode: reward in USD" },
662
+ max_budget_usd: { type: "number", description: "Proposal mode: maximum budget in USD (alternative to reward_usd)" },
663
+ proposal_deadline_minutes: { type: "number", description: "Proposal window length in minutes (default: 120)" },
627
664
  deadline: { type: "string", description: "ISO 8601 deadline for task completion" },
628
665
  proof_requirements: { type: "array", items: { type: "string" } },
629
666
  task_type: {
@@ -634,7 +671,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
634
671
  callback_secret: { type: "string", description: "Optional secret to sign webhook callbacks" },
635
672
  idempotency_key: { type: "string", description: "Optional key to make task creation safe to retry" },
636
673
  },
637
- required: ["title", "description", "location", "reward_usd", "deadline", "proof_requirements", "task_type"],
674
+ required: ["title", "description", "location", "deadline", "proof_requirements", "task_type"],
638
675
  },
639
676
  },
640
677
  {
@@ -738,6 +775,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
738
775
  required: ["task_id"],
739
776
  },
740
777
  },
778
+ {
779
+ name: "submit_review",
780
+ description: "Submit a 1-10 rating (and optional comment) for the operator on a COMPLETED task. One review per task. Comments are screened for safety.",
781
+ inputSchema: {
782
+ type: "object",
783
+ properties: {
784
+ task_id: { type: "string", description: "The completed task ID" },
785
+ rating: { type: "number", description: "Integer rating 1-10" },
786
+ comment: { type: "string", description: "Optional comment (max 1000 chars)" },
787
+ },
788
+ required: ["task_id", "rating"],
789
+ },
790
+ },
791
+ {
792
+ name: "get_operator_reviews",
793
+ description: "Get an operator's approved reviews and aggregate rating (agent-facing). Useful for building trust signals and choosing operators.",
794
+ inputSchema: {
795
+ type: "object",
796
+ properties: {
797
+ operator_id: { type: "string", description: "Operator ID" },
798
+ },
799
+ required: ["operator_id"],
800
+ },
801
+ },
741
802
  {
742
803
  name: "register_agent",
743
804
  description: "Register a new HumanOps agent account. Returns an API key for authentication. You start in SANDBOX tier (tasks auto-complete with simulated operators). Verify your email to upgrade to VERIFIED tier.",
@@ -845,6 +906,49 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
845
906
  required: ["task_id"],
846
907
  },
847
908
  },
909
+ {
910
+ name: "list_proposals",
911
+ description: "List proposals for a proposal-mode task. Use this after creating a task with max_budget_usd.",
912
+ inputSchema: {
913
+ type: "object",
914
+ properties: { task_id: { type: "string", description: "The task ID" } },
915
+ required: ["task_id"],
916
+ },
917
+ },
918
+ {
919
+ name: "award_proposal",
920
+ description: "Award a proposal for a proposal-mode task. This creates escrow at award time and assigns the operator.",
921
+ inputSchema: {
922
+ type: "object",
923
+ properties: {
924
+ task_id: { type: "string", description: "The task ID" },
925
+ proposal_id: { type: "string", description: "The proposal ID to award" },
926
+ },
927
+ required: ["task_id", "proposal_id"],
928
+ },
929
+ },
930
+ {
931
+ name: "reject_proposal",
932
+ description: "Reject a proposal for a proposal-mode task (optional reason).",
933
+ inputSchema: {
934
+ type: "object",
935
+ properties: {
936
+ task_id: { type: "string", description: "The task ID" },
937
+ proposal_id: { type: "string", description: "The proposal ID to reject" },
938
+ reason: { type: "string", description: "Optional rejection reason" },
939
+ },
940
+ required: ["task_id", "proposal_id"],
941
+ },
942
+ },
943
+ {
944
+ name: "cancel_proposal_window",
945
+ description: "Close the proposal window by cancelling the task. This is useful when you no longer want proposals for a task in proposal mode.",
946
+ inputSchema: {
947
+ type: "object",
948
+ properties: { task_id: { type: "string", description: "The task ID to cancel" } },
949
+ required: ["task_id"],
950
+ },
951
+ },
848
952
  {
849
953
  name: "approve_estimate",
850
954
  description: "Approve an operator's time estimate for a task. The operator will be notified and can start working. Only works when task status is ESTIMATE_PENDING.",
@@ -1038,6 +1142,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1038
1142
  };
1039
1143
  return { content: [{ type: "text", text: JSON.stringify(focused, null, 2) }] };
1040
1144
  }
1145
+ case "submit_review": {
1146
+ const parsed = SubmitReviewInputSchema.safeParse(args);
1147
+ if (!parsed.success) {
1148
+ return {
1149
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1150
+ isError: true,
1151
+ };
1152
+ }
1153
+ const { task_id, rating, comment } = parsed.data;
1154
+ const result = await apiRequest(`/api/v1/tasks/${encodeURIComponent(task_id)}/review`, {
1155
+ method: "POST",
1156
+ body: JSON.stringify({ rating, ...(comment ? { comment } : {}) }),
1157
+ });
1158
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1159
+ }
1160
+ case "get_operator_reviews": {
1161
+ const parsed = GetOperatorReviewsInputSchema.safeParse(args);
1162
+ if (!parsed.success) {
1163
+ return {
1164
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1165
+ isError: true,
1166
+ };
1167
+ }
1168
+ const result = await apiRequest(`/api/v1/operators/${encodeURIComponent(parsed.data.operator_id)}/reviews`, {
1169
+ method: "GET",
1170
+ });
1171
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1172
+ }
1041
1173
  case "get_deposit_address": {
1042
1174
  const result = await apiRequest("/api/v1/agents/deposit-address", {
1043
1175
  method: "GET",
@@ -1228,6 +1360,61 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1228
1360
  });
1229
1361
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1230
1362
  }
1363
+ case "list_proposals": {
1364
+ const parsed = ListProposalsInputSchema.safeParse(args);
1365
+ if (!parsed.success) {
1366
+ return {
1367
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1368
+ isError: true,
1369
+ };
1370
+ }
1371
+ const data = await apiRequest(`/api/v1/tasks/${encodeURIComponent(parsed.data.task_id)}/proposals`, {
1372
+ method: "GET",
1373
+ });
1374
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1375
+ }
1376
+ case "award_proposal": {
1377
+ const parsed = AwardProposalToolInputSchema.safeParse(args);
1378
+ if (!parsed.success) {
1379
+ return {
1380
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1381
+ isError: true,
1382
+ };
1383
+ }
1384
+ const result = await apiRequest(`/api/v1/tasks/${encodeURIComponent(parsed.data.task_id)}/award-proposal`, {
1385
+ method: "POST",
1386
+ body: JSON.stringify({ proposal_id: parsed.data.proposal_id }),
1387
+ });
1388
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1389
+ }
1390
+ case "reject_proposal": {
1391
+ const parsed = RejectProposalToolInputSchema.safeParse(args);
1392
+ if (!parsed.success) {
1393
+ return {
1394
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1395
+ isError: true,
1396
+ };
1397
+ }
1398
+ const body = parsed.data.reason ? { reason: parsed.data.reason } : {};
1399
+ const result = await apiRequest(`/api/v1/tasks/${encodeURIComponent(parsed.data.task_id)}/proposals/${encodeURIComponent(parsed.data.proposal_id)}/reject`, {
1400
+ method: "POST",
1401
+ body: JSON.stringify(body),
1402
+ });
1403
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1404
+ }
1405
+ case "cancel_proposal_window": {
1406
+ const parsed = CancelProposalWindowInputSchema.safeParse(args);
1407
+ if (!parsed.success) {
1408
+ return {
1409
+ content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
1410
+ isError: true,
1411
+ };
1412
+ }
1413
+ const result = await apiRequest(`/api/v1/tasks/${encodeURIComponent(parsed.data.task_id)}/cancel`, {
1414
+ method: "POST",
1415
+ });
1416
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1417
+ }
1231
1418
  case "approve_estimate": {
1232
1419
  const parsed = GetTaskResultInputSchema.safeParse(args);
1233
1420
  if (!parsed.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanops/mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "mcpName": "io.github.thepianistdirector/humanops",
5
5
  "description": "MCP server for AI agents to dispatch real-world tasks to verified human operators via HumanOps",
6
6
  "type": "module",