agentworld-mcp 1.0.0__py3-none-any.whl
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.
- agentworld_mcp/__init__.py +2 -0
- agentworld_mcp/server.py +121 -0
- agentworld_mcp/tools.json +2514 -0
- agentworld_mcp-1.0.0.dist-info/METADATA +118 -0
- agentworld_mcp-1.0.0.dist-info/RECORD +8 -0
- agentworld_mcp-1.0.0.dist-info/WHEEL +5 -0
- agentworld_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- agentworld_mcp-1.0.0.dist-info/top_level.txt +1 -0
agentworld_mcp/server.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""AgentWorld MCP server.
|
|
3
|
+
|
|
4
|
+
Serves the 164 AgentWorld tools over stdio and proxies every call to the
|
|
5
|
+
hosted AgentWorld endpoint (https://agentworld.me/mcp).
|
|
6
|
+
|
|
7
|
+
Free tools need no credentials. Paid tools answer HTTP 402 with x402 terms;
|
|
8
|
+
set AGENTWORLD_PAYMENT to a settled x402 receipt to have it forwarded as the
|
|
9
|
+
X-Payment header.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
from mcp import types
|
|
17
|
+
from mcp.server.mcpserver import MCPServer
|
|
18
|
+
from mcp.server.mcpserver.server import MCPTool
|
|
19
|
+
|
|
20
|
+
REMOTE_URL = os.environ.get("AGENTWORLD_MCP_URL", "https://agentworld.me/mcp")
|
|
21
|
+
PAYMENT = os.environ.get("AGENTWORLD_PAYMENT", "").strip()
|
|
22
|
+
TOOLS = json.loads((Path(__file__).parent / "tools.json").read_text(encoding="utf-8"))
|
|
23
|
+
|
|
24
|
+
class Remote:
|
|
25
|
+
"""Minimal streamable-HTTP MCP client: one session, reused across calls."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.session_id = None
|
|
29
|
+
self._next_id = 0
|
|
30
|
+
|
|
31
|
+
def _headers(self):
|
|
32
|
+
h = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"Accept": "application/json, text/event-stream",
|
|
35
|
+
}
|
|
36
|
+
if self.session_id:
|
|
37
|
+
h["Mcp-Session-Id"] = self.session_id
|
|
38
|
+
if PAYMENT:
|
|
39
|
+
h["X-Payment"] = PAYMENT
|
|
40
|
+
return h
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _payload(raw):
|
|
44
|
+
if raw.startswith("event:") or "\ndata:" in raw:
|
|
45
|
+
for line in raw.splitlines():
|
|
46
|
+
if line.startswith("data:"):
|
|
47
|
+
return json.loads(line[5:].strip())
|
|
48
|
+
return json.loads(raw)
|
|
49
|
+
|
|
50
|
+
def rpc(self, method, params=None):
|
|
51
|
+
self._next_id += 1
|
|
52
|
+
body = {"jsonrpc": "2.0", "id": self._next_id, "method": method, "params": params or {}}
|
|
53
|
+
with httpx.Client(timeout=90.0) as client:
|
|
54
|
+
r = client.post(REMOTE_URL, json=body, headers=self._headers())
|
|
55
|
+
if r.headers.get("mcp-session-id"):
|
|
56
|
+
self.session_id = r.headers["mcp-session-id"]
|
|
57
|
+
if r.status_code == 402:
|
|
58
|
+
return None, 402, r.text[:400]
|
|
59
|
+
r.raise_for_status()
|
|
60
|
+
return self._payload(r.text), r.status_code, None
|
|
61
|
+
|
|
62
|
+
def ensure_session(self):
|
|
63
|
+
if self.session_id:
|
|
64
|
+
return
|
|
65
|
+
self.rpc("initialize", {
|
|
66
|
+
"protocolVersion": "2025-06-18",
|
|
67
|
+
"capabilities": {},
|
|
68
|
+
"clientInfo": {"name": "agentworld-mcp", "version": "1.0.0"},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
def call(self, name, arguments):
|
|
72
|
+
self.ensure_session()
|
|
73
|
+
data, status, err = self.rpc("tools/call", {"name": name, "arguments": arguments or {}})
|
|
74
|
+
if status == 402:
|
|
75
|
+
return ("This tool is paid (x402, USDC on Base). The server answered 402.\n"
|
|
76
|
+
"Settle with the AgentPay facilitator, then set AGENTWORLD_PAYMENT to the "
|
|
77
|
+
"receipt and retry.\n\n" + (err or ""))
|
|
78
|
+
res = (data or {}).get("result") or {}
|
|
79
|
+
parts = []
|
|
80
|
+
for block in res.get("content") or []:
|
|
81
|
+
if block.get("type") == "text":
|
|
82
|
+
parts.append(block.get("text", ""))
|
|
83
|
+
if res.get("isError"):
|
|
84
|
+
parts.append("(tool reported an error)")
|
|
85
|
+
return "\n".join(p for p in parts if p) or json.dumps(res)[:2000]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
remote = Remote()
|
|
89
|
+
|
|
90
|
+
class AgentWorldServer(MCPServer):
|
|
91
|
+
"""Serves the AgentWorld tool catalog with its real input schemas."""
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
super().__init__(name="agentworld")
|
|
95
|
+
|
|
96
|
+
async def list_tools(self):
|
|
97
|
+
return [
|
|
98
|
+
MCPTool(
|
|
99
|
+
name=t["name"],
|
|
100
|
+
description=t["description"] or t["name"],
|
|
101
|
+
input_schema=t["inputSchema"],
|
|
102
|
+
)
|
|
103
|
+
for t in TOOLS
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
async def call_tool(self, name, arguments, context=None):
|
|
107
|
+
if not any(t["name"] == name for t in TOOLS):
|
|
108
|
+
return types.CallToolResult(
|
|
109
|
+
content=[types.TextContent(type="text", text="unknown tool: %s" % name)],
|
|
110
|
+
is_error=True,
|
|
111
|
+
)
|
|
112
|
+
text = remote.call(name, arguments or {})
|
|
113
|
+
return types.CallToolResult(content=[types.TextContent(type="text", text=text)])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main():
|
|
117
|
+
AgentWorldServer().run("stdio")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
main()
|