{initialsFor(person) || (
diff --git a/templates/calendar/app/lib/mcp-embed.spec.ts b/templates/calendar/app/lib/mcp-embed.spec.ts
new file mode 100644
index 000000000..b861aba5d
--- /dev/null
+++ b/templates/calendar/app/lib/mcp-embed.spec.ts
@@ -0,0 +1,27 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { isMcpEmbedSurface } from "./mcp-embed";
+
+describe("isMcpEmbedSurface (calendar)", () => {
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ it("returns false outside the browser", () => {
+ expect(isMcpEmbedSurface()).toBe(false);
+ });
+
+ it("detects ?embedded=1 query params (MCP iframe surface)", () => {
+ vi.stubGlobal("window", { location: { search: "?embedded=1" } });
+ expect(isMcpEmbedSurface()).toBe(true);
+ });
+
+ it("accepts the truthy 'true' legacy value", () => {
+ vi.stubGlobal("window", { location: { search: "?embedded=true" } });
+ expect(isMcpEmbedSurface()).toBe(true);
+ });
+
+ it("ignores ordinary in-app routes without the embed flag", () => {
+ vi.stubGlobal("window", { location: { search: "?date=2026-05-23" } });
+ expect(isMcpEmbedSurface()).toBe(false);
+ });
+});
diff --git a/templates/calendar/app/lib/mcp-embed.ts b/templates/calendar/app/lib/mcp-embed.ts
new file mode 100644
index 000000000..2f317e7d2
--- /dev/null
+++ b/templates/calendar/app/lib/mcp-embed.ts
@@ -0,0 +1,20 @@
+/**
+ * MCP embed surface detection for Calendar.
+ *
+ * When the Calendar UI is rendered inside an MCP host's iframe (ChatGPT /
+ * Claude.ai render `*.agent-native.com` through their own sandboxed wrapper
+ * with strict COEP/CORP headers), cross-origin third-party images
+ * (`googleusercontent.com` avatars, etc.) get blocked at the browser level
+ * and produce noisy console errors. Templates that ship images that work
+ * fine in their own UI need to gate them on this flag and fall back to
+ * a same-origin placeholder when embedded.
+ *
+ * Mirrors `templates/mail/app/lib/mcp-embed.ts` (added in PR #883). A future
+ * refactor could lift this to `@agent-native/core/client` so every template
+ * uses one helper — for now keep it template-local to match the Mail PR.
+ */
+export function isMcpEmbedSurface(): boolean {
+ if (typeof window === "undefined") return false;
+ const value = new URLSearchParams(window.location.search).get("embedded");
+ return value === "1" || value === "true";
+}
diff --git a/templates/calendar/app/pages/CalendarView.tsx b/templates/calendar/app/pages/CalendarView.tsx
index 0a3c1db4c..f9e7e050d 100644
--- a/templates/calendar/app/pages/CalendarView.tsx
+++ b/templates/calendar/app/pages/CalendarView.tsx
@@ -1,6 +1,7 @@
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
import { Link } from "react-router";
import { cn } from "@/lib/utils";
+import { isMcpEmbedSurface } from "@/lib/mcp-embed";
import {
format,
startOfMonth,
@@ -1492,7 +1493,7 @@ function AccountAvatars() {
zIndex: accounts.length - i,
}}
>
- {account.photoUrl ? (
+ {account.photoUrl && !isMcpEmbedSurface() ? (
) : (
+ // MCP host iframes (ChatGPT / Claude) ship strict COEP/CORP
+ // headers that block cross-origin googleusercontent.com
+ // avatars and produce noisy console errors. Fall back to a
+ // same-origin initial chip when embedded. See
+ // `templates/calendar/app/lib/mcp-embed.ts`.
{account.email[0]?.toUpperCase()}