Summary
send_email, save_draft and forward_email make every tool call fail on any llama.cpp-backed local model (LM Studio, Ollama, llama-server). The whole request is rejected in ~0.14s with zero tokens generated:
invalid_request_error: Failed to initialize samplers: failed to parse grammar
One of these three tools anywhere in the tools array poisons the request, so the other 46 email tools become unusable too — and so does every non-email tool in the same array.
Cause
These three tools declare recipients with z.string().email():
src/tools/send.tool.ts — to / cc / bcc on send_email, and to / cc on forward_email
src/tools/drafts.tool.ts — to / cc / bcc on save_draft
With zod@^4.3.6, .email() emits its default (Gmail-style) regex into the JSON Schema pattern, and that regex contains negative lookaheads:
^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
llama.cpp compiles tool schemas to GBNF for constrained decoding, and GBNF has no lookahead assertions, so grammar compilation aborts before inference starts.
This is not local-only. The same schema is rejected by OpenAI's tool-schema validator with Invalid JSON schema: regex lookaround is not supported — see SmartBear/smartbear-mcp#491, which is the identical bug in a different MCP server.
Reproduction
10/10 deterministic against LM Studio behind an OpenAI-compatible gateway. Field-isolated on a single property:
| schema |
result |
{"type":"string"} |
ok |
{"type":"string","format":"email"} |
ok — format is ignored |
{"type":"string","pattern":"^(?!\\.)(?!.*\\.\\.)..."} |
fail |
And by bisecting a real 76-tool array (76 → 38 → 19 → 10 → 5 → 2 → 1):
| tools sent |
result |
| all 76 |
fail |
| 76 minus these 3 |
ok |
| 76 minus all 49 email tools |
ok |
Suggested fix
Zod 4 ships lookahead-free alternatives, so this is a drop-in at the five call sites:
- to: z.array(z.string().email()).min(1).describe('Recipient email addresses'),
+ to: z.array(z.email({ pattern: z.regexes.html5Email })).min(1).describe('Recipient email addresses'),
z.regexes.html5Email is the regex browsers use for input[type=email] and has no lookarounds. z.regexes.rfc5322Email also works if you prefer stricter parsing.
Alternatively, plain z.string() at these call sites: for an MCP input schema the pattern is advisory to the model rather than a real validator, and malformed addresses are rejected by the SMTP layer regardless.
Either way the constraint should not be expressed as a lookahead regex, because two major tool-calling runtimes now refuse to compile it.
Environment
@codefuturist/email-mcp@0.2.0, zod@^4.3.6
- macOS 27.0, Node via npm global
- Clients: OpenCode 1.18.3 → Bifrost gateway → LM Studio
Happy to open a PR if the z.regexes.html5Email direction looks right to you.
Summary
send_email,save_draftandforward_emailmake every tool call fail on any llama.cpp-backed local model (LM Studio, Ollama, llama-server). The whole request is rejected in ~0.14s with zero tokens generated:One of these three tools anywhere in the
toolsarray poisons the request, so the other 46 email tools become unusable too — and so does every non-email tool in the same array.Cause
These three tools declare recipients with
z.string().email():src/tools/send.tool.ts—to/cc/bcconsend_email, andto/cconforward_emailsrc/tools/drafts.tool.ts—to/cc/bcconsave_draftWith
zod@^4.3.6,.email()emits its default (Gmail-style) regex into the JSON Schemapattern, and that regex contains negative lookaheads:llama.cpp compiles tool schemas to GBNF for constrained decoding, and GBNF has no lookahead assertions, so grammar compilation aborts before inference starts.
This is not local-only. The same schema is rejected by OpenAI's tool-schema validator with
Invalid JSON schema: regex lookaround is not supported— see SmartBear/smartbear-mcp#491, which is the identical bug in a different MCP server.Reproduction
10/10 deterministic against LM Studio behind an OpenAI-compatible gateway. Field-isolated on a single property:
{"type":"string"}{"type":"string","format":"email"}formatis ignored{"type":"string","pattern":"^(?!\\.)(?!.*\\.\\.)..."}And by bisecting a real 76-tool array (76 → 38 → 19 → 10 → 5 → 2 → 1):
Suggested fix
Zod 4 ships lookahead-free alternatives, so this is a drop-in at the five call sites:
z.regexes.html5Emailis the regex browsers use forinput[type=email]and has no lookarounds.z.regexes.rfc5322Emailalso works if you prefer stricter parsing.Alternatively, plain
z.string()at these call sites: for an MCP input schema the pattern is advisory to the model rather than a real validator, and malformed addresses are rejected by the SMTP layer regardless.Either way the constraint should not be expressed as a lookahead regex, because two major tool-calling runtimes now refuse to compile it.
Environment
@codefuturist/email-mcp@0.2.0,zod@^4.3.6Happy to open a PR if the
z.regexes.html5Emaildirection looks right to you.