@humanops/mcp-server 0.3.1 → 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.
- package/README.md +10 -2
- package/dist/index.js +400 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ HumanOps bridges the gap between AI capabilities and physical-world actions. Whe
|
|
|
24
24
|
|
|
25
25
|
| Variable | Required | Description |
|
|
26
26
|
|----------|----------|-------------|
|
|
27
|
-
| `HUMANOPS_API_KEY` | Yes | Your HumanOps API key |
|
|
27
|
+
| `HUMANOPS_API_KEY` | Yes* | Your HumanOps API key. Not required to run the unauthenticated `register_agent` tool, but required for all other tools. |
|
|
28
28
|
| `HUMANOPS_API_URL` | No | API base URL (default: `https://api.humanops.io`) |
|
|
29
29
|
| `HUMANOPS_DEV_ALLOW_LOCALHOST` | No | Set to `true` to allow `HUMANOPS_API_URL` to be `http://localhost:8787` for local development |
|
|
30
30
|
| `HUMANOPS_ALLOW_ANY_API_HOST` | No | Set to `true` to allow non-`*.humanops.io` API hosts (still blocks private/internal hosts) |
|
|
@@ -32,7 +32,15 @@ HumanOps bridges the gap between AI capabilities and physical-world actions. Whe
|
|
|
32
32
|
|
|
33
33
|
## Prerequisites
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Register your agent (get an API key) using one of the following:
|
|
36
|
+
|
|
37
|
+
### Option A: Register via MCP (Recommended)
|
|
38
|
+
|
|
39
|
+
Call the `register_agent` tool first (no `HUMANOPS_API_KEY` required). It returns an `api_key`.
|
|
40
|
+
|
|
41
|
+
Then set `HUMANOPS_API_KEY` to that key (restart the MCP server) to use authenticated tools.
|
|
42
|
+
|
|
43
|
+
### Option B: Register via REST API
|
|
36
44
|
|
|
37
45
|
```bash
|
|
38
46
|
curl -X POST https://api.humanops.io/api/v1/agents/register \
|
package/dist/index.js
CHANGED
|
@@ -395,23 +395,30 @@ function getApiUrl() {
|
|
|
395
395
|
}
|
|
396
396
|
return normalized;
|
|
397
397
|
}
|
|
398
|
-
function
|
|
398
|
+
function getApiKeyOptional() {
|
|
399
399
|
const apiKey = process.env.HUMANOPS_API_KEY?.trim();
|
|
400
|
-
if (!apiKey)
|
|
401
|
-
|
|
402
|
-
}
|
|
400
|
+
if (!apiKey)
|
|
401
|
+
return null;
|
|
403
402
|
if (!/^ho_(live|test)_[a-zA-Z0-9]{32,}$/.test(apiKey)) {
|
|
404
403
|
console.error("Warning: HUMANOPS_API_KEY does not match expected format (ho_live_... or ho_test_...)");
|
|
405
404
|
}
|
|
406
405
|
return apiKey;
|
|
407
406
|
}
|
|
407
|
+
function getApiKeyRequired() {
|
|
408
|
+
const apiKey = getApiKeyOptional();
|
|
409
|
+
if (!apiKey) {
|
|
410
|
+
throw new Error("HUMANOPS_API_KEY environment variable is required for this tool.");
|
|
411
|
+
}
|
|
412
|
+
return apiKey;
|
|
413
|
+
}
|
|
408
414
|
const MCP_REQUEST_TIMEOUT = 30_000;
|
|
409
|
-
async function apiRequest(path, init = {}) {
|
|
415
|
+
async function apiRequest(path, init = {}, opts) {
|
|
410
416
|
const baseUrl = getApiUrl();
|
|
411
|
-
const apiKey = getApiKey();
|
|
412
417
|
const url = `${baseUrl}${path}`;
|
|
413
418
|
const headers = new Headers(init.headers);
|
|
414
|
-
|
|
419
|
+
if (opts?.auth !== false) {
|
|
420
|
+
headers.set("X-API-Key", getApiKeyRequired());
|
|
421
|
+
}
|
|
415
422
|
if (!headers.has("Content-Type") && init.body && typeof init.body === "string") {
|
|
416
423
|
headers.set("Content-Type", "application/json");
|
|
417
424
|
}
|
|
@@ -515,7 +522,10 @@ const PostTaskInputSchema = z.object({
|
|
|
515
522
|
lng: z.number(),
|
|
516
523
|
address: z.string().min(1),
|
|
517
524
|
}),
|
|
518
|
-
|
|
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(),
|
|
519
529
|
deadline: z.string().datetime(),
|
|
520
530
|
proof_requirements: z.array(z.string().min(1).max(500)).min(1).max(10),
|
|
521
531
|
task_type: TaskTypeEnum,
|
|
@@ -528,10 +538,37 @@ const PostTaskInputSchema = z.object({
|
|
|
528
538
|
.optional(),
|
|
529
539
|
callback_secret: z.string().min(16).max(128).optional(),
|
|
530
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"],
|
|
531
545
|
});
|
|
532
546
|
const GetTaskResultInputSchema = z.object({
|
|
533
547
|
task_id: z.string().min(1),
|
|
534
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
|
+
});
|
|
535
572
|
const FundAccountInputSchema = z.object({
|
|
536
573
|
amount_usd: z.number().min(MIN_DEPOSIT_USD).max(MAX_DEPOSIT_USD),
|
|
537
574
|
payment_method: z.enum(["usdc", "card", "bank_transfer"]).default("usdc"),
|
|
@@ -545,6 +582,20 @@ const FundAccountInputSchema = z.object({
|
|
|
545
582
|
})
|
|
546
583
|
.optional(),
|
|
547
584
|
});
|
|
585
|
+
const GetWalletChallengeInputSchema = z.object({
|
|
586
|
+
chain: z.enum(["base"]).optional(),
|
|
587
|
+
});
|
|
588
|
+
const BindWalletInputSchema = z.object({
|
|
589
|
+
wallet_address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid wallet_address (must be a 0x-prefixed address)"),
|
|
590
|
+
nonce: z.string().min(1).max(200),
|
|
591
|
+
signature: z.string().min(10).max(5000),
|
|
592
|
+
chain: z.enum(["base"]).optional(),
|
|
593
|
+
});
|
|
594
|
+
const RegisterAgentInputSchema = z.object({
|
|
595
|
+
name: z.string().min(1).max(200),
|
|
596
|
+
email: z.string().email(),
|
|
597
|
+
company: z.string().min(1).max(200).optional(),
|
|
598
|
+
});
|
|
548
599
|
const RequestPayoutInputSchema = z.object({
|
|
549
600
|
amount_usd: z.number().min(MIN_PAYOUT_USD),
|
|
550
601
|
});
|
|
@@ -591,7 +642,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
591
642
|
},
|
|
592
643
|
{
|
|
593
644
|
name: "post_task",
|
|
594
|
-
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.
|
|
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).",
|
|
595
646
|
inputSchema: {
|
|
596
647
|
type: "object",
|
|
597
648
|
properties: {
|
|
@@ -602,7 +653,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
602
653
|
properties: { lat: { type: "number" }, lng: { type: "number" }, address: { type: "string" } },
|
|
603
654
|
required: ["lat", "lng", "address"],
|
|
604
655
|
},
|
|
605
|
-
|
|
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)" },
|
|
606
664
|
deadline: { type: "string", description: "ISO 8601 deadline for task completion" },
|
|
607
665
|
proof_requirements: { type: "array", items: { type: "string" } },
|
|
608
666
|
task_type: {
|
|
@@ -613,7 +671,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
613
671
|
callback_secret: { type: "string", description: "Optional secret to sign webhook callbacks" },
|
|
614
672
|
idempotency_key: { type: "string", description: "Optional key to make task creation safe to retry" },
|
|
615
673
|
},
|
|
616
|
-
required: ["title", "description", "location", "
|
|
674
|
+
required: ["title", "description", "location", "deadline", "proof_requirements", "task_type"],
|
|
617
675
|
},
|
|
618
676
|
},
|
|
619
677
|
{
|
|
@@ -717,6 +775,52 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
717
775
|
required: ["task_id"],
|
|
718
776
|
},
|
|
719
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
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "register_agent",
|
|
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.",
|
|
805
|
+
inputSchema: {
|
|
806
|
+
type: "object",
|
|
807
|
+
properties: {
|
|
808
|
+
name: { type: "string", description: "Agent name" },
|
|
809
|
+
email: { type: "string", description: "Agent email (must be unique)" },
|
|
810
|
+
company: { type: "string", description: "Optional company name" },
|
|
811
|
+
},
|
|
812
|
+
required: ["name", "email"],
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
name: "get_agent_status",
|
|
817
|
+
description: "Check your current agent status including tier, email verification, wallet binding, and balances. Also returns clear upgrade steps.",
|
|
818
|
+
inputSchema: {
|
|
819
|
+
type: "object",
|
|
820
|
+
properties: {},
|
|
821
|
+
required: [],
|
|
822
|
+
},
|
|
823
|
+
},
|
|
720
824
|
{
|
|
721
825
|
name: "get_deposit_address",
|
|
722
826
|
description: "Get your USDC deposit address. Send USDC on Base L2 to fund your HumanOps account. This is the recommended way to add funds.",
|
|
@@ -726,6 +830,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
726
830
|
required: [],
|
|
727
831
|
},
|
|
728
832
|
},
|
|
833
|
+
{
|
|
834
|
+
name: "get_wallet_challenge",
|
|
835
|
+
description: "Get a challenge message to sign with your EVM wallet. Sign this message to prove wallet ownership before depositing USDC. In production, wallet binding is required before deposits can be confirmed.",
|
|
836
|
+
inputSchema: {
|
|
837
|
+
type: "object",
|
|
838
|
+
properties: {
|
|
839
|
+
chain: { type: "string", enum: ["base"], description: "Blockchain network (default: base)" },
|
|
840
|
+
},
|
|
841
|
+
required: [],
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
name: "bind_wallet",
|
|
846
|
+
description: "Bind your EVM wallet after signing the challenge message. This verifies wallet ownership. Required once before confirming USDC deposits in production.",
|
|
847
|
+
inputSchema: {
|
|
848
|
+
type: "object",
|
|
849
|
+
properties: {
|
|
850
|
+
wallet_address: { type: "string", description: "0x-prefixed EVM address (sender wallet)" },
|
|
851
|
+
nonce: { type: "string", description: "Nonce from get_wallet_challenge" },
|
|
852
|
+
signature: { type: "string", description: "Signature over the challenge message (personal_sign)" },
|
|
853
|
+
chain: { type: "string", enum: ["base"], description: "Blockchain network (default: base)" },
|
|
854
|
+
},
|
|
855
|
+
required: ["wallet_address", "nonce", "signature"],
|
|
856
|
+
},
|
|
857
|
+
},
|
|
729
858
|
{
|
|
730
859
|
name: "fund_account",
|
|
731
860
|
description: "Add funds to your HumanOps account. **Recommended: use USDC deposits** (default). Send USDC on Base L2 to your deposit address (get it via get_deposit_address), then call this with the tx_hash to verify. Fiat methods (card, bank_transfer) are coming soon.",
|
|
@@ -752,7 +881,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
752
881
|
},
|
|
753
882
|
{
|
|
754
883
|
name: "request_payout",
|
|
755
|
-
description: "
|
|
884
|
+
description: "Operator-only: agents cannot request payouts. Use this tool to understand payout options (withdrawals happen in the operator portal / operator API).",
|
|
756
885
|
inputSchema: {
|
|
757
886
|
type: "object",
|
|
758
887
|
properties: { amount_usd: { type: "number", description: "Amount to withdraw in USD (min: $10)" } },
|
|
@@ -777,6 +906,49 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
777
906
|
required: ["task_id"],
|
|
778
907
|
},
|
|
779
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
|
+
},
|
|
780
952
|
{
|
|
781
953
|
name: "approve_estimate",
|
|
782
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.",
|
|
@@ -895,6 +1067,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
895
1067
|
}
|
|
896
1068
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
897
1069
|
}
|
|
1070
|
+
case "register_agent": {
|
|
1071
|
+
const parsed = RegisterAgentInputSchema.safeParse(args);
|
|
1072
|
+
if (!parsed.success) {
|
|
1073
|
+
return {
|
|
1074
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
1075
|
+
isError: true,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
const result = await apiRequest("/api/v1/agents/register", {
|
|
1080
|
+
method: "POST",
|
|
1081
|
+
body: JSON.stringify({
|
|
1082
|
+
name: parsed.data.name,
|
|
1083
|
+
email: parsed.data.email,
|
|
1084
|
+
...(parsed.data.company ? { company: parsed.data.company } : {}),
|
|
1085
|
+
}),
|
|
1086
|
+
}, { auth: false });
|
|
1087
|
+
const apiKey = result && typeof result.api_key === "string" ? result.api_key : null;
|
|
1088
|
+
const banner = apiKey
|
|
1089
|
+
? `CRITICAL — SAVE THIS API KEY:\nHUMANOPS_API_KEY=${apiKey}\n\nRestart your MCP server with this environment variable to use authenticated tools.`
|
|
1090
|
+
: "Registration succeeded.";
|
|
1091
|
+
return {
|
|
1092
|
+
content: [{ type: "text", text: `${banner}\n\n${JSON.stringify(result, null, 2)}` }],
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
catch (err) {
|
|
1096
|
+
return {
|
|
1097
|
+
content: [
|
|
1098
|
+
{
|
|
1099
|
+
type: "text",
|
|
1100
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}\n\nTip: If you already registered with this email, use a different email address.`,
|
|
1101
|
+
},
|
|
1102
|
+
],
|
|
1103
|
+
isError: true,
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
case "get_agent_status": {
|
|
1108
|
+
try {
|
|
1109
|
+
const result = await apiRequest("/api/v1/agents/status", { method: "GET" });
|
|
1110
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
return {
|
|
1114
|
+
content: [
|
|
1115
|
+
{
|
|
1116
|
+
type: "text",
|
|
1117
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}\n\nIf you haven't registered yet, call register_agent first. Otherwise, set HUMANOPS_API_KEY and restart the MCP server.`,
|
|
1118
|
+
},
|
|
1119
|
+
],
|
|
1120
|
+
isError: true,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
898
1124
|
case "check_verification_status": {
|
|
899
1125
|
const parsed = GetTaskResultInputSchema.safeParse(args);
|
|
900
1126
|
if (!parsed.success) {
|
|
@@ -916,17 +1142,112 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
916
1142
|
};
|
|
917
1143
|
return { content: [{ type: "text", text: JSON.stringify(focused, null, 2) }] };
|
|
918
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
|
+
}
|
|
919
1173
|
case "get_deposit_address": {
|
|
920
1174
|
const result = await apiRequest("/api/v1/agents/deposit-address", {
|
|
921
1175
|
method: "GET",
|
|
922
1176
|
});
|
|
1177
|
+
const obj = result;
|
|
1178
|
+
const walletVerified = Boolean(obj.wallet_verified);
|
|
1179
|
+
const walletAddress = typeof obj.wallet_address === "string" ? obj.wallet_address : null;
|
|
1180
|
+
const walletBound = walletVerified || Boolean(walletAddress);
|
|
1181
|
+
const walletBindingRequired = Boolean(obj.wallet_verification?.required);
|
|
1182
|
+
const instructions = walletBindingRequired
|
|
1183
|
+
? (walletBound
|
|
1184
|
+
? "Send USDC to this address, then call fund_account with payment_method='usdc', tx_hash, and chain to confirm the deposit."
|
|
1185
|
+
: "STEP 1: Call get_wallet_challenge to get a signing message. STEP 2: Sign the message with your wallet (personal_sign). STEP 3: Call bind_wallet with wallet_address + nonce + signature. STEP 4: Send USDC. STEP 5: Call fund_account with tx_hash + chain.")
|
|
1186
|
+
: (walletBound
|
|
1187
|
+
? "Send USDC to this address, then call fund_account with tx_hash + chain to confirm the deposit."
|
|
1188
|
+
: "Wallet binding is optional in dev/test. For production parity, bind your wallet first: get_wallet_challenge → bind_wallet. Then send USDC and confirm via fund_account with tx_hash + chain.");
|
|
1189
|
+
return {
|
|
1190
|
+
content: [
|
|
1191
|
+
{
|
|
1192
|
+
type: "text",
|
|
1193
|
+
text: JSON.stringify({
|
|
1194
|
+
...obj,
|
|
1195
|
+
wallet_bound: walletBound,
|
|
1196
|
+
wallet_binding_required: walletBindingRequired,
|
|
1197
|
+
_instructions: instructions,
|
|
1198
|
+
}, null, 2),
|
|
1199
|
+
},
|
|
1200
|
+
],
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
case "get_wallet_challenge": {
|
|
1204
|
+
const parsed = GetWalletChallengeInputSchema.safeParse(args ?? {});
|
|
1205
|
+
if (!parsed.success) {
|
|
1206
|
+
return {
|
|
1207
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
1208
|
+
isError: true,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
const result = await apiRequest("/api/v1/agents/wallet/challenge", {
|
|
1212
|
+
method: "POST",
|
|
1213
|
+
body: JSON.stringify({ chain: parsed.data.chain ?? "base" }),
|
|
1214
|
+
});
|
|
923
1215
|
return {
|
|
924
1216
|
content: [
|
|
925
1217
|
{
|
|
926
1218
|
type: "text",
|
|
927
1219
|
text: JSON.stringify({
|
|
928
1220
|
...result,
|
|
929
|
-
_instructions: "
|
|
1221
|
+
_instructions: "Sign the returned message with your wallet using personal_sign. Then call bind_wallet with wallet_address + nonce + signature (and chain if needed).",
|
|
1222
|
+
}, null, 2),
|
|
1223
|
+
},
|
|
1224
|
+
],
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
case "bind_wallet": {
|
|
1228
|
+
const parsed = BindWalletInputSchema.safeParse(args);
|
|
1229
|
+
if (!parsed.success) {
|
|
1230
|
+
return {
|
|
1231
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
1232
|
+
isError: true,
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
const result = await apiRequest("/api/v1/agents/wallet", {
|
|
1236
|
+
method: "PUT",
|
|
1237
|
+
body: JSON.stringify({
|
|
1238
|
+
wallet_address: parsed.data.wallet_address,
|
|
1239
|
+
nonce: parsed.data.nonce,
|
|
1240
|
+
signature: parsed.data.signature,
|
|
1241
|
+
chain: parsed.data.chain ?? "base",
|
|
1242
|
+
}),
|
|
1243
|
+
});
|
|
1244
|
+
return {
|
|
1245
|
+
content: [
|
|
1246
|
+
{
|
|
1247
|
+
type: "text",
|
|
1248
|
+
text: JSON.stringify({
|
|
1249
|
+
...result,
|
|
1250
|
+
_instructions: "Wallet bound. You can now deposit USDC: call get_deposit_address, send USDC, then call fund_account with tx_hash + chain.",
|
|
930
1251
|
}, null, 2),
|
|
931
1252
|
},
|
|
932
1253
|
],
|
|
@@ -955,6 +1276,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
955
1276
|
}, null, 2),
|
|
956
1277
|
},
|
|
957
1278
|
],
|
|
1279
|
+
isError: true,
|
|
958
1280
|
};
|
|
959
1281
|
}
|
|
960
1282
|
// USDC deposit verification
|
|
@@ -970,7 +1292,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
970
1292
|
text: JSON.stringify({
|
|
971
1293
|
status: "awaiting_deposit",
|
|
972
1294
|
...addressResult,
|
|
973
|
-
message: "No tx_hash provided. Send USDC to the address above. If the
|
|
1295
|
+
message: "No tx_hash provided. Send USDC to the address above. If the deposit response indicates wallet binding is required, bind your wallet first (get_wallet_challenge → bind_wallet). Then call fund_account again with tx_hash + chain to verify your deposit.",
|
|
974
1296
|
}, null, 2),
|
|
975
1297
|
},
|
|
976
1298
|
],
|
|
@@ -1005,6 +1327,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1005
1327
|
}, null, 2),
|
|
1006
1328
|
},
|
|
1007
1329
|
],
|
|
1330
|
+
isError: true,
|
|
1008
1331
|
};
|
|
1009
1332
|
}
|
|
1010
1333
|
case "get_balance": {
|
|
@@ -1037,6 +1360,61 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1037
1360
|
});
|
|
1038
1361
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1039
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
|
+
}
|
|
1040
1418
|
case "approve_estimate": {
|
|
1041
1419
|
const parsed = GetTaskResultInputSchema.safeParse(args);
|
|
1042
1420
|
if (!parsed.success) {
|
|
@@ -1186,17 +1564,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1186
1564
|
const response = {
|
|
1187
1565
|
...result,
|
|
1188
1566
|
private_key: keyPair.privateKey,
|
|
1189
|
-
_notice: "
|
|
1567
|
+
_notice: "Save the private_key securely. It is required to decrypt the credential using retrieve_credential. If lost, the credential is unrecoverable (single-read).",
|
|
1190
1568
|
};
|
|
1569
|
+
const warningText = `CRITICAL — SAVE THIS PRIVATE KEY:\n` +
|
|
1570
|
+
`private_key: ${keyPair.privateKey}\n\n` +
|
|
1571
|
+
`You MUST store this key securely. It is required to decrypt the credential\n` +
|
|
1572
|
+
`via retrieve_credential. If lost, the credential is unrecoverable (single-read).\n\n` +
|
|
1573
|
+
`${JSON.stringify(response, null, 2)}`;
|
|
1191
1574
|
const credResultObj = result;
|
|
1192
1575
|
if (credResultObj.sandbox) {
|
|
1193
1576
|
return {
|
|
1194
1577
|
content: [
|
|
1195
|
-
{ type: "text", text: `SANDBOX MODE: ${credResultObj.sandbox_notice ?? "This credential task will auto-complete with simulated data. No real credentials will be delivered."}\n\n${
|
|
1578
|
+
{ type: "text", text: `SANDBOX MODE: ${credResultObj.sandbox_notice ?? "This credential task will auto-complete with simulated data. No real credentials will be delivered."}\n\n${warningText}` },
|
|
1196
1579
|
],
|
|
1197
1580
|
};
|
|
1198
1581
|
}
|
|
1199
|
-
return { content: [{ type: "text", text:
|
|
1582
|
+
return { content: [{ type: "text", text: warningText }] };
|
|
1200
1583
|
}
|
|
1201
1584
|
case "retrieve_credential": {
|
|
1202
1585
|
const parsed = RetrieveCredentialInputSchema.safeParse(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanops/mcp-server",
|
|
3
|
-
"version": "0.3.
|
|
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",
|