Skip to content

Make your first MCP and REST calls

Turn two Python functions into a local service. You will calculate a price (12.5 × 2 = 25.0) and check stock (3 ≤ 10 gives true). Prepare the example once, then choose REST or MCP. No plugin, Docker or AI account is needed for REST or the Python MCP client.

1. Prepare your workspace

Install Apizr with the pip instructions and keep core/ activated. You need Python 3.11–3.15, curl, a POSIX shell on macOS/Linux and internet access for the example and server dependencies. In the same terminal:

mkdir apizr-quickstart
cd apizr-quickstart
apizr --version
python3 -m venv runtime

apizr --version should report outerspace-apizr 0.4.6. The new runtime/ environment will hold the generated server's dependencies separately from Apizr.

2. Get the example

Download the three files from the recorded shop example:

mkdir shop
curl --fail --location https://raw.githubusercontent.com/Alien6-Studio/outerspace-apizr/37259eaf0a231d747f1ae0eb2f7ce94b2a2b306f/examples/repository-shop/api.py --output shop/api.py
curl --fail --location https://raw.githubusercontent.com/Alien6-Studio/outerspace-apizr/37259eaf0a231d747f1ae0eb2f7ce94b2a2b306f/examples/repository-shop/inventory.py --output shop/inventory.py
curl --fail --location https://raw.githubusercontent.com/Alien6-Studio/outerspace-apizr/37259eaf0a231d747f1ae0eb2f7ce94b2a2b306f/examples/repository-shop/pricing.py --output shop/pricing.py
ls shop

You should have api.py, inventory.py and pricing.py in shop/. Create the configuration that selects api.quote and inventory.available and permits Apizr to analyze this directory:

cat > readiness-direct.json <<'JSON'
{"execution":{"modes":["direct"]}}
JSON
cat > exposure-direct.json <<'JSON'
{"selection":{"include":["python:api:quote","python:inventory:available"]},"interfaces":["rest","mcp"],"execution":{"allowed":["direct"]}}
JSON
python - <<'PYTHON'
import json
from pathlib import Path
root = str(Path("shop").resolve(strict=True))
policy = {
    "schema": "apizr.operator-policy/v1",
    "grants": [{
        "adapter": "repository",
        "operation": "analyze",
        "target": {"kind": "local", "root": root},
        "permissions": ["source.analyze"],
    }],
}
Path("operator.json").write_text(json.dumps(policy, indent=2) + "\n")
print("Authorized source:", root)
PYTHON

The three files sit beside shop/. Keep that directory at the same location; its analysis permission names this exact path.

What do these three configuration files do?
  • Readiness: describes the kind of interface Apizr can generate here.
  • Exposure: selects the public functions and allows direct execution.
  • Operator: permits source analysis of this directory. It grants neither execution nor publication.

Private helpers are included when needed, but do not become public functions. For another project, review its source, select its functions and create an operator grant for its own root.

3. Choose your interface

Use REST Use MCP

Both paths use the files prepared above; choose one. Generation reads the source without running it. The generated server uses direct mode: calls execute trusted Python with your user permissions. Use execution policies when you need fresh workers or execution limits.

Use REST

Generate the API and install its runtime dependencies:

apizr expose build rest shop --operator-policy operator.json --readiness-policy readiness-direct.json --policy exposure-direct.json --output-dir build/rest
runtime/bin/python -m pip install -r build/rest/requirements.txt

build/rest contains app.py, openapi.json and the supporting source. Start the server on a free local port:

runtime/bin/uvicorn app:app --app-dir build/rest --host 127.0.0.1 --port 8000

Keep this terminal open. In a second terminal, call the two endpoints:

curl --fail --silent --show-error http://127.0.0.1:8000/capabilities/api.quote --header 'Content-Type: application/json' --data '{"unit_price":12.5,"quantity":2}'
curl --fail --silent --show-error http://127.0.0.1:8000/capabilities/inventory.available --header 'Content-Type: application/json' --data '{"stock":10,"requested":3}'

The response bodies are 25.0 and true (curl does not append a newline). Open the interactive API docs to try other inputs. Stop the server with Ctrl+C when finished.

You can now continue with your own code, or try MCP in the original terminal. For a different example, the REST guide includes a video showing function selection, HTTP calls and input validation.

Use MCP

Generate the MCP bundle

Generate a server for the same two functions:

apizr expose build mcp shop --operator-policy operator.json --readiness-policy readiness-direct.json --policy exposure-direct.json --output-dir build/mcp

build/mcp contains server.py, mcp-tools.json, requirements.txt and the supporting source. The tools are named api.quote and inventory.available. Use a new output directory if you generate again.

This is your business-function server. The optional Apizr analysis MCP server is a separate tool for inspecting projects; you do not need it here.

Connect a client and make two calls

Install the server dependencies in the runtime environment:

runtime/bin/python -m pip install -r build/mcp/requirements.txt

Choose your usual MCP client or the Python client below.

Connect your usual MCP client

For Claude Desktop on macOS, follow the official local-server guide. In Settings → Developer → Edit Config, add this entry to your existing mcpServers object. Replace both /absolute/path/apizr-quickstart prefixes with the full directory path printed by pwd; keep your other servers.

{
  "mcpServers": {
    "repository-shop": {
      "command": "/absolute/path/apizr-quickstart/runtime/bin/python",
      "args": [
        "/absolute/path/apizr-quickstart/build/mcp/server.py",
        "--transport",
        "stdio"
      ]
    }
  }
}

Restart the client. Ask it to call api.quote with unit_price=12.5, quantity=2, then inventory.available with stock=10, requested=3. Approve those calls and look at the tool results: {"result": 25.0} and {"result": true}. Other MCP clients can use the same command and arguments in their configuration.

Use Python — no AI account required

Run this from apizr-quickstart. It starts the server, discovers the tools and calls both of them using the installed MCP SDK:

runtime/bin/python - <<'PY'
import asyncio
import json
import sys
from pathlib import Path

from mcp import Client, StdioServerParameters


async def main():
    server = StdioServerParameters(
        command=sys.executable,
        args=[str(Path("build/mcp/server.py").resolve()), "--transport", "stdio"],
    )
    async with Client(server, read_timeout_seconds=10) as client:
        names = sorted(tool.name for tool in (await client.list_tools()).tools)
        assert names == ["api.quote", "inventory.available"], names
        print("tools:", ", ".join(names))
        quote = await client.call_tool("api.quote", {"unit_price": 12.5, "quantity": 2})
        available = await client.call_tool("inventory.available", {"stock": 10, "requested": 3})
        assert not quote.is_error and not available.is_error
        assert quote.structured_content == {"result": 25.0}
        assert available.structured_content == {"result": True}
        print("quote:", json.dumps(quote.structured_content))
        print("available:", json.dumps(available.structured_content))


asyncio.run(main())
PY

Expected output:

tools: api.quote, inventory.available
quote: {"result": 25.0}
available: {"result": true}

The client closes the connection when finished. The server may also write logs to stderr. See the official Python client documentation for more client options.

Starting server.py --transport stdio alone waits for an MCP client; it is not a chat terminal. The MCP guide includes a video of discovery, calls and error handling with the Python client.

Continue with your own code

Initialize your project, inspect its functions and select the ones to expose. Review its source and dependencies before running the generated service. Follow The full journey to understand each stage, or the exposure guide for repository configuration.

For Apizr 0.4.6, your selected function can use statically resolvable private helpers in other modules without exposing those helpers.

Working with experiments instead? Start with the notebook guide or Python and Jupyter tracking.