Every Application — inline or folder-based — has window.charmiq available before your first line of script runs. The platform injects it automatically. You don't import it. You don't initialize it. It's just there.This page covers the full surface of the bridge.window.charmiq.appContent
Collaborative text blocks stored in the document. OT-managed. Safe for concurrent writes.| Method | Returns | Description |
|---|
onChange$(selector?) | Observable<AppContentChange> | Watch for changes. Fires immediately with current state, then on every change from any source. |
get(selector?) | Promise<string> | Get the current text content of a block. |
set(content, selector?, name?) | Promise<void> | Replace full content. Creates a new block if selector doesn't match. |
applyChanges(changes, selector?) | Promise<void> | Apply incremental text edits through OT. Prefer over set() for editor-like Applications. |
remove(selector?) | Promise<void> | Delete an app-content block. |
type AppContentChange = {
id: string; // Stable ProseMirror node ID
name?: string; // Human-readable name
content: string; // Current text
deleted?: boolean; // True if the block was removed
};
type TextChange = {
from: number; // Start offset
to: number; // End offset (same as from for pure inserts)
insert: string; // Text to insert (empty for pure deletes)
};
See Developer | App-Content and App-State for patterns and the selector reference.window.charmiq.appState
A single JSON blob stored in the document. Last-write-wins.| Method | Returns | Description |
|---|
onChange$() | Observable<any> | Watch for state changes. Fires immediately with current state. |
get() | Promise<any> | Get the current state object. |
set(state) | Promise<void> | Replace the entire state object. |
window.charmiq.oauth
Authenticate with external providers (Google, GitHub, Microsoft, Slack, and 30+ others) using the same OAuth infrastructure that powers Charm integrations.Your Organization administrator must have an Integration configured for the provider before getValidAuth() will work. The integration exists at the Organization level — Applications use it, they don't create it.| Method | Returns | Description |
|---|
register(config) | Promise<void> | Register your application identity. Call once on load before calling getValidAuth(). |
getValidAuth(options) | Promise<AuthResult> | Get a valid (non-expired) access token. Opens a consent popup if needed. Refreshes automatically if expired. |
refreshAuth(auth) | Promise<AuthResult> | Explicitly refresh an auth result. |
revokeAuth(auth) | Promise<void> | Revoke access. Pass the AuthResult object. |
// 1. Register (required before getValidAuth)
await window.charmiq.oauth.register({
appId: 'com.mycompany.analytics', // Unique reverse-domain identifier
name: 'Analytics Dashboard',
description: 'Reads Google Analytics data',
});
// 2. Get a token
const auth = await window.charmiq.oauth.getValidAuth({
providerUrl: 'https://accounts.google.com',
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
});
// auth.accessToken — ready to use
// 3. Use it
const res = await fetch('https://analyticsdata.googleapis.com/...', {
headers: { Authorization: `Bearer ${auth.accessToken}` },
});
// 4. Revoke when done
await window.charmiq.oauth.revokeAuth(auth);
getValidAuth() options:| Option | Description |
|---|
providerUrl | OAuth provider URL. Platform resolves the matching Integration from your Organization config. |
integrationId | Exact Integration ID. Use when you need to target a specific Integration (dev/testing). |
scopes | Requested scopes. Falls back to the Integration's configured defaultScopes if omitted. |
prompt | 'auto' (default) — uses cached token or auto-selects single account. 'select_account' — always shows the account picker. |
Token isolation: Each Application gets its own isolated token scope. Application A's tokens cannot be read or used by Application B, even if both connect to the same provider with the same account.window.charmiq.mcp
Connect to MCP servers and call their tools. CharmIQ manages credentials, transport and connection lifecycle on behalf of your iframe.| Method | Returns | Description |
|---|
listServers() | Promise<McpServerDescriptor[]> | List all available MCP servers: CharmIQ servers and Organization-defined servers. |
connect(alias, config) | Promise<McpServerInfo> | Connect to an Organization-defined or explicit server. Platform servers don't need connect(). |
disconnect(alias) | Promise<void> | Disconnect a server by its alias. |
listTools(serverAlias) | Promise<McpToolDescriptor[]> | List tools on a connected server. |
callTool(serverAlias, toolName, args?) | Promise<McpToolCallResult> | Call a tool. |
CharmIQ MCP Servers
CharmIQ's built-in MCP servers are pre-connected. Call their tools directly using the server name as the alias — no connect() call needed.| Server Alias | Purpose |
|---|
'vfs' | Virtual file system — read/write documents, folders, app-content |
'agent' | Run Charms, manage conversations |
'chat' | Create and manage Chat documents; run completions with Charms |
'collection' | Document collection management |
'docs' | Browse and read CharmIQ documentation |
'generation' | Generate images, video, audio via AI models |
'usage' | Query credit balances and usage records |
'echo' | Deterministic testing tool |
// No connect() needed for Platform servers
const tools = await window.charmiq.mcp.listTools('generation');
const result = await window.charmiq.mcp.callTool('generation', 'create_image', {
prompt: 'A mountain at sunrise',
});
console.log(result.content[0].text);
Organization-Defined Servers
Servers your Organization administrator has configured. Discover them via listServers().const servers = await window.charmiq.mcp.listServers();
const orgServers = servers.filter(s => !s.isPlatform);
// → [{ id: 'abc-123', name: 'Company BigQuery', allowedTools: ['execute_sql'] }]
await window.charmiq.mcp.connect('bq', { mcpServerId: orgServers[0].id });
const tools = await window.charmiq.mcp.listTools('bq');
const result = await window.charmiq.mcp.callTool('bq', 'execute_sql', {
query: 'SELECT COUNT(*) FROM `project.dataset.orders`',
});
The first argument to connect() is an alias you choose — a local name you use in subsequent listTools, callTool, and disconnect calls.Explicit Connection (Dev/Testing)
For servers not configured at the Organization level:// OAuth by provider URL
await window.charmiq.mcp.connect('bigquery', {
url: 'https://bigquery.googleapis.com/mcp',
transport: 'proxy', // Routes through CharmIQ cloud function (bypasses CORS)
auth: {
type: 'oauth',
providerUrl: 'https://accounts.google.com',
scopes: ['https://www.googleapis.com/auth/bigquery'],
},
});
// No-auth server (direct browser connection)
await window.charmiq.mcp.connect('local', {
url: 'http://localhost:8080/mcp',
transport: 'direct', // Browser connects directly (requires permissive CORS)
auth: { type: 'none' },
});
Transport options:| Transport | When to Use |
|---|
'direct' | Local servers, LAN servers, or servers with permissive CORS headers |
'proxy' | Remote servers that block browser CORS (e.g. googleapis.com) |
Auth types:| Auth Type | Description |
|---|
{ type: 'none' } | No authentication |
{ type: 'platform' } | CharmIQ platform token (for CharmIQ's own servers — rarely needed explicitly) |
{ type: 'oauth', providerUrl, scopes? } | OAuth via provider URL — platform resolves Integration from Org config |
{ type: 'oauth', integrationId, scopes, app } | OAuth via exact Integration ID — developer specifies directly |
Tool Call Results
type McpToolCallResult = {
content: Array<{ type: string; text: string }>;
structuredContent?: unknown;
isError?: boolean;
};
const result = await window.charmiq.mcp.callTool('bq', 'execute_sql', { query });
if (result.isError) {
console.error(result.content[0].text);
} else {
const data = JSON.parse(result.content[0].text);
}
Organization Tool Ceilings
When connecting via an Organization-defined server, the allowedTools field in the server descriptor lists the tools your Organization administrator has permitted. listTools() returns only what's allowed. You can never call a tool outside the ceiling regardless of what the MCP server advertises.Application Discovery
Application discovery is how Applications find each other and communicate. See Developer | Application Discovery for the full reference.At a glance:// Advertise a capability
window.charmiq.advertise('chart', {
setData: (data) => { chart.update(data); },
data$: () => dataSubject.asObservable(),
});
// Discover and call
window.charmiq.discover('chart').subscribe(providers => {
if (providers[0]) providers[0].setData(myData);
});
Observables
The bridge uses RxJS Observables for reactive data. rxjs is available globally inside every Application — you don't need to import it. Use rxjs.BehaviorSubject, rxjs.switchMap, rxjs.EMPTY, etc. directly.
Next Steps
→ Developer | App-Content and App-State — Patterns for the two storage channels.→ Developer | MCP in Applications — Connecting to MCP servers in depth.→ Developer | Application Discovery — Applications talking to each other.→ Developer | The Build Pipeline — How the manifest, virtual file system, and esbuild work together.