@humanops/mcp-server 0.3.0 → 0.3.2
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 +210 -14
- package/package.json +2 -2
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
|
}
|
|
@@ -545,6 +552,20 @@ const FundAccountInputSchema = z.object({
|
|
|
545
552
|
})
|
|
546
553
|
.optional(),
|
|
547
554
|
});
|
|
555
|
+
const GetWalletChallengeInputSchema = z.object({
|
|
556
|
+
chain: z.enum(["base"]).optional(),
|
|
557
|
+
});
|
|
558
|
+
const BindWalletInputSchema = z.object({
|
|
559
|
+
wallet_address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid wallet_address (must be a 0x-prefixed address)"),
|
|
560
|
+
nonce: z.string().min(1).max(200),
|
|
561
|
+
signature: z.string().min(10).max(5000),
|
|
562
|
+
chain: z.enum(["base"]).optional(),
|
|
563
|
+
});
|
|
564
|
+
const RegisterAgentInputSchema = z.object({
|
|
565
|
+
name: z.string().min(1).max(200),
|
|
566
|
+
email: z.string().email(),
|
|
567
|
+
company: z.string().min(1).max(200).optional(),
|
|
568
|
+
});
|
|
548
569
|
const RequestPayoutInputSchema = z.object({
|
|
549
570
|
amount_usd: z.number().min(MIN_PAYOUT_USD),
|
|
550
571
|
});
|
|
@@ -565,7 +586,7 @@ const ALL_TASK_TYPES = [
|
|
|
565
586
|
];
|
|
566
587
|
const server = new Server({
|
|
567
588
|
name: "humanops",
|
|
568
|
-
version: "0.3.
|
|
589
|
+
version: "0.3.1",
|
|
569
590
|
}, {
|
|
570
591
|
capabilities: { tools: {} },
|
|
571
592
|
});
|
|
@@ -717,6 +738,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
717
738
|
required: ["task_id"],
|
|
718
739
|
},
|
|
719
740
|
},
|
|
741
|
+
{
|
|
742
|
+
name: "register_agent",
|
|
743
|
+
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.",
|
|
744
|
+
inputSchema: {
|
|
745
|
+
type: "object",
|
|
746
|
+
properties: {
|
|
747
|
+
name: { type: "string", description: "Agent name" },
|
|
748
|
+
email: { type: "string", description: "Agent email (must be unique)" },
|
|
749
|
+
company: { type: "string", description: "Optional company name" },
|
|
750
|
+
},
|
|
751
|
+
required: ["name", "email"],
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
name: "get_agent_status",
|
|
756
|
+
description: "Check your current agent status including tier, email verification, wallet binding, and balances. Also returns clear upgrade steps.",
|
|
757
|
+
inputSchema: {
|
|
758
|
+
type: "object",
|
|
759
|
+
properties: {},
|
|
760
|
+
required: [],
|
|
761
|
+
},
|
|
762
|
+
},
|
|
720
763
|
{
|
|
721
764
|
name: "get_deposit_address",
|
|
722
765
|
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 +769,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
726
769
|
required: [],
|
|
727
770
|
},
|
|
728
771
|
},
|
|
772
|
+
{
|
|
773
|
+
name: "get_wallet_challenge",
|
|
774
|
+
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.",
|
|
775
|
+
inputSchema: {
|
|
776
|
+
type: "object",
|
|
777
|
+
properties: {
|
|
778
|
+
chain: { type: "string", enum: ["base"], description: "Blockchain network (default: base)" },
|
|
779
|
+
},
|
|
780
|
+
required: [],
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
name: "bind_wallet",
|
|
785
|
+
description: "Bind your EVM wallet after signing the challenge message. This verifies wallet ownership. Required once before confirming USDC deposits in production.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
wallet_address: { type: "string", description: "0x-prefixed EVM address (sender wallet)" },
|
|
790
|
+
nonce: { type: "string", description: "Nonce from get_wallet_challenge" },
|
|
791
|
+
signature: { type: "string", description: "Signature over the challenge message (personal_sign)" },
|
|
792
|
+
chain: { type: "string", enum: ["base"], description: "Blockchain network (default: base)" },
|
|
793
|
+
},
|
|
794
|
+
required: ["wallet_address", "nonce", "signature"],
|
|
795
|
+
},
|
|
796
|
+
},
|
|
729
797
|
{
|
|
730
798
|
name: "fund_account",
|
|
731
799
|
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 +820,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
752
820
|
},
|
|
753
821
|
{
|
|
754
822
|
name: "request_payout",
|
|
755
|
-
description: "
|
|
823
|
+
description: "Operator-only: agents cannot request payouts. Use this tool to understand payout options (withdrawals happen in the operator portal / operator API).",
|
|
756
824
|
inputSchema: {
|
|
757
825
|
type: "object",
|
|
758
826
|
properties: { amount_usd: { type: "number", description: "Amount to withdraw in USD (min: $10)" } },
|
|
@@ -895,6 +963,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
895
963
|
}
|
|
896
964
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
897
965
|
}
|
|
966
|
+
case "register_agent": {
|
|
967
|
+
const parsed = RegisterAgentInputSchema.safeParse(args);
|
|
968
|
+
if (!parsed.success) {
|
|
969
|
+
return {
|
|
970
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
971
|
+
isError: true,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
const result = await apiRequest("/api/v1/agents/register", {
|
|
976
|
+
method: "POST",
|
|
977
|
+
body: JSON.stringify({
|
|
978
|
+
name: parsed.data.name,
|
|
979
|
+
email: parsed.data.email,
|
|
980
|
+
...(parsed.data.company ? { company: parsed.data.company } : {}),
|
|
981
|
+
}),
|
|
982
|
+
}, { auth: false });
|
|
983
|
+
const apiKey = result && typeof result.api_key === "string" ? result.api_key : null;
|
|
984
|
+
const banner = apiKey
|
|
985
|
+
? `CRITICAL — SAVE THIS API KEY:\nHUMANOPS_API_KEY=${apiKey}\n\nRestart your MCP server with this environment variable to use authenticated tools.`
|
|
986
|
+
: "Registration succeeded.";
|
|
987
|
+
return {
|
|
988
|
+
content: [{ type: "text", text: `${banner}\n\n${JSON.stringify(result, null, 2)}` }],
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
catch (err) {
|
|
992
|
+
return {
|
|
993
|
+
content: [
|
|
994
|
+
{
|
|
995
|
+
type: "text",
|
|
996
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}\n\nTip: If you already registered with this email, use a different email address.`,
|
|
997
|
+
},
|
|
998
|
+
],
|
|
999
|
+
isError: true,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
case "get_agent_status": {
|
|
1004
|
+
try {
|
|
1005
|
+
const result = await apiRequest("/api/v1/agents/status", { method: "GET" });
|
|
1006
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1007
|
+
}
|
|
1008
|
+
catch (err) {
|
|
1009
|
+
return {
|
|
1010
|
+
content: [
|
|
1011
|
+
{
|
|
1012
|
+
type: "text",
|
|
1013
|
+
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.`,
|
|
1014
|
+
},
|
|
1015
|
+
],
|
|
1016
|
+
isError: true,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
898
1020
|
case "check_verification_status": {
|
|
899
1021
|
const parsed = GetTaskResultInputSchema.safeParse(args);
|
|
900
1022
|
if (!parsed.success) {
|
|
@@ -920,13 +1042,80 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
920
1042
|
const result = await apiRequest("/api/v1/agents/deposit-address", {
|
|
921
1043
|
method: "GET",
|
|
922
1044
|
});
|
|
1045
|
+
const obj = result;
|
|
1046
|
+
const walletVerified = Boolean(obj.wallet_verified);
|
|
1047
|
+
const walletAddress = typeof obj.wallet_address === "string" ? obj.wallet_address : null;
|
|
1048
|
+
const walletBound = walletVerified || Boolean(walletAddress);
|
|
1049
|
+
const walletBindingRequired = Boolean(obj.wallet_verification?.required);
|
|
1050
|
+
const instructions = walletBindingRequired
|
|
1051
|
+
? (walletBound
|
|
1052
|
+
? "Send USDC to this address, then call fund_account with payment_method='usdc', tx_hash, and chain to confirm the deposit."
|
|
1053
|
+
: "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.")
|
|
1054
|
+
: (walletBound
|
|
1055
|
+
? "Send USDC to this address, then call fund_account with tx_hash + chain to confirm the deposit."
|
|
1056
|
+
: "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.");
|
|
1057
|
+
return {
|
|
1058
|
+
content: [
|
|
1059
|
+
{
|
|
1060
|
+
type: "text",
|
|
1061
|
+
text: JSON.stringify({
|
|
1062
|
+
...obj,
|
|
1063
|
+
wallet_bound: walletBound,
|
|
1064
|
+
wallet_binding_required: walletBindingRequired,
|
|
1065
|
+
_instructions: instructions,
|
|
1066
|
+
}, null, 2),
|
|
1067
|
+
},
|
|
1068
|
+
],
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
case "get_wallet_challenge": {
|
|
1072
|
+
const parsed = GetWalletChallengeInputSchema.safeParse(args ?? {});
|
|
1073
|
+
if (!parsed.success) {
|
|
1074
|
+
return {
|
|
1075
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
1076
|
+
isError: true,
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
const result = await apiRequest("/api/v1/agents/wallet/challenge", {
|
|
1080
|
+
method: "POST",
|
|
1081
|
+
body: JSON.stringify({ chain: parsed.data.chain ?? "base" }),
|
|
1082
|
+
});
|
|
1083
|
+
return {
|
|
1084
|
+
content: [
|
|
1085
|
+
{
|
|
1086
|
+
type: "text",
|
|
1087
|
+
text: JSON.stringify({
|
|
1088
|
+
...result,
|
|
1089
|
+
_instructions: "Sign the returned message with your wallet using personal_sign. Then call bind_wallet with wallet_address + nonce + signature (and chain if needed).",
|
|
1090
|
+
}, null, 2),
|
|
1091
|
+
},
|
|
1092
|
+
],
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
case "bind_wallet": {
|
|
1096
|
+
const parsed = BindWalletInputSchema.safeParse(args);
|
|
1097
|
+
if (!parsed.success) {
|
|
1098
|
+
return {
|
|
1099
|
+
content: [{ type: "text", text: `Error: invalid input: ${parsed.error.message}` }],
|
|
1100
|
+
isError: true,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
const result = await apiRequest("/api/v1/agents/wallet", {
|
|
1104
|
+
method: "PUT",
|
|
1105
|
+
body: JSON.stringify({
|
|
1106
|
+
wallet_address: parsed.data.wallet_address,
|
|
1107
|
+
nonce: parsed.data.nonce,
|
|
1108
|
+
signature: parsed.data.signature,
|
|
1109
|
+
chain: parsed.data.chain ?? "base",
|
|
1110
|
+
}),
|
|
1111
|
+
});
|
|
923
1112
|
return {
|
|
924
1113
|
content: [
|
|
925
1114
|
{
|
|
926
1115
|
type: "text",
|
|
927
1116
|
text: JSON.stringify({
|
|
928
1117
|
...result,
|
|
929
|
-
_instructions: "
|
|
1118
|
+
_instructions: "Wallet bound. You can now deposit USDC: call get_deposit_address, send USDC, then call fund_account with tx_hash + chain.",
|
|
930
1119
|
}, null, 2),
|
|
931
1120
|
},
|
|
932
1121
|
],
|
|
@@ -955,6 +1144,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
955
1144
|
}, null, 2),
|
|
956
1145
|
},
|
|
957
1146
|
],
|
|
1147
|
+
isError: true,
|
|
958
1148
|
};
|
|
959
1149
|
}
|
|
960
1150
|
// USDC deposit verification
|
|
@@ -970,7 +1160,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
970
1160
|
text: JSON.stringify({
|
|
971
1161
|
status: "awaiting_deposit",
|
|
972
1162
|
...addressResult,
|
|
973
|
-
message: "No tx_hash provided. Send USDC to the address above. If the
|
|
1163
|
+
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
1164
|
}, null, 2),
|
|
975
1165
|
},
|
|
976
1166
|
],
|
|
@@ -1005,6 +1195,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1005
1195
|
}, null, 2),
|
|
1006
1196
|
},
|
|
1007
1197
|
],
|
|
1198
|
+
isError: true,
|
|
1008
1199
|
};
|
|
1009
1200
|
}
|
|
1010
1201
|
case "get_balance": {
|
|
@@ -1186,17 +1377,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1186
1377
|
const response = {
|
|
1187
1378
|
...result,
|
|
1188
1379
|
private_key: keyPair.privateKey,
|
|
1189
|
-
_notice: "
|
|
1380
|
+
_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
1381
|
};
|
|
1382
|
+
const warningText = `CRITICAL — SAVE THIS PRIVATE KEY:\n` +
|
|
1383
|
+
`private_key: ${keyPair.privateKey}\n\n` +
|
|
1384
|
+
`You MUST store this key securely. It is required to decrypt the credential\n` +
|
|
1385
|
+
`via retrieve_credential. If lost, the credential is unrecoverable (single-read).\n\n` +
|
|
1386
|
+
`${JSON.stringify(response, null, 2)}`;
|
|
1191
1387
|
const credResultObj = result;
|
|
1192
1388
|
if (credResultObj.sandbox) {
|
|
1193
1389
|
return {
|
|
1194
1390
|
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${
|
|
1391
|
+
{ 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
1392
|
],
|
|
1197
1393
|
};
|
|
1198
1394
|
}
|
|
1199
|
-
return { content: [{ type: "text", text:
|
|
1395
|
+
return { content: [{ type: "text", text: warningText }] };
|
|
1200
1396
|
}
|
|
1201
1397
|
case "retrieve_credential": {
|
|
1202
1398
|
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.2",
|
|
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",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
|
40
|
-
"url": "https://github.com/thepianistdirector/humanops"
|
|
40
|
+
"url": "git+https://github.com/thepianistdirector/humanops.git"
|
|
41
41
|
}
|
|
42
42
|
}
|