Skip to main content
Glama

Laya MCP

An MCP server for Laya, the open decision model from ConvAI Innovations. It lets Claude and any other MCP client ask typed questions about a piece of text or JSON and get back a structured answer with probabilities, in tens of milliseconds and without generating text.

It also exposes a small REST endpoint, so tools like n8n can use the same model without speaking MCP.

This is an unofficial project and is not affiliated with ConvAI Innovations.

state:     {"body": "I asked for a refund three weeks ago. If I don't hear back by Friday I'm cancelling."}
questions: intent (choice), urgency (score), churn_risk (noul)

answer:    intent = cancel (p 0.71), urgency = high, churn_risk = 0.88   (illustrative)

Read this before relying on it

Laya is fast and cheap, but the base checkpoints are not accurate out of the box on most domains. In our own tests on accessibility checks, the base multilingual model was close to random on qualitative judgments, and some wrong answers came back with a confidence above 0.9. The Laya authors say the same thing in their model card: the checkpoints ship overconfident and should be recalibrated on your data before you trust the probabilities.

In practice:

  • Measure it on 50 to 200 examples you have labeled yourself before putting it in any workflow.

  • Use include_all: true in batch calls to get every answer, not only the uncertain ones, when you measure.

  • Fit a temperature per question type with scripts/fit_temperature.py and load it with CALIBRATION_PATH. Calibration makes the confidence honest. It does not make the model more accurate; for that you need fine-tuning.

  • Treat a high confidence as a reason to route automatically only after you have seen, on your data, that high confidence and correct answers go together.

Binary questions with concrete, observable criteria work best. Questions that need to understand the meaning of a text ("does this error message help the user fix the problem?") are where the base model struggles most.

Related MCP server: rbac-rag-assistant

Tools

Tool

What it does

laya_classify

Answers the questions for one state. Returns every answer with probabilities and confidence.

laya_classify_many

Same questions over up to MAX_ITEMS states. Returns counts per option (choice), means (score, noul) and the answers below the confidence threshold. include_all: true adds every answer. An id field in each item is echoed back.

laya_info

Loaded checkpoints, limits, threshold and calibration in use.

All tools are read-only and idempotent.

Question types

{
  "intent": {
    "type": "choice",
    "instructions": "What does the customer want?",
    "criteria": { "refund": "Wants money back", "cancel": "Wants to cancel", "other": "Anything else" }
  },
  "urgency": {
    "type": "score",
    "instructions": "How urgent is this message?",
    "criteria": ["low", "medium", "high", "critical"]
  },
  "churn_risk": {
    "type": "noul",
    "instructions": "The customer is threatening to leave."
  }
}
  • choice picks one option. criteria is an object from option to description. Keep it under about 20 options.

  • score places the state on an ordered scale. criteria is a list of levels. It is the weakest of the three types.

  • noul returns the probability that the instruction is true for the state.

A full example is in examples/questions.example.json.

Quick start

You need Docker and a machine with at least 4 GB of free RAM. No GPU is required.

git clone https://github.com/OWNER/laya-mcp.git
cd laya-mcp
cp .env.example .env
# set API_TOKENS to the output of: openssl rand -hex 32
docker compose up -d
docker compose logs -f   # the first start downloads about 1.3 GB of weights

When curl localhost:8000/healthz returns {"status": "ok"}, the server is ready. The MCP endpoint is http://localhost:8000/mcp.

Add it to Claude Code:

claude mcp add --transport http laya http://localhost:8000/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

Authentication

Pick one mode with AUTH_MODE.

Mode

Use it for

How clients authenticate

bearer (default)

Claude Code, n8n, scripts, any client that can send a header

Authorization: Bearer <token>, tokens listed in API_TOKENS

github

claude.ai custom connectors, which authenticate with OAuth

GitHub login, restricted to ALLOWED_GITHUB_USERS

none

Local development on 127.0.0.1 only

Nothing. The server logs a warning if it listens on another address.

Setting up GitHub OAuth for claude.ai

  1. On GitHub, go to Settings, Developer settings, OAuth Apps (not GitHub Apps), New OAuth App.

  2. Homepage URL: your public URL, for example https://laya.example.com. Authorization callback URL: the same URL followed by /auth/callback.

  3. Copy the client ID, generate a client secret, and put both in .env together with BASE_URL, ALLOWED_GITHUB_USERS (GitHub usernames, comma separated) and APP_SECRET (openssl rand -hex 32).

  4. In claude.ai, add a custom connector with the URL https://laya.example.com/mcp.

GitHub lets any account complete the login, so the allowlist is enforced on every tool call. OAuth client registrations and tokens are stored encrypted in DATA_DIR and survive restarts as long as the volume does. Do not change APP_SECRET after the first start, or every client will have to reconnect.

In github mode the REST endpoint is disabled unless you also set API_TOKENS.

Deploy

The server is a single Docker image that needs roughly:

  • RAM: 2.5 to 3 GB per loaded checkpoint, plus about 1 GB. One checkpoint fits in 4 GB.

  • CPU: 2 vCPU are enough for interactive use. Set OMP_NUM_THREADS below the core count on shared hosts.

  • Disk: about 5 GB for the image and the weights.

  • A persistent volume on /data, so weights and OAuth state are not lost on restart.

  • HTTPS in front of it if clients reach it over the internet.

Every push to main publishes an image to ghcr.io/OWNER/laya-mcp through the included GitHub Actions workflow, so you can deploy either from the repo or from the image.

Any Docker host

Use docker-compose.yml as shown in the quick start, behind the reverse proxy you already have (Caddy, Traefik, nginx). The MCP transport is stateless and returns plain JSON, so it works behind proxies that do not handle streaming well.

Hugging Face Spaces (free CPU tier)

  1. Create a Space with the Docker SDK.

  2. Push this repository to it and add this front matter at the top of the Space's README.md:

    ---
    title: Laya MCP
    sdk: docker
    app_port: 8000
    ---
  3. Set the variables from .env.example as Space secrets, with DATA_DIR=/data and HF_HOME=/data/hf.

  4. The endpoint is https://<user>-<space>.hf.space/mcp.

Limits of the free tier: the Space sleeps after a period of inactivity, the first request after that waits for a cold start, and without paid persistent storage the weights are downloaded again and OAuth registrations are lost at every restart. With AUTH_MODE=github that means reconnecting in claude.ai. bearer mode has no such problem.

CapRover

The repo includes a captain-definition, so you can create an app and deploy it from the repository with the "Deploy from Github" method, or deploy the published image.

In the app settings:

  • Container HTTP port: 8000.

  • Persistent directory: /data.

  • Environment variables from .env.example.

  • In HTTP Settings, edit the nginx configuration and add these lines inside the location / block. CapRover proxies with HTTP/1.0 by default, which breaks MCP clients.

    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_read_timeout 120s;

Check that the host has enough free memory: a container killed with exit code 137 was out of memory.

Other platforms

Any platform that builds a Dockerfile, gives you a persistent volume and at least 4 GB of RAM will work. There is nothing platform-specific in the image.

Without Docker

uv venv --python 3.11
uv pip install torch -r requirements.txt   # add --index-url https://download.pytorch.org/whl/cpu for CPU-only torch on Linux
cp .env.example .env                      # set DATA_DIR=./data and HF_HOME=./data/hf
set -a; source .env; set +a
.venv/bin/python server.py

Configuration

Variable

Default

Description

AUTH_MODE

bearer

none, bearer or github

API_TOKENS

Comma-separated bearer tokens

BASE_URL

Public URL of the server, required in github mode

GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET

GitHub OAuth App credentials

ALLOWED_GITHUB_USERS

GitHub usernames allowed to use the tools

APP_SECRET

Signs tokens and encrypts OAuth state. Keep it stable.

LAYA_CHECKPOINTS

multilingual

multilingual, english, typed-decisions, comma separated. Use multilingual for anything that is not English.

MAX_ITEMS

50

Maximum items per batch call

LOW_CONFIDENCE

0.7

Answers below this are reported in low_confidence

LOW_CONFIDENCE_FIELD

answer_confidence

Field used when no calibration is loaded: answer_confidence or the stricter confidence

CALIBRATION_PATH

JSON file with temperatures

OMP_NUM_THREADS

CPU threads for inference

HOST, PORT

0.0.0.0, 8000

Listen address

DATA_DIR

./data (/data in Docker)

OAuth state and anything else the server persists

HF_HOME

Where the model weights are cached

LOG_LEVEL

INFO

Calibration

  1. Label some data yourself: at least 50 examples per question type, ideally a few hundred, with both outcomes represented.

  2. Run them through laya_classify_many or POST /v1/classify with include_all: true.

  3. Write one JSONL line per answered question:

    {"type": "choice", "probabilities": {"pass": 0.91, "fail": 0.09}, "expected": "fail"}
    {"type": "noul", "noul": 0.83, "expected": true}
  4. Fit the temperatures and load them:

    python scripts/fit_temperature.py labeled.jsonl > data/calibration.json
    # then set CALIBRATION_PATH=/data/calibration.json and restart

The script prints accuracy and expected calibration error before and after, per group. Keep a separate test set that you never use for fitting, and pick your routing thresholds on that one.

With calibration loaded, every answer gets a calibrated block next to the original fields, and the low-confidence threshold applies to calibrated.answer_confidence.

REST API

POST /v1/classify with a bearer token.

curl -X POST https://laya.example.com/v1/classify \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d @examples/questions.example.json

Send state for one item, or items (plus optional include_all) for a batch. The responses have the same shape as the MCP tools. GET /healthz is open and returns 503 while the model is loading.

Contributing

Issues and pull requests are welcome, especially:

  • measured accuracy on public datasets, in languages other than English;

  • calibration files for common question shapes;

  • deployment notes for platforms not covered here.

Credits and license

Laya is developed by ConvAI Innovations and released under Apache 2.0. This server is also released under the Apache License 2.0, see LICENSE.

Built on FastMCP.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to ask plain-language questions and receive answers grounded only in documents the configured role is cleared to read, with the same access-controlled tools available across any client.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Serves a governed knowledge system of record over MCP, letting agents answer questions with citations drawn only from approved documents and explicitly decline when the record does not cover the question.
    505 npm
    -
  • A
    license
    A
    quality
    C
    maintenance
    Enables MCP clients to call TypeSafe's JEV classifier and receive structured, typed judgments with probabilities for binary, choice, and scoring questions.
    1
    7
    MIT