Skip to content

About

DecisionNode MCP server: the decision engine's calibrated judgments as tools for AI agents

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

DecisionNode MCP server

DecisionNode is a decision engine: send it text, JSON or images with a question, and it answers with calibrated probabilities.

DecisionNode's judgments as tools for AI agents. An agent asks a narrow question about a piece of content (is this claim supported, which team should take this ticket, how urgent is it, how many line items does the invoice list, where in the photo is the damaged package) and gets a calibrated answer back in one tool call.

Every tool call is a DecisionNode API request made with your own API key. The server stores no key, state, question or answer.

Install

You need a DecisionNode API key and uv. The server runs on your machine and your MCP client starts it over stdio:

uvx bynn-decisionnode-mcp

Before the package's first release on PyPI, install it from this repository instead:

uvx --from git+https://github.com/Bynn-Intelligence/decision-node-mcp-server bynn-decisionnode-mcp

In the client setups below, the git form replaces bynn-decisionnode-mcp with --from git+https://github.com/Bynn-Intelligence/decision-node-mcp-server bynn-decisionnode-mcp.

Claude Code

claude mcp add decisionnode -e DECISIONNODE_API_KEY=<key> -- uvx bynn-decisionnode-mcp

Claude Desktop

Add the server to claude_desktop_config.json (Settings, Developer, Edit Config) and restart Claude Desktop:

{
  "mcpServers": {
    "decisionnode": {
      "command": "uvx",
      "args": ["bynn-decisionnode-mcp"],
      "env": { "DECISIONNODE_API_KEY": "<key>" }
    }
  }
}

Other MCP clients

Cursor, Windsurf and other clients configured by a JSON file take the same entry under mcpServers; VS Code takes it under servers. Any client that launches a server over stdio works: the command is uvx, the argument is bynn-decisionnode-mcp, and the key goes in the server's environment as DECISIONNODE_API_KEY. The server name (decisionnode above) is yours to choose.

Over HTTP

The same package serves Streamable HTTP, for a team that runs one server for several clients:

uvx bynn-decisionnode-mcp --transport http --host 0.0.0.0 --port 8000

The endpoint is the root path, stateless, with GET /health. Each client sends its own key on every request, Authorization: Bearer <key>, and the server forwards it to the API; it never uses a key of its own. Over HTTP a tool never reads a file path, and the base64 image data of one call is limited to 3 MiB (see Images below).

claude mcp add --transport http decisionnode http://127.0.0.1:8000/ \
  --header "Authorization: Bearer $DECISIONNODE_API_KEY"

Tools

All twelve are read-only and idempotent (readOnlyHint and idempotentHint true, destructiveHint false). Each declares an output schema and returns its result twice: as structured content, and as the same JSON in a text block.

Tool Use it to Returns Costs
guide Learn how to use the other tools well The guide, as text Nothing
truth Ask one yes-or-no question about a state {"truth": 0.94, "cost": {...}} A request
choice Pick one option from a set you define (1 to 255) {"choice", "confidence", "probabilities", "cost"} A request
score Place a state on an ordered scale (1 to 10 levels) {"score", "confidence", "legend", "probabilities", "cost"} A request
number Ask for a count or a quantity within a range {"number", "expected", "confidence", "probabilities", "cost"} A request
point Find one spot in an image {"x", "y", "present", "confidence", "image", "width", "height", "cost"} A request
box Draw a box around one thing in an image {"x1", "y1", "x2", "y2", "present", "confidence", "image", "width", "height", "cost"} A request
decide Ask several questions of any type about one state, with boosters if you like {"answers": {id: answer}, "model", "input_tokens", "cost"}, and evidence with boosters One request, plus one booster call per picture a booster reads
decide_many Ask the same questions about many states (1 to 50 items) {"results": {item id: {"answers", "cost"} or {"error"}}, "input_tokens"} One request per item
decide_video Ask questions of any type about videos sent as frames {"answers", "model", "input_tokens", "cost", "video_frames", "video_seconds"}, and warnings One request
search_knowledge See the passages a knowledge base gives a query, without a decision The reading: {"collection", "passages", "warnings", "pending_items", ...}, each passage with its text One knowledge call
models List the models the key may use, with their limits {"models": [...]} Nothing

Inputs:

Tool Arguments
truth instructions, state, if_true, if_false, images, model
choice instructions, options (a list of names, or an object of name to description), state, images, model
score instructions, levels (lowest first), state, images, model
number instructions, min, max, step, state, images, model
point, box instructions, images (at least one), image, units, state, model
decide questions (id to {type, instructions, criteria}, {type, instructions, min, max, step} for a number, or {type, instructions, image, units} for a point or a box, as the API takes them), state, images, boosters, model
decide_many items (a list of {id, state, images}, images optional and each item's own), questions, model
decide_video questions, videos, state, images, model
search_knowledge collection (a knowledge base's col_ id), query, scope, top_k

cost, on every tool that asks a question, is what the call cost as the API booked it, in USD: usd, split into model_usd and boosters_usd, and free_tier. It is passed through as sent and appears when the API sends it.

state is a string or any JSON value and may be left out. model defaults to decisionnode-moe; decisionnode-flash is the faster, lower-cost tier. Both mean the current release; a result's model is the pinned id of the version that answered. The question types are truth, choice, score, number, point and box.

Values are passed through exactly as the API sent them. A result has a safety field only when the API's safety check flagged a category for the key.

Images

Up to 16 images a call, within the model's token limit, 10 MB each, JPEG, PNG or WebP. An image is {id, url}, {id, data, media_type} or, on your own machine, {id, path} for a file. A url must start with https://; any other scheme is refused before a request is made.

There are two ways to say which image a question is about:

  1. By id (recommended). Give each image a short, meaningful id and use it in the instructions, in backticks: "Does the total on receipt match the amount in the claim?"
  2. By number. Images are numbered from 1 in the order of images: "image 2", "the second image", "Picture 2" and "the last image" all work.

The model sees each image as Picture 1 (receipt):, Picture 2 (claim_photo):, so both ways point to the same image. Prefer ids: they stay right when images are added or reordered. Use names, not digits, as ids (front, not 2). Videos work the same way, by id or by number ("video 1"), numbered separately in the order of videos. The image of a point or box question takes the id.

Over HTTP one call's body is limited to 4 MiB by the MCP transport, so the base64 data of all images in a call (every item's, in decide_many) is limited to 3 MiB in total (3,145,728 characters, about 2.25 MiB of image bytes). A call with more is refused with an invalid_request_error tool result before any request is made, and a body over 4 MiB is answered 413. Send larger images by https url; the API fetches them itself, up to the model's own limit (10 MB an image).

Number questions

A number question asks for a count or a quantity within a range: how many people are in the photo, how many line items the invoice lists, the year a contract was signed, a rating in halves. min and max bound the answer (0 and 255 when left out) and step sets the grid of possible answers (1 when left out; 0.5 for halves). The grid holds at most 4,096 values; keep the range as narrow as the question allows.

{"type": "number", "instructions": "How many cars are visible?", "min": 0, "max": 50}

The answer:

{"number": 7, "expected": 7.3, "confidence": 0.62, "probabilities": {"6": 0.21, "7": 0.62, "8": 0.14}}

number is the most probable value on the grid and confidence its probability. expected is the probability-weighted mean, a fractional estimate even on a grid of whole numbers. probabilities lists the values above 0.001, keyed by the value as a string, so they can sum to less than 1. Number questions work on text, images and video frames, alone with the number tool or beside other questions in decide, decide_many and decide_video.

Points and boxes

A point or box question asks where something is in an image: point gives one spot, box a rectangle around the thing. The instructions say what to find, as a phrase ("the yellow package"), a description with conditions ("the person standing nearest the door") or a question ("Where is the crack?"), and can refer to the state and to the other images ("the part that differs from the reference image").

{"type": "box", "instructions": "Draw a box around the damaged package.", "image": "belt"}
Field
instructions What to find.
image The id of the image to look in (a string). Needed with two or more images; with one, that one is used. A reference by number ("Picture 2") goes in the instructions.
units pixels (the default): pixels of the image as sent. normalized: fractions of the width and the height, from 0 to 1.

A point or box question needs at least one image in the call. The point and box tools require images and check image before any request is made.

The answers:

{"x": 812, "y": 455, "present": 0.97, "confidence": 0.91, "image": "belt", "width": 1920, "height": 1080}
{"x1": 300, "y1": 120, "x2": 520, "y2": 410, "present": 0.95, "confidence": 0.88, "image": "belt", "width": 1920, "height": 1080}
Field
x, y The point, from the image's top-left corner, x to the right and y down: whole pixels, or fractions with normalized units.
x1, y1, x2, y2 The box: its top-left corner (x1, y1) and bottom-right corner (x2, y2), in the same units.
present The probability that what the instructions describe is in the image at all. Under a low present the place is only a best guess for something probably not there.
confidence The probability that the place is right if the thing is there: for a point, that it lies on the thing; for a box, that it overlaps the true box by at least half (an intersection over union of 0.5 or more).
image, width, height The image looked in and its size in pixels, to draw or convert the answer without another lookup.

Every field is always there, so the shape never changes. Act on an answer when both present and confidence are high, and hand the image to a person when either is low. The thresholds are yours. When several things match ("the yellow package" with three on the belt), the answer is the best match; the instructions can say which one ("the leftmost yellow package"). Point and box questions mix with the other types in decide, and in decide_many each item can carry its own images, such as one photo per item.

Boosters

decide takes boosters: specialist models that read the call's images, or your own documents (knowledge, below), before the model answers. Their readings are evidence the model weighs, and they come back in the result as evidence: per booster its version, status, latency_ms, billed_calls and results, one reading per picture, exactly as the API sent them. Up to 4 a call, each once, in your order: a name, or {"name", "images", "require", "options", "timeout_ms"} to read only some images, to fail the call when the booster fails ("require": true), to set its options or to give it its own time budget. The names are free text, so a booster added to the API works without an update of this server. Each picture a booster reads is billed as one booster call; a booster that fails or runs out of time is free.

Booster Reads Options
age The age of the person in a picture none
ai_generated Whether a picture was made by a generator none
face_detection The faces in a picture: faces (how many) and detections, a box and a confidence per face none
face_recognition The faces in a picture matched against a collection: per match a box, a score, the collection and the item's reference, with your label or a public figure's name collection (required: celebrities, or the id of one of your workspace's face collections), threshold 0 to 1 (0.5)
ocr The text a picture shows prompt
bynn_depth (Depth Vision) Distances in meters: ahead (the nearest obstacle straight ahead, the floor left out, or null), directions (7, left to right), ground, and a grid with depth_min, depth_max and depth_median grid_size, 2 to 16 (8)
knowledge The passages of your knowledge base that apply to the call (below); reads no images collection (required), scope, query, top_k, max_tokens, match, filter, pinned, min_score, include_text
bynn_segment (Segment Vision) What prompt names: per match label, score, box, polygon, area, centroid, opening (the hole of a gate, doorway or ring, or null), and with depth on its depth and size_m in meters prompt (required: one phrase, or a list of 1 to 4 of at most 120 characters), max_instances 1 to 16 (8), min_score 0 to 1 (0.25), polygon_points 3 to 64 (24), depth (true)

Depth Vision and Segment Vision are made for robots and drones. Segment Vision's coordinates are fractions of the picture from its top left, and descriptive phrases ("orange H landing pad", "purple octagonal frame on a pole") match better than a single word. Their distances and sizes are estimates from one picture, for decisions and planning, never a safety stop: a robot or a drone uses stereo cameras or LiDAR for that.

{
  "state": "Drone at 1.5 m, heading to the next gate of the course.",
  "images": [{"id": "front", "path": "~/frames/front.jpg"}],
  "boosters": [
    {"name": "bynn_depth", "options": {"grid_size": 8}},
    {"name": "bynn_segment", "options": {"prompt": ["purple gate", "orange H landing pad"]}}
  ],
  "questions": {
    "clear": {"type": "truth", "instructions": "Is the path straight ahead clear for at least 2 meters?"},
    "gate": {"type": "point", "instructions": "The centre of the opening of the purple gate.", "image": "front"}
  }
}

Ask the question your code acts on and read its answer; the readings in evidence are there to check and log. Every booster DecisionNode offers is listed at decisionnode.com/docs/concepts/boosters.

Knowledge

A knowledge base is a collection of your own documents: a claims policy, house rules, a mission's special instructions, a robot's operating manual. The knowledge booster finds the passages that apply to a call and gives them to the model as facts and rules to apply, and evidence.knowledge says what it read, with a citation for each passage.

{
  "state": {"claim": {"amount": "740.00", "receipt": false}, "mission": "M-42"},
  "boosters": [
    {"name": "knowledge", "require": true,
     "options": {"collection": "col_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80", "scope": "M-42"}}
  ],
  "questions": {
    "approve": {"type": "truth", "instructions": "Should the claim be approved under our policy?"}
  }
}
Option
collection Required: the knowledge base's col_ id. The booster takes the object form only, and no images.
query What to search for, 1 to 2,000 characters. Left out: built from the questions' instructions and the state.
scope The value of the knowledge base's scope key (such as a mission id): the global documents and that scope's are read. Left out on a scoped knowledge base: the global documents only, with the warning scope_not_named.
top_k The most passages found by search, 0 to 20 (4). 0: the pinned documents only.
max_tokens The passages' token budget: "auto" (the default) or 128 to 8,192.
match, filter Narrow by metadata: match by equality ({"region": "SE"}), filter as a typed tree of conditions.
pinned false leaves the pinned documents out.
min_score 0 to 1: a floor for passages found by meaning alone.
include_text true adds each passage's text to the evidence; needs a full-scope key.
  • Pinned documents are always given, whatever the query. A rule that must always apply is pinned, not left to search.
  • Citations. evidence.knowledge.results[0].passages lists what the model read, pinned first, each with n, pinned, item (the document's id), reference, title, heading, chunk, byte_start and byte_end (UTF-8 byte offsets, end exclusive, into the document's stored text), tokens and score (null for a pin).
  • require. Without it a reading that did not run (no document ready yet, a timeout) leaves the decision to run without it, unbilled, and evidence says why. With "require": true the call fails instead: booster_unavailable (529, with retry_after) while documents are still being processed, and knowledge_collection_empty (409, with details.items and details.failed) when the knowledge base has no document ready or on its way.
  • search_knowledge runs the same retrieval without a decision and returns the reading with each passage's text, to check what a knowledge base gives a query before a decision relies on it. It needs a full-scope key.
  • Each knowledge reading and each search is billed as one knowledge call. Documents are added and managed with the SDKs, the dn CLI or the console; this server only reads.

More at decisionnode.com/docs/knowledge.

Video (frames)

Only frames are sent: no video file, video URL or sound ever reaches the API. A video is the frames a camera or a script took, each with its time, and the model reads them as one video, in order and with their times:

{
  "id": "dock",
  "fps": 2,
  "frames": [
    {"t": 30.0, "media_type": "image/jpeg", "data": "<base64>"},
    {"t": 30.5, "media_type": "image/jpeg", "data": "<base64>"}
  ]
}
Field
id Unique within the call, across images and videos. The model sees the video as "Video " (its place in videos, from 1) with this id beside it, so questions can refer to it by either.
frames The frames in time order, all of one size: t in seconds, larger than the frame before; media_type image/jpeg, image/png or image/webp; data the image as base64.
fps A cap on the frames read a second: 2 when left out, at most 4.
start, end A window in seconds on the frames' own times; every frame when left out.

On your own machine a video can also be a file, {"id": "dock", "path": "~/clips/dock.mp4", "fps": 2, "start": 30, "end": 45}. The server samples it into JPEG frames on that machine with ffmpeg, at fps within start and end (seconds from the video's first frame), at most max_frames of them (32 when left out, up to 128). A window that holds more frames at that rate is cut there, and the result's warnings say so. The frames are then sent as above; the file itself and its sound stay on the machine. ffmpeg and ffprobe must be on the PATH the client starts the server with (set PATH in the server's env if they are not); without them a path video is refused with an error that says so, and frames still work. Over HTTP the server never reads a path.

Send as few frames as the question needs: one or two for one item on a sorting line, about one a second for an incident such as a fall or smoke, about two a second for counting what passes. The API publishes its video limits with the models (models). Over HTTP the base64 data of the images and the frames of one call share the 3 MiB above.

A decide_video result carries answers, model and input_tokens as decide does, plus video_frames and video_seconds (what the API read), and warnings when the API read a video at a lower rate or a smaller size than sent, or a file's window was cut.

Errors

A failed call is a tool result with isError true and the error as structured content:

{
  "error_type": "invalid_request_error",
  "message": "Too many score levels. Must have at most 10 levels.",
  "reason": "question 'score': a score takes up to 10 levels, got 11",
  "status": 400,
  "request_id": "req_..."
}

reason is the API's explanation in words, so the agent can correct the call. A request refused under the usage policy has error_type refusal_error and its category. A 422 adds entries; a 429 or 529 adds retry_after in seconds; an error whose body carries details (such as knowledge_collection_empty's items and failed) adds them as sent. A call that named boosters adds boosters, what each one did (status and latency_ms, from the x-decisionnode-boosters header), so a required booster that failed (the 529 booster_unavailable) shows which. A booster's refused setting is a 400 api_usage_error whose reason names it, such as "options": 'prompt' is required. Temporary failures (408, 429, 5xx, timeouts, dropped connections) are retried twice before an error is reported; 400, 401, 402, 403, 404 and 422 are never retried.

Configuration

Variable Default
DECISIONNODE_API_KEY none The API key, on stdio. Over HTTP each caller's own key is used and never this one.
DECISIONNODE_BASE_URL https://api.decisionnode.com The API the tools call. Must be https://; plain http:// is accepted only for a loopback address (localhost, 127.0.0.1, ::1), and any other is refused at startup.
DECISIONNODE_MODEL decisionnode-moe The default model.
DECISIONNODE_MCP_RATE_LIMIT 120 over HTTP, 0 (off) on stdio Requests a minute per key on each server process; 0 turns it off. A negative or non-numeric value is refused at startup. Several processes each count on their own. At most 10,000 keys are tracked per process; when the table is full, a new key replaces one whose minute has passed, and if every tracked key has called within the minute the new key's call is refused with the rate-limit error.
MCP_TRANSPORT stdio Or http; the --transport flag wins.
MCP_HOST, MCP_PORT, MCP_PATH 127.0.0.1, 8000, / HTTP only; the --host, --port and --path flags win.

Over HTTP the server accepts any well-formed bearer value and refuses a request without one (401). The API decides whether the key is valid. A key the API has just answered 401 for is refused for the next 60 seconds with the same error result, and no request is made; the server holds only the key's SHA-256 digest, in memory. The key is never written to a log.

Development

Requires Python 3.10 or later and uv.

git clone https://github.com/Bynn-Intelligence/decision-node-mcp-server
cd decision-node-mcp-server
uv sync
uv run bynn-decisionnode-mcp                      # stdio
uv run bynn-decisionnode-mcp --transport http     # http://127.0.0.1:8000/

The tests run against the mock API server of the DecisionNode SDK conformance suite and never call a real endpoint. Clone the suite beside this repository, or point DECISIONNODE_CONFORMANCE_DIR at it:

git clone https://github.com/Bynn-Intelligence/decision-node-conformance ../decision-node-conformance
uv run pytest

The video file tests need ffmpeg and ffprobe and are skipped without them. After a deliberate change to a tool's schema or result, regenerate the golden files with UPDATE_GOLDEN=1 uv run pytest tests/test_golden.py.

License

MIT. See LICENSE.

About

DecisionNode MCP server: the decision engine's calibrated judgments as tools for AI agents

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages