openport
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@openportresume my last session and load memory for the desk project"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpenPortable
OpenPortable is open + portable shared memory over MCP.
OpenPortable gives MCP clients and agents a shared store for memory, skills, and session handoffs. Connect them to the same database to continue from what earlier sessions saved. Run it as a standalone MCP server or embed its session and gating logic in your own MCP server.
License: MIT.
Security: SECURITY.md.
Contributing: CONTRIBUTING.md.
What you can do after install
Run one OpenPortable host — local SQLite (
~/.openport/…vianpx) or Cloudflare D1.Plug that MCP into each client — they share one store.
Talk normally. OpenPortable instructs the agent to call
start_session, follownext_action, and save a handoff before stopping. A seed or blank local triggers personalization; a personalized local resumes work. The agent handles tool calls and session IDs.
Limit: OpenPortable still can’t force an agent to invoke OpenPortable or save a final handoff if the conversation abruptly ends. It enforces prerequisites when a protected tool is called. Checkpoints preserve what has already been saved.
Both hosts expose the same tools. Clients on one machine can share a local database; clients on different machines need to reach the same hosted endpoint. Separate local installs do not synchronize automatically. If a client ignores MCP instructions, add client-instructions to its standing rules.
Layer | Purpose |
Memory | What is true: scoped prefs and notes |
Skill | How to work: unlocked for this session via the gate |
Session | Handoffs so the next client can continue |
Related MCP server: mindmap-mcp-server
Skills and sessions
Skills are markdown procedures stored by id in mcp_skills. Starting a session loads one skill; the gate records its revision so a changed skill must be reloaded before protected work continues.
How | Call |
Default on start |
|
Named skill on start |
|
Bind mid-session |
|
Create / edit |
|
List / read |
|
start_session returns a session_id bound to one local and a skill revision, valid for eight hours. Protected tools check it before executing. _workflow is only a readable status summary; independent gate records live in mcp_sessions. Clients are instructed to manage these calls during normal conversation. Calling get_skill alone does not establish a work session.
What this is (and isn’t)
OpenPortable provides a standalone memory server and a reusable session/gating core. Domain behavior belongs to the application that calls or embeds it.
Term | Meaning here |
OpenPortable | The MCP server, reusable core, and scoped load/write contract |
Operator | Who hosts and owns the database |
Host | Where the server runs (local Node/SQLite, Cloudflare Workers/D1, …) |
Client | An MCP caller (Cursor, Claude, Codex, a custom app, …) |
Memory | Durable markdown in scopes |
Local | One named instruction set / context id (seed example: |
Skill | A procedure stored by id; unlocked by the gate for a work session |
Session / handoff |
|
Gate | Server checks the session ID, expiry, local scope, and skill revision; the client handles IDs automatically |
Applications supply their own domain tools and orchestration. OpenPortable supplies scoped memory, skills, workflow prerequisites, and handoffs; the embedding API applies those prerequisites to application tools.
Why this exists
Most setups do one of two things:
Stuff everything into the prompt every turn (slow, noisy, expensive)
Keep memory inside one client (fine until you switch tools)
OpenPortable keeps those records outside any single client. Durable truth lives as markdown scopes in SQLite or D1. Clients call MCP tools to load a small slice, do the work, then write back what changed.
What you get
Scoped load contract.
get_context()lists available scopes without memory bodies. Scoped reads include must-load scopes and any configured shared scopes. Ascopeshorthand allows one local; an explicitscopeslist may name several. Shared scopes do not count as locals.One-shot start.
start_sessionloads memory, binds a skill (bootstrap/resume-work/ your id), and returnssession_id, expiry, andnext_action.Skills by id. Author with
update_skill; bind withstart_session({ skill })orlearn_workflow. Unchanged skills can skip the body via hash.Enforced gate. Missing/expired sessions, wrong locals, or changed skills return a corrective
next_actionbefore protected tools execute.Reusable integration. Attach the same tools and gate to another MCP server via
registerOpenPortTools.Session handoffs.
finish_sessionsaves a note and closes the gate;update_contextsaves checkpoints while continuing. Notes are pruned when handoffs are saved or_sessionis collapsed (default retention: 14 days).Write guards. Configurable validation for designated durable scopes; default guards cover
_global,_important, and_protected.Two hosts. Same tool surface on local SQLite or Cloudflare D1.
Markdown backup. Export/import a zip of scopes, skills, and docs on the local host.
Host-agnostic core.
/srctalks to aSqlDatabaseinterface. New platforms go underplatforms/.
Requirements
Node.js 22.13+ for the local host (built-in SQLite without an extra flag).
For Cloudflare: a Cloudflare account. Wrangler comes in via
npm.
Quick start (one-liner)
No clone required. The local stdio host seeds a starter desk + example _session handoff on first run. DB persists at ~/.openport/openport.sqlite.
npx -y openportableCursor
~/.cursor/mcp.json:
{
"mcpServers": {
"openportable": {
"command": "npx",
"args": ["-y", "openportable"]
}
}
}Claude Code
claude mcp add openportable -- npx -y openportableCodex
[mcp_servers.openportable]
command = "npx"
args = ["-y", "openportable"]MCP instructions ask the agent to manage the session lifecycle automatically. Prompts resume / handoff provide explicit entry points when the client surfaces them; the client instructions are a fallback for clients that need standing rules.
The npm package is openportable; its executable is openportable. Use npm install -g openportable for a global command. To run directly from GitHub instead, use npx -y github:theodorexli/openportable.
Quick start (clone)
git clone https://github.com/theodorexli/openportable.git
cd openportable
npm install
npm run local:stdio # good default for Cursor / Claude / Codex
# or
npm run local # HTTP MCP at http://127.0.0.1:8787/mcp
# or
npm run openportable # same stdio entry via package binExisting OPENPORT_* environment variables, X-OpenPort-* headers, database paths, and exported OpenPort* APIs retain their names for compatibility.
A clone defaults to platforms/local/data/openport.sqlite; a packaged install defaults to ~/.openport/openport.sqlite. Set OPENPORT_DB to the same absolute path if both should share memory.
A fresh DB seeds an example desk local scope (usable defaults + <!-- openport:seed -->), a starter _session handoff, and skills bootstrap / resume-work.
Cursor (clone path)
Put this in ~/.cursor/mcp.json (use your real clone path):
{
"mcpServers": {
"openportable": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/openportable/bin/openportable.mjs"]
}
}
}HTTP works too. Run npm run local first, then point mcp-remote at it:
{
"mcpServers": {
"openportable": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8787/mcp"]
}
}
}Claude Desktop
Same stdio block in claude_desktop_config.json (one-liner or clone path).
Claude Code (clone)
# Replace the absolute path with your clone location.
claude mcp add openportable -- node /ABSOLUTE/PATH/TO/openportable/bin/openportable.mjs
# or HTTP after `npm run local`
claude mcp add --transport http openportable http://127.0.0.1:8787/mcpCodex (clone)
Codex reads TOML from ~/.codex/config.toml (or a trusted project's .codex/config.toml):
[mcp_servers.openportable]
command = "node"
args = ["/ABSOLUTE/PATH/TO/openportable/bin/openportable.mjs"]HTTP after npm run local:
[mcp_servers.openportable]
url = "http://127.0.0.1:8787/mcp"Or use the CLI with your absolute clone path:
codex mcp add openportable -- node /ABSOLUTE/PATH/TO/openportable/bin/openportable.mjs
# or
codex mcp add openportable --url http://127.0.0.1:8787/mcpEnv vars, reseed, and backup live in platforms/local/README.md.
First session
You talk normally. Under the hood the client should run something like:
1. start_session({ local: "desk" }) → keep session_id
2. If mode is "bootstrap": this local is not personalized yet —
short interview, rewrite desk, then real work
If mode is "resume": continue from prefs / open threads / _session
3. … do the work …
4. finish_session({
session_id: "<returned session_id>",
session_note: "Finished triage; left VIP thread open"
})start_session hits the DB behind the host you wired (SQLite or D1). A seed or blank local triggers bootstrap; a personalized local resumes. Other scopes may already contain history. For checkpoints, update_context({ session_id, scope: "desk", context: "", session_note: "new: …" }) saves a handoff without closing the session. The agent handles session_id; the human does not.
OpenPortable is a memory mechanism, not an automatic journal. A skill file or home-grown agent must explicitly call start_session, save checkpoints, and finish_session. If those calls are never made, nothing is stored and the next client has nothing to resume. Put the calls in the skill, system prompt, or embedding instructions.
How memory works
Scopes
Kind | Id | Role |
Important |
| Must-load callouts supplied to the agent |
Protected |
| Must-load constraints supplied to the agent |
Global |
| Shared across locals (opt-in) |
Local | any other id | One named instruction set / context |
Session |
| Work-session handoffs; retention prunes old notes |
Workflow |
| Read-only summary of the most recently loaded skill; not gate authority |
Load contract (server-enforced)
get_context()returns an index of stored scopes:id,kind(localorreserved), andupdatedAt. It does not load memory bodies or open a session. Empty or whitespace-only selectors also return the index;include_session/include_globalapply only when a non-empty scope is selected.Use
scope=for one local, orscopes=to load several locals.start_session({ local: "<id>" })begins work on one local.Must-load scopes and configured shared scopes are merged into every scoped read. Defaults must-load
_importantand_protected. Shared scopes default to none. Embedded hosts set both lists; shared scopes do not count as locals.A scope shorthand allows one local. An explicit
scopeslist may name several. The index can list all locals. Zero locals is fine if you only need standing rules.Pull
_session/_globalwith flags or by listing them. Don't reload every local "just in case."Persist durable truth with
update_context. Use shortsession_notesummaries for handoffs; keep live domain state in its source system.
MCP tools
Tool | Role |
| Discover scopes with a bare call, or load by |
| Load memory, bind a skill, return |
| Bind/reload a skill using |
| Save a final handoff and close |
| Requires |
| Requires |
| Read or author skills by id |
| Read or author docs; updates require |
| Health check |
update_context, update_doc, collapse_context, and tools registered through the embedding gate require a current session. Local writes must match its local. Context, doc, and skill writes compare-and-swap, so concurrent appends do not drop each other. Shared reserved scopes remain shared; _workflow cannot be edited through memory tools. Read tools and update_skill remain available before setup so a missing skill can be installed or repaired. This is workflow enforcement, not authentication or multi-user authorization.
The gate verifies recorded prerequisites, not whether a model understood or followed the skill. Stored _protected rules are instructions for the agent, not an authorization policy for domain actions. MCP clients vary in how they use server instructions.
Starter docs also live under seed/docs/.
Embed in another MCP
OpenPortable is independent of any domain app. Use its portable core as a library, or connect to its standalone MCP. See the complete embedding example.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
import { DEFAULT_CONFIG, OpenPortStore, registerOpenPortTools } from "openportable"
// db implements SqlDatabase and has the core migrations applied (see below).
// Seed bootstrap/resume-work and install your review skill once (see example).
const store = new OpenPortStore(db)
const server = new McpServer(
{ name: "my-app", version: "1.0.0" },
{ instructions: DEFAULT_CONFIG.instructions + "\nUse the review skill to review items." },
)
const openport = registerOpenPortTools(server, { store })
openport.registerTool("review_item", {
description: "Review one item.",
inputSchema: z.object({ item: z.string() }),
workflow: { local: "desk", skill: "review" },
handoff: (_result, args) => `Reviewed ${args.item}.`,
}, async ({ item }, session) => {
// Runs only after session, scope, skill revision, and bootstrap checks pass.
return { content: [{ type: "text", text: `Reviewed ${item}` }] }
})The wrapper adds session_id to the tool schema. A blocked call tells the agent which prerequisite to complete. workflow can also be a function of validated arguments, for tools whose local scope varies by request. Domain tools require an initialized local by default; set requireInitialized: false for a tool intentionally available during bootstrap. Do not register the same domain action a second time without the wrapper.
The optional handoff callback saves a short, application-authored note after a successful tool result. It never copies raw results automatically. If the action succeeds but saving fails, the result warns the agent to retry only the handoff. Without the callback, the client uses checkpoints or finish_session. Attach OpenPortable's instructions when constructing your server; registration does not overwrite existing instructions.
For non-MCP hosts, OpenPortSessions exposes the same start, require, learn, and finish lifecycle. OpenPortStore is a trusted low-level storage API; its direct methods do not enforce MCP write guards. Application authentication stays with the host.
Install the library with npm install openportable (or npm install github:theodorexli/openportable to use the repository). The package exports compiled ESM and TypeScript declarations; npm run build builds them from a checkout.
Upgrade existing hosts
This changes protected tool calls: agents must pass the session_id returned by start_session. Update standing client instructions and any saved skills/examples that call write tools. Do not reseed a personalized database just to update docs.
Local hosts apply core migrations on startup. Existing remote databases need migrations/006_mcp_sessions.sql; rerunning db:migrate with the appropriate auth choice also applies it. These scripts add missing tables and do not drop existing tables or data. Active gates are ephemeral and excluded from markdown backups; restored clients start fresh sessions. Durable memory and handoffs keep their existing format.
Deploy on Cloudflare
Reference host is Workers + D1. Longer guide: platforms/cloudflare/README.md.
Skill-assisted install: point a client at seed/skills/install.md. It asks about auth, retention, and write guards, waits for your confirm, then runs the Wrangler steps.
Manual:
npm install
npx wrangler login
npm run db:create
# Paste database_id into platforms/cloudflare/wrangler.toml
npm run db:migrate -- --auth none # choose none, personal, full, or static
npm run db:seed # fresh database only: writes starter content
# Configure auth below before deploying.
npm run deployPoint your client at https://YOUR_WORKER.workers.dev/mcp (or /mcp/{token} for personal auth).
Local Worker preview: npm run db:migrate:local -- --auth none, then npm run dev (port 8788). Local D1 is separate from remote D1 and local Node SQLite; the current db:seed script targets remote D1 only.
Cloudflare auth modes
Mode | When | Behavior |
none | Local / low sensitivity | Bare |
personal | Solo operator, shareable link | Rotating |
full | External gateway / IdP | The gateway must enforce auth; OpenPortable itself serves bare |
static | Legacy shared key | Set |
These are deployment choices, not a built-in AUTH_MODE setting. --auth selects database tables only. For personal, configure OPENPORT_SETTINGS_KEY and obtain a token from /api/mcp/connect. For full, configure your gateway separately. none and full leave OpenPortable's auth secrets unset. Local Node HTTP supports an optional OPENPORT_MCP_KEY; the rotating connect API belongs to the Cloudflare host.
Cloudflare request auditing uses mcp_requests in every auth mode. Local Node hosts do not currently write this audit table. Work-session gating remains active regardless of authentication mode.
Database tables by deployment
Tables | Migration files | Needed when |
|
| Every OpenPortable host / embedded core |
|
| Cloudflare host, all auth modes |
|
| Cloudflare personal auth with rotating connect tokens |
mcp_sessions tracks workflow prerequisites. mcp_tokens grants access to a personal-auth MCP endpoint. They have different purposes and lifetimes.
npm run db:migrate -- --auth none # core + Cloudflare audit
npm run db:migrate -- --auth full # same tables; configure the gateway separately
npm run db:migrate -- --auth static # same tables; configure the static secret separately
npm run db:migrate -- --auth personal # core + audit + rotating-token table
npm run db:migrate -- --auth personal --dry-run # print selection; change nothingChoose one auth profile. Use db:migrate:local for the same selection against local D1. The local Node host automatically applies only the core migrations. Embedded hosts can use the exported migrationFiles({ host: "local" }) to obtain core SQL filenames; the files are available under openportable/migrations/<filename>.sql.
schema.sql remains an explicit all-features superset for compatibility; applying it creates the optional token and audit tables too. The numbered files are idempotent table definitions, not a mandatory all-files migration sequence. Switching profiles never deletes existing optional tables or configures/removes secrets; manage auth configuration separately.
Configuration
Set these in platforms/cloudflare/wrangler.toml under [vars]:
Variable | Default | Meaning |
|
| How long to keep |
|
|
|
Secret | Used for |
| personal mode: mint connect URLs |
| Legacy static key; path or auth header on Cloudflare |
Backup (local host)
npm run local:export # → platforms/local/data/openport-backup.zip
npm run local:import -- ./backup.zipThe zip is markdown for scopes, skills, and docs. Ids are encoded reversibly and recorded in manifest.json, so team/review and team_review both survive export/import.
Repository layout
src/ # Portable memory core (SqlDatabase, host-agnostic)
platforms/cloudflare/ # Workers + D1 adapter
platforms/local/ # Node HTTP + stdio + SQLite + export/import
seed/ # Starter scopes, skills, docs, install skill
migrations/ + schema.sql # SQLite / D1 schemaThe core does not depend on Cloudflare types. Each host implements SqlDatabase and serves MCP. Durable context, docs, and skills are markdown in SQLite rows. Gate records, auth tokens, and audit rows are operational data, separate from those documents.
Develop
npm run build # compiled library and declarations
npm test # unit + MCP + local SQLite/backup tests
npm run typecheck
npm run local # HTTP host on :8787
npm run local:stdio
npm run local:export
npm run dev # Cloudflare local Worker on :8788Area | Covered by tests |
Auth / access | Path tokens, static key, none/personal gate |
Session | Independent gates, expiry, skill changes, bootstrap, handoffs, retention |
Write guards |
|
Store | Scoped load, append, skills/docs |
MCP tools | Load-contract enforcement, guards, |
Local host | File SQLite, stateless HTTP persistence, concurrent handoffs, backup round-trip |
Embedding / schema | Gated domain tools, automatic handoffs, migration profiles |
D1/Wrangler integration is manual via npm run dev / deploy.
Non-goals
Out of scope on purpose. Open an issue only if you have a design that still respects the load contract.
Non-goal | Why |
Full-text / semantic search over memory | Load by known scope id |
Multi-user / multi-tenant ACL inside OpenPortable | One operator (or your gateway) owns the URL |
Cross-device sync / CRDT | One database per deploy is the source of truth |
Vector store / RAG platform | Skills + scoped markdown, not embeddings-as-memory |
Bundled domain tools (email, broker, etc.) | Integrations belong outside this memory core |
Bundled applications or agent orchestration | Host applications provide behavior; OpenPortable exposes reusable memory and gating |
Contributing
See CONTRIBUTING.md. Issues and PRs welcome.
Keep
/srcfree of vendor lock-in (useSqlDatabase).Cloudflare changes go under
platforms/cloudflare/.Local Node host changes go under
platforms/local/.New hosts: add
platforms/<name>and docs. Don't break the MCP tool surface without talking about it first.Respect Non-goals and the load contract.
Add or extend tests under
src/*.test.tswhen you change store or tool behavior.
Links
License
MIT © TXL
This server cannot be deployed
Maintenance
Related MCP Connectors
An MCP memory server. One memory your agents share — across models, devices and apps.
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
- LogRackOAuthai.lograck
Shared memory for your AI tools: tasks, notes and context every tool can read and write over MCP.
Cross-tool persistent memory and context for AI assistants over MCP.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceAn MCP-native, local-first memory server that gives AI agents persistent, structured memory across sessions and tools, enabling them to maintain identity and context without reconfiguration.3MIT
- AlicenseAqualityCmaintenanceA local-first MCP server for shared memory across AI tools, enabling context capture and resume across sessions.2626 npm6Apache 2.0
- AlicenseNot gradedqualityDmaintenanceA portable MCP server providing a shared intelligent memory system for any MCP-compatible AI tool, enabling storage, retrieval, extraction, and governance of memories across sessions.2 npmMIT
- AlicenseNot gradedqualityDmaintenanceA portable self-hosted memory layer for AI tools, storing context, memories, and handoffs for access from any MCP-compatible client.22 npmMIT