Export & MCP
Exports are how Beside’s memory leaves the runtime — into Markdown on disk, into Claude or Cursor or ChatGPT via MCP, or into anything else you wire up. Every page write, page delete, and reorganisation summary is fanned out to every active export plugin.
Both shipped exports run locally: the Markdown mirror writes to your own disk,
and the MCP server binds to 127.0.0.1 so only processes on your machine can
talk to it. Agents that connect over MCP get bounded excerpts, never the raw
screenshots, which keeps the privacy boundary the same whether you’re using a
local agent or a hosted one.
interface IExport {
readonly name: string;
start(): Promise<void>;
stop(): Promise<void>;
onPageUpdate(page: IndexPage): Promise<void>;
onPageDelete(pagePath: string): Promise<void>;
onReorganisation(summary: ReorganisationSummary): Promise<void>;
fullSync(index: IndexState, strategy: IIndexStrategy): Promise<void>;
getStatus(): ExportStatus;
bindServices?(services: ExportServices): void;
}
ExportServices exposes storage, strategy, model, embeddingModelName,
embeddingSearchWeight, dataDir, a triggerReindex(full?) callback, and an
optional summarizeMeeting(id) so an export can request fresh artifacts on
demand.
Two exports ship with Beside, and both are on by default.
markdown — human-readable mirror
export:
plugins:
- name: markdown
enabled: true
profile: obsidian # obsidian | logseq | portable
path: ~/.beside/export/markdown
What it does:
- Mirrors the wiki to
~/.beside/export/markdown/(override withmarkdown.path). - Always-on, always idempotent — you can
rm -rfthe export directory and the next page write rebuilds it. - Strips internal-only fields and keeps screenshot links relative to the exported page.
- Supports tool profiles:
obsidianandlogseqpreserve native[[wikilinks]];portablerewrites wikilinks to normal relative Markdown links for imports, GitHub, editors, and static-site generators.
This is the export to point your existing tools at: Obsidian, Logseq, your own
note-taking flow, Notion Markdown import, VS Code/Cursor, a git repo, Quartz,
or anything else that consumes Markdown.
mcp — agents talk to Beside via MCP
This is the marquee export. The MCP plugin runs a local Model Context Protocol server and exposes Beside memory as MCP tools and resources to any compatible AI agent — Claude Desktop, Cursor, Windsurf, and increasingly ChatGPT desktop.
export:
plugins:
- name: mcp
enabled: true
host: 127.0.0.1
port: 3456
transport: http # 'http' | 'stdio'
text_excerpt_chars: 5000
Two transports:
- HTTP (default,
http://127.0.0.1:3456). Good for Cursor / Windsurf / Claude Desktop’s MCP-over-HTTP clients, and easy to confirm withcurl http://127.0.0.1:3456/health. - stdio. Used when an agent spawns Beside as a subprocess (the way Claude Desktop’s built-in MCP launcher works).
What the MCP server exposes
The current server exposes concrete, citation-friendly tools:
| Tool | Use it for |
|---|---|
search_memory | Default blended recall across wiki memory chunks, keyword frame search, and semantic embeddings. |
remember_memory | Add a manual memory chunk with explicit source refs. |
memory_status | Inspect generated/manual chunk counts, embedding coverage, stale chunks, and frame embedding coverage. |
search_frames | Precise "when did I see X?" search over OCR/accessibility text, titles, URLs, and optional semantic matches. |
get_frame_context | Neighbouring frames around one screenshot/frame id. |
get_journal | Day timeline grouped by sessions, meetings, and events. |
get_daily_summary | Fresh daily digest: totals, top apps/entities/hosts, sessions, calendar events, Slack/code-review/open-loop signals. |
list_meetings, get_meeting, summarize_meeting | Meeting detection, fused transcripts, screenshot manifests, and structured summaries. |
get_calendar_events, get_open_loops, get_slack_activity | Heuristic digest extractors that return source frame ids for follow-up. |
list_entities, get_entity, get_entity_frames, list_entity_neighbours, get_entity_timeline | Entity graph navigation. |
list_sessions, get_activity_session | Activity-session listing and drill-down. |
get_page, get_index | Read wiki pages directly. |
query_raw_events, get_session | Raw event-log and time-slice access. |
trigger_reindex, get_reindex_status | Queue and observe incremental/full reindex work. |
MCP clients also see resources for the live index root and individual wiki pages. Tools return bounded text/JSON payloads by default; screenshot bytes stay on disk unless a local client follows an explicit asset path.
Hooking it into your agent
Most MCP clients want a JSON config snippet. The HTTP form looks like this in Cursor / Claude Desktop:
{
"mcpServers": {
"beside": {
"url": "http://127.0.0.1:3456"
}
}
}
For stdio-launched clients (no Beside daemon running):
{
"mcpServers": {
"beside": {
"command": "beside",
"args": ["mcp", "--stdio"]
}
}
}
Once it’s wired in, you can ask the agent things like:
- "What did we decide on pricing this week?"
- "Summarise my last three Acme meetings."
- "What follow-ups are still open in Slack and Mail?"
- "Before drafting this reply, recall any related context Beside has."
The agent does the asking; Beside does the remembering. Because MCP is the common protocol, one Beside instance feeds Claude and Cursor and ChatGPT and whatever you adopt next — long-term memory becomes an external, swappable service, exactly the gap stateless LLMs leave today.
Writing a custom export
Common reasons to write one: send memory chunks into a team wiki, mirror the journal to a calendar, push action items into a task tracker, ship daily digests over email/Slack, or expose a custom HTTP API for a homegrown agent.
import type { IExport, IndexPage, ReorganisationSummary, ExportServices, PluginFactory } from '@beside/interfaces';
const factory: PluginFactory<IExport> = ({ dataDir, config, logger }) => {
let services: ExportServices | null = null;
return {
name: 'team-wiki',
bindServices(s) { services = s; },
async start() { /* open connections etc. */ },
async stop() { /* close them */ },
async onPageUpdate(page: IndexPage) { /* push diff to remote wiki */ },
async onPageDelete(pagePath: string) { /* archive remote */ },
async onReorganisation(summary: ReorganisationSummary) {
logger.info(`reorg: merged=${summary.merged.length} split=${summary.split.length}`);
},
async fullSync(state, strategy) {
const root = await strategy.readRootIndex();
// walk pages and push them all
},
getStatus() {
return { name: 'team-wiki', running: true, lastSync: null, pendingUpdates: 0, errorCount: 0 };
},
};
};
export default factory;
The runtime calls start once, fans page-level events to your handlers as
they happen, and calls fullSync after a full reindex or on demand. You can
use services.triggerReindex(false) if your export wants to nudge the
strategy after an external change.
Adding an export never replaces the existing ones — your team-wiki bridge or Slack digest sits next to MCP and Markdown, all driven by the same page events. Markdown stays the human source of truth, MCP stays the agent source of truth, and the two can never drift because they’re fed from the same strategy output.
