GEMINI LABJP
MCPCONN — Gemini in Google Workspace now connects to Asana, Atlassian Rovo, HubSpot, Mailchimp, QuickBooks, Monday and Salesforce over MCP. It is on by default and managed from the admin consoleGLOBAL — The Gmail Search AI Overviews rollout described as global comes with conditions. The announcement itself requires English as the display language and excludes personal accounts in Japan10/02 — gemini-2.5-flash-image shuts down on October 2, ten days away. The replacement is gemini-3.1-flash-image-preview402 — Depleted prepay credits now return HTTP 402 instead of 429. The status field still reads RESOURCE_EXHAUSTED, so retry logic that branches on status will never stopNEW — The day I first disconnected an MCP server: how I choose which tools stay enabledSKILLS — In-app notices point from Gems to Skills, but Skills is described as unavailable on work and school accounts, and the dates have not been confirmed officiallyMCPCONN — Gemini in Google Workspace now connects to Asana, Atlassian Rovo, HubSpot, Mailchimp, QuickBooks, Monday and Salesforce over MCP. It is on by default and managed from the admin consoleGLOBAL — The Gmail Search AI Overviews rollout described as global comes with conditions. The announcement itself requires English as the display language and excludes personal accounts in Japan10/02 — gemini-2.5-flash-image shuts down on October 2, ten days away. The replacement is gemini-3.1-flash-image-preview402 — Depleted prepay credits now return HTTP 402 instead of 429. The status field still reads RESOURCE_EXHAUSTED, so retry logic that branches on status will never stopNEW — The day I first disconnected an MCP server: how I choose which tools stay enabledSKILLS — In-app notices point from Gems to Skills, but Skills is described as unavailable on work and school accounts, and the dates have not been confirmed officially
Articles/API / SDK
API / SDK/2026-09-22Advanced

When Function Calls Turn Into Plain Text, Suspect the Property Names in Your Tool Declarations

A record of chasing MALFORMED_FUNCTION_CALL after output limits and schema conflicts were ruled out, bisecting nine tool declarations down to one property name, and adding a linter that stops bad names before they ship.

Gemini API241function calling4MALFORMED_FUNCTION_CALLschema design2Python48

Premium Article

I added one small tool to the classification pipeline behind my ukiyo-e wallpaper app, and the next morning calls that had worked all week came back with finishReason: MALFORMED_FUNCTION_CALL. The new tool only returned a colour histogram. I had not touched a single line of the existing declarations.

My first suspicion was the output token limit. My second was a conflict between the schema and the natural-language instructions. Both were wrong — doubling the limit changed nothing, and emptying the instruction text changed nothing either. The calls kept collapsing into prose at exactly the same point.

What it came down to was neither the description nor the argument type of the new tool. It was the name of its property. One trailing underscore, and the problem stopped that same night. Here is how I narrowed it down, and the line I drew afterwards.

What Came Back Was Not an Error, It Was Prose

MALFORMED_FUNCTION_CALL is not an HTTP error. The status is 200 and only the body is broken, which is part of why it took me so long to look in the right place.

What actually came back looked like this.

{
  "candidates": [
    {
      "content": {
        "parts": [
          { "text": "print(default_api.classify_palette(in=\"new/2026-09/ukiyoe-0431.jpg\"))" }
        ],
        "role": "model"
      },
      "finishReason": "MALFORMED_FUNCTION_CALL"
    }
  ]
}

There is no functionCall in parts. Instead there is a function call written out as characters in a text part. The model tried to call a tool, could not finish assembling the call, and fell back into writing prose about it.

The part that mattered most: classify_palette was not the only casualty. The two other tools that should have fired in the same turn came back as prose too. One broken declaration takes the whole turn with it.

One declaration is broken, but every call in that turn shows the symptom. That is exactly why starting from "the tool I added last" leads you astray.

Rule Out the Output Limit and the Instruction Conflict First

Before you start suspecting names, clear the two well-known causes out of the way. Both take a few minutes, and skipping the order costs more time than it saves.

Checking the limit

Raise maxOutputTokens generously and send the same input again. If the limit was the cause, you get STOP back here. If you do not, the limit is irrelevant. On models that spend a thinking budget, that budget comes out of the same allowance, so leave real headroom when you test.

Checking the instructions

Temporarily strip anything from the system instruction and user input that constrains the output shape — return JSON, wrap it in a code block, and so on. Structured output and tool declarations do not always sit well together. That layer is the same territory I covered in When responseSchema Ignores Your enum in the Gemini API.

Once those two were out, I accepted that the problem lived inside the declarations themselves. That is where this gets interesting.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will be able to narrow nine tool declarations down to the single guilty one in four API calls, instead of guessing your way through them
You will have a short list of names you refuse to put in a schema, so the day you add one more tool does not quietly break the calls that worked yesterday
You will stop treating MALFORMED_FUNCTION_CALL as something that just happens, and separate it into three layers: output limits, conflicting instructions, and the names in your declarations
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

API / SDK2026-09-12
gemini-2.5-flash-image stops on October 2, and the replacement the table names retired in June
gemini-2.5-flash-image shuts down on October 2, 2026, but the recommended replacement listed in the official table, gemini-3.1-flash-image-preview, was already retired on June 25. Here is the script I wrote to follow replacement chains to their end, and what it found across every row of the table.
API / SDK2026-09-07
The day Lyria 3.5 landed, I changed how my audio folders are laid out
When Lyria 3.5 brought full-length generation, my generated takes were sitting in the same folder as the tracks I had chosen by hand. Here is the forty-line ledger gate that draws the line by hash, not by filename.
API / SDK2026-08-27
Your Spreadsheet Breaks Before Gemini Ever Sees It
Merged cells and two-row headers quietly strip rows of their keys during extraction, long before the model reads anything. Here is what gets lost, measured, plus the Python that flattens the table and catches the total row.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links