Metadata-Version: 2.4
Name: liberrex-mcp-server
Version: 0.1.0
Summary: Publish a platform's tools on the Liberrex MCP mesh (peer keys, per-call user identity, permissions), plus the Liberrex AI MCP server.
Author: Liberrex
License: Liberrex MCP server — Proprietary License
        
        Copyright (c) 2026 Liberrex. All rights reserved.
        
        1. Grant. Subject to these terms, Liberrex grants you a non-exclusive, non-transferable, revocable license to install and use
           this software, unmodified or modified, solely to integrate your own applications with the Liberrex platform while you
           (or the organisation you act for) hold a valid Liberrex subscription or a written evaluation agreement with Liberrex.
        
        2. Restrictions. You may not (a) use this software to access any service other than the Liberrex platform; (b) sell,
           sublicense, rent or distribute it on its own, except as an unmodifiable part of your application delivered to your end
           users; (c) remove or alter this notice; (d) use it to build a product that competes with the Liberrex platform.
        
        3. Ownership. Liberrex retains all rights, title and interest in this software. No other rights are granted, by implication
           or otherwise.
        
        4. Termination. This license ends automatically when your Liberrex subscription or evaluation ends, or if you breach these
           terms. On termination you must stop using the software.
        
        5. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY,
           FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
        
        6. Limitation of liability. TO THE MAXIMUM EXTENT PERMITTED BY LAW, LIBERREX SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
           SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM THE USE OF THE SOFTWARE.
        
        7. Your agreement with Liberrex. If you have a signed agreement with Liberrex that covers this software, that agreement
           prevails over these terms.
        
        Contact: https://ai.liberrex.com
        
Project-URL: Homepage, https://ai.liberrex.com
Keywords: liberrex,mcp,model-context-protocol,ai,agents,integration
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Typing :: Typed
Classifier: License :: Other/Proprietary License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<3,>=2.3
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Requires-Dist: uvicorn>=0.30
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: uvicorn>=0.30; extra == "dev"
Dynamic: license-file

# liberrex-mcp-server (Python)

Publish a platform's tools on the **Liberrex MCP mesh**, so that Liberrex AI, Animatoria, the ERP and any other
platform of the ecosystem can call them. It follows the shared [mesh conventions](../../MCP_MESH.md): peer keys,
per-call user identity, permissions, write control and `[code]` errors. The package also ships the **Liberrex AI
platform server** (`liberrex-mcp-server`): agents, copilot and sales intelligence (next best action, next best
product…) as tools.

- Python ≥ 3.10, built on the official `mcp` SDK v2. Stateless Streamable HTTP (any instance answers any call) or stdio.
- Pair it with [`liberrex-mcp-client`](../../mcp-client/python) on the calling side.
- TypeScript equivalent: [`@liberrex/mcp-server`](../typescript).

```bash
pip install liberrex-mcp-server
```

## Publish your platform's tools

```python
from typing import Optional
from liberrex_mcp_server import PeerRegistry, PlatformError, PlatformServer, ToolContext

server = PlatformServer("erp", title="Acme ERP", peers=PeerRegistry.from_env())

@server.tool(permission="invoices.read", category="finance")
def overdue_invoices(ctx: ToolContext, customer: Optional[str] = None, limit: int = 20) -> dict:
    """Overdue invoices the user can see, oldest first."""
    user = ctx.require_identity()            # who the call acts for: user_id, tenant_id, roles, locale
    rows = invoices.overdue(tenant=user.tenant_id, user=user.user_id, customer=customer, limit=limit)
    if rows is None:
        raise PlatformError("not_found", "Unknown customer")      # → "[not_found] Unknown customer"
    return {"items": rows}

@server.tool(permission="invoices.write", write=True)
async def send_reminder(ctx: ToolContext, invoice_id: str) -> dict:
    """Send a payment reminder."""
    ...
```

Then serve it:

```python
app.mount("/mcp", server.asgi())       # FastAPI / Starlette; the endpoint answers at /mcp/
server.run_http(port=8765)             # standalone, at /mcp
server.run_stdio()                     # local desktop clients
```

The SDK checks these before your function runs:

| check | refusal |
|---|---|
| The `Authorization: Bearer` key is a valid (not expired) key of a configured peer | HTTP 401 `[unauthorized]` |
| At most `rate_per_minute × 10` HTTP requests per minute from that peer (flood guard) | HTTP 429 + `Retry-After` |
| At most `rate_per_minute` tool calls per minute from that peer | `[rate_limited]` |
| The identity's tenant is one the peer may act in | `[tenant_not_allowed]` |
| The identity's roles, capped by the peer's `roles`, include the tool's `permission` | `[forbidden]` / `[identity_required]` |
| `write=True` tools only for peers with `writes` | `[writes_disabled]` |
| At most 4 platform hops | `[too_many_hops]` |
| Any other exception: logged, and not leaked to the caller | `[internal_error] <tool> failed` |

Sync functions run in a worker thread, so they may block, for example on a database call. Use
`ctx.forward_meta()` when a tool calls another platform: the same person and trace go along, with `hops + 1`.

### Already integrated with the Liberrex copilot?

Publish every tool you declared with `liberrex_copilot` in one line. The schemas, permission checks and error codes stay
the same:

```python
from liberrex_mcp_server.copilot import publish_copilot
publish_copilot(server, copilot, user=lambda identity: load_user_with_roles(identity))
```

This is how Animatoria publishes its 43 tools at `/mcp`. The `user` callback lets your platform compute the user's
roles itself, rather than trust the roles the caller sends.

### Tools that already have a JSON Schema

```python
server.raw_tool("lookup", description="…", input_schema={"type": "object", "properties": {...}},
                handler=lambda ctx, arguments: ...)
```

## Peers (who may call)

```bash
liberrex-mcp-server new-key liberrex-ai     # prints a key for the caller and the sha256 entry for you
```

```bash
LIBERREX_MCP_PEERS='{"liberrex-ai": {"keys": [{"key": "sha256:…", "expires": "2027-01-31"}, "sha256:…"],
                                     "tenants": "*", "writes": false, "rate_per_minute": 300},
                     "erp": {"key": "sha256:…", "tenants": ["acme"], "roles": ["invoices.read"]}}'
LIBERREX_MCP_PEERS_FILE=/etc/erp/mcp-peers.json    # or: the same JSON in a file, re-read when it changes
```

| field | meaning | default |
|---|---|---|
| `key` / `keys` | `sha256:<64 hex>` (preferred) or the raw key (24 characters at least); or `{"key", "expires": "YYYY-MM-DD"}`: valid until the end of that UTC day | required |
| `tenants` | `"*"` or the tenant ids the peer may act in | `"*"` |
| `writes` | may call `write=True` tools | `false` |
| `roles` | ceiling: the identity's roles are intersected with it | none |
| `rate_per_minute` | tool calls per minute (1 to 10,000), per process; HTTP requests are capped at 10× that | `120` |

Keep only hashes on the server, give each caller its own key, and grant `writes` only where needed. Rotate with an
overlap: add the new hash to `keys`, give the caller the new key, then remove the old hash (or let its `expires` pass).
`python -m liberrex_mcp_server.keys <peer>` also prints a key and its hash.

With `LIBERREX_MCP_PEERS_FILE` (`FilePeerRegistry`), the file is re-read when its mtime changes (checked at most every
2 s): revoke a caller without a restart. Invalid at startup → `PeerConfigError`; invalid later → logged, the previous
peers stay; missing → no caller is accepted.

### Audit

Every tool call (`ok` / `refused` / `error`) and every refused HTTP request (`unauthorized`, `rate_limited`) produces one
event for `PlatformServer(..., audit=fn)` (default: a JSON line on the `liberrex_mcp_server.audit` logger; `None`: off):
`at`, `platform`, `event`, `tool`, `write`, `peer`, `user`, `tenant`, `trace_id`, `origin`, `hops`, `status`, `code`,
`duration_ms` (and `client` for HTTP events). Tool arguments are never included. The fields are the same in the
TypeScript server; see [MCP_MESH.md §6](../../MCP_MESH.md).

## The Liberrex AI platform server

```bash
liberrex-mcp-server --check                     # credentials, peers, tools
liberrex-mcp-server --transport http --port 8765   # the mesh endpoint at /mcp (needs LIBERREX_MCP_PEERS)
liberrex-mcp-server                              # stdio for Claude Desktop / Cursor (acts as LIBERREX_USER_ID)
```

| Variable | Meaning |
|---|---|
| `LIBERREX_API_URL` | Liberrex API (default `https://ai-api.liberrex.com`) |
| `LIBERREX_API_KEY` | Developer API key `key_id:secret`: agents, conversations, usage (company-wide tools) |
| `LIBERREX_SECRET_KEY` | Embedded copilot key `lxsk_…`: copilot and surfaces, **for the identity of each call** |
| `LIBERREX_SURFACES` | `next_best_action=nba-queue,next_best_product=nbp-queue`: modes of `sales_recommendations` |
| `LIBERREX_MCP_PEERS` / `LIBERREX_MCP_PEERS_FILE` | Platforms allowed to call (see above) |
| `LIBERREX_USER_ID`, `LIBERREX_TENANT_ID`, `LIBERREX_USER_ROLES`, `LIBERREX_LOCALE` | stdio only: the user a desktop client acts as |
| `LIBERREX_MCP_READ_ONLY` | `1` leaves out confirm_action, create_action_batch and end_conversation |

The 20 tools are:

| Group | Tools |
|---|---|
| Agents | whoami, list_agents, get_agent, ask_agent, list_copilot_agents, start_agent_group, ask_agent_group, get_agent_group, list_conversations, get_conversation, end_conversation, get_usage |
| Copilot | copilot_ask, copilot_list_threads, copilot_get_thread |
| Sales intelligence | sales_recommendations, render_surface, suggest_field, run_surface_action, create_action_batch, confirm_action, cancel_action, record_feedback |
| Billing | get_copilot_usage |

Each calling user gets their own copilot session, minted for the `user.id` and `tenant.id` the calling platform sends.
Recommendations are therefore computed from that user's own data, under their own roles.

## Development

```bash
python3.12 -m venv .venv && .venv/bin/pip install -e ".[dev]" -e ../../mcp-client/python -e ../../python
.venv/bin/pytest      # 39 tests, including server ↔ client end to end over HTTP
```
