langchain-tensorust 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.
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-11-15)
4
+
5
+ First release.
6
+
7
+ - Seven LangChain tools over the Tensorust Ontology, each with a typed input schema and sync and
8
+ async paths: `TensorustLookupOrganization`, `TensorustWhoOwnsHome`, `TensorustLenderMarks`,
9
+ `TensorustContractorCredit`, `TensorustSanctionsScreen`, `TensorustIDROffer`,
10
+ `TensorustDatasetSearch`.
11
+ - `TensorustToolkit` returns them sharing one client; `include=` picks a subset.
12
+ `aget_mcp_tools()` loads every tool on the Tensorust MCP server through
13
+ `langchain-mcp-adapters` (extra `mcp`).
14
+ - Keyless by default (free previews). `TENSORUST_API_KEY` sends a bearer token and asks for full
15
+ records. The endpoint must be https (http only for localhost).
16
+ - Errors (bad input, 401, 402, 429, tool errors) come back to the agent as text instead of
17
+ raising.
18
+ - Passes the LangChain standard tool tests (`langchain-tests`) for every tool.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Busel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-tensorust
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for the Tensorust Ontology: who owns or controls US companies and homes, private credit loan marks, contractor credit, OFAC and banned-vendor screens, from public records.
5
+ Project-URL: Homepage, https://github.com/Mattbusel/langchain-tensorust
6
+ Project-URL: Documentation, https://github.com/Mattbusel/langchain-tensorust/blob/main/docs/index.md
7
+ Project-URL: Changelog, https://github.com/Mattbusel/langchain-tensorust/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/Mattbusel/langchain-tensorust/issues
9
+ Author: Matthew Busel
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,kyb,langchain,mcp,ofac,ownership,private-credit,sanctions,tools
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: httpx<1,>=0.27
24
+ Requires-Dist: langchain-core<2,>=1.0
25
+ Requires-Dist: pydantic<3,>=2.7
26
+ Provides-Extra: mcp
27
+ Requires-Dist: langchain-mcp-adapters>=0.1; extra == 'mcp'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # langchain-tensorust
31
+
32
+ LangChain tools for the [Tensorust Ontology](https://tensorust.com): ownership, private
33
+ credit and compliance lookups built from US public records (SEC subsidiary lists and BDC
34
+ schedules of investments, state business registries, county property rolls, FCC, federal
35
+ awards, OFAC, the CMS Federal IDR file).
36
+
37
+ ```bash
38
+ pip install -U langchain-tensorust
39
+ ```
40
+
41
+ ```python
42
+ from langchain.agents import create_agent
43
+ from langchain_tensorust import TensorustToolkit
44
+
45
+ agent = create_agent("anthropic:claude-sonnet-4-6", tools=TensorustToolkit().get_tools())
46
+ agent.invoke({"messages": [("user", "Who controls Invitation Homes, and what do lenders mark Pluralsight's loan at?")]})
47
+ ```
48
+
49
+ | Tool | Answers |
50
+ |---|---|
51
+ | `TensorustLookupOrganization` | Who owns or controls a company: parent, control group, subsidiaries, homes, FCC licenses, awards, rules that apply |
52
+ | `TensorustWhoOwnsHome` | Owner of record of a US single-family home and its parent control group |
53
+ | `TensorustLenderMarks` | What each BDC lender marks a private company's first-lien loan at, and the spread |
54
+ | `TensorustContractorCredit` | Credit health of a federal contractor: obligations, lender marks, headcount trend |
55
+ | `TensorustSanctionsScreen` | OFAC plus DoD 1260H, Section 889, FCC Covered List, UFLPA and BIOSECURE screen |
56
+ | `TensorustIDROffer` | No Surprises Act arbitration outcomes for a CPT code, payer and state |
57
+ | `TensorustDatasetSearch` | Search 145 entity-level datasets built from public records |
58
+
59
+ No key is needed: every tool answers with a free preview. Set `TENSORUST_API_KEY` to a
60
+ `tdk_...` key for full records. `await TensorustToolkit().aget_mcp_tools()` (with the `mcp`
61
+ extra) loads every tool on the Tensorust MCP server, about 40.
62
+
63
+ Docs: https://github.com/Mattbusel/langchain-tensorust/blob/main/docs/index.md
@@ -0,0 +1,113 @@
1
+ # Tensorust tools for LangChain
2
+
3
+ `langchain-tensorust` gives LangChain agents ownership, private credit and compliance lookups
4
+ built from US public records: SEC subsidiary lists and BDC schedules of investments, state
5
+ business registries, county property rolls, FCC licensing, federal awards, OFAC and the CMS
6
+ Federal IDR public use file. The data comes from the [Tensorust Ontology](https://tensorust.com).
7
+
8
+ ## Overview
9
+
10
+ | Class | Package | Serializable | JS support | Version |
11
+ |---|---|---|---|---|
12
+ | `TensorustToolkit` | `langchain-tensorust` | no | no | 0.1.0 |
13
+
14
+ ### Tools
15
+
16
+ | Tool | Remote tool | What it answers |
17
+ |---|---|---|
18
+ | `TensorustLookupOrganization` | `lookup_organization` | Who owns or controls a company: parent, control group, subsidiaries, homes, FCC licenses, federal awards, and the federal rules that apply to it |
19
+ | `TensorustWhoOwnsHome` | `who_owns_home` | Owner of record of a US single-family home and its parent control group (17 large counties) |
20
+ | `TensorustLenderMarks` | `lender_marks` | What each BDC lender marks a private company's first-lien loan at in its latest 10-Q, and the spread between lenders |
21
+ | `TensorustContractorCredit` | `contractor_credit` | Credit health of a federal contractor: obligations by agency, lender marks and their trend, headcount |
22
+ | `TensorustSanctionsScreen` | `query_ontology` + `exposure_screen_purchases` | OFAC match plus DoD 1260H, Section 889, FCC Covered List, UFLPA and BIOSECURE screen, with the rule and citation |
23
+ | `TensorustIDROffer` | `idr_offer` | No Surprises Act arbitration outcomes for a CPT/HCPCS code, payer and state |
24
+ | `TensorustDatasetSearch` | `list_datasets` | Search 145 entity-level datasets (money owed, at risk, unclaimed, about to expire) |
25
+
26
+ Every tool has a typed input schema and both sync (`invoke`) and async (`ainvoke`) paths.
27
+
28
+ ## Setup
29
+
30
+ ```bash
31
+ pip install -U langchain-tensorust
32
+ # every tool on the Tensorust MCP server, through langchain-mcp-adapters:
33
+ pip install -U "langchain-tensorust[mcp]"
34
+ ```
35
+
36
+ ### Credentials
37
+
38
+ None needed to start. Without a key every tool returns a free preview (counts, the top
39
+ matches, the headline numbers). With a Tensorust key the tools ask for the full record:
40
+
41
+ ```bash
42
+ export TENSORUST_API_KEY="tdk_..."
43
+ ```
44
+
45
+ or pass it in code: `TensorustToolkit(api_wrapper=TensorustAPIWrapper(api_key="tdk_..."))`.
46
+ Keys: https://tensorust.com. `TENSORUST_API_URL` overrides the endpoint (it must be https;
47
+ plain http is allowed only for localhost).
48
+
49
+ ## Instantiation
50
+
51
+ ```python
52
+ from langchain_tensorust import TensorustToolkit
53
+
54
+ toolkit = TensorustToolkit()
55
+ tools = toolkit.get_tools()
56
+
57
+ # only some of them
58
+ toolkit = TensorustToolkit(include=["tensorust_lender_marks", "tensorust_sanctions_screen"])
59
+ ```
60
+
61
+ ## Invocation
62
+
63
+ ```python
64
+ from langchain_tensorust import TensorustLenderMarks, TensorustSanctionsScreen
65
+
66
+ TensorustLenderMarks().invoke({"company": "Pluralsight"})
67
+ # {"company": "Pluralsight LLC", "as_of": "2026-06-30", "lowest_mark": 0.808, "lowest_manager": "Blackrock", ...}
68
+
69
+ TensorustSanctionsScreen().invoke({"name": "Hikvision"})
70
+ # {"sanctions": {"total": 1, ...}, "restricted_vendor_lists": {"by_vendor": {"hikvision": {"lists": ["1260H", "FCC_COVERED", "SEC889"]}}, "flags": [...]}}
71
+ ```
72
+
73
+ Tools return JSON strings. Errors (empty input, 401 bad key, 402 paid call, 429 rate limit, no
74
+ match) come back to the model as text, so the agent can correct itself instead of crashing.
75
+
76
+ ## Use within an agent
77
+
78
+ ```python
79
+ from langchain.agents import create_agent
80
+ from langchain_tensorust import TensorustToolkit
81
+
82
+ agent = create_agent(
83
+ "anthropic:claude-sonnet-4-6",
84
+ tools=TensorustToolkit().get_tools(),
85
+ system_prompt="You are a counterparty analyst. Cite the numbers the tools return.",
86
+ )
87
+ agent.invoke({"messages": [("user", "Run a counterparty check on Peraton.")]})
88
+ ```
89
+
90
+ See [`examples/kyb_agent.py`](../examples/kyb_agent.py).
91
+
92
+ ## Every tool on the MCP server
93
+
94
+ The Tensorust endpoint is also an MCP server with about 40 tools (FCC Upper C-band
95
+ clearinghouse checks, E-Rate deadlines, the ROAD Act investor screen, research-security
96
+ exposure by institution, small-business set-aside performance, aircraft owners and more).
97
+ `aget_mcp_tools()` loads all of them through `langchain-mcp-adapters`, with your key attached:
98
+
99
+ ```python
100
+ import asyncio
101
+ from langchain_tensorust import TensorustToolkit
102
+
103
+ tools = asyncio.run(TensorustToolkit().aget_mcp_tools())
104
+ ```
105
+
106
+ `toolkit.mcp_connection()` returns the connection dict if you already run a
107
+ `MultiServerMCPClient` with other servers.
108
+
109
+ ## API reference
110
+
111
+ - `TensorustAPIWrapper(api_key=None, base_url=None, timeout=30.0)`: the client. `call(tool, arguments)` / `acall(...)` call any remote tool by name.
112
+ - `TensorustToolkit(api_wrapper=..., include=None)`: `get_tools()`, `aget_mcp_tools()`, `mcp_connection()`.
113
+ - Source: https://github.com/Mattbusel/langchain-tensorust (MIT).
@@ -0,0 +1,21 @@
1
+ """Load every tool on the Tensorust MCP server (about 40) through langchain-mcp-adapters.
2
+
3
+ pip install "langchain-tensorust[mcp]"
4
+ python examples/all_mcp_tools.py
5
+ """
6
+
7
+ import asyncio
8
+
9
+ from langchain_tensorust import TensorustToolkit
10
+
11
+
12
+ async def main() -> None:
13
+ tools = await TensorustToolkit().aget_mcp_tools()
14
+ print(f"{len(tools)} tools")
15
+ for tool in tools:
16
+ print(f"- {tool.name}: {tool.description.splitlines()[0][:90]}")
17
+ rule = next(t for t in tools if t.name == "rule_coverage")
18
+ print(await rule.ainvoke({}))
19
+
20
+
21
+ asyncio.run(main())
@@ -0,0 +1,24 @@
1
+ """Call the Tensorust tools directly, no LLM needed. Runs keyless (free previews).
2
+
3
+ pip install langchain-tensorust
4
+ python examples/direct_calls.py
5
+ """
6
+
7
+ import json
8
+
9
+ from langchain_tensorust import TensorustToolkit
10
+
11
+ tools = {tool.name: tool for tool in TensorustToolkit().get_tools()}
12
+
13
+ card = json.loads(tools["tensorust_lookup_organization"].invoke({"name": "Invitation Homes"}))
14
+ subsidiaries = card.get("links", {}).get("~SUBSIDIARY_OF", {}).get("count")
15
+ print(f"{card['name']}: {subsidiaries} subsidiaries on record, sources {card['sources']}")
16
+
17
+ marks = json.loads(tools["tensorust_lender_marks"].invoke({"company": "Pluralsight"}))
18
+ print(f"{marks['company']}: lowest lender mark {marks['lowest_mark']} ({marks['lowest_manager']})")
19
+
20
+ screen = json.loads(tools["tensorust_sanctions_screen"].invoke({"name": "Hikvision"}))
21
+ vendors = screen["restricted_vendor_lists"].get("by_vendor", {})
22
+ lists = sorted({name for v in vendors.values() for name in v.get("lists", [])})
23
+ rules = [flag["rule"] for flag in screen["restricted_vendor_lists"].get("flags", [])]
24
+ print(f"Hikvision: {screen['sanctions']['total']} OFAC match(es), lists {lists}, rules {rules}")
@@ -0,0 +1,29 @@
1
+ """A small KYB / counterparty agent on LangChain v1 with the Tensorust tools.
2
+
3
+ pip install langchain-tensorust langchain-anthropic
4
+ export ANTHROPIC_API_KEY=...
5
+ export TENSORUST_API_KEY=tdk_... # optional: full records instead of previews
6
+ python examples/kyb_agent.py "Peraton"
7
+ """
8
+
9
+ import sys
10
+
11
+ from langchain.agents import create_agent
12
+
13
+ from langchain_tensorust import TensorustToolkit
14
+
15
+ company = sys.argv[1] if len(sys.argv) > 1 else "Peraton"
16
+
17
+ agent = create_agent(
18
+ "anthropic:claude-sonnet-4-6",
19
+ tools=TensorustToolkit().get_tools(),
20
+ system_prompt=(
21
+ "You are a counterparty analyst. Use the Tensorust tools to find who controls the "
22
+ "company, whether it is on a sanctions or restricted-vendor list, and what its private "
23
+ "credit lenders mark its loan at. Cite the numbers the tools return. A screen is a "
24
+ "starting point for review, not a legal determination."
25
+ ),
26
+ )
27
+
28
+ result = agent.invoke({"messages": [("user", f"Run a counterparty check on {company}.")]})
29
+ print(result["messages"][-1].content)
@@ -0,0 +1,33 @@
1
+ """LangChain integration for the Tensorust Ontology.
2
+
3
+ Ownership, private credit and compliance lookups from US public records (SEC, state business
4
+ registries, county property rolls, FCC, federal awards, OFAC, CMS) as LangChain tools.
5
+ """
6
+
7
+ from langchain_tensorust._client import DEFAULT_BASE_URL, TensorustAPIWrapper
8
+ from langchain_tensorust.toolkits import TensorustToolkit
9
+ from langchain_tensorust.tools import (
10
+ TensorustContractorCredit,
11
+ TensorustDatasetSearch,
12
+ TensorustIDROffer,
13
+ TensorustLenderMarks,
14
+ TensorustLookupOrganization,
15
+ TensorustSanctionsScreen,
16
+ TensorustWhoOwnsHome,
17
+ )
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ __all__ = [
22
+ "DEFAULT_BASE_URL",
23
+ "TensorustAPIWrapper",
24
+ "TensorustContractorCredit",
25
+ "TensorustDatasetSearch",
26
+ "TensorustIDROffer",
27
+ "TensorustLenderMarks",
28
+ "TensorustLookupOrganization",
29
+ "TensorustSanctionsScreen",
30
+ "TensorustToolkit",
31
+ "TensorustWhoOwnsHome",
32
+ "__version__",
33
+ ]
@@ -0,0 +1,189 @@
1
+ """HTTP client for the Tensorust Ontology endpoint (JSON-RPC ``tools/call``)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from typing import Any
8
+ from urllib.parse import urlsplit
9
+
10
+ import httpx
11
+ from langchain_core.tools import ToolException
12
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
13
+
14
+ DEFAULT_BASE_URL = "https://shop.tensorust.com/api/mcp"
15
+ _LOOPBACK = {"localhost", "127.0.0.1", "::1"}
16
+ _USER_AGENT = "langchain-tensorust"
17
+
18
+
19
+ class TensorustAPIWrapper(BaseModel):
20
+ """Calls the Tensorust Ontology over HTTPS.
21
+
22
+ The endpoint speaks MCP's JSON-RPC, so a plain ``tools/call`` POST works without an MCP
23
+ session or SDK. With no API key every tool answers with its free preview; with a
24
+ ``tdk_...`` key (argument ``api_key`` or env ``TENSORUST_API_KEY``) the key is sent as a
25
+ bearer token and tools that support it return the full record.
26
+
27
+ Example:
28
+ ```python
29
+ from langchain_tensorust import TensorustAPIWrapper
30
+
31
+ api = TensorustAPIWrapper() # keyless: free previews
32
+ print(api.call("lookup_organization", {"name": "Invitation Homes"}))
33
+ ```
34
+ """
35
+
36
+ model_config = ConfigDict(extra="forbid")
37
+
38
+ api_key: SecretStr | None = Field(default=None)
39
+ """Tensorust API key (``tdk_...``). Falls back to ``TENSORUST_API_KEY``. Optional."""
40
+
41
+ base_url: str = Field(default="")
42
+ """Endpoint URL. Falls back to ``TENSORUST_API_URL``, then the public endpoint."""
43
+
44
+ timeout: float = 30.0
45
+ """Request timeout in seconds."""
46
+
47
+ @model_validator(mode="before")
48
+ @classmethod
49
+ def _from_env(cls, values: Any) -> Any:
50
+ if not isinstance(values, dict):
51
+ return values
52
+ if not values.get("api_key"):
53
+ key = os.environ.get("TENSORUST_API_KEY", "").strip()
54
+ if key:
55
+ values["api_key"] = key
56
+ if not values.get("base_url"):
57
+ values["base_url"] = os.environ.get("TENSORUST_API_URL", "").strip() or DEFAULT_BASE_URL
58
+ return values
59
+
60
+ @model_validator(mode="after")
61
+ def _check_url(self) -> TensorustAPIWrapper:
62
+ self.base_url = self.base_url.rstrip("/")
63
+ parsed = urlsplit(self.base_url)
64
+ ok = bool(parsed.hostname) and (
65
+ parsed.scheme == "https" or (parsed.scheme == "http" and parsed.hostname in _LOOPBACK)
66
+ )
67
+ if not ok:
68
+ # The key travels as a bearer token, so never send it over plain http to a remote host.
69
+ msg = f"Tensorust base_url must be an https:// URL with a host, got {self.base_url!r}"
70
+ raise ValueError(msg)
71
+ return self
72
+
73
+ @property
74
+ def has_key(self) -> bool:
75
+ """True when an API key is configured (tools then ask for the full record)."""
76
+ return bool(self.api_key and self.api_key.get_secret_value().strip())
77
+
78
+ def headers(self) -> dict[str, str]:
79
+ """Request headers, with the bearer token when a key is configured."""
80
+ headers = {
81
+ "Content-Type": "application/json",
82
+ "Accept": "application/json",
83
+ "User-Agent": _USER_AGENT,
84
+ }
85
+ if self.has_key and self.api_key is not None:
86
+ headers["Authorization"] = f"Bearer {self.api_key.get_secret_value().strip()}"
87
+ return headers
88
+
89
+ def with_full(self, arguments: dict[str, Any]) -> dict[str, Any]:
90
+ """Add ``full: true`` when a key is configured; keyless calls get the preview."""
91
+ return {**arguments, "full": True} if self.has_key else arguments
92
+
93
+ @staticmethod
94
+ def payload(tool: str, arguments: dict[str, Any]) -> dict[str, Any]:
95
+ """The JSON-RPC body for one ``tools/call``."""
96
+ return {
97
+ "jsonrpc": "2.0",
98
+ "id": 1,
99
+ "method": "tools/call",
100
+ "params": {"name": tool, "arguments": arguments},
101
+ }
102
+
103
+ def call(self, tool: str, arguments: dict[str, Any]) -> str:
104
+ """Call one Tensorust tool and return its text answer.
105
+
106
+ Raises:
107
+ ToolException: on a transport error, an HTTP error or a tool error, with a
108
+ message the agent can act on.
109
+ """
110
+ try:
111
+ response = httpx.post(
112
+ self.base_url,
113
+ json=self.payload(tool, arguments),
114
+ headers=self.headers(),
115
+ timeout=self.timeout,
116
+ )
117
+ except httpx.HTTPError as e:
118
+ msg = f"Tensorust request failed: {e}"
119
+ raise ToolException(msg) from e
120
+ return parse_response(response)
121
+
122
+ async def acall(self, tool: str, arguments: dict[str, Any]) -> str:
123
+ """Async :meth:`call`."""
124
+ try:
125
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
126
+ response = await client.post(
127
+ self.base_url, json=self.payload(tool, arguments), headers=self.headers()
128
+ )
129
+ except httpx.HTTPError as e:
130
+ msg = f"Tensorust request failed: {e}"
131
+ raise ToolException(msg) from e
132
+ return parse_response(response)
133
+
134
+
135
+ def parse_response(response: httpx.Response) -> str:
136
+ """Turn a JSON-RPC response into the tool's text, or raise ToolException."""
137
+ if not response.is_success:
138
+ label = {
139
+ 401: "Tensorust authentication failed (401): check TENSORUST_API_KEY",
140
+ 402: "This Tensorust call needs a paid key or payment (402)",
141
+ 429: "Tensorust rate limit exceeded (429)",
142
+ }.get(response.status_code, f"Tensorust API error ({response.status_code})")
143
+ raise ToolException(label)
144
+ try:
145
+ body = response.json()
146
+ except ValueError as e:
147
+ msg = "The Tensorust API returned a non-JSON response."
148
+ raise ToolException(msg) from e
149
+ if not isinstance(body, dict):
150
+ msg = "Unexpected response from the Tensorust API."
151
+ raise ToolException(msg)
152
+ if body.get("error"):
153
+ error = body["error"]
154
+ message = error.get("message") if isinstance(error, dict) else str(error)
155
+ raise ToolException(f"Tensorust API error: {message}")
156
+ result = body.get("result")
157
+ content = result.get("content") if isinstance(result, dict) else None
158
+ if not isinstance(content, list):
159
+ msg = "Unexpected response from the Tensorust API."
160
+ raise ToolException(msg)
161
+ text = "\n".join(str(part.get("text", "")) for part in content if isinstance(part, dict))
162
+ if isinstance(result, dict) and result.get("isError"):
163
+ raise ToolException(text or "The Tensorust tool returned an error.")
164
+ return text
165
+
166
+
167
+ def merge_screen(name: str, sanctions: str, vendors: str) -> str:
168
+ """Combine the OFAC search and the restricted-vendor screen into one JSON answer."""
169
+ result: dict[str, Any] = {"name": name}
170
+ try:
171
+ found = json.loads(sanctions)
172
+ result["sanctions"] = {
173
+ "source": "OFAC sanctions lists",
174
+ "total": found.get("total", 0),
175
+ "matches": found.get("items", []),
176
+ }
177
+ except (ValueError, AttributeError):
178
+ result["sanctions"] = {"error": sanctions}
179
+ try:
180
+ screen = json.loads(vendors)
181
+ keep = ("as_of", "lists_pulled", "matched_lines", "by_vendor", "by_rule")
182
+ section = {key: screen[key] for key in keep if key in screen}
183
+ section["flags"] = [
184
+ flag for item in screen.get("items", []) for flag in item.get("flags", [])
185
+ ]
186
+ result["restricted_vendor_lists"] = section
187
+ except (ValueError, AttributeError, TypeError):
188
+ result["restricted_vendor_lists"] = {"error": vendors}
189
+ return json.dumps(result, indent=2)
File without changes
@@ -0,0 +1,114 @@
1
+ """TensorustToolkit: the curated tools, or every tool on the Tensorust MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Any
7
+
8
+ from langchain_core.tools import BaseTool, BaseToolkit
9
+ from pydantic import ConfigDict, Field
10
+
11
+ from langchain_tensorust._client import TensorustAPIWrapper
12
+ from langchain_tensorust.tools import (
13
+ TensorustContractorCredit,
14
+ TensorustDatasetSearch,
15
+ TensorustIDROffer,
16
+ TensorustLenderMarks,
17
+ TensorustLookupOrganization,
18
+ TensorustSanctionsScreen,
19
+ TensorustWhoOwnsHome,
20
+ )
21
+
22
+ _CURATED = (
23
+ TensorustLookupOrganization,
24
+ TensorustWhoOwnsHome,
25
+ TensorustLenderMarks,
26
+ TensorustContractorCredit,
27
+ TensorustSanctionsScreen,
28
+ TensorustIDROffer,
29
+ TensorustDatasetSearch,
30
+ )
31
+
32
+
33
+ class TensorustToolkit(BaseToolkit):
34
+ """Ownership, private credit and compliance tools backed by the Tensorust Ontology.
35
+
36
+ `get_tools()` returns seven curated tools with typed inputs. `aget_mcp_tools()` loads
37
+ every tool the Tensorust MCP server exposes (about 40, including the FCC clearinghouse,
38
+ E-Rate, ROAD Act and research-security exposure tools) through `langchain-mcp-adapters`;
39
+ install it with `pip install "langchain-tensorust[mcp]"`.
40
+
41
+ Both work without a key (free previews). Set `TENSORUST_API_KEY` to a `tdk_...` key for
42
+ full records.
43
+
44
+ Setup:
45
+ ```bash
46
+ pip install -U langchain-tensorust
47
+ export TENSORUST_API_KEY="tdk_..." # optional
48
+ ```
49
+
50
+ Instantiate:
51
+ ```python
52
+ from langchain_tensorust import TensorustToolkit
53
+
54
+ toolkit = TensorustToolkit()
55
+ tools = toolkit.get_tools()
56
+ ```
57
+
58
+ Use within an agent:
59
+ ```python
60
+ from langchain.agents import create_agent
61
+
62
+ agent = create_agent("anthropic:claude-sonnet-4-6", tools=toolkit.get_tools())
63
+ agent.invoke({"messages": [("user", "Who controls Invitation Homes?")]})
64
+ ```
65
+ """
66
+
67
+ model_config = ConfigDict(arbitrary_types_allowed=True)
68
+
69
+ api_wrapper: TensorustAPIWrapper = Field(default_factory=TensorustAPIWrapper)
70
+ """Client shared by every tool. Reads TENSORUST_API_KEY / TENSORUST_API_URL."""
71
+
72
+ include: Sequence[str] | None = None
73
+ """Optional subset of tool names to return from `get_tools()`."""
74
+
75
+ def get_tools(self) -> list[BaseTool]:
76
+ """The curated Tensorust tools, sharing this toolkit's client."""
77
+ tools: list[BaseTool] = [cls(api_wrapper=self.api_wrapper) for cls in _CURATED]
78
+ if self.include is not None:
79
+ wanted = set(self.include)
80
+ unknown = wanted - {t.name for t in tools}
81
+ if unknown:
82
+ msg = f"Unknown Tensorust tool names: {sorted(unknown)}"
83
+ raise ValueError(msg)
84
+ tools = [t for t in tools if t.name in wanted]
85
+ return tools
86
+
87
+ def mcp_connection(self) -> dict[str, Any]:
88
+ """The `langchain-mcp-adapters` connection for the Tensorust MCP server."""
89
+ headers = {
90
+ k: v
91
+ for k, v in self.api_wrapper.headers().items()
92
+ if k in {"Authorization", "User-Agent"}
93
+ }
94
+ return {
95
+ "transport": "streamable_http",
96
+ "url": self.api_wrapper.base_url,
97
+ "headers": headers,
98
+ }
99
+
100
+ async def aget_mcp_tools(self) -> list[BaseTool]:
101
+ """Every tool on the Tensorust MCP server, as LangChain tools.
102
+
103
+ Requires `pip install "langchain-tensorust[mcp]"`.
104
+ """
105
+ try:
106
+ from langchain_mcp_adapters.client import MultiServerMCPClient
107
+ except ImportError as e: # pragma: no cover - exercised only without the extra
108
+ msg = (
109
+ "aget_mcp_tools needs langchain-mcp-adapters: "
110
+ 'pip install "langchain-tensorust[mcp]"'
111
+ )
112
+ raise ImportError(msg) from e
113
+ client = MultiServerMCPClient({"tensorust": self.mcp_connection()}) # type: ignore[dict-item]
114
+ return list(await client.get_tools())