pino 9 validates every `redact.paths` entry by calling `Function(...)`
through fast-redact. Cloudflare Workers and other edge runtimes forbid
code generation from strings, so the runtime threw while building its
logger. The validator swallows the real EvalError and blames the first
path in the array, which made the failure read as "redact paths array
contains an invalid path (pid)" and sent earlier triage after `pid`
itself. Every path fails, not just `pid`.
`base: null` is pino's own switch for omitting `pid` and `hostname`, it
produces identical output, and it needs no code generation.
Closes#2355
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Intelligence transport hand-built its REST body by naming fields, which
silently dropped the AG-UI `resume` array. A standard interrupt (RUN_FINISHED
with outcome "interrupt") carries its resume payload at the top level, so
resuming one against an Intelligence runtime never reached the server and the
graph re-entered the same gate with no error. The legacy `on_interrupt` path
was unaffected because it travels inside forwardedProps.
Spread the input instead of naming fields, so a future protocol field cannot
be lost the same way, and add a test that fails if any field is dropped.
Fixes OSS-1132
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add three e2e tests driving bindMcpApp's AppBridge through ui/initialize, so the
package that now owns the protocol pins the negotiation contract itself (the
compile-time tie to the spec the extraction argues for), not only react-core:
- well-formed initialize -> host returns protocolVersion negotiated to the
host's latest and advertises the construction-seeded host context
(theme/platform), proving the deterministic (non-race) seam
- initialize omitting required fields (empty params) -> the bridge validates
against the spec schema and rejects with -32603 before any host handler
- widget declaring a different version ("2025-06-18") -> host returns its own
MCP Apps version, not the widget's echoed value
20 tests green, check-types + oxlint clean.
Reduce MCPAppsActivityRenderer from a 786-line self-contained implementation to
a thin React adapter over the framework-agnostic bindMcpApp:
- the React shell owns the iframe (create / mount / size / remove) and wires the
session hooks (onResource, onSizeChanged, onError) to React state; all protocol
logic (AppBridge, sandbox proxy, request queue, ui/message + open-link, tool
input/result) now lives in @copilotkit/mcp-apps-renderer
- the bridge-free activity surface (MCPAppsActivityType, content schema, follow-up
runner) is re-exported statically from the package's new ./activity entry, so
registering the activity does not pull the ext-apps bundle
- bindMcpApp is loaded via a lazy import("@copilotkit/mcp-apps-renderer") inside
the effect, keeping ext-apps (~40-50 kB gzipped) out of the main chunk
Dependency move (FR-007): react-core drops its direct @modelcontextprotocol/ext-apps
dependency, the @modelcontextprotocol/sdk peer + devDependency, and the zod >=3.25
peer floor (reverted to >=3.0.0), and depends on @copilotkit/mcp-apps-renderer
instead. The package becomes the sole owner of the MCP libraries: sdk moves from a
peer to a dependency there (satisfying ext-apps' required sdk peer), and a
bridge-free ./activity subpath is exported.
Register the package in the monorepo release scope and regenerate the public API
manifest. react-core's MCP e2e suite (37 tests across 4 files) stays green;
check-types + oxlint clean; behavior-preserving.
Ship the bound behavior as a framework-agnostic custom element that owns the
sandbox iframe and drives it through bindMcpApp:
- creates the iframe exactly once, imperatively, in a static slot (outside the
reactive template) so re-renders and property updates never remount it - the
stable-node invariant fullscreen (inline<->fullscreen without reload) needs
- binds the session when agent + host + content are all present; forwards tool
input/result to the widget on content change (dedup by JSON)
- sizes the iframe from the widget's size-changed notifications
- surfaces lifecycle as DOM events: copilotkit-mcp-initialized,
copilotkit-mcp-size-changed, copilotkit-mcp-error
- tears the session down on disconnect (does not remove the iframe itself)
define.ts registers <copilotkit-mcp-app> idempotently. jsdom tests assert
single-iframe / no-remount-on-content-change / initialized event / teardown.
17 tests green, check-types + build + oxlint clean.
Port react-core's MCP Apps protocol (Effects 0/1/3/4) into a framework-agnostic
bindMcpApp({ iframe, getContent, getAgent, host, hooks }) -> { sendToolInput,
sendToolResult, teardown }:
- fetch the widget resource through the agent proxy (resources/read, queued)
- configure the sandbox iframe (sandbox attr, shared testid, srcdoc) and connect
the ext-apps AppBridge over a PostMessage transport (host context seeded at
construction)
- app->host: ui/message (role/followUp via _meta.copilotkit, #5819 follow-up),
ui/open-link (scheme denylist), tools/call + resources/read proxy to the agent
- app->host notifications -> hooks (onSizeChanged, onInitialized, onError,
onResource); tool input/result buffered until the widget is initialized
The adapter owns the iframe (creates/mounts/sizes/removes); the core only talks
to it through the bridge - never creates, moves, or removes it (keeps fullscreen
non-remounting reachable later).
e2e tests (jsdom, mock agent, no framework): resource fetch + sandbox load,
tools/call proxy round-trip, open-link allow(https)/deny(javascript:), ui/message
addMessage with _meta role, onInitialized. 12 tests green, check-types + build +
oxlint clean.
New framework-agnostic package @copilotkit/mcp-apps-renderer (modeled on
@copilotkit/a2ui-renderer): it will own the MCP Apps app<->host protocol so the
React/Vue/Angular renderers become thin adapters and stop duplicating (and
drifting on) it. This package is the sole owner of @modelcontextprotocol/ext-apps
(dep) and @modelcontextprotocol/sdk (peer).
First extraction pass, verbatim from react-core (framework-free modules):
- buildSandboxHTML (sandbox proxy)
- MCPAppsRequestQueue (per-thread serialization) + shared instance
- ɵrunMcpFollowUp + ɵMcpFollowUpHost (ui/message follow-up, issue #5819)
- MCPAppsActivityContentSchema / MCPAppsActivityContent
- constants: MCPAppsActivityType, MCP_APPS_PROTOCOL_VERSION (single source via
ext-apps LATEST), MCP_OPEN_LINK_BLOCKED_SCHEMES
Web Component entry points (./web-components, ./web-components/define) are stubbed
for now. No consumer wiring yet (react-core untouched); pure additive scaffold.
## Summary
- give locked Threads and Learning a shared conversion-focused layout
- add streamlined Loom demos, setup and engineer CTAs, and detailed
capability sections
- remove the disabled Threads preview so both locked experiences use the
same full-page treatment
## Why
Users without Intelligence enabled should immediately understand what
each feature provides and have clear paths to configure it or talk with
the team.
## How
- render both gates through one responsive locked-feature surface
- use minimal Loom embeds with feature-specific videos
- align content on a single centered rail with theme-aware styling
- add product-specific copy, capability icons, accessibility labels,
telemetry coverage, and fixture assertions
- verify with `pnpm nx run @copilotkit/web-inspector:test` and `pnpm nx
run @copilotkit/web-inspector:check-types`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added unified locked views for Threads and Learning with feature
videos, capability outlines, setup guidance, and a “Talk to an Engineer”
call to action.
* Added responsive and dark-theme styling for locked-feature overviews.
* Updated self-hosted and setup messaging.
* **Bug Fixes**
* Prevented unavailable Threads content, examples, controls, and
metadata actions from appearing.
* Removed legacy runtime entitlement diagnostics from the interface.
* Standardized locked-state telemetry and CTA behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
`CopilotKitIntelligence` performed no validation on `apiKey`. It assigned the
value and sent it verbatim as a Bearer credential, so a blank key produced
`Authorization: Bearer ` and surfaced much later as a 401 that named nothing.
`apiKey: string` is required on the config type, so TypeScript catches a missing
property. It does not catch an empty one, and the shape that actually happens is
a `process.env` read TypeScript is told to trust: `?? ""` in the starter wiring
block, `!` in this file's own JSDoc examples. Both yield a blank key when the
variable is unset.
Throw at construction instead. Every caller builds the client during boot, so the
error lands at startup rather than on a user's first message. The message names
`CPK_INTELLIGENCE_API_KEY` and `copilotkit project select`, and echoes none of the
key value — the rule `parseProjectIdFromApiKey` already follows for a malformed
key.
This mirrors `configuredUrl`, which already treats a blank `apiUrl`/`wsUrl` as
unset for the same `?? ""` reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Release channels v0.9.2
**Scope:** `channels` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `channels` packages to `0.9.2`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `channels` packages to npm at version `0.9.2`
- Creates git tag `channels/v0.9.2`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## What does this PR do?
Hooks can now expose a frontend tool to browser agents through the
WebMCP browser API, next to the normal agent registration. Set `webmcp:
true`, or pass `{ annotations }` for WebMCP hints:
```ts
useFrontendTool({
name: "searchOrders",
description: "Search the signed-in user's orders by status",
parameters: z.object({ status: z.enum(["open", "shipped", "delivered"]) }),
handler: async ({ status }) => searchOrders(status),
webmcp: { annotations: { readOnlyHint: true } },
});
```
How it works:
1. `FrontendTool` in `@copilotkit/core` gains the `webmcp` option. A new
`WebMCPRegistry` registers the tool on `document.modelContext` with its
name, description, input schema, and annotations. `execute` runs the
tool's own handler. The handler context has no `agent` there.
2. Every tool registry change in `RunHandler` reconciles the WebMCP
registrations. The same availability rules apply as for the agent tool
list. Removing a tool aborts its registration signal, and the browser
then unregisters it.
3. Each adapter picks the option up from core: v2 `useFrontendTool`
(React, Vue, React Native), the v1 `useCopilotAction` and
`useFrontendTool` wrappers (React, Vue), and Angular's
`registerFrontendTool`. Where WebMCP is not available (SSR, React
Native, browsers without the API), registration is a no-op.
The `webmcp` prop is documented on the React, Vue, and Angular reference
pages in shell-docs.
## Related PRs and Issues
- None.
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [ ] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
## Testing
**Commands run**
- `pnpm nx run-many -t check-types
--projects=@copilotkit/core,@copilotkit/react-core,@copilotkit/vue,@copilotkit/angular`
— all pass.
- Full test suites: core (829 tests), vue (103), and angular pass.
react-core passes standalone (1589 tests). Under the lefthook pre-commit
hook, react-core flakes on pre-existing e2e tests (A2UI, MCP Apps) that
do not touch this code. Those tests pass when run alone.
**Manual test**
Requires Chrome 149+ with the WebMCP origin trial, or the testing flag.
1. Enable `chrome://flags/#enable-webmcp-testing`, then relaunch Chrome.
2. In an app that uses CopilotKit, register a tool with `webmcp: true`.
3. Run `await document.modelContext.getTools()` in DevTools. The tool is
listed with its schema and annotations.
4. Unmount the hook. Run the command again. The tool is gone.
**How this PR makes testing easy**
The behavior has automated tests on this branch:
- `packages/core/src/core/__tests__/run-handler-webmcp.test.ts` — 15
tests with a `document.modelContext` stub: registration, annotations,
unregistration, availability rules, name collisions, stale-rejection
races, and handler execution.
-
`packages/react-core/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.tsx`
and the mirrored
`packages/vue/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
pass-through, re-registration, and agent-scoped cases at the hook level.
- `packages/vue/src/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
reactive `webmcp` getters through the v1 Vue API.
## Risk / rollback
Low. The feature is opt-in per tool. Without `webmcp`, no code path
changes. Where WebMCP is unsupported, registration is a no-op. Revert
this PR to roll back.
## Public API change
New optional `webmcp` prop on frontend tool registrations. Existing call
sites do not change.
**Before**
```ts
useFrontendTool({
name: "searchOrders",
description: "Search orders by status",
parameters: z.object({ status: z.string() }),
handler: async ({ status }) => searchOrders(status),
});
```
**After**
```ts
useFrontendTool({
name: "searchOrders",
description: "Search orders by status",
parameters: z.object({ status: z.string() }),
handler: async ({ status }) => searchOrders(status),
webmcp: { annotations: { readOnlyHint: true } },
});
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Tools can now be exposed to browser agents through WebMCP.
* Added support for custom annotations and automatic parameter schema
generation.
* WebMCP registrations stay synchronized as tools are added, removed,
enabled, or updated.
* Available across Angular, React, and Vue tool APIs.
* WebMCP reuses existing handlers and safely does nothing when
unavailable.
* **Documentation**
* Added usage guidance and examples for configuring WebMCP-enabled
tools.
* Documented that WebMCP invocations do not include an agent context.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
#6818 renamed the Intelligence docs from `/premium/` to `/intelligence/`
and left the published packages alone. This cleans them up.
## The console notice
`@copilotkit/shared` printed this to every developer who used
`useCopilotChatHeadless_c` without a license key:
> To enable this **premium** feature, add your public license key…
> To learn more about **premium** features, read the documentation here:
docs.copilotkit.ai/**premium**/overview
"Premium" is not a tier we have. The notice now uses the sentence the
Headless UI docs page already uses — "Headless UI requires a CopilotKit
Intelligence license key" — so a developer who reads the notice and then
opens the docs finds the same words.
## One link was broken, not just stale
The "Show me how" button on the missing-public-API-key error opened
`/premium/overview#getting-access`. That heading was deleted on
2026-06-16 in 449237af0c, so the redirect carried the fragment to a page
that has no such anchor and the button landed at the top. It had been
doing that for two and a half months. It now points at
`#plans-and-access`, the section that answers how to get a key.
This was not in the ticket. It came out of tracing the links rather than
replacing them.
## The rest
Stale but working links move from `/premium/*` to `/intelligence/*` in
`react-core`, `web-inspector` and the runtime skill reference (two
byte-identical copies). Every one of them resolved through the redirects
from #6818; each cost a hop and named a retired tier. Four test files
assert these hrefs, so they move with the strings.
Not touched: `seo-redirects.ts` in `showcase/` keeps `/premium/`, since
those entries are what makes the old links work. `user_type: 'premium'`
in the provider examples is invented customer metadata, not a tier.
## Verification
- `nx run-many -t check-types,test` for `shared`, `react-core`,
`web-inspector`, `runtime`, `runtime-client-gql`: all green (17, 33,
152, 138 and 5 test files)
- On a cold worktree, `nx affected` first reported
`runtime-client-gql:check-types`, `runtime:check-types`, `runtime:test`
and `runtime:generate-graphql-schema` as failures. Cause: `check-types`
runs `tsc` concurrently with the `build` that generates
`src/graphql/@generated/*`, so the first run type-checks against files
that do not exist yet. Running `build` first makes all of them pass, and
Nx labels the same four as flaky. Unrelated to this change.
## Blocked on the docs promote
`docs.copilotkit.ai` has not been promoted to production since #6818
merged. Right now production serves `/premium/*` directly with a 200 and
returns 404 for every `/intelligence/*` path. Staging serves the new
paths with a 200 and redirects `/premium/overview` to
`/intelligence/overview` in one hop, so the code is right and the deploy
is pending.
CodeRabbit flagged this and it is correct. Promote `shell-docs` to
production before the next monorepo release, or these links 404 for
anyone who clicks them. Merging is safe on its own, because the packages
only reach users through a release.
## Companion change
The Intelligence repo carries the same rename for the CLI onboarding
prompt and the verify hint: CopilotKit/Intelligence#1117. The two ship
independently.
Refs OSS-1085
`@copilotkit/shared` re-exported `telemetry/telemetry-client.ts` from its
root entry. That module imports `@segment/analytics-node`, which imports
`node-fetch`, which imports the Node built-ins `stream`, `http`, `https`
and `zlib`. Browser bundlers resolve the whole static module graph before
they tree-shake, so every browser build of a dependent package printed
"Module ... has been externalized for browser compatibility" warnings,
even when the consumer never touched telemetry.
Measured with Vite 7.3.2 against a consumer that imports only
browser-safe symbols: 663 modules and 4 warnings before, 451 modules and
0 warnings after. `vite optimize` no longer pre-bundles
`@segment/analytics-node` either.
Deferring the import does not fix this. A dynamic import defers
evaluation but keeps the graph edge, so the resolve step still reaches
`node-fetch`. The edge itself has to stay out of the browser entry.
- `isTelemetryDisabled` moves to its own module so the root entry can
keep exporting it without reaching the client.
- The root entry keeps `isTelemetryDisabled`, the `lambdaClient` surface,
the sampling helpers, and the `TelemetryCapture` / `TelemetryIdentity`
types (type-only, so no runtime edge).
- `TelemetryClient` is now reachable at `@copilotkit/shared/telemetry`
instead of the root. It is our internal metrics client, so no
application code is expected to import it. A runtime re-export from
the root would reintroduce the bug, so there is no shim.
- A test walks the value-level import graph from `src/index.ts` and fails
if it reaches a Node-only package.
Fixes#4151
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Headless UI console notice told developers about "premium features" and
pointed at /premium/overview. The tier is called CopilotKit Intelligence now, so
the notice named a product that no longer exists. It now uses the same sentence
the Headless UI docs page uses.
The docs links in react-core, web-inspector and the runtime skill reference move
from /premium/* to /intelligence/*. They worked through the redirects added in
#6818, but each cost a hop and carried the old name.
One of them was broken, not just stale: the "Show me how" button on the missing
public API key error opened /premium/overview#getting-access. That heading was
deleted on 2026-06-16 in 449237af0c, so the button had been landing at the top
of the page for two and a half months. It now points at #plans-and-access, the
section that answers how to get a key.
Tests assert these hrefs, so they move with the strings.
Refs OSS-1085
## Release angular v0.5.0
**Scope:** `angular` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `angular` packages to `0.5.0`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `angular` packages to npm at version `0.5.0`
- Creates git tag `angular/v0.5.0`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.