Package Information
Documentation
n8n-nodes-mcp-vertex-safe
Two n8n community nodes for Google Vertex AI:
- MCP Client Tool (Vertex-Safe) — sanitizes MCP tool schemas so Gemini's
restricted JSON Schema subset stops rejecting them with400 Bad Request. - Anthropic Chat Model (Vertex) — calls Claude correctly via Vertex Model
Garden. n8n's built-in Google Vertex Chat Model node only speaks Gemini's
wire format; pointing it at a Claude model ID sends a Gemini-shaped request
Vertex rejects outright, regardless of how clean your tool schemas are.
MCP Client Tool (Vertex-Safe)
Fixes the 400 Bad Request you get when an AI Agent node uses Google
Vertex Gemini as its chat model with one or more MCP Client Tool nodes
attached — tracked upstream as
n8n#15553 and
n8n#14650, still open at time
of writing.
Root cause
Vertex's function-calling schema follows a restricted OpenAPI 3.0 subset,
not full JSON Schema. n8n's stock MCP Client Tool node passes each MCP
server's raw inputSchema straight through. Constructs that are completely
valid JSON Schema — type unions ("type": ["string", "null"]), $ref,oneOf/anyOf/allOf, additionalProperties, unsupported format values —
get rejected by Vertex's proto-based validator before the model ever sees the
prompt. The more MCP servers/tools you attach, the more likely at least one
schema trips this.
What this node does differently
Same connection surface as the stock MCP Client Tool node (endpoint URL,
transport, tool filtering). The difference: every tool's inputSchema is run
through schemaSanitizer.ts before being handed to the agent:
- type unions → single
type+nullable: true - local
$ref→ resolved and inlined oneOf/anyOf→ prefers the non-null branch and setsnullable: true
(the common Pydantic/Zod-generator idiomanyOf: [{type: "x"}, {type: "null"}]);allOf→ mergedconst→ single-valueenum(otherwise it falls through the type
inference and mangles the property)- empty or null-only
enum→ dropped, since Vertex rejectsenum: []
outright and a null-only enum adds nothing oncenullableis set additionalProperties,patternProperties→ dropped- unsupported
formatvalues → dropped (onlydate-time,enum,float,double,int32,int64survive) - empty
objectschemas → given a placeholder property (Vertex has been
observed rejectingobjecttypes with no properties) requiredentries with no matching property → pruned
This mirrors n8n's own fix for the same bug class
(PR #32683). Seetest/schemaSanitizer.test.ts for the exact failure patterns this
addresses, taken from the upstream bug reports' error messages.
Anthropic Chat Model (Vertex)
Why the built-in Google Vertex Chat Model node can't do this
n8n's @n8n/n8n-nodes-langchain.lmChatGoogleVertex node wraps@langchain/google-vertexai, which only ever calls Vertex's GeminigenerateContent endpoint. Typing a Claude model ID into its Model Name
field doesn't fail loudly — it sends a Gemini-shaped request (contents,tools[].function_declarations, generationConfig) to a model that doesn't
understand that shape, and Vertex rejects the whole payload with a genericBad request - please check your parameters / 400. This happens on every
call regardless of how many MCP tools are attached — the request itself is
wrong before tools ever enter into it. n8n's Anthropic Chat Model node
doesn't help either: it only supports a direct Anthropic API key, no GCP
service-account/Vertex auth path.
How this node calls Claude on Vertex correctly
It reuses @langchain/anthropic's ChatAnthropic — the same class that
implements Claude's tool-calling, streaming, and message conversion — and
swaps only its transport via the createClient extension point that package
documents for exactly this purpose:
"Optional method that returns an initialized underlying Anthropic client.
Useful for accessing Anthropic models hosted on other cloud services such
as Google Vertex."
The transport is @anthropic-ai/vertex-sdk's AnthropicVertex client
(Anthropic's own official Vertex SDK), authenticated with agoogle-auth-library GoogleAuth built from the same credential fields
(email, privateKey) as n8n's built-in Google Service Account API
credential — reuse the credential you already set up for the Gemini Vertex
node, no new credential type needed.
Setup
- Add the Anthropic Chat Model (Vertex) node in place of Google Vertex
Chat Model, and connect it to your AI Agent's model input. - Select your existing Google Service Account credential.
- Fill in Project ID, Region, and Model Name.
- Region matters here in a way it doesn't for Gemini: Claude models on
Vertex are published to specific regions (e.g.us-east5), notglobal. Check the model card in Vertex AI Model Garden. - Model Name is the exact Vertex Model Garden ID including the
@
date suffix (e.g.claude-sonnet-4-5@20250929) — this differs from the
model IDs in Anthropic's own API docs.
- Region matters here in a way it doesn't for Gemini: Claude models on
Claude's tool-calling accepts standard JSON Schema (no Gemini-style proto
restrictions), so MCP Client Tool (Vertex-Safe) isn't required here — the
stock MCP Client Tool node's raw schemas work fine against this node. Using
the Vertex-Safe one anyway is harmless, just slightly lossier on schema
precision (see Known limitations below).
Install
In n8n: Settings → Community Nodes → Install → n8n-nodes-mcp-vertex-safe.
For local dev before publishing:
npm install
npm run build
npm link
# in your n8n data dir / wherever N8N_CUSTOM_EXTENSIONS points:
npm link n8n-nodes-mcp-vertex-safe
Restart n8n. Search "MCP Client Tool (Vertex-Safe)" in the node panel.
Testing the sanitizer in isolation
npm test
This runs test/schemaSanitizer.test.ts against the exact union-type,$ref, oneOf, additionalProperties, and empty-object patterns that
appear in the upstream GitHub issues, plus an end-to-end check that the
sanitized output converts to a working Zod schema.
Why this package bundles its own dependencies
n8n installs every community node into one shared ~/.n8n/nodes/node_modules
tree. If you have other community nodes installed alongside this one, npm's
hoisting has to reconcile all of their dependency graphs together - and@langchain/core in particular is a common collision point, since many
community nodes still pin the pre-1.0 ^0.3.x line while this package needs^1.2.5 for @langchain/anthropic. That produced two different failure
modes in practice: @langchain/core resolving to a version too old to
satisfy this package (Package subpath './errors' is not defined by "exports"), and a transitive dependency (gcp-metadata, pulled in viagoogle-auth-library) getting hoisted to a shared location this package's
loader wouldn't find, rather than nested directly under its ownnode_modules (ENOENT ... gcp-metadata/build/src/index.js). Both were
non-deterministic across reinstalls, since the outcome depended on whatever
else happened to be installed in that shared tree at the time.
bundledDependencies in package.json sidesteps this: the full production
dependency closure (131 packages, computed via a clean --omit=dev install)
ships physically inside this package's own published tarball, nested under
its own node_modules, regardless of what other community nodes or their
version pins are present elsewhere in the shared tree. Verified by installing
the actual npm pack tarball into an isolated directory and loading both
compiled nodes from it directly.
If you add or upgrade a dependency, regenerate this list rather than hand-editing it:
rm -rf /tmp/prod-only && mkdir /tmp/prod-only && cd /tmp/prod-only
node -e "require('fs').writeFileSync('package.json', JSON.stringify({name:'p',version:'0.0.0',private:true,dependencies:require('/path/to/n8n-nodes-mcp-vertex-safe/package.json').dependencies}))"
npm install --omit=dev
node -e "
const fs=require('fs'), path=require('path');
const nm=path.join(process.cwd(),'node_modules');
const pkgs=[];
for (const e of fs.readdirSync(nm)) {
if (e.startsWith('.')) continue;
if (e.startsWith('@')) for (const s of fs.readdirSync(path.join(nm,e))) pkgs.push(e+'/'+s);
else pkgs.push(e);
}
console.log(JSON.stringify(pkgs.sort(), null, 2));
"
# paste the result into package.json's bundledDependencies
Then verify before publishing:
npm run build && npm pack
tar -xzf n8n-nodes-mcp-vertex-safe-*.tgz -C /tmp/tarball-check
find /tmp/tarball-check -maxdepth 2 # should show package/node_modules
Known limitations — please read before relying on this in production
- Composition collapsing is lossy.
oneOf/anyOfbranches with more
than one concrete (non-null) branch collapse to just the first — if a
tool's schema meaningfully depends on picking between very different
branches, the agent only sees one of them. This trades schema precision
for not getting a 400 at all; it's the same trade the Vertex API itself
forces since it has no union type support. - Only local
$refs resolve. External$refs (pointing outside the
same schema document) fall back to a permissivestringtype. - Auth is a single optional header. If your MCP servers need OAuth or
something more elaborate, extendMcpClientVertexSafeApi.credentials.ts. - Anthropic Chat Model (Vertex) has no streaming/thinking-budget UI yet.
ChatAnthropicsupports both; this node currently only exposestemperatureandmaxTokens. Extend theoptionscollection inLmChatAnthropicVertex.node.tsif you need more.
Publishing a new version
Published to npm as n8n-nodes-mcp-vertex-safe. To release a new version:
npm version patch # or minor/major — bumps package.json, creates a git tag
npm publish --access public --otp=<your-6-digit-code>
npm requires either a 2FA OTP or a granular access token with "bypass 2FA for
publish" enabled on this account.
n8n's own community-node publishing checklist (naming conventionn8n-nodes-*, required n8n field in package.json, no runtime deps that
conflict with n8n's own) is already satisfied by this scaffold — see
https://docs.n8n.io/integrations/creating-nodes/build/publish/ before your
first publish in case the requirements have changed since this was written.
License
MIT