Repository navigation
v1.5.0
Every deployment should upgrade: the agent, Google Drive and image MCP servers
answered without a login, an empty AUTH_PASSWORD let anyone sign in with empty
fields, any signed-in user could fire a trigger, and a widget admin key could change
another agent's files. The release brings costs in US dollars with dollar budgets,
a Playground to compare models and instructions, Trace Replay on the Visual
Builder canvas, an image tool for any LiteLLM image model, alerts on fallback
model use and to Slack and Discord, and a Docker image on GHCR. MySQL works
for the first time, and A2A works again on a2a-sdk 1.x.
Before upgrading:
- MCP clients of
/agents/<name>/mcp,/gdrive/mcpand/images/mcpneed
credentials, for example a personal access token. POST /triggers/{id}/firewithout the trigger's key needs an admin.- With
CSP_MODE=enforce, inline scripts need the nonce;report-only, the default,
blocks nothing. SUPABASE_BUCKETdefaults toartifactseverywhere; set it if you relied on
public-bucket.
Migrations V037 to V043 run on startup.
Added
- Trace Replay on the Visual Builder canvas - a traced chat session can be played back on the agent graph as one run, turn after turn (the time between turns cut to a short pause): agents light up in the order they ran, the edge into a sub-agent lights up while it is handed the conversation, each agent shows the model or tool it is using and how long it has run, finished agents show their time, and an agent whose model failed shows which fallback model answered. The session list only offers sessions of the project on the canvas. Open it with Replay in the Visual Builder toolbar, or Replay on canvas on a trace. It reads the
trace_spansrows recorded withOTEL_TRACING_ENABLED=true(ADK runtime only), through two new admin routes,GET /dashboard/api/traces/sessionsandGET /dashboard/api/traces/replay. Thegen_ai.inferencespan now recordsmate.model.fallbackand the fallback asgen_ai.response.modelwhen the fallback answered. Seedocs/user/trace-replay.md - Docker image on GitHub Container Registry (#249) - every release tag now publishes
ghcr.io/antiv/mateforlinux/amd64andlinux/arm64, tagged1.5.0,1.5andlatest, so MATE runs with onedocker runand no clone or local build. The workflow starts the image and checks/healthbefore it pushes anything. Seedocs/dev/deployment.md - Agent MCP servers offer a prompt and a resource - next to its tool, each agent exposed over MCP now lists the prompt
ask_<agent>, which asks the agent a question through its tool, and the resourcemate://agents/<agent>/about, with the agent's name, description and how to call it (not its instruction).initializeannounces both capabilities. Seedocs/dev/mcp-servers.md GET /mcplists the agents exposed over MCP (#231) - with a personal access token (or any other way the agent servers accept), it returns the agents inMCP_EXPOSED_AGENTSthat the caller may use, each with its name, description and full MCP URL, so a client can be set up without knowing the names. Seedocs/dev/mcp-servers.md- Agent MCP servers describe themselves (#231) - the
initializeresponse of/agents/<name>/mcpnow carries the agent's description inserverInfo(title,description) and asinstructions. MCP registries read it: without it the LobeHub market CLI (lhm plugin init) refused the server with "Could not infer a plugin description" - Playground: compare models and instructions side by side (#178) - on the Evals page, a Playground tab runs one prompt, or an agent's active eval suite, on two or three variants of the agent: its current config or a stored version, with another model and/or other instructions. It shows each variant's average score, reply time, cost in US dollars and tokens, and every reply with its own score, time and cost. Variants run in memory like eval runs: nothing is saved or deployed, and a variant that cannot run shows its error while the others still run
- Cost in US dollars, not only tokens (#178) - tokens were tracked per agent, user and project, but no price was shown anywhere. Every model call is now priced when it is logged (
token_usage_logs.cost_usd), and the Usage page shows the period's cost, the cost per agent and per project, and what agents' fallback models cost apart (calls a fallback answered are now marked,is_fallback). Prices come from a price set on the new Model prices part of the Settings page, then foropenrouter/models OpenRouter's own price list (LiteLLM has no entry for most of them, and reports 0 for those), then LiteLLM's. A model none of them prices has no cost rather than a cost of $0, and the Usage page shows the share of tokens left out. Setting a price reprices the model's past calls; Fill in missing costs prices calls logged before their model had a price, andopenrouter/calls logged before OpenRouter's list arrived are priced as soon as it does. Costs are list prices, without discounts for cached input. Rate Limits takes budgets in dollars per day and per month for a user, agent or project (usd_per_day,usd_per_month), each measured against that scope's own spend, with the same warn or block actions, and the Budget threshold alert can be in dollars, with its own limit or the Rate Limits one. Calls without a price do not count toward a dollar budget - The image tool takes any image model LiteLLM supports - the agent form offered three models, and the tool had two hand-written paths: OpenAI's
images.generateand a separate Nano Banana path through OpenRouter chat, so Imagen, FLUX, Stability, Recraft, xAI or Bedrock models could not be used. Generation now goes throughlitellm.aimage_generation, except for OpenRouter models, which go through its chat completions asking for an image (modalities), since LiteLLM's image call for OpenRouter leaves that out and the model then answers in text only. Models are named as for agents (gemini/gemini-2.5-flash-image,black_forest_labs/flux-pro-1.1, ...) and each provider's usual key; Gemini also takesGOOGLE_API_KEY. Image Model in the agent form is a text field with suggestions. The default for agents that name none is set on a new Settings page in the dashboard (Control Room → System), or with the newIMAGE_MODEL, elsedall-e-3; the page shows which applies and whether its provider's key is set, and every change is audited. The page stores its values in a newsystem_settingstable (migration V040). Existing configs keep working: bare OpenAI names with the same defaults and key fallbacks,nano-banana, and the tool namegenerate_image_nano_banana. Images are saved with the type they came in (JPEG, WebP, ...) instead of always as PNG; only PNGs carry the Art. 50 AI-generated marker. The setup check behind/images/mcp/healthnow looks for the default model's key instead of listing OpenAI models. Seedocs/dev/image-generation.md - Alert when agents answer on their fallback model - the fallback model hides a provider outage from the person chatting, and the Agent errors condition could not show it either: on ADK it counts the primary's failure like any other error, on LangGraph not at all. A new Fallback model used condition counts the requests a fallback answered in a time window, per agent, project, user or globally, from the
agent.model_fallbackaudit entries both runtimes write. Its webhook event ismodel_fallback_alert. Seedocs/user/alerts.md - Slack and Discord as alert destinations - an alert posted to a Slack or Discord webhook URL as an HTTP destination was refused, since those take a fixed body (
text,content) rather than MATE's payload. Alert rules can now name a Slack (incoming webhook) or Discord (webhook) destination, which posts the alert message as text. Slack's<>&are escaped and Discord's mentions turned off, so a rule or agent name cannot ping the channel, and the rule list shows the destination type instead of the webhook URL. Seedocs/user/alerts.md - Ratings from standalone builds - a standalone build showed no 👍/👎, since it had nowhere to keep them, so whoever distributed one never learned which answers were bad. With
MATE_FEEDBACK_URLandMATE_FEEDBACK_KEY(the standalone feedback key of a widget key for the same agent, shown under Embed Code) in the build's.env, the chat shows the buttons and the note after a thumbs-down, and the build forwards each rating to that MATE, where it lands in Evals → Rated Down with Add to evals and Suggest a fix. The central server cannot read the build's sessions, so the build sends the rated question and answer, read from its own session store, never from the browser; they are stored on the rating (migration V038) and shown as sent by a standalone build, and used only when the server has no session for it. The server takes them only with the feedback key (X-Widget-Feedback-Key, migration V039 generates one for every widget key): with the public key, which is in every page that embeds the widget, the rating is recorded and the text ignored, so a visitor cannot feed made-up exchanges to Suggest a fix. A rating can no longer be changed through another agent's widget key. The build's/feedbackroute takes at most 30 ratings a minute per client, and forgets idle clients. Off by default. Seedocs/dev/standalone-build.md(#125)
Changed
- Patch releases for an older minor version - a release tag now marks the GitHub release as Latest and the Docker image as
latestonly when it is the highest finalvX.Y.Ztag, so av1.5.1cut from arelease/1.5branch afterv2.0.0leaves both on 2.0.0. CI also runs on pushes torelease/**. How releases and maintenance branches work:docs/dev/releases.md - Dollar budgets can see calls without a price and sub-agents (#222) - a dollar budget could quietly never trigger: calls on a model with no price (
openrouter/models while OpenRouter's price list can't be fetched, local or new models) added nothing to the spend, a database error while summing counted as $0 and let the request through, and an agent's budget left out what its sub-agents spent. Each Rate Limits config now has Treat calls without a price as over the dollar budget (block_unpriced) and, for agents, Include sub-agents (include_sub_agents, counting the agent and every agent below it in its daily token limit and dollar budgets); both are off by default (migration V043). A Block dollar budget now refuses the request when its spend cannot be read. The Usage vs Limits panel warns when the scope had calls without a price, and a dollar Budget threshold alert says how many calls it left out - google-adk 2.9.2 → 2.11.0 (#147) - ADK 2.10 and 2.11 list no breaking changes; among their fixes,
/run_ssenow cancels a run when its client disconnects, and a new index on the session events table is added on startup. Only ADK moves: the rest of the lock waits for the monthly upgrade, which takes releases at least a week old. Checked with the unit tests and a real conversation through the auth server on both runtimes, on SQLite and on PostgreSQL 16, which also ran migrations V037 to V040 there for the first time - Monthly dependency upgrade (#147) - a scheduled workflow recompiles
requirements.lockon the 1st of each month with the newest allowed releases at least a week old (a package the lock already has at a newer release stays there rather than moving back), runs the tests on it in a job with a read-only token and no stored credentials, and opens a pull request listing what moved from a second job that installs nothing; actions are pinned by commit; Dependabot keeps the GitHub Actions current. See the README's Dependencies section - Migration V037 drops the CHECK constraints V026 put on
alert_rules.condition_typeanddestination_type(SQLite rebuilds the table, keeping every rule), which refused any type added after them. The dashboard API validates both lists, and tables created by SQLAlchemy never had the constraints - One default per environment variable (#157) -
SUPABASE_BUCKETdefaults toartifactseverywhere. The Supabase storage tools usedpublic-bucket, so with the variable unset they and the artifact service wrote to different buckets; if you relied on that, setSUPABASE_BUCKET. Image links for Supabase artifacts are now built with that default too, where they used to be left out. Because the tools and the artifacts now share a bucket by default, the Supabase storage tools refuse paths underpublic/, where the artifact service keeps every user's chat artifacts; an agent steered by a chat message could otherwise read, overwrite or delete them. This applied already to deployments that set one bucket for both, as the old.env.exampledid.DB_NAMEdefaults tomate_agentfor the application database as it already did for sessions, so PostgreSQL and MySQL need onlyDB_USERandDB_PASSWORD. Defaults now live inshared/utils/settings.py get_otlp_endpoint()andget_otlp_protocol()are removed from the tracing config; nothing called them, and thehttp://localhost:4318default they advertised never applied: withoutOTEL_EXPORTER_OTLP_ENDPOINTno OTLP exporter is added
Fixed
- Traces showed no span attributes on PostgreSQL -
trace_spans.attributesisJSONBthere, which the driver returns already parsed, and the Traces page only parsed text, so every span showed none. It now takes both - A2A was off on the pinned a2a-sdk 1.2.1 (#223) -
adk_main.pywas written for a2a-sdk 0.3, whosea2a.server.appsno longer exists in 1.x. Starting with--a2a, which the dashboard always does, printed "A2A dependencies not available" and mounted no/a2a/*routes, so A2A calls failed, and so did agents exposed over MCP, which go through A2A.- The fix: the routes are now built through ADK's
google.adk.a2a._compat(shared/utils/a2a_routes.py), which works on 0.3 and 1.x. - Method names:
/a2a/{agent}accepts the 1.x methods (SendMessage,GetTask, ...) and the 0.3 ones (message/send,tasks/get, ...). - The card: it is served in the 1.x form (
supportedInterfaces). It now names the agent's own endpoint (/a2a/{agent}) instead of the server root. Its input and output modes aretext/plain, since the agent reads and writes text. Anagents/<name>/agent.jsonin the 0.3 form is still read and converted. - MCP replies: the MCP bridge returns the agent's reply text, taken from the task's artifacts, instead of the whole task printed as a dictionary.
- A test now fails when a dependency bump breaks the routes; until now the only sign was a line at startup.
- The fix: the routes are now built through ADK's
- MATE did not start on a new MySQL database (#178) - the schema was never created on a new MySQL database, so the server ran without tables.
- The migrations stopped at V001. MySQL takes a default on a
TEXTcolumn only as an expression (DEFAULT ('x')), and thecredentialsunique key was longer than MySQL's 3072 bytes. Twenty later migrations then failed on the missing tables or onADD COLUMN IF NOT EXISTS, which MySQL does not have. - The fix edits the released MySQL files, which the guide otherwise rules out. Those statements never ran on any MySQL, and nothing re-runs a version that is already recorded.
- The credentials key now takes the first 255 characters of
credential_key. - Re-runs skip indexes that exist. On MySQL, a re-run now skips "Duplicate key name" errors, as it already skipped duplicate columns.
- The agent server kept no sessions on MySQL. It did not recognise the
mysql+pymysql://session URL and fell back to an in-memory store, which also failed to start, so every new chat failed. Sessions are now stored in MySQL throughaiomysql. - Checked on a fresh MySQL 8.0: all 42 migrations at startup, every dashboard page, prices, dollar budgets and alert rules, and a conversation, which was stored and priced.
- The migrations stopped at V001. MySQL takes a default on a
DB_TYPE=mysqlfailed at startup on a default install - MATE connects to MySQL withpymysql, and ADK keeps sessions on MySQL withaiomysql, but both were commented out inrequirements.txtand missing fromrequirements.lock, so the server stopped withNo module named 'pymysql'. Both are now installed, like the PostgreSQL drivers (#178)- The Visual Builder's History button did nothing - the agent form on the Visual Builder has a History button, but the page loaded neither the version history panel nor its script, so clicking it raised an error. It now opens the agent's version history, as on the Agents page
shared/migrate.pywithout a.envlooked for PostgreSQL (#157) - run on its own, the migration runner fell back toDB_TYPE=postgresqland, for SQLite, tomate_agent.dbrelative to the working directory, while the application uses SQLite andmy_agent_data.dbin the project root.migrate.py statusandruntherefore failed with "No database engine available", or migrated a different file. The runner, the database client, the session store and the dashboard now read the database settings from one place- MySQL sessions used port 5432 (#157) - with
DB_TYPE=mysqlandDB_PORTunset, the application database used 3306 but the ADK session store 5432 - Alerts from a global rule read "global None" - every message from a rule with the global scope began "global None …", in emails, webhooks and, since the Slack and Discord destinations, in chat. It now says "all agents". The
messagetext of existing HTTP and email alerts changes accordingly;scopeandscope_idin the payload do not. The fallback alert's message now reads "The fallback model answered for … N times …" - The widget showed agent server errors cut off (#229) - the widget chat route copied every header of the agent server's
/run_ssereply,Content-Lengthincluded, and put its session event in front of the body, so an error reply ran past its length and was cut off, with an SSE line in front of the JSON. It no longer copiesContent-Length,Content-Encoding,Transfer-Encoding,ConnectionorKeep-Alive, and adds the session event only to a successful reply, so the widget can read an error's detail - The Documentation page's API tab pointed at port 8000 (#240) - Swagger and ReDoc were loaded from the dashboard's host on a hard-coded port 8000, so with the dashboard published on another port (Docker
-p 9000:8000) the tab showed nothing. They now load from the auth server's own/docsand/redoc, which already forward to the agent server. Thanks to @ningyixi - Help panel links to
https://dashboard/docs(#234) - the help agent often rewrote the/dashboard/docs?...links its tool gave it ashttps://dashboard/docs?..., which the panel treated as an external site and opened on a host that doesn't exist. Such links (and//dashboard/...,dashboard/...) are now turned back into/dashboard/docsbefore the answer is shown. Thanks to @ningyixi - An empty Work Room offered non-admins a link to create an agent (#239) - the link led to the Agents page, which sends non-admins back to the Work Room. It is now shown to admins only; others are told to ask an admin. Thanks to @ningyixi
- Notifications ignored their duration (#242) -
showNotificationalways closed after 5 seconds, so the Triggers page's test-fire result, meant to stay 8 seconds, went as fast as the rest. It now takes the duration, 5 seconds by default. Thanks to @kocaemre - The Visual Builder's Add MCP Server and new agent dialogs were always dark (#245) - they used fixed dark colors and now use the dashboard's theme colors, so they follow the light theme. Thanks to @CRYPTONIKAV
- Icon-only buttons on every page had no name for screen readers (#246) - the agent server's Start, Stop and Restart, the sidebar toggle, and the close buttons of the tokens dialog and of notifications now have an
aria-label. Thanks to @CRYPTONIKAV - The Integrations page showed the Control Room sidebar (#237) -
/dashboard/integrationswas missing from the Studio pages inbase.html, so it opened with the Control Room menu and its own item was nowhere to be seen. Thanks to @sammist2 - Rate Limits had an Alert webhook URL field that did nothing (#241) - budget alerts moved to the Alerts page in V026, which copied the old URLs into alert rules, but the form still offered the field. It is gone; the column stays, and saving a config through the dashboard now clears it. Thanks to @cestercian
Security
- A widget admin key could change another agent's files - the widget admin file routes took any
store_namefor an upload and deleted any file search document by id, so the admin key of one widget could add files to, or delete files from, any agent's stores, in any project.POST /widget/api/files/uploadnow refuses a store not linked to the key's agent (404), andDELETE /widget/api/files/{id}answers "File not found" for a file in such a store - Any signed-in user could fire a trigger -
POST /triggers/{id}/firewithout the trigger's key fell back to dashboard login, which accepts any user, not only admins, and the route sits outside/dashboard/api, where admin-only access is enforced. Without the key, firing a trigger now needs an admin (403otherwise); callers with the fire key are unchanged. Seedocs/dev/trigger-engine.md - "My Usage" showed every user's usage - the usage page is open to people without the admin role, as My Usage, but it rendered the figures of the whole installation: totals, the busiest agents, cost by agent and project, ratings and latency, and in the Raw Data view the last day's model calls of every user, with their user IDs. The page now narrows every figure to the signed-in user's own traffic unless they are an admin; ratings are those given in their own conversations. The APIs behind the page were already admin-only
- Confirmation dialogs showed names as HTML -
showConfirmput its title, message and button labels into the page as markup, and some messages carry a name someone else chose (an imported template, a rate-limit scope such as a widget visitor's user ID). They are set as text now - The agent and Google Drive MCP servers needed no login (#231) - every route of
/agents/<name>/mcpand/gdrive/mcpbut the health check answered without credentials. Anyone who reached the server could run the agents inMCP_EXPOSED_AGENTSon its provider keys, outside per-user budgets and the agent'sallowed_for_roles(the call reaches the agent over A2A without the caller's identity), and could list, search and read Google Drive files, sharing permissions included, through the server's service account. Both now take the callers the OpenAI-compatible API takes: a personal access token (Authorization: Bearer mate_pat_...), Basic auth, the login token or a dashboard session, of an admin or a user with a role inALLOWED_API_ROLES; an agent's routes also require a role the agent allows, and an agent with none is admin-only. Without credentials they answer 401, without access 403. MCP clients that connected without credentials, including other MATE instances that use these agents throughmcp_servers_config, now need a token;${VAR}inheaderskeeps it out of the config. Seedocs/dev/mcp-servers.md - An empty
AUTH_PASSWORDlet anyone log in with empty fields -.env.examplelistsAUTH_USERNAMEandAUTH_PASSWORDempty, andos.getenvreturns""for them rather than its default. So the Quick Start'scp .env.example .envconfigured an empty username and password, and both the login form and Basic auth (curl -u ":") were accepted, admin APIs included. The production check looked only for the default passwordmate, so it let an empty one through, also withMATE_ENV=production. Empty values now count as unset: the built-in account isadmin/mate, which development warns about and production refuses at startup. An empty password never matches, whatever is configured. The credentials are read in one place,shared/utils/settings.py - Python in the Work Room canvas runs in the sandbox - HTML and JavaScript already ran on the sandboxed canvas page, but Python ran in an unsandboxed frame on MATE's origin. An agent writes that code, so instructions planted in what it reads (a web page, a document) could produce Python whose
jsmodule acted as the person who clicked Run: an admin's session could create users or read agent configs, keys included. Python now runs on the sandboxed page too, where it can reach neither the dashboard nor MATE's storage; Open in new tab also runs Python there, instead of treating it as JavaScript. - CSP allows scripts from jsdelivr and cdnjs only by path -
script-srcallowed the whole ofcdn.jsdelivr.netandcdnjs.cloudflare.com, which serve any npm package or GitHub repository, so an HTML injection could load the attacker's own script even underenforce. The policy now lists each library's path with its version (Chart.js, Monaco, React and React Flow, markdown-it, Swagger UI, Redoc, Ace), and a test fails on a template script it does not cover.'wasm-unsafe-eval'is gone from the dashboard's policy, since Pyodide no longer runs there. A custom template that loads another script from those CDNs needs its path inCSP_EXTRA_SOURCES - OpenRouter prices are checked before use - a price that is not a finite number from 0 to $0.1 per token is skipped, so a NaN in the list can no longer make every sum over a model's calls NaN and with it the dollar budgets, and the list is read up to 25 MB
- Playground runs are audited - each run writes
evals.playground_runwith the admin who started it, the mode, and each variant's model and version; changed instructions are recorded by length and hash, not as text. The runs themselves are logged under the eval user, so this is what ties them to a person - CSP no longer allows
unpkg.comandd3js.org- the only page that loaded from them,templates/graph.html(a graph view no route served), is removed. A custom template that loads a script from either host now needs it inCSP_EXTRA_SOURCES - CSP:
enforcenow blocks inline scripts without the nonce, and inline handlers (#124) - every response gets a fresh nonce, and the policy allows inline scripts only with it and no inlineonclick=handlers. Without'unsafe-inline', the policy stops an injected script rather than only limiting where it loads code from.CSP_MODE=enforceenforces it; in 1.4 it enforced a policy with'unsafe-inline'.CSP_MODE=report-only(the default) reports against it and blocks nothing; it is also the way back if a page breaks underenforce. ADK's dev UI, whose markup is not MATE's, keeps'unsafe-inline'. Handlers becomedata-clickactions (static/js/csp-actions.js), which call only functions the page allows. Converted: the layout every dashboard page shares, the login page, the overview, the Agents page and the Visual Builder with all their modals, the monitoring pages (alerts, audit logs, guardrail logs, sessions, traces, rate limits, integrations, triggers), evals, usage, users, migrations, docs, the template gallery and the wizard pages (leads, orders, pricing), and the Work Room, the widget chat and admin pages and the standalone chat: every page. The Work Room canvas runs agent code on a page of its own (/dashboard/workroom/canvas) whose policy sandboxes it, so that code keeps its inline scripts,onclick=and CDNs once the dashboard's policy is strict. A library from a CDN no list names (cdn.plot.ly) now loads in the canvas too. The admin Swagger UI's inline script gets the nonce, and the agent wizard (/wizard/embed) takes itsframe-ancestorsfrom the partner's allowed origins: it had the dashboard'sframe-ancestors 'self', so underCSP_MODE=enforceno partner site could frame it. Doing so also fixed:- the token list's Revoke button, which put the token name into a JavaScript string unescaped;
- the file search store list, which did the same with store names;
- the wizard leads list, which did the same with the company name a visitor typed into the wizard;
- the integrations list, which showed labels, agent names and team IDs as HTML and put each row into an inline handler unescaped;
- the usage log table and its details, which showed user IDs, agent and model names and error text as HTML (a widget visitor partly chooses their user ID);
- the migrations page's notices, which showed a server error as HTML;
- the widget admin panel's memory block list, which put block IDs into an inline handler unescaped
- "Open in new tab" ran canvas code on MATE's origin - the Work Room canvas opened an agent's HTML or JavaScript in a new tab as a
blob:URL. Ablob:page has the origin of the page that made it and no sandbox, so code an agent wrote, steered by a prompt injection for example, could call the dashboard API as whoever clicked. The tab now opens the sandboxed canvas page, whose code has no access to MATE - The image tool could be pointed anywhere through
tool_config- since the image tool moved to LiteLLM (unreleased), everytool_config.image_toolskey but a short denylist reachedlitellm.aimage_generation. An agent steered by a chat intoupdate_agentcould setmock_responseto an internal URL, which the tool then downloaded and saved as an artifact (cloud metadata, the ADK server), route every prompt to an outside logging host, or point Bedrock at its own endpoint. Only image parameters pass now (size,quality,n,style,aspect_ratio, ...), and an image URL from a provider is downloaded only over http(s), without redirects, at most 25 MB and not from a private address (IMAGE_ALLOW_PRIVATE_NETWORKallows it for a local model) /images/mcpneeded no login - its tools spend the server's provider keys, and the Nano Banana tool took the model and request parameters from the caller. Every route but the health check now needs a signed-in caller, callers can no longer choose the model, and the public health check no longer names the default model or missing keys. The image MCP server had also been off since the image tool change, whatever keys were set: its startup check read fields the setup check no longer returns- Notifications rendered their message as HTML -
showNotificationput its text into the page as markup, and some messages echo server or user input (an error naming the model you typed). It is set as text now, for every dashboard page - Image generation errors given to the agent carry the first line of the provider's message, not LiteLLM's traceback and server paths, which stay in the log