Dynamic discovery · MCP · Python
Your tools, discovered and ready for MCP.
EyeTools discovers tool packages from configuration, manages their execution, and exposes them through MCP and HTTP.
01 / HOW IT WORKS
From a folder to a tool
Point the server at one or more tool directories. At startup it scans recursively for config.yaml files, registers the tools it finds, and exposes their input schemas to MCP clients. Adding a tool does not require editing the server.
A config and a Python class.
Configuration becomes a registered tool.
Clients list and invoke your tools.
The open-source framework starts with an empty tools/ directory. It does not include domain tools, model weights, or datasets.
02 / GET STARTED
Start the server
Install Python 3.12+ and uv, then clone and install the project:
git clone https://github.com/yyyanbj/eyetools.git
cd eyetools
uv syncStart the server from the project root:
uv run eyetools-mcp serve \
--host 127.0.0.1 --port 8000 \
--tools-dir tools --lifecycle-mode lazyThe empty server reports {"status":"ok","tools":0} at http://127.0.0.1:8000/health. The HTTP API documentation is at /docs, and the streamable HTTP MCP endpoint is /mcp.
03 / EXTEND
Add your first tool
Create the following two files inside the scanned tool root:
tools/
└── echo/
├── config.yaml
└── echo_impl.py1. Describe the tool
entry identifies the module and class. The input schema describes the arguments shown to MCP clients.
id: echo
package: echo
entry: echo_impl:EchoTool
runtime:
load_mode: inproc
device_policy: cpu_only
model:
device: cpu
io:
description: Return the supplied message.
input_schema:
type: object
properties:
message:
type: string
required: [message]2. Implement the class
The server constructs the class with (meta, params) and passes input arguments under request["inputs"]. Return a JSON-serializable value.
class EchoTool:
def __init__(self, meta, params):
self.meta = meta
self.params = params
def predict(self, request):
return {"message": request["inputs"]["message"]}Restart the server to discover a newly added tool. To scan additional directories, repeat --tools-dir. Optional lifecycle hooks include prepare, load_model, warmup, and shutdown.
If you subclass ToolBase and override load_model, call super().load_model() after loading to record the loaded state.
04 / CONNECT
Call the tool
After restarting, list the discovered tools and call echo through HTTP:
curl http://127.0.0.1:8000/tools
curl -X POST http://127.0.0.1:8000/predict \
-H 'Content-Type: application/json' \
-d '{"tool_id":"echo","request":{"inputs":{"message":"hello"}}}'The response contains the tool output and request metadata (the timestamp below is illustrative):
{
"output": {"message": "hello"},
"__meta__": {"tool_id": "echo", "inputs": {"message": "hello"}, "ts": 0}
}For an MCP client that supports streamable HTTP, configure the endpoint using the client’s own settings. For example:
{
"mcpServers": {
"eyetools": {"url": "http://127.0.0.1:8000/mcp"}
}
}List tools in the client and call echo with a message argument.
05 / OPERATE
Lifecycle and API
Set --lifecycle-mode to choose when tools load and unload:
| Mode | Behavior |
|---|---|
lazy | Load tools on first use. |
eager | Preload tools at startup, including subprocess workers. |
dynamic | Load on demand and run background idle/unload maintenance. |
Dynamic mode accepts --dynamic-mark-idle-s, --dynamic-unload-s, and --dynamic-interval-s. Useful HTTP endpoints include:
| Endpoint | Purpose |
|---|---|
GET /health | Service status and discovered tool count. |
GET /tools | List registered tools and their schemas. |
POST /predict | Invoke a tool over HTTP. |
GET /lifecycle | Inspect lifecycle states. |
GET /metrics | Inspect execution metrics. |
POST /admin/evict?tool_id=... | Unload a tool or stop its worker. |
GET /docs | Interactive HTTP API reference. |
The API has no built-in authentication. If it is reachable beyond localhost, put it behind an authenticated proxy.
06 / ISOLATE
Tool environments
For a tool with its own dependencies, use a subprocess and a dedicated uv project under envs/. From the EyeTools project root:
mkdir -p envs/my-tool
cd envs/my-tool
uv init --no-package --no-workspace --python 3.12
uv add --no-workspace --editable ../..
# Add the dependencies your tool actually needs:
# uv add --no-workspace <tool-dependency>
cd ../..Set these fields in that tool’s config.yaml:
environment_ref: my-tool
runtime:
load_mode: subprocessInstall the tool’s actual dependencies in that environment. The server working directory is the workspace root used to resolve envs/. For GPU tools, explicit model.weight_paths can be used with uv run eyetools-mcp estimate-vram --tool-path tools.