kulikowski.me
← Writing

Trying Jev with WebMCP

4 min read#agentic-web #webmcp #agents #jevview as markdown

TypeSafe recently introduced Jev, a new model built for making structured decisions.

Jev is not meant to generate text. Instead, you provide the state of your application and ask typed questions such as: yes or no?, which one of these options?, or what score would you give this?. Jev returns the answer together with probabilities and confidence.

Jev currently has three possible output types:

OutputWhat it returnsSimple example
NoulYes or no, with a probabilityShould this request be escalated?
ChoiceOne option from a list, with a probability for every option and overall confidenceWhich WebMCP tool should run next?
ScoreA value on a scale, with probabilities across that scale and overall confidenceHow risky is this action?

When I first saw it, I immediately started thinking about WebMCP tools (of course!). And I built a small Chrome extension to try it. 😁

The whole experiment is on GitHub if you want to try it yourself: Kulikowski/webmcp-jev-experiment

Here it is solving the WebMCP Maze using only Jev 👇.

This pattern could work with any structured decision model - a model that chooses from options derived from the available tools and their input schemas, instead of generating arbitrary text.

Tool calls with Jev

In this experiment, Jev is enough even when a tool has parameters - as long as those parameters are enumerable, meaning their possible values can be listed in advance:

  • Enums: the model picks from options such as light, dark or system.
  • Booleans: the model chooses true or false.

A tool with no parameters, or only enumerable parameters, is what I will call a fully enumerable tool. None of these tools needs the model to generate a free-text value.

This idea immediately reminded me of the WebMCP Maze from the webmcp-tools repository. It is a demo where an agent has to start a game and find its way through a maze using WebMCP tools.

The tools exposed by the default demo are fully enumerable:

  • start_game: no arguments.
  • look: no arguments - it returns the current position, open directions, nearby blockers and items.
  • move: one direction argument with four options: north, south, east or west.
  • pickup: no arguments.
  • drop: no arguments.
  • use: the same four direction options, used to open a door or clear a rock with the item currently held by the player.

The default maze is therefore a multi-tool task made entirely from fully enumerable tools. In this experiment, Jev can choose every tool and every argument without generating text.

The user can ask:

Start a new game and solve the maze.

The extension sends Jev the user request and a choice between all available tools. I also add __finish_task__ as one of the options, so Jev can say that no more tools need to be called.

If Jev selects move, the extension reads its schema and asks another choice question for the direction argument (from the enum).

The result is a valid call without asking the model to generate JSON, a tool name or an arbitrary argument. Once that call finishes, its result goes back to Jev so it can choose the next action.

And then I needed a string

But what happens when a tool needs a search query, an email address, a destination or any other value that is not known in advance?

Jev cannot create that value, because generating arbitrary text is deliberately not what it does - so I've added fallback to LLM 😁.

The split now looks like this:

1️⃣ Jev decides which tool should run next.

2️⃣ Jev fills every enumerable parameter. If the tool has no inputs, or no other parameters remain, Jev handles the whole call.

3️⃣ If the same tool also has free-text, numeric or other non-enumerable parameters, LLM fills only those remaining values.

LLM does not choose from the full list of tools. It receives only the tool already selected by Jev, together with the parameters that Jev has already filled. It is constrained to fill the rest and call that tool.

Jev always decides what should happen next. It also fills every value it can pick from an enumerated list. LLM joins the same tool call only when some values are left.

The activity log

Instead of returning verbose why - Jev returns the choice, confidence value, and probabilities for the available options.

The extension shows those values in the activity log. You can see which tool "won", how the other tools were scored, which arguments Jev selected and whether LLM was needed.

Here is one tool call split between both models - Jev fills amenities, while LLM fills max_price 👇.

It also counts Jev and LLM requests separately. I want to see how often a WebMCP workflow can stay on the Jev path and which tool schemas cause the handover to LLM.

This is an experiment

It is a small prototype for checking one idea: how much of WebMCP tool calling can be expressed as structured decisions?

A few takeaways so far:

1️⃣ Fully enumerable tools are an interesting category on their own - If a tool has no parameters, or every parameter has a known list of possible values, a structured decision model can select both the tool and its arguments. The maze demo shows that even a multi-step task can be built entirely from those tools.

2️⃣ The parameter schema decides which model fills each value - Jev fills the enumerable parameters. If the same tool also needs free-text, numeric or other non-enumerable values, the extension asks an LLM to fill only those remaining parts.

3️⃣ A multi-tool loop does not need one model "type" to do everything - Jev can decide what happens next, an LLM can fill the free-text input, and harness code can validate and execute the call.

4️⃣ The probabilities could become part of the workflow - Right now I only show them in the log. But could the harness use them to ask for confirmation, choose the LLM fallback or stop when Jev is not confident enough? 🤔 I think so!

5️⃣ Several decisions can come back in one Jev request - TypeSafe says Jev produces all answers in a query in parallel, instead of generating them token by token. The extension already uses this: the same request asks which tool should run next and asks for the enumerable parameters of the fully enumerable tools. The WebMCP tools still run one after another - it is the decision-making inside each step that happens in parallel.

6️⃣ Jev cannot come up with another choice - The extension provides the available tool names as the options. Jev can still choose the wrong tool, but it cannot return a tool name that was not offered. That is a useful difference from asking a generative model to write the name.

I'll be exploring this further and sharing what I learn 🚀.

More soon. ✨