ai2human-mcp 0.1.0__tar.gz
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.
- ai2human_mcp-0.1.0/.gitignore +23 -0
- ai2human_mcp-0.1.0/PKG-INFO +75 -0
- ai2human_mcp-0.1.0/README.md +56 -0
- ai2human_mcp-0.1.0/pyproject.toml +32 -0
- ai2human_mcp-0.1.0/src/ai2human_mcp/__init__.py +3 -0
- ai2human_mcp-0.1.0/src/ai2human_mcp/client.py +104 -0
- ai2human_mcp-0.1.0/src/ai2human_mcp/server.py +98 -0
- ai2human_mcp-0.1.0/tests/__init__.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# Node
|
|
5
|
+
node_modules/
|
|
6
|
+
|
|
7
|
+
# Next.js
|
|
8
|
+
.next/
|
|
9
|
+
out/
|
|
10
|
+
|
|
11
|
+
# Logs
|
|
12
|
+
npm-debug.log*
|
|
13
|
+
yarn-debug.log*
|
|
14
|
+
yarn-error.log*
|
|
15
|
+
pnpm-debug.log*
|
|
16
|
+
|
|
17
|
+
# Env
|
|
18
|
+
.env*
|
|
19
|
+
|
|
20
|
+
# Local MVP data and generated campaign artifacts.
|
|
21
|
+
data/*
|
|
22
|
+
!data/db.seed.json
|
|
23
|
+
.vercel
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai2human-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI2Human MCP server — Claude can dispatch human-execution tasks with proof verification.
|
|
5
|
+
Author: AI2Human
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: agent,ai2human,claude,human-in-the-loop,mcp
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: fastmcp<4,>=3.0
|
|
17
|
+
Requires-Dist: httpx<1,>=0.27
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# AI2Human MCP Server
|
|
21
|
+
|
|
22
|
+
Claude can dispatch human-execution tasks with proof verification.
|
|
23
|
+
|
|
24
|
+
When your Claude agent hits a step it can't complete alone — local verification, physical checks, identity actions, compliance reviews — AI2Human dispatches it to a human operator with cryptographic proof verification.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install ai2human-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Set your API key:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export AI2HUMAN_API_KEY="your-key"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ai2human-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Claude Desktop Config
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"ai2human": {
|
|
50
|
+
"command": "uvx",
|
|
51
|
+
"args": ["ai2human-mcp"],
|
|
52
|
+
"env": {
|
|
53
|
+
"AI2HUMAN_API_KEY": "YOUR_KEY"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Tools
|
|
61
|
+
|
|
62
|
+
| Tool | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `ai2human_list_categories` | List supported task categories and proof types |
|
|
65
|
+
| `ai2human_create_task` | Dispatch a human-execution task |
|
|
66
|
+
| `ai2human_check_task` | Check task status, verification, and settlement |
|
|
67
|
+
| `ai2human_get_proof` | Retrieve evidence bundle and settlement receipt |
|
|
68
|
+
|
|
69
|
+
## Environment
|
|
70
|
+
|
|
71
|
+
| Variable | Required | Default |
|
|
72
|
+
|----------|----------|---------|
|
|
73
|
+
| `AI2HUMAN_API_KEY` | Yes | — |
|
|
74
|
+
| `AI2HUMAN_AGENT_KEY` | No | Fallback for API key |
|
|
75
|
+
| `AI2HUMAN_BASE_URL` | No | `https://ai2human.io` |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# AI2Human MCP Server
|
|
2
|
+
|
|
3
|
+
Claude can dispatch human-execution tasks with proof verification.
|
|
4
|
+
|
|
5
|
+
When your Claude agent hits a step it can't complete alone — local verification, physical checks, identity actions, compliance reviews — AI2Human dispatches it to a human operator with cryptographic proof verification.
|
|
6
|
+
|
|
7
|
+
## Quickstart
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install ai2human-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Set your API key:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export AI2HUMAN_API_KEY="your-key"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ai2human-mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Claude Desktop Config
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"ai2human": {
|
|
31
|
+
"command": "uvx",
|
|
32
|
+
"args": ["ai2human-mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"AI2HUMAN_API_KEY": "YOUR_KEY"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
| Tool | Description |
|
|
44
|
+
|------|-------------|
|
|
45
|
+
| `ai2human_list_categories` | List supported task categories and proof types |
|
|
46
|
+
| `ai2human_create_task` | Dispatch a human-execution task |
|
|
47
|
+
| `ai2human_check_task` | Check task status, verification, and settlement |
|
|
48
|
+
| `ai2human_get_proof` | Retrieve evidence bundle and settlement receipt |
|
|
49
|
+
|
|
50
|
+
## Environment
|
|
51
|
+
|
|
52
|
+
| Variable | Required | Default |
|
|
53
|
+
|----------|----------|---------|
|
|
54
|
+
| `AI2HUMAN_API_KEY` | Yes | — |
|
|
55
|
+
| `AI2HUMAN_AGENT_KEY` | No | Fallback for API key |
|
|
56
|
+
| `AI2HUMAN_BASE_URL` | No | `https://ai2human.io` |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai2human-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI2Human MCP server — Claude can dispatch human-execution tasks with proof verification."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "AI2Human" }]
|
|
13
|
+
keywords = ["ai2human", "mcp", "claude", "agent", "human-in-the-loop"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"fastmcp>=3.0,<4",
|
|
25
|
+
"httpx>=0.27,<1",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
ai2human-mcp = "ai2human_mcp.server:main"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/ai2human_mcp"]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AI2HumanAPIError(RuntimeError):
|
|
10
|
+
def __init__(self, message: str, status_code: int, details: Any) -> None:
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
self.status_code = status_code
|
|
13
|
+
self.details = details
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AI2HumanClient:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
api_key: str | None = None,
|
|
21
|
+
base_url: str | None = None,
|
|
22
|
+
transport: httpx.BaseTransport | None = None,
|
|
23
|
+
async_transport: httpx.AsyncBaseTransport | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
self.api_key = api_key or os.getenv("AI2HUMAN_API_KEY") or os.getenv("AI2HUMAN_AGENT_KEY") or ""
|
|
26
|
+
self.base_url = (base_url or os.getenv("AI2HUMAN_BASE_URL") or "https://ai2human.io").rstrip("/")
|
|
27
|
+
self.transport = transport
|
|
28
|
+
self.async_transport = async_transport
|
|
29
|
+
|
|
30
|
+
def _headers(self) -> dict[str, str]:
|
|
31
|
+
if not self.api_key:
|
|
32
|
+
raise AI2HumanAPIError(
|
|
33
|
+
"AI2HUMAN_API_KEY is required. Create one at https://ai2human.io/developers/api-keys.",
|
|
34
|
+
401,
|
|
35
|
+
{"code": "AGENT_API_KEY_REQUIRED"},
|
|
36
|
+
)
|
|
37
|
+
return {"Accept": "application/json", "x-agent-api-key": self.api_key}
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _read(response: httpx.Response) -> dict[str, Any]:
|
|
41
|
+
try:
|
|
42
|
+
payload = response.json()
|
|
43
|
+
except ValueError:
|
|
44
|
+
payload = {"error": f"HTTP {response.status_code}"}
|
|
45
|
+
if response.is_error:
|
|
46
|
+
raise AI2HumanAPIError(str(payload.get("error") or response.reason_phrase), response.status_code, payload)
|
|
47
|
+
return payload
|
|
48
|
+
|
|
49
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
|
|
50
|
+
with httpx.Client(base_url=self.base_url, headers=self._headers(), transport=self.transport, timeout=30) as client:
|
|
51
|
+
return self._read(client.request(method, path, **kwargs))
|
|
52
|
+
|
|
53
|
+
async def _arequest(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
|
|
54
|
+
async with httpx.AsyncClient(
|
|
55
|
+
base_url=self.base_url,
|
|
56
|
+
headers=self._headers(),
|
|
57
|
+
transport=self.async_transport,
|
|
58
|
+
timeout=30,
|
|
59
|
+
) as client:
|
|
60
|
+
return self._read(await client.request(method, path, **kwargs))
|
|
61
|
+
|
|
62
|
+
def create_task(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
63
|
+
return self._request("POST", "/api/agent/tasks", json=payload)
|
|
64
|
+
|
|
65
|
+
async def acreate_task(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
66
|
+
return await self._arequest("POST", "/api/agent/tasks", json=payload)
|
|
67
|
+
|
|
68
|
+
def check_task(self, task_id: str) -> dict[str, Any]:
|
|
69
|
+
return self._request("GET", f"/api/tasks/{task_id}")
|
|
70
|
+
|
|
71
|
+
async def acheck_task(self, task_id: str) -> dict[str, Any]:
|
|
72
|
+
return await self._arequest("GET", f"/api/tasks/{task_id}")
|
|
73
|
+
|
|
74
|
+
def get_proof(self, task_id: str) -> dict[str, Any]:
|
|
75
|
+
payload = self.check_task(task_id)
|
|
76
|
+
task = payload.get("task") or {}
|
|
77
|
+
campaign = task.get("campaign") or {}
|
|
78
|
+
return {
|
|
79
|
+
"task_id": task.get("id"),
|
|
80
|
+
"title": task.get("title"),
|
|
81
|
+
"proof_requirements": campaign.get("proofRequirements") or [],
|
|
82
|
+
"evidence": task.get("evidence") or [],
|
|
83
|
+
"verification_status": task.get("status"),
|
|
84
|
+
"payment": payload.get("payment"),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async def aget_proof(self, task_id: str) -> dict[str, Any]:
|
|
88
|
+
payload = await self.acheck_task(task_id)
|
|
89
|
+
task = payload.get("task") or {}
|
|
90
|
+
campaign = task.get("campaign") or {}
|
|
91
|
+
return {
|
|
92
|
+
"task_id": task.get("id"),
|
|
93
|
+
"title": task.get("title"),
|
|
94
|
+
"proof_requirements": campaign.get("proofRequirements") or [],
|
|
95
|
+
"evidence": task.get("evidence") or [],
|
|
96
|
+
"verification_status": task.get("status"),
|
|
97
|
+
"payment": payload.get("payment"),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def list_categories(self) -> dict[str, Any]:
|
|
101
|
+
return self._request("GET", "/api/agent/tasks/categories")
|
|
102
|
+
|
|
103
|
+
async def alist_categories(self) -> dict[str, Any]:
|
|
104
|
+
return await self._arequest("GET", "/api/agent/tasks/categories")
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""AI2Human MCP server — expose human-execution tools to Claude and other MCP clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from .client import AI2HumanClient
|
|
11
|
+
|
|
12
|
+
mcp = FastMCP("ai2human")
|
|
13
|
+
client = AI2HumanClient()
|
|
14
|
+
|
|
15
|
+
TaskCategory = Literal[
|
|
16
|
+
"local_verification", "identity_action", "physical_task",
|
|
17
|
+
"digital_task", "compliance_check", "errand",
|
|
18
|
+
]
|
|
19
|
+
ProofType = Literal[
|
|
20
|
+
"screenshot", "photo", "url", "timestamp", "receipt", "signature", "notes",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _json(v: object) -> str:
|
|
25
|
+
return json.dumps(v, ensure_ascii=False, indent=2)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@mcp.tool(
|
|
29
|
+
name="ai2human_create_task",
|
|
30
|
+
description=(
|
|
31
|
+
"Dispatch a workflow step that software cannot complete alone to a human operator. "
|
|
32
|
+
"Use for local verification, physical checks, identity-bound actions, document review, "
|
|
33
|
+
"compliance checks, or errands. Payment remains gated by proof verification."
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
async def ai2human_create_task(
|
|
37
|
+
title: str,
|
|
38
|
+
description: str,
|
|
39
|
+
category: TaskCategory, # type: ignore[valid-type]
|
|
40
|
+
proof_requirements: list[ProofType], # type: ignore[valid-type]
|
|
41
|
+
reward_usdc: float,
|
|
42
|
+
deadline_hours: float,
|
|
43
|
+
location: str | None = None,
|
|
44
|
+
agent_name: str | None = None,
|
|
45
|
+
acceptance_criteria: str | None = None,
|
|
46
|
+
) -> str:
|
|
47
|
+
payload: dict[str, object] = {
|
|
48
|
+
"title": title,
|
|
49
|
+
"description": description,
|
|
50
|
+
"category": category,
|
|
51
|
+
"proof_requirements": proof_requirements,
|
|
52
|
+
"reward_usdc": reward_usdc,
|
|
53
|
+
"deadline_hours": deadline_hours,
|
|
54
|
+
}
|
|
55
|
+
if location is not None:
|
|
56
|
+
payload["location"] = location
|
|
57
|
+
if agent_name is not None:
|
|
58
|
+
payload["agent_name"] = agent_name
|
|
59
|
+
if acceptance_criteria is not None:
|
|
60
|
+
payload["acceptance_criteria"] = acceptance_criteria
|
|
61
|
+
|
|
62
|
+
result = await client.acreate_task(payload)
|
|
63
|
+
return _json(result)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@mcp.tool(
|
|
67
|
+
name="ai2human_check_task",
|
|
68
|
+
description="Check human execution, proof verification, and settlement state for a dispatched task.",
|
|
69
|
+
)
|
|
70
|
+
async def ai2human_check_task(task_id: str) -> str:
|
|
71
|
+
result = await client.acheck_task(task_id)
|
|
72
|
+
return _json(result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@mcp.tool(
|
|
76
|
+
name="ai2human_get_proof",
|
|
77
|
+
description="Retrieve the structured evidence bundle and recorded settlement receipt for a dispatched task.",
|
|
78
|
+
)
|
|
79
|
+
async def ai2human_get_proof(task_id: str) -> str:
|
|
80
|
+
result = await client.aget_proof(task_id)
|
|
81
|
+
return _json(result)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@mcp.tool(
|
|
85
|
+
name="ai2human_list_categories",
|
|
86
|
+
description="List supported reality-bound task categories and proof types before dispatching human work.",
|
|
87
|
+
)
|
|
88
|
+
async def ai2human_list_categories() -> str:
|
|
89
|
+
result = await client.alist_categories()
|
|
90
|
+
return _json(result)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main() -> None:
|
|
94
|
+
mcp.run(transport="stdio")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
File without changes
|