Make your first MCP and REST calls
Working with a training notebook? Follow the Data Scientist quickstart for Apizr 0.4.5: inspect, record two Runs, compare and serve a prediction.
Inspect a training script first (0.4.5)
With Apizr 0.4.5 installed, you can start with experiment evidence before building a service. This example requires only Apizr's core:
cat > example.py <<'PY'
from sklearn.metrics import roc_auc_score
learning_rate = 0.05
roc_auc_score(labels, predictions)
PY
apizr experiment inspect example.py
Parameters shows a static candidate; Metrics shows a recognized call with its value unobserved. Undefined names and an absent sklearn installation are fine because inspection does not execute the file. See the experiment guide for data fingerprints, notebooks and all eight sections.
Run a trusted experiment (0.4.5)
inspect never executes your code. run executes trusted code in a fresh
process with host filesystem, network and subprocess access. It is not a security
sandbox. Use a complete workload with its dependencies already installed in the
same environment as Apizr. This separate example needs only the standard library:
cat > local_run.py <<'PY'
from pathlib import Path
mean = sum([2, 4, 6]) / 3
Path("model.json").write_text('{"mean": 4.0}\n')
PY
apizr experiment inspect local_run.py
RUN=$(apizr experiment run local_run.py --metric mean=mean --output model=model.json --format json | python3 -c 'import json,sys; print(json.load(sys.stdin)["run_digest"])')
apizr experiment list
apizr experiment show "$RUN"
The Run records the actual mean binding and the output's byte identity. The
local store contains evidence JSON, not a copy of the model. See
running experiments for notebooks, inputs,
timeouts, failed Runs and the difference between intent and observation.
Inspect → Run → Compare
“My ROC AUC changed. What else changed between these runs?” This core-only example records two trusted local runs, then compares their saved evidence.
cat > compare_train.py <<'PYTHON'
max_depth = 8
score = 0.91
PYTHON
apizr experiment inspect compare_train.py
A=$(apizr experiment run compare_train.py --metric roc_auc=score --format json | python3 -c 'import json,sys; print(json.load(sys.stdin)["run_digest"])')
cat > compare_train.py <<'PYTHON'
max_depth = 12
score = 0.92
PYTHON
B=$(apizr experiment run compare_train.py --metric roc_auc=score --format json | python3 -c 'import json,sys; print(json.load(sys.stdin)["run_digest"])')
apizr experiment list
apizr experiment diff "$A" "$B"
rm compare_train.py
apizr experiment diff "$A" "$B" --format json > comparison.json
A → B means evidence added in B or removed from A; numeric deltas are B - A.
show reads one Run; diff compares two Runs from the same validated local store.
Comparison still works after deleting the source. Unknown observations remain unknown.
Apizr identifies recorded differences; it does not prove which one caused the metric change.
Read the comparison guide for data, environment and output evidence.
After reviewing a successful Run, use experiment expose RUN --capability ID
--interface rest|mcp --output-dir DIR with explicit source authority. Select
resources with --artifact NAME and dependencies with --dependency DISTRIBUTION;
the recorded bytes and versions bind the resulting bundle. Repository Readiness
still decides eligibility. See Experiment → serving.
Make a service from functions
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.
Prepare an isolated workspace
Install Apizr with the pip instructions and keep core/ activated.
You need Python 3.11–3.14, 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.5. The new runtime/
environment will hold the generated server's dependencies separately from Apizr.
Get the example and choose its public functions
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.
Choose your interface
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.5, your selected function can use statically resolvable private helpers in other modules without exposing those helpers.