nodeproxy-tools 0.1.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.
@@ -0,0 +1,21 @@
1
+ """NodeProxy client + framework integrations for upstream agent packages."""
2
+
3
+ from nodeproxy_tools.client import NodeProxyClient, NodeProxyError, ParseResult
4
+ from nodeproxy_tools.constants import DEFAULT_API_URL, TOOL_DESCRIPTION, TOOL_NAME
5
+
6
+ try:
7
+ from nodeproxy_tools.langchain import NodeProxyMarkdownTool as LangChainNodeProxyTool
8
+ except ImportError:
9
+ LangChainNodeProxyTool = None # type: ignore[misc, assignment]
10
+
11
+ __all__ = [
12
+ "DEFAULT_API_URL",
13
+ "LangChainNodeProxyTool",
14
+ "NodeProxyClient",
15
+ "NodeProxyError",
16
+ "ParseResult",
17
+ "TOOL_DESCRIPTION",
18
+ "TOOL_NAME",
19
+ ]
20
+
21
+ __version__ = "0.1.0"
nodeproxy_tools/cli.py ADDED
@@ -0,0 +1,29 @@
1
+ """CLI entrypoint for quick NodeProxy smoke tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from nodeproxy_tools.client import NodeProxyClient, NodeProxyError
8
+
9
+
10
+ def main() -> None:
11
+ if len(sys.argv) < 2:
12
+ print("Usage: nodeproxy-parse <url>", file=sys.stderr)
13
+ raise SystemExit(2)
14
+
15
+ url = sys.argv[1]
16
+ client = NodeProxyClient()
17
+ try:
18
+ result = client.parse_url(url)
19
+ except NodeProxyError as exc:
20
+ print(f"Error: {exc}", file=sys.stderr)
21
+ raise SystemExit(1) from exc
22
+
23
+ if result.transaction:
24
+ print(f"# settled: {result.transaction} ({result.network})", file=sys.stderr)
25
+ print(result.markdown)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,123 @@
1
+ """x402-aware HTTP client for the NodeProxy surface markdown parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from dataclasses import dataclass
8
+ from typing import Any, Optional
9
+
10
+ from nodeproxy_tools.constants import DEFAULT_API_URL, TOOL_NAME
11
+
12
+
13
+ class NodeProxyError(Exception):
14
+ """Raised when NodeProxy returns a non-success response."""
15
+
16
+
17
+ @dataclass
18
+ class ParseResult:
19
+ """Successful parse payload."""
20
+
21
+ markdown: str
22
+ transaction: Optional[str] = None
23
+ network: Optional[str] = None
24
+ raw: Optional[dict[str, Any]] = None
25
+
26
+
27
+ class NodeProxyClient:
28
+ """
29
+ Calls NodeProxy ``POST /mcp/execute`` with automatic x402 payment handling.
30
+
31
+ Requires ``EVM_PRIVATE_KEY`` (or pass ``evm_private_key=``) with USDC on the
32
+ network your NodeProxy deployment uses (Base Sepolia testnet or Base mainnet).
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *,
38
+ api_url: str = DEFAULT_API_URL,
39
+ evm_private_key: Optional[str] = None,
40
+ max_price_usdc: float = 0.05,
41
+ ) -> None:
42
+ self.api_url = api_url.rstrip("/")
43
+ self.evm_private_key = evm_private_key or os.getenv("EVM_PRIVATE_KEY")
44
+ self.max_price_usdc = max_price_usdc
45
+ self._session = None
46
+ self._x402_client = None
47
+
48
+ def _ensure_x402(self) -> None:
49
+ if self._x402_client is not None:
50
+ return
51
+ if not self.evm_private_key:
52
+ raise NodeProxyError(
53
+ "EVM_PRIVATE_KEY is required for paid NodeProxy calls. "
54
+ "Set the env var or pass evm_private_key= to NodeProxyClient()."
55
+ )
56
+
57
+ try:
58
+ from eth_account import Account
59
+ from x402 import x402ClientSync
60
+ from x402.http.clients import x402_requests
61
+ from x402.mechanisms.evm import EthAccountSigner
62
+ from x402.mechanisms.evm.exact.register import register_exact_evm_client
63
+ except ImportError as exc:
64
+ raise NodeProxyError(
65
+ 'Install x402 extras: pip install "nodeproxy-tools[x402]"'
66
+ ) from exc
67
+
68
+ client = x402ClientSync()
69
+ account = Account.from_key(self.evm_private_key)
70
+ register_exact_evm_client(client, EthAccountSigner(account))
71
+ self._x402_client = client
72
+ self._session = x402_requests(client)
73
+
74
+ def parse_url(self, url: str) -> ParseResult:
75
+ """Fetch *url* via NodeProxy and return cleaned Markdown."""
76
+ self._ensure_x402()
77
+ assert self._session is not None
78
+
79
+ payload = {"tool": TOOL_NAME, "arguments": {"url": url}}
80
+
81
+ with self._session as session:
82
+ response = session.post(
83
+ self.api_url,
84
+ json=payload,
85
+ headers={"Content-Type": "application/json"},
86
+ timeout=120,
87
+ )
88
+
89
+ if response.status_code != 200:
90
+ detail = response.text[:500]
91
+ try:
92
+ detail = response.json().get("error", detail)
93
+ except json.JSONDecodeError:
94
+ pass
95
+ raise NodeProxyError(f"NodeProxy HTTP {response.status_code}: {detail}")
96
+
97
+ data = response.json()
98
+ content = data.get("content") or []
99
+ if not content or not content[0].get("text"):
100
+ raise NodeProxyError(f"Unexpected NodeProxy response shape: {data!r}")
101
+
102
+ settlement = data.get("settlement") or {}
103
+ return ParseResult(
104
+ markdown=content[0]["text"],
105
+ transaction=settlement.get("transaction"),
106
+ network=settlement.get("network"),
107
+ raw=data,
108
+ )
109
+
110
+ def parse_url_text(self, url: str) -> str:
111
+ """Convenience wrapper returning Markdown string only."""
112
+ return self.parse_url(url).markdown
113
+
114
+ def close(self) -> None:
115
+ """Release underlying HTTP session if held."""
116
+ self._session = None
117
+ self._x402_client = None
118
+
119
+ def __enter__(self) -> "NodeProxyClient":
120
+ return self
121
+
122
+ def __exit__(self, *args: object) -> None:
123
+ self.close()
@@ -0,0 +1,9 @@
1
+ DEFAULT_API_URL = "https://nodeproxy-production.up.railway.app/mcp/execute"
2
+
3
+ TOOL_NAME = "surface_markdown_parser"
4
+
5
+ TOOL_DESCRIPTION = (
6
+ "Executes fetch on any public URL, strips scripts/ads/nav noise, and returns "
7
+ "compressed semantic Markdown optimized for LLM token ingestion. "
8
+ "Paid per request via x402 USDC micropayment."
9
+ )
@@ -0,0 +1,34 @@
1
+ """CrewAI ``BaseTool`` wrapper for NodeProxy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Type
6
+
7
+ from crewai.tools import BaseTool
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from nodeproxy_tools.client import NodeProxyClient, NodeProxyError
11
+ from nodeproxy_tools.constants import TOOL_DESCRIPTION, TOOL_NAME
12
+
13
+
14
+ class NodeProxyInput(BaseModel):
15
+ """Input schema for the NodeProxy markdown parser."""
16
+
17
+ url: str = Field(..., description="Public website URL to parse into LLM-ready Markdown.")
18
+
19
+
20
+ class NodeProxyMarkdownTool(BaseTool):
21
+ """CrewAI tool for the NodeProxy x402-gated web surface parser."""
22
+
23
+ name: str = TOOL_NAME
24
+ description: str = TOOL_DESCRIPTION
25
+ args_schema: Type[BaseModel] = NodeProxyInput
26
+
27
+ client: NodeProxyClient = Field(default_factory=NodeProxyClient)
28
+ model_config = ConfigDict(arbitrary_types_allowed=True)
29
+
30
+ def _run(self, url: str) -> str:
31
+ try:
32
+ return self.client.parse_url_text(url)
33
+ except NodeProxyError as exc:
34
+ return f"NodeProxy error: {exc}"
@@ -0,0 +1,43 @@
1
+ """LangChain ``BaseTool`` wrapper for NodeProxy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, Type
6
+
7
+ from langchain_core.callbacks import CallbackManagerForToolRun
8
+ from langchain_core.tools import BaseTool
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ from nodeproxy_tools.client import NodeProxyClient, NodeProxyError
12
+ from nodeproxy_tools.constants import TOOL_DESCRIPTION, TOOL_NAME
13
+
14
+
15
+ class NodeProxyInput(BaseModel):
16
+ """Input schema for the NodeProxy markdown parser."""
17
+
18
+ url: str = Field(description="Public website URL to parse into LLM-ready Markdown.")
19
+
20
+
21
+ class NodeProxyMarkdownTool(BaseTool):
22
+ """
23
+ LangChain tool that calls the NodeProxy x402-gated web surface parser.
24
+
25
+ .. versionadded:: 0.1.0
26
+ """
27
+
28
+ name: str = TOOL_NAME
29
+ description: str = TOOL_DESCRIPTION
30
+ args_schema: Type[BaseModel] = NodeProxyInput
31
+
32
+ client: NodeProxyClient = Field(default_factory=NodeProxyClient)
33
+ model_config = ConfigDict(arbitrary_types_allowed=True)
34
+
35
+ def _run(
36
+ self,
37
+ url: str,
38
+ run_manager: Optional[CallbackManagerForToolRun] = None,
39
+ ) -> str:
40
+ try:
41
+ return self.client.parse_url_text(url)
42
+ except NodeProxyError as exc:
43
+ return f"NodeProxy error: {exc}"
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: nodeproxy-tools
3
+ Version: 0.1.0
4
+ Summary: LangChain/CrewAI integrations for the NodeProxy x402 web surface markdown parser
5
+ Author-email: pgalyen1987 <pgalyen87@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pgalyen1987/NodeProxy
8
+ Project-URL: Documentation, https://github.com/pgalyen1987/NodeProxy/tree/main/integrations
9
+ Project-URL: Repository, https://github.com/pgalyen1987/NodeProxy
10
+ Keywords: langchain,crewai,mcp,x402,web-scraping,markdown,agents
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: pydantic>=2.0
18
+ Requires-Dist: requests>=2.31
19
+ Provides-Extra: x402
20
+ Requires-Dist: x402[evm,requests]>=2.0; extra == "x402"
21
+ Requires-Dist: eth-account>=0.10; extra == "x402"
22
+ Provides-Extra: langchain
23
+ Requires-Dist: langchain-core>=0.2; extra == "langchain"
24
+ Requires-Dist: x402[evm,requests]>=2.0; extra == "langchain"
25
+ Requires-Dist: eth-account>=0.10; extra == "langchain"
26
+ Provides-Extra: crewai
27
+ Requires-Dist: crewai>=0.80; extra == "crewai"
28
+ Requires-Dist: x402[evm,requests]>=2.0; extra == "crewai"
29
+ Requires-Dist: eth-account>=0.10; extra == "crewai"
30
+ Provides-Extra: all
31
+ Requires-Dist: nodeproxy-tools[crewai,langchain]; extra == "all"
32
+
33
+ # nodeproxy-tools
34
+
35
+ Python client and framework wrappers for [NodeProxy](https://github.com/pgalyen1987/NodeProxy).
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install "nodeproxy-tools[x402,langchain]"
41
+ export EVM_PRIVATE_KEY=0x...
42
+ ```
43
+
44
+ ## LangChain
45
+
46
+ ```python
47
+ from nodeproxy_tools.langchain import NodeProxyMarkdownTool
48
+
49
+ tool = NodeProxyMarkdownTool()
50
+ print(tool.invoke({"url": "https://example.com"}))
51
+ ```
52
+
53
+ ## CrewAI
54
+
55
+ ```python
56
+ from nodeproxy_tools.crewai import NodeProxyMarkdownTool
57
+
58
+ tool = NodeProxyMarkdownTool()
59
+ print(tool._run("https://example.com"))
60
+ ```
61
+
62
+ ## CLI
63
+
64
+ ```bash
65
+ nodeproxy-parse https://example.com
66
+ ```
@@ -0,0 +1,11 @@
1
+ nodeproxy_tools/__init__.py,sha256=ijJwzcsRbjGY6tKBtMyaPAntHfF2yHXgKKPEHAFPtjQ,621
2
+ nodeproxy_tools/cli.py,sha256=2SBTzLK_aWSHSupluxDetpMBImkHskkmc9ST7AKDbzo,711
3
+ nodeproxy_tools/client.py,sha256=-2MeQgzIkAnTIui9TgW9yJSHFOi7gVoCsjn2I3NPhJI,4025
4
+ nodeproxy_tools/constants.py,sha256=IDCXtkpKDG2QblgxGtPFEgQk8Jc4TMcbxnojcYh0nrw,344
5
+ nodeproxy_tools/crewai.py,sha256=7XEm---47oSOfLA6SJpIBDPZQwk2yW9GMdBXWNDTHY4,1061
6
+ nodeproxy_tools/langchain.py,sha256=R4sQ6uvWb6eELVVw-ExenWwmhcad0nHpcK94t5gA5Qw,1277
7
+ nodeproxy_tools-0.1.0.dist-info/METADATA,sha256=MmCCZrRKLbKZ30E3M9x7bmshNTLkAMtDDRliuZ-FTtc,2012
8
+ nodeproxy_tools-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ nodeproxy_tools-0.1.0.dist-info/entry_points.txt,sha256=AbGnGzuqm87ARxarLlVNtKN2nKG1fHsUKHdKa_0BwIA,61
10
+ nodeproxy_tools-0.1.0.dist-info/top_level.txt,sha256=VjbAFlZTJNnHwIcsTkXQYyZVV6xnsoX_dSbZuDFZT_Y,16
11
+ nodeproxy_tools-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nodeproxy-parse = nodeproxy_tools.cli:main
@@ -0,0 +1 @@
1
+ nodeproxy_tools