Skip to content

API Reference ​

The AMC Plugin SDK exposes 21 APIs to plugins. Some are available on both the frontend (via the AgentMC bridge) and the backend (via the PluginContext); others are backend-only or frontend-only.

API Summary ​

APIDescriptionAvailabilityPermission
StorageKey-value storageBothstorage
SecretsKeychain-encrypted credentialsBothsecrets
DatabaseCollection-based CRUDBothstorage
SessionsSpawn and manage Claude sessionsBothsessions
AIGenerate text with AI modelsBothai
FilesystemRead and write filesBackend onlystorage
HTTPFetch external URLsBackend onlynetwork
CronSchedule recurring tasksBackend onlycron
CLI EndpointsRegister REST endpointsBackend onlycli
SidebarBadge count and item listBothNone
ToastIn-app and OS notificationsBothnotifications (for notify)
SettingsRead plugin settingsBothNone
EventsMessaging between your UI and backendBothNone
LoggingStructured log outputBackend onlyNone
InboxSurface items in AMC's inboxBothinbox
AuthSigned-in identity and scoped account tokensBothauth (auth.session for tokens)
RecordingScreen recording, host-mediatedBackend onlyrecording
Text to SpeechSpeak text with the user's configured voiceWebview onlytts
Session HistoryRead past sessions the user has grantedWebview onlysessions.readHistory
FirebaseThe user's Firebase accounts and projectsWebview onlyfirebase
SpendRead-only AI cost and usage totalsBackend onlyspend
WorkspaceThe user's real project checkouts and worktreesBackend onlyworkspace.read / .write / .exec

Access Patterns ​

Backend -- the PluginContext object is passed to your plugin's activate() function:

typescript
// src/backend/index.ts
import type { PluginContext } from '@agent-mc/plugin-sdk'

export function activate(ctx: PluginContext) {
  ctx.storage.set('initialized', true)
  ctx.log.info('Plugin activated')
}

Frontend -- the global AgentMC bridge is available in your UI code:

typescript
// src/ui/plugin.ts
const value = await AgentMC.storage.get('initialized')
AgentMC.toast.show({ type: 'info', message: 'Hello from the UI' })

Bridge-Only APIs ​

The following APIs are available only on the frontend through the AgentMC bridge. They do not have backend equivalents and do not require permissions.

Theme ​

Read and react to AMC's current theme.

typescript
// Get the current theme — this is a PROMISE
const theme = await AgentMC.theme.get()
// theme: { mode: string; accent: string; surfaces: Record<string, unknown> }
// NOTE: static placeholder host-side — mode is always 'dark' today.

// Listen for theme changes
const unsubscribe = AgentMC.theme.onChange((theme) => {
  document.body.className = theme.mode
})
MethodReturnsDescription
get()Promise<{ mode: string; accent: string; surfaces: Record<string, unknown> }>Current theme. visualTheme never existed; get() is async
onChange(callback)() => voidSubscribe to theme changes. Returns an unsubscribe function

Export ​

Save files, generate PDFs, and interact with the user's filesystem through system dialogs.

typescript
// Save a text file (shows Save As dialog)
await AgentMC.export.saveFile({
  filename: 'report.md',
  content: markdownContent,
  type: 'text/markdown',
})

// Generate and save a PDF — note the casing, and it takes HTML
const saved = await AgentMC.export.savePdf({
  filename: 'report.pdf',
  html: renderedHtml,
})
if (!saved.saved) console.log('user cancelled')

// Let the user pick a folder
const folder = await AgentMC.export.pickFolder({
  title: 'Choose output directory',
})

// Write multiple files to a directory
await AgentMC.export.writeFiles({
  directory: folder,
  files: [
    { name: 'index.ts', content: tsCode },
    { name: 'styles.css', content: cssCode },
  ],
})

// Verify files exist
await AgentMC.export.verifyFiles({
  directory: folder,
  files: ['index.ts', 'styles.css'],
})

// Open a folder in the system file manager
await AgentMC.export.openFolder({ path: folder })
MethodDescription
saveFile(opts)Show a Save As dialog and write a file
savePdf(opts)Render HTML to PDF and save. (savePDF with that casing does not exist)
pickFolder(opts?)Show a folder picker dialog, returns the selected path
writeFiles(opts)Write multiple files to a directory
verifyFiles(opts)Check that files exist in a directory
openFolder(opts)Open a folder in the OS file manager

Project ​

Query and create AMC projects.

typescript
// List all projects
const projects = await AgentMC.project.listAll()

// Find a project by folder path
const project = await AgentMC.project.findByFolder('/path/to/repo')

// Create a new project
const newProject = await AgentMC.project.create({
  name: 'My App',
  folderPath: '/path/to/my-app',
})

// Open AMC's Add Project dialog with a folder pre-selected
await AgentMC.project.openAddDialog({
  preselectedFolder: '/path/to/repo',
})
MethodDescription
listAll()List all projects in AMC
findByFolder(folderPath)Find a project by its folder path
create(opts)Create a new project
openAddDialog(opts)Open the native Add Project dialog

Assets ​

Read files bundled with your plugin (from the assets/ directory in your plugin package).

typescript
// Read a bundled file
const template = await AgentMC.assets.readFile('templates/default.md')

// List files in a directory
const files = await AgentMC.assets.listFiles('templates')
MethodReturnsDescription
readFile(path)Promise<string>Read a bundled asset file as text
listFiles(path)Promise<string[]>List files in a bundled asset directory

Documents ​

Read and append to files the user picked from the OS file picker. Webview-only — there is no ctx.documents — and it needs no permission, because the picker itself is the consent.

typescript
// Ask for a file. Resolves [] if the user cancels.
const [doc] = await AgentMC.documents.open({
  mode: 'readwrite',
  title: 'Choose a PDF',
  filters: [{ name: 'PDF', extensions: ['pdf'] }]
})
if (!doc) return

// Read the bytes via fetch. `url` is opaque — never parse it.
const bytes = await (await fetch(doc.url)).arrayBuffer()

// Append. Always stat() first — expectedLength is only loosely checked.
const base64Delta = btoa('appended text')
const { length } = await AgentMC.documents.stat(doc.id)
await AgentMC.documents.append(doc.id, base64Delta, { expectedLength: length })

// Recover Handles after a webview reload, and release when done.
console.log(`${(await AgentMC.documents.list()).length} still open`)
await AgentMC.documents.close(doc.id)
MethodReturnsDescription
open(options)Promise<DocumentHandle[]>Show the file picker and mint a Handle per chosen file; [] if cancelled
list()Promise<DocumentHandle[]>The Handles this plugin still holds, with freshly re-stat'd sizes
stat(handleId)Promise<{ length: number }>The Document's current length — the value to pass as expectedLength
append(handleId, base64, options)Promise<{ length: number }>The namespace's only write; resolves the new length
close(handleId)Promise<void>Drop the Handle; the file itself is untouched

A DocumentHandle is { id, name, size, mode, url } and carries no path. Two of those fields mislead if you skim them:

  • size is live when the Handle is serialized, not when the file was picked — but it freezes the moment the call resolves. Never pass a size you are holding as expectedLength; call stat() each time. A stale length is not reliably an error: the host refuses only when the region you would discard is larger than the payload you write, so a slightly stale one silently overwrites your own recent bytes.
  • url is opaque, and only works through fetch. Never parse, log, or persist it. Documents are served as application/octet-stream with nosniff and Content-Disposition: attachment, so it is not usable as an <img src> or <iframe src>. Its path shape is not a contract, and it is on its way to carrying a capability token that grants read access to the file.

append takes its arguments as (handleId, base64, options). Both leading arguments are strings, so transposing them still typechecks — the host rejects the id at runtime instead.

A read Handle refuses append; the mode is fixed when the Handle is minted. And if the file is replaced on disk — which an ordinary Save in Preview or Word does — append refuses not-the-picked-document and that Handle is finished: call open() again for a fresh gesture.

No offline mock yet

The SDK ships no mock for documents. Both its mocks — createTestContext() and the dev shell's context — implement PluginContext, the backend type, and documents is deliberately absent from it, so neither can host this namespace without typing a capability a plugin backend cannot call. Faking it would repeat the ctx.events failure, where an in-memory fake kept plugin tests green for months while the production path was dead in both directions. Stub it yourself until a webview mock surface exists.

Gating is in flux

documents is live and ungated on AMC master, but an in-flight host change puts it behind an in-development plugin-documents-io flag that defaults off. On a build with that flag, every call rejects with This capability is not available. — a passing typecheck here is not evidence the host will answer.

AMC Plugin SDK