Liquid

by ertad-family

Not rated
GitHub

About

Connect your agent to any HTTP API on the fly - discovers + maps any REST API once, then fetches typed data deterministically

Details

Author
ertad-family
Categories
Developer Tools, API, Automation

Setup

Install Liquid in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/ertad-family/liquid

Follow the installation instructions in the repository README, then restart your MCP client.

Connect your AI agent to anything — with no connector to write or maintain.

Point Liquid at a URL or a database and it works out the interface for you: discovers its shape, maps it to the fields you asked for, and handles auth, pagination and normalization — typed records, no client code. When the upstream drifts, it re-maps and keeps going. The same small API —fetch·query·write·sense— reaches web APIs, databases, other agents (MCP/A2A), email, and even IoT and industrial systems (MQTT, Modbus, OPC UA, BACnet). An LLM does the learning at setup (and on drift); the data path itself makes no model call.

One agent-facing API (fetch·query·write·sense) over everything an agent might need to touch — Liquid figures outhow to talk to itso the agent doesn't have to. It's the agent's sensesandhands:fetch/queryprobe,senseperceives a live event stream,writeacts on the world.

Point it at ahttps://…endpoint, apostgres://…/mongodb://…/redis://…DSN, agrpc://…target, or another MCP server — discovery identifies the interface, learns its shape, and hands your agent typed records. The samefetch/query/writeworks regardless of what's underneath. No per-service connector to hand-write; the integration maintains itself when the upstream changes.

# A web API it has never seen — no spec, no connector, no auth adapter = await liquid.get_or_create( "https://api.openbrewerydb.org/v1/breweries", target_model={"name": "str", "city": "str", "country": "str"}, auto_approve=True, ) breweries = await liquid.fetch(adapter) # typed records # A database is just another interface — same API, and it writes too db = await liquid.get_or_create("postgresql://reader@host/shop", target_model={"id": "int", "email": "str"}, auto_approve=True) orders = await liquid.fetch(db, "/public/orders") await liquid.write(db, "/public/orders", op="insert", values={"email": "a@b.com", "total_cents": 9900}, allow_write=True) # opt-in; mutates the store

You hand-write no connector and no schema: an LLM learns the interface once at setup (databases introspect themselves and skip even that), and the integrationrepairs itselfwhen the upstream drifts. The runtime is plain deterministic transport — predictable cost, reproducible behavior, nothing to babysit.

Built for the constraints real agents hit

Reaching everything is half of it. The other half is that agents pay for every token, get confused by inconsistent shapes, and can't parse error prose. Liquid answers each with a concrete primitive — all shipped, all on PyPI.

# Search / aggregate server-side instead of fetch-then-filter — 10-100x fewer tokens orders = await liquid.search(adapter, "/orders", where={"total_cents": {"$gt": 10000}, "status": "paid"}, limit=20) stats = await liquid.aggregate(adapter, "/orders", group_by="status", agg={"total_cents": "sum", "id": "count"}) hits = await liquid.text_search(adapter, "/tickets", "shipping delay") # BM25-lite data = await liquid.fetch(adapter, "/orders", max_tokens=2000) # budget cap data = await liquid.fetch(adapter, "/customers", verbosity="terse") # id + 1-2 fields
liquid = Liquid(..., normalize_output=True) # Stripe {amount:1000,currency:"usd"} · PayPal {value:"10.00",currency_code:"USD"} # → Money(amount_cents=1000, currency="USD", amount_decimal=Decimal("10.00"))

Timestamps (Unix / ISO 8601 / RFC 2822) collapse to UTCdatetime; pagination envelopes ({data:[…]}/{results:[…]}/ Link headers) flatten; ID fields normalize acrossid/_id/uuid/_id.

Canonical intents — one mental model across services

await liquid.execute_intent(adapter, "charge_customer", {"customer_id": "cus_xyz", "amount_cents": 9999, "currency": "USD"}) # Same intent on Stripe / Braintree / Square / Adyen — 71 canonical intents

Structured recovery — agents self-heal without parsing text

try: await liquid.fetch(adapter, "/orders") except LiquidError as e: if e.recovery and e.recovery.next_action: await agent.call_tool(e.recovery.next_action.tool, e.recovery.next_action.args)

Every error carries aRecoverywithnext_action: ToolCall,retry_safe, andretry_after_seconds. 401 →store_credentials. 404/410 →repair_adapter. 429 → retry after the given delay. And when the upstream's schema drifts, adaptersself-heal(repair_adapter) — the agent keeps working.

est = await liquid.estimate_fetch(adapter, "/orders") # FetchEstimate(expected_items=250, expected_tokens=52_000, confidence="high", …) if est.expected_tokens < my_budget: data = await liquid.fetch(adapter, "/orders")

Tools emitted byto_tools()carry ametadatablock (cost_credits,typical_latency_ms,cached,idempotent,side_effects,related_tools) so the agent can reason about which tool to pick — and ambient tools (liquid_check_quota,liquid_list_adapters, …) let it ask about state instead of memorizing it.

Deterministic benchmarks on realistic agent tasks (500-order, 200-ticket fixtures, mocked HTTP) — reproducible viapython -m benchmarks.run:

Full methodology + per-task breakdown:benchmarks/RESULTS.md.

pip install liquid-api # core + bundled MCP server (the liquid-mcp command) pip install 'liquid-api[discovery]' # + an LLM for discovering spec-less REST APIs & field mapping

Do you need an LLM extra?Self-describing interfaces — OpenAPI, GraphQL, gRPC, MCP, A2A, WSDL — andall databases(introspection) discover withno LLM, and the whole runtime (fetch/query/write/sense) never calls a model. You only need an LLM backend todiscover a REST API that has no machine-readable spec(heuristic + LLM) and tomapits fields.[discovery]pulls LiteLLM, which reaches OpenAI / Gemini / Anthropic / local / 100+ providers; or pick one directly:

pip install 'liquid-api[gemini]' # Google Gemini (or [anthropic]; OpenAI/local work with no extra via base_url) pip install 'liquid-api[grpc]' # gRPC transport (reflection) pip install 'liquid-api[ws]' # WebSocket transport pip install 'liquid-api[pg]' # Postgres / pgvector (asyncpg) pip install 'liquid-api[mysql]' # MySQL / MariaDB (aiomysql); SQLite needs no extra pip install 'liquid-api[neo4j]' # Neo4j graph (Bolt / Cypher) pip install 'liquid-api[duckdb]' # DuckDB (embedded analytics) pip install 'liquid-api[mssql]' # SQL Server (ODBC; needs a system ODBC driver) pip install 'liquid-api[mongodb]' # MongoDB (collections as endpoints) pip install 'liquid-api[redis]' # Redis (keyspace namespaces as endpoints) pip install 'liquid-api[mqtt]' # MQTT (IoT pub/sub) pip install 'liquid-api[modbus]' # Modbus (industrial registers) pip install 'liquid-api[opcua]' # OPC UA (Industry-4.0 nodes + subscriptions) pip install 'liquid-api[bacnet]' # BACnet (building automation; ADB needs the system adb binary) # Framework integration (LangChain / OpenAI / Anthropic / MCP) is built in — no extra package.

The core is dependency-free — every backend's library is an optional extra, imported only when used.

Point Liquid at an API it has never seen (no adapter, no OpenAPI spec, no auth) and get typed records back — you write no connector; discovery + mapping is the only place a model runs. Runnable end to end viaexamples/live_quickstart.py:

Connecting to an API Liquid has never seen: https://api.openbrewerydb.org/v1/breweries discovery method : rest_heuristic mapped fields : ['name', 'city', 'state', 'country'] LLM calls so far : 2 (discovery + mapping) fetch() -> 50 typed records; first 3: {'name': '(405) Brewing Co', 'city': 'Norman', 'state': 'Oklahoma', 'country': 'United States'} {'name': '(512) Brewing Co', 'city': 'Austin', 'state': 'Texas', 'country': 'United States'} {'name': '1 of Us Brewing Company', 'city': 'Mount Pleasant', 'state': 'Wisconsin', 'country': 'United States'} LLM calls during fetch : 0 LLM calls on 2nd fetch : 0

You wrote no connector, no schema, no auth glue — Liquid learned the interface for you, and will re-learn it if it changes. That's the point: integrations you don't build or babysit.

Run as an MCP server (open source, self-hosted)

Expose the engine to any MCP client (Claude Desktop, Cursor, Claude Code) — it runsin your own process, no cloud, no account, no lock-in:

One-click in Cursor (the button writes the server into yourmcp.json; add yourOPENAI_API_KEYin Cursor's MCP settings afterward). Or set it up manually:

pip install liquid-api export OPENAI_API_KEY=sk-... # or GEMINI_API_KEY / ANTHROPIC_API_KEY, # or OPENAI_BASE_URL=http://localhost:11434/v1 for local (Ollama/vLLM) liquid-mcp # or: python -m liquid.mcp_server

Zero-install withuvx(theliquid-mcppackage makes the command run by name) — Claude Code:

claude mcp add liquid --scope user -e OPENAI_API_KEY=sk-... -- uvx liquid-mcp
{ "mcpServers": { "liquid": { "command": "uvx", "args": ["liquid-mcp"], "env": { "OPENAI_API_KEY": "sk-..." } } } }

(Or afterpip install liquid-api, dropuvxand use"command": "liquid-mcp"directly.)

One-click in Claude Desktop:install the.mcpbbundle— it prompts for your model key on install (stored in the OS keychain), with no JSON to edit. Requiresuvon the machine.

Tools:liquid_connect(discover + map any interface),liquid_fetch,liquid_query(server-side search/aggregate),liquid_estimate(pre-flight cost/size, no call),liquid_list_adapters,liquid_discover. The surface isread-only by default; start the server withLIQUID_ALLOW_WRITES=1to also exposeliquid_execute(database insert/update/delete). Adapters and credentials persist under~/.liquid. Backed byany LLM— OpenAI, Gemini, Anthropic, any OpenAI-compatible/local endpoint viabase_url,100+ providers via LiteLLM, or your own function throughCallableBackend.

from liquid import Liquid, InMemoryCache, RateLimiter from liquid._defaults import InMemoryVault, InMemoryAdapterRegistry, CollectorSink from liquid_langchain import LiquidToolkit from langgraph.prebuilt import create_react_agent from langchain_openai import ChatOpenAI liquid = Liquid( llm=my_llm, vault=InMemoryVault(), sink=CollectorSink(), registry=InMemoryAdapterRegistry(), cache=InMemoryCache(), rate_limiter=RateLimiter(), normalize_output=True, # cross-source canonical shapes include_meta=True, # _meta block on every response ) adapter = await liquid.get_or_create( "https://api.shopify.com", target_model={"id": "str", "total_cents": "int", "customer_email": "str"}, credentials={"access_token": "shpat_..."}, auto_approve=True, ) tools = LiquidToolkit(adapter, liquid).get_tools() agent = create_react_agent(ChatOpenAI(model="gpt-4o-mini"), tools) result = await agent.ainvoke( {"messages": [("user", "Find 5 recent orders over $100 from VIP customers")]} )

The agent's tools come with rich descriptions (WHEN to use, NOT FOR what, return shape, cost), structured recovery on every error, and server-side search so it never pulls 500 orders to find 5.

Discovery identifies the target and tags each endpoint with a protocol; a pluggable transport driver runs it — but the agent-facing API (fetch,query,write, mapping, recovery, cache, rate limits) is identical across all of them.

Read and write.liquid.write(adapter, endpoint, op="insert", values={...}, allow_write=True)mutates any database (SQLINSERT/UPDATE/DELETE, Mongo insert/update/delete, RedisSET/HSET/DEL, Neo4j node CRUD); web/agent writes go through verified actions. Identifiers come from introspection and values are parameterized;update/deleterequire awhere(no blanket mutations); writes areoff until you opt inwithallow_write=True.

Sense — the afferent organ.liquid.sense(adapter, endpoint)perceives a live event stream wherever one exists: SQL row deltas (and PostgresLISTEN/NOTIFY), Redis pub/sub, WebSocket frames, HTTP server-push (SSE/NDJSON), and MCP notifications — each yielded as a modality-agnostic event. Pointedinward,liquid.sense_webhook(port=…, verifier=…)hosts an inbound endpoint so a service (or a human, via a webhook) POSTing to the agent becomes a perceivable signal too. All bounded bymax_events/max_seconds, so an agent can drain-by-pull.

The sensorimotor loop.react(stream, handler)drives a handler for each perceived event — with error isolation and bounded concurrency — so a host canperceive → wake the agent → act.merge_senses(streams)fans several senses into one loop, so one agent can watch a database, a queue, and a webhook at once:

events = merge_senses( await liquid.sense(orders, "/orders"), await liquid.sense_webhook(port=8088, verifier=stripe_verifier), ) await react(events, on_event, max_concurrency=4)

Discovery is automatic — and identifies on the fly.Before the pipeline runs, a fingerprint step names the target: a barehost:portis normalized by well-known port (db:5432postgresql://db:5432), andliquid.identify(url)answers "what is this, and is its driver installed?" with an install hint when a backend is missing. (Identifying a protocol is feasible on the fly;speakinga new authenticated binary protocol isn't — so unknowns are named, not guessed at.)

Add a backend without writing code.For the SQL family the contract is declarative enough to bedata: adialect manifest(quoting, placeholder style, pagination, introspection SQL, error map, DBAPI2 module) registered viaregister_sql_manifest({...})installs a working driver + discovery — so a new SQL / wire-compatible store (CockroachDB, ClickHouse, any DBAPI2 driver), even one fetched from the network as JSON, connects without a release. New protocols otherwise plug in via theliquid.transport.ProtocolDriverprotocol; SQL backends share a dialect-aware core, so a new one is a ~80-line adapter.

Want to teach Liquid a new protocol?A complete transport driver (fetch/write/sense) is typically ~150 lines — seedocs/ADDING_A_DRIVER.mdfor the walkthrough and a wishlist (CAN bus, CoAP, KNX, AMQP, NATS, SNMP, …). Contributions welcome.

2,500+ APIs are pre-discovered and pre-mapped in theglobal catalog— most popular services connect with zero discovery cost.

URL / DSN Agent ↓ ↑ FINGERPRINT → DISCOVERY FETCH · QUERY · WRITE · SEARCH · AGGREGATE ↓ ↑ one ProtocolDriver per Deterministic per-protocol transport interface: • Query DSL (server-side filter) REST GraphQL gRPC WS SSE MQTT • Output normalization MCP A2A · SQL graph doc KV · • Verbosity / max_tokens / _meta Modbus OPC-UA BACnet ADB … • (full protocol list in the table above) ↓ • Structured recovery + self-heal APISchema • Rate-limit-aware token bucket ↓ • Response cache (Cache-Control aware) AI MAPPING (setup only) • Empirical probing data (Cloud) ↓ AdapterConfig

AI participates at setup only.Runtime is pure transport with transforms — no LLM per call, predictable cost, reproducible behavior (exceptsearch_nl, which caches its compilations).

Every cross-cutting concern is aProtocolyou can replace:

from liquid.protocols import ( Vault, LLMBackend, DataSink, KnowledgeStore, AdapterRegistry, CacheStore, )

In-memory implementations ship for all of them;liquid-cloudprovidesPostgresVault,RedisCache, etc. for hosted deployments.

adapter.to_tools(format="anthropic") # Claude tool use adapter.to_tools(format="openai") # OpenAI function calling (LangChain/CrewAI consume these) adapter.to_tools(format="mcp") # MCP (Claude Desktop, Cursor)

No extra packages to install — it's built intoliquid-api.adapter.to_tools(format="anthropic" | "openai" | "mcp")emits ready-to-use tool definitions for Claude tool use, OpenAI function calling (which LangChain / LangGraph and CrewAI consume directly), and any MCP client (Claude Desktop, Cursor, …). The bundledliquid-mcpserver also exposes Liquid as MCP tools out of the box.

- Quickstart— discover → map → fetch, plus theno-LLM runtime
-
OSS vs. Cloud— the honest boundary: free/self-hosted vs. hosted
-
Architecture
-
Extending— implement your own Vault / LLM / Sink
-
Write operations spec

This is a web browser that enables your coding agent, such as Claude Code, to visit websites on your behalf and assist you in identifying bugs or creating UI test cases.

The MCP server for Bitrix24 provides AI assistants with structured access to the Bitrix24 API. It delivers up-to-date method descriptions, parameters, and valid values, allowing assistants to work with precise data instead of guesswork. This reduces code errors and accelerates Bitrix24 integration development.

One remote MCP server for 500+ production APIs — Stripe, HubSpot, Postgres, Gmail, and more. OAuth and API key auth, credential management, and a CLI.

Single tool to control all 100+ API integrations, and UI components

Agent-native developer Q&A API with MCP + A2A endpoints for citations, job pickup, and answer submission.

Self-hosted MCP gateway: convert REST/SOAP/GraphQL/SQL APIs into MCP tools with 29 pre-built adapters, OAuth2, RBAC and audit log.

A universal bridge to convert any web API into an MCP server, supporting multiple transport types.

Dynamically creates MCP servers from web API configurations, integrating any REST API, GraphQL endpoint, or web service into MCP-compatible tools.

Hosted MCP server and coordination layer for AI coding agents — live API contracts, database schema, frontend/backend mismatch detection, and shared handoff tickets for Claude Code, Cursor, Codex, and Lovable.

An MCP server that dynamically loads tools from an external JSON file configured via an environment variable.

A lightweight server exposing Axone's capabilities through the Model-Context Protocol.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.