Show HN: Toolnexus for Python – MCP, agent skills,a2a for any LLM

muthuishere1 pts0 comments

toolnexus · PyPI

Skip to main content<br>Switch to mobile version

Warning

You are using an unsupported browser, upgrade to a newer version.

Warning

Some features may not work without JavaScript. Please try enabling it if you encounter problems.

Search PyPI

Search

toolnexus 0.3.1

pip install toolnexus

Copy PIP instructions

Latest release

Released:<br>Jul 1, 2026

Provider-agnostic toolkit: dynamic MCP servers + agent skills for any LLM.

Navigation

Verified details

These details have been verified by PyPI<br>Maintainers

muthuishere

Unverified details

These details have not been verified by PyPI

Meta

License: MIT

Author: Muthukumaran Navaneethakrishnan

Requires: Python >=3.11

Provides-Extra:<br>test

Report project as malware

Project description

toolnexus

Build an agent in a few lines. Point at an mcp.json and a skills/ folder, call run(),<br>and you have a working agent — MCP servers, agent skills, your own functions, and HTTP endpoints<br>unified as one tool set, driving any LLM.

Right-sized. Not a framework (no builders, no config to wade through), not a toy that falls<br>over the moment you need streaming or a retry. Everything a real agent needs — the loop, hooks,<br>streaming, retries, memory — and nothing it doesn't.

The Python port of toolnexus — the same library,<br>byte-identical, also in JavaScript, Go, Java, and C# . Built on the official MCP Python SDK<br>(the mcp package). Python ≥ 3.11.

Install

pip install toolnexus

Quick start — a working agent in 5 lines

No mcp.json, no skills folder. The 10 built-in tools (bash, read, grep, webfetch, …)<br>are on by default, so the model can actually do things right away:

import asyncio<br>from toolnexus import create_toolkit, create_client

async def main():<br>tk = await create_toolkit() # built-in tools, on by default<br>agent = create_client(<br>base_url="https://openrouter.ai/api/v1", style="openai",<br>model="deepseek/deepseek-chat", # any OpenRouter/OpenAI/Anthropic model<br>res = await agent.run("List the files here, then count them.", tk)<br>print(res.text)<br>await tk.close()

asyncio.run(main())

export OPENROUTER_API_KEY=... # or OPENAI_API_KEY / ANTHROPIC_API_KEY

create_client reads the key from OPENROUTER_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY<br>(no api_key= needed).

With MCP servers + skills

The MCP SDK is async, so the toolkit is async:

import asyncio<br>from toolnexus import create_toolkit, create_client

async def main():<br># 1. tools from an mcp.json + a skills/ folder<br>tk = await create_toolkit(mcp_config="./mcp.json", skills_dir="./skills")

# 2. point at any OpenAI- or Anthropic-style endpoint<br>agent = create_client(<br>base_url="https://openrouter.ai/api/v1",<br>style="openai", # or "anthropic"<br>model="openai/gpt-4o-mini",

# 3. run — skills injected, tools called for you, looped to an answer<br>res = await agent.run("Refund order 1234 for the customer.", tk)<br>print(res.text)<br>await tk.close()

asyncio.run(main())

The Toolkit is also an async context manager (async with await create_toolkit(...) as tk:)<br>if you'd rather not call close() yourself.

Conversations / memory

run() is stateless — each call starts fresh. For a multi-turn thread that remembers, use<br>ask(prompt, tk, id=...). Give it an id and the client's ConversationStore does the work:<br>load that thread's transcript → run → save the updated transcript. The next ask with the same<br>id continues where it left off. Call ask without an id and it's a stateless one-shot —<br>identical to run.

agent = create_client(base_url="https://openrouter.ai/api/v1", style="openai",<br>model="openai/gpt-4o-mini")

await agent.ask("I trade NIFTY.", tk, id="trader-42")<br>res = await agent.ask("What do I trade?", tk, id="trader-42")<br>print(res.text) # -> "NIFTY" — the second turn remembers the first

Every client has a store — by default an in-memory InMemoryConversationStore that lives as long<br>as the client. To persist across processes (a file, a DB, Redis), pass your own to create_client:

from toolnexus import create_client, ConversationStore

class FileStore: # implements ConversationStore<br>async def get(self, id): # -> list[messages] | None<br>...<br>async def save(self, id, messages): # persist the updated transcript<br>...

agent = create_client(base_url=..., style="openai", model=..., store=FileStore())

ConversationStore is just two async methods — get(id) and save(id, messages). The A2A<br>serve side uses the same store: an inbound peer's turns are keyed by their A2A contextId, so a<br>served agent remembers a caller across tasks (see A2A agents).

Add your own tools

from toolnexus import define_tool, http_tool

# a plain function → a tool (schema inferred from the signature)<br>def add(a: float, b: float) -> str:<br>"""Add two numbers and return the sum."""<br>return str(a + b)

tk.register(define_tool(add, name="add"))

# a REST endpoint → a tool<br>tk.register(http_tool(<br>name="create_ticket", description="Create a ticket", method="POST",<br>url="https://api.example.com/tickets",<br>headers={"Authorization": "Bearer ${API_TOKEN}"}, # ${ENV} expands from...

agent toolnexus skills create_client async await

Related Articles