EyeTools

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.

01 / DESCRIBETool package

A config and a Python class.

02 / DISCOVEREyeTools server

Configuration becomes a registered tool.

03 / CONNECTMCP & HTTP

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:

terminal
git clone https://github.com/yyyanbj/eyetools.git
cd eyetools
uv sync

Start the server from the project root:

terminal
uv run eyetools-mcp serve \
  --host 127.0.0.1 --port 8000 \
  --tools-dir tools --lifecycle-mode lazy

The 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:

directory
tools/
└── echo/
    ├── config.yaml
    └── echo_impl.py

1. Describe the tool

entry identifies the module and class. The input schema describes the arguments shown to MCP clients.

tools/echo/config.yaml
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.

tools/echo/echo_impl.py
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:

terminal
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):

response
{
  "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:

MCP client configuration
{
  "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:

ModeBehavior
lazyLoad tools on first use.
eagerPreload tools at startup, including subprocess workers.
dynamicLoad 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:

EndpointPurpose
GET /healthService status and discovered tool count.
GET /toolsList registered tools and their schemas.
POST /predictInvoke a tool over HTTP.
GET /lifecycleInspect lifecycle states.
GET /metricsInspect execution metrics.
POST /admin/evict?tool_id=...Unload a tool or stop its worker.
GET /docsInteractive 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:

terminal
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:

config.yaml
environment_ref: my-tool
runtime:
  load_mode: subprocess

Install 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.