Files
simplex-chat/packages/simplex-chat-mcp/spec/architecture.md
T
Evgeny @ SimpleX Chat be668564df mcp server: files, /leave and library methods (#7962)
* bots, nodejs: document chat, connection, member contact and network commands, typed commands in library

* bots, nodejs: restore apiConnect, wrap domain once, test all connect responses

* nodejs: JSDoc for new methods

* docs: plan for bot API commands and typed Node library methods

* tests: channel prepared with a domain has a verified SimpleX name

* docs: apply review fixes to the plan for bot API commands

* docs: apply second review pass to the plan for bot API commands

* docs: refer the bot API plan to the rewritten test commit

* docs: record decisions in the plan for bot API commands

* docs: plan tests of the generated Python command strings

* python: test the generated strings of the documented commands

* docs: plan for files, /leave and library methods in the MCP server

* docs: wait at stop in the MCP plan

* docs: library methods and /leave mechanics in the MCP plan

* mcp: typed library methods for chat commands

* docs: plan the message information command for bots

* mcp: /leave CHAT owner command to leave a chat at once

* docs: file servers, GIF and video choices decided in the MCP plan

* docs: file mechanics in the MCP plan

* mcp: files of received messages

* bots, nodejs: message information with the file servers for bots

* docs: plan the relay approval option of receiving files in Node

* mcp: download of received files with owner approval

* docs: name the xftp-server test helper in the files plan

* nodejs: receive files without approving unknown relays

* docs: plan the user servers command for bots

* bots, nodejs: user servers and relays by operator for bots

* nodejs: document the message information method

* docs: plan the file upload progress event for bots

* mcp: send files with owner approval, refusing private keys

* bots: file upload progress event for bots

* mcp: /leave template in the agent control chat menu

* docs: sent copies are deleted for contacts too, at sndFileCompleteXFTP

* bots: drop the file upload progress event

* docs: drop the upload progress event plan

* nodejs: document user servers and relay approval of received files

* mcp: download cards name unknown file servers after the upload

* docs: record the wait for the file description in the files plan

* mcp: fixed wait at stop for cancelled cards, without /sql

* docs: record decision C4, a fixed wait at stop, in the files plan

* ci: test the MCP server package on pull requests

* ci: state when the MCP workflow can be dispatched

* ci: run the MCP server tests when its libraries change

* mcp: log the errors of image previews and resizing

* mcp: keep files in transfer in the data folder

* mcp: restrict sent files by the real paths of refused folders

* mcp: validate the file at the send by its inode and change time

* mcp: limit ffmpeg and ffprobe to local files and no input commands

* mcp: ask the owner for unknown file servers; read approvals at creation

* mcp: name downloaded files by their ID; return paths of downloads only

* mcp: end each download with a result for the agent and the card

* mcp: show files above their sender's size limit as failed

* mcp: refuse empty files, as without file and captions too long to send

* mcp: tell the owner about failed uploads and unposted received files

* mcp: delete resized copies left by the last run at start

* mcp: name downloads and leaving in the help and greeting, align docs

* mcp: compute the download path for received files only

* mcp: send a /receive file to the owner under its name

* mcp: keep files received without a download in the chat's folder

* mcp: keep a left chat's files and delete them with /delete CHAT files

* mcp: test that sent images are resized by the core

* mcp: wait 30 minutes for a download's progress before cancelling it

* mcp: end a download as deleted only when its message is not found

* mcp: delete the copy of a /receive file whose post fails

* mcp: count the quoted content in a message's length, as the core sends it

* mcp: name received bytes, unknown-server downloads and left chat files plainly

* mcp: test refused folders, file changes and download approvals

* docs: align MCP server docs with refused folders, leaves and download waits

* mcp: move received files only in full, delete partials only of downloads

* mcp: report a received file missing from its folder as failed

* mcp: move received files of a group's member chats into its folder

* mcp: read a video's preview and duration as an MP4 or QuickTime file

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.1

* mcp: file folders by chat type, /delete cancels downloads, files reply

* docs: MCP server specs and plan for 0.2.0

* mcp: /receive gives the agent a file, /forward sends it to the owner

* docs: MCP server specs for /receive and /forward of download cards

* docs: MCP plan records the temp folder command as a library exception

* mcp: /delete cancels resumed downloads; /forward quote fits the limit

* docs: MCP specs and plans for /forward quotes and resumed downloads

* docs: MCP specs name the source commit they are checked against

* docs: MCP PLAN states the /forward quote and move rules

* mcp: a download moves its completed file whatever its size

* mcp: /receive N all covers its card and later files, not pending ones

* docs: MCP files approval scope, reply text and README server wording

* mcp: a download approves relays only for a card naming unknown servers

* mcp: files in member chats of a group are tracked as in the main chat

* docs: MCP relay approval of downloads and files in member chats

* mcp: a rejected file gets a card again under the files approval

* docs: MCP rejected files under the files approval, README scope

* mcp: an offered file without a description gets no card or action

* docs: MCP a file without a description is not downloaded

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.2

* ci: test the MCP server as a scope of build-linux
2026-11-06 18:38:34 +00:00

57 KiB
Raw Blame History

SimpleX MCP server: architecture

System structure, modules, state, process lifecycle, approval model and security boundaries of @simplex-chat/mcp 0.2.0.

Related specs: README | Flows | API Related docs: README | Reference | PLAN

Source: cli.ts | serve.ts | http.ts | tools.ts | actions.ts | control.ts | messages.ts | files.ts | register.ts | log.ts


Table of contents

  1. System diagram
  2. Modules
  3. State
  4. Process lifecycle
  5. Approval model
  6. Security boundaries
  7. Limits

1. System diagram

flowchart LR
  subgraph host["MCP host"]
    cc["Claude Code"]
  end
  subgraph proc["Node.js process: simplex-mcp serve NAME"]
    http["http.ts<br/>127.0.0.1:PORT/mcp<br/>Host and Origin guards<br/>bearer token<br/>path /mcp"]
    sdk["MCP SDK<br/>createMcpHandler<br/>McpServer per request"]
    tools["tools.ts<br/>AgentTools"]
    actions["actions.ts<br/>Actions"]
    control["control.ts<br/>Control"]
    messages["messages.ts<br/>ChatItemsWatch"]
    serve["serve.ts<br/>start, events, stop"]
    files["files.ts"]
    log["log.ts"]
    lib["simplex-chat<br/>ChatApi"]
    core["SimpleX core<br/>libsimplex core-7.1.0.20"]
  end
  cli["cli.ts"] --> serve
  cli --> register["register.ts"]
  register -->|"claude mcp add-json"| cc
  cc -->|"headersHelper:<br/>cat headers.json"| data
  cc -->|"POST /mcp<br/>Authorization: Bearer"| http
  http --> sdk --> tools
  tools --> actions
  actions <--> control
  tools --> messages
  serve --> control
  serve --> tools
  serve --> messages
  serve --> files
  files --> data[("DATA:<br/>token<br/>headers.json<br/>server.json<br/>lock")]
  tools --> folder[("DATA/files:<br/>group-N, contact-N<br/>sent")]
  tools --> lib
  control --> lib
  serve --> lib
  lib --> core
  core --> db[("DATA:<br/>simplex_chat.db<br/>simplex_agent.db<br/>tmp")]
  core <-->|"SMP<br/>private routing"| smp["SMP servers"]
  core <-->|"XFTP"| xftp["XFTP servers"]
  smp <--> owner["Owner's SimpleX app<br/>agent control chat"]
  smp <--> peers["Peers:<br/>contacts and bots<br/>groups and channels"]
  log --> term["Terminal:<br/>stdout<br/>stderr"]

Data flow of a tool call:

  1. Claude Code runs the headersHelper command of its entry (register.ts:50) and sends the Authorization header it prints (Reference).
  2. The server checks Host, Origin, token, path and readiness, then passes the request to the SDK handler (http.ts:19-28).
  3. An McpServer is built from the factory for each request, and the arguments are validated against the input schema by the SDK (serve.ts:111-115, mcp-BumlNux0.cjs:1812-1817).
  4. The handler result is returned by AgentTools.call as structuredContent and as JSON text (tools.ts:500-507).
  5. The server calls ChatApi methods of the core (messages.ts:267-283); actions are created in Actions and shown to the owner by Control (actions.ts:155-186).
  6. The core emits events; the server dispatches them in one handler of serve.ts to the tools, the item watch and the agent control chat (serve.ts:129-165).

2. Modules

2.1 cli.ts

Entry point of the simplex-mcp binary.

Symbol Line Contents
helpText 10 --help output
Command 40 help, serve with ServeOptions, register with RegisterOptions
parseCommand 45 command, NAME and options to a Command
serveOptions 54 defaults and option switch of serve
registerOptions 110 defaults and option switch of register
OptionReader 127 value: the next argument, required
optionalValue: the next argument, absent when missing or starting with --
validAgentName 149 ^[a-z0-9](-?[a-z0-9])*-?$, at most 32 characters
usageError 194 ExitError with code 2
main 198 help to stdout, serve, register
top-level catch 211-220 ExitError: message on stderr, its code; other errors: logError, code 1

2.2 serve.ts

Start sequence, event dispatch and stop sequence of one agent.

Symbol Line Contents
version 15 the package version
OwnerEntry 19 {memberId, name} of --owners
ServeOptions 24 name, data folder and its dataGiven flag, display name, mode, owners, expiry, reset and token flags, port, QR code size, stop wait
RunningServer 39 token, ChatApi, user, Actions, stop
serve 47 startServer, then SIGINT and SIGTERM handlers: the first signal stops, a second ends the stop wait
startServer 65 data folder, lock, token, headers file, ChatApi.init, startAgent; cleanup on error
startAgent 88 user, network config, temp folder, control group, wiring of Actions, AgentTools, Control, HTTP, event handler, stop
registerHint 185 the register command line printed after the owner setup
waitForCancelledCards 189 the fixed wait at stop for cards edited or posted at the stop
setTempFolder 205 DATA/tmp, mode 0700, as the core's temp folder
updatedUser 211 display name and peer type agent of the profile
controlGroup 218 found, created or reset agent control chat
listen 225 startHttp; EADDRINUSE to ExitError code 1
bufferEvents 234 events received before the handler is installed are queued and replayed in order

2.3 http.ts

Symbol Line Contents
HttpOptions 7 port, token, McpServerFactory, readiness
startHttp 14 http.Server on 127.0.0.1:port: guards, SDK handler, timeouts
authorized 34 length check and timingSafeEqual against Bearer <token>
stopHttp 48 server.close and closeAllConnections

2.4 tools.ts

Tool definitions, call handlers, connect plans, perform steps, files.

Symbol Line Contents
ConnectInput, SendInput, ReceiveInput, LeaveInput, DownloadInput 46-80 tool inputs
PlanSummary 86 card kind, chat type, name, SimpleX name, peer type, description, welcome, commands, takesMessage
ChatFields 98 chat fields of connect and list results
Agent 112 ChatApi, user, Actions, ChatItemsWatch, control chat reference, standing approver lookup, refused file post, data folder
JoinMethod 123 prepared, prepareChannel, viaAddress, prepareContact, connect
schemas 178-401 input and output JSON schemas
descriptions, instructions 403-441 tool descriptions and server instructions
definitions 451 name, description, schemas and action flag of the six tools
Download 462 one download: its promise, chat, item, file ID, size, path, the last fileSource seen, received bytes and their time, check timer
AgentTools.register 488 registerTool for each definition; _meta in mode host
AgentTools.call 500 handler result to CallToolResult; errors to isError
AgentTools.perform 509 perform step of an approved action
downloadFile, forwardFile, downloadTo 522-549 one download per file ID, shared by download and /forward; a forwarded file moved to the download path for download
file events 551-588 onNewItems, onFile, onFileComplete, onFileError, onFileCancelled, onFileDescription
onContactConnected, onUserJoinedGroup 590-598 completion of connect actions by events
actionResult 636 wait for the decision, result fields, removal
attachment, isRefused, fileToSend, resizedCopy 860-896 the file of a send
copyForSending, chatFiles, deleteChatFiles 898-927 /forward copies; the files of a chat, forwarded ones included; its downloads cancelled, also those the core continues after a restart
leaveChat 950 perform step of leave and /leave; the chat's forwarded files deleted
download, fileServers, unknownServers 960-1006 the download call before its action
startDownload, checkDownload, currentFile, settleFile, settleReceived, moveReceived 1024-1091 a download's steps
agentChats, knownChat 1160-1177 the agent's chats; a checked chat reference
classifyPlan 1260 apiConnectPlan response to a join plan or a known chat
chatSubject, isConnected, isActive, memberCurrent 1356-1409 chat and member predicates shared with control.ts
fitsMessage, quoteContent 1433-1449 the message as the core sends it, within 15602 bytes; for send and the post of /forward
realFile 1465 real path, size, inode and change time of a file
attachmentMedia, videoFrame, videoDuration 1512-1546 previews and durations
toolCall 1562 ServerContext to ToolCall: abort signal and progress notifications

2.5 actions.ts

Symbol Line Contents
Mode 5 requests, host
State 6 pending, performing, done, failed, rejected, expired
Standing 7 messages, files
ToolError 19 errors the model can correct; text returned as is
Subject 21 kind, name, SimpleX name, incognito flag of a card
ConnRef 28 pending connection, contact or group of a connect action
ActionRecord 34 number, key, times, change signal, standing flag, state, card, decider, open calls, interrupted flag, error, card image
ConnectAction, SendAction, LeaveAction, DownloadAction 51-110 per-tool fields; Attachment, the file of a send
ActionHooks 116 refusal, saveCounter, postCard, perform, outcome
Actions 124 action map, expiry timer, posting and outcome promises
create 155 refusal checks, number, log line, counter write, card
decided 206 wait with progress until a decided state, until, or the abort signal
stop 240 cancellation of pending and performing actions
summary, performedText, progressText 315-354 log and progress texts

Hooks are bound in serve.ts:100-106: refusal, saveCounter, postCard and outcome to Control; perform to AgentTools.

2.6 control.ts

Symbol Line Contents
controlCommands 10 the command menu
standings 22 custom data key and reply words of the approvals of messages and files
ControlData 43 {control: true, nextAction}
ChatApproval 60 {approvedBy, approvedById, approvedAt}
OwnerRequest 66 parsed owner command
Control.load 102 lost check, group profile and menu, member list
Control.start 111 owner setup per --owners
Control.refusal 119 bytes left for the link texts of a connect card, card size of a send, connect or download, approver presence
Control.standingApprover 130 approver name of a chat's standing approval of messages or files
Control.saveCounter 135 queued counter writes
Control.postCard, Control.outcome 141-152 card post and card edit; the kept-files message after a done leave
Control.refusedFile, Control.fileNotSent 154-169 posts about files
Control.onMessage 171 owner commands, queued one at a time
Control.onEvent 183 membership changes and loss of the control chat
Control.ownerCommand 200 command dispatch and replies
approveChat, revoke, forward, leave, deleteFiles 243-307 owner requests
cardContent, fittedImage 441-461 image and video cards with the file's preview
findControlGroup, createControlChat, resetControlChat 499-527 control group lookup, creation, reset
isControlEvent 529 membership events of the control group
parseCommand 545 one-line /keyword params
cardText, requestLines, commandsLine 552-616 card text per tool, state and mode
largestCard, encodedLength 622-630 card size check
serversLine, sizeText 664-673 servers line and sizes of download cards
codeSpan, safeName, mentionText 683-695 quoting of peer text
ownerRequest 710 command words to an OwnerRequest
qrCodeText 725 terminal QR code
losesControl, isLost 741-756 loss of the control chat
entryText, restartText 774-784 --owners entries in restart lines

2.7 messages.ts

Symbol Line Contents
Message, MessageFile, FileOffer 16-50 message item of tool results; its file; the file of a download
ToolCall 57 abort signal and progress callback of one call
Collected 62 collected items, change signal, IDs of the items over the first 100, time of the last change
Changes 69 set of wake callbacks
ChatItemsWatch 86 listeners per chat reference; received message items gathered by collect
chatRef, anyScopeChatRef, isChatRef 135-152 @N and #N references: of the main conversation, or also of a group's member chat
toMessage, messageFile, receivedFileError, receivedPath 174-213 chat item to Message; where a received file is: its download path or its forwarded path
filePaths, chatFolder 215-223 DATA/files/group-N, DATA/files/contact-N or the same under DATA/forwarded; the download path and the forwarded path of a received file
fileOffer, fileContent 225-250 file fields of an item; message content of a file
getChat, receivingFiles, markRead 267-283 apiGetChat and apiChatItemsRead by chat reference; the IDs of a chat's files in status rcvAccepted, rcvTransfer or rcvWarning, paged
unreadItems 285 unread items of a chat after its initial read, paged
reportProgress, nextChange, awaitResponses 301-331 waits
validName, viewName 337-363 display name rules and @name form

2.8 files.ts

Symbol Line Contents
createDataFolder 12 mkdir -p mode 0700; chmod 0700 except on Windows
acquireLock, releaseLock 17-28 DATA/lock with the PID
serverToken 30 DATA/token: read, or 32 random bytes in base64url
writeHeadersFile 42 DATA/headers.json
writeServerFile, readServerFile 46-53 DATA/server.json

2.9 register.ts

Symbol Line Contents
register 33 claude mcp remove, then claude mcp add-json
registryEntry 46 {type, url, headersHelper, timeout}
manualEntryText 55 entry text when claude is missing
projectRoot 60 project key of --scope local
runCommand 86 spawnSync with ENOENT detection; on Windows after ENOENT, once more through cmd.exe with cmdArgument quoting, and exit status 9009 as missing

2.10 log.ts

Symbol Line Contents
ExitError 18 message and exit code
shellWord, shellPath 24-31 POSIX single quotes or Windows double quotes
oneLine 33 runs of \p{Cc}, \p{Cf}, \p{Zl}, \p{Zp} to one space
log, logError 37-43 stdout lines; stderr lines
errorText, chatErrorText 45-53 error text, folded to one line: the apps' wording for known error shapes, otherwise the type chain
appErrorText 55-64 wording of the apps' alerts for chat, agent and store errors
fileError 197-201 the error of a received file: the apps' words for its status, or the size limit of its sender
serverHostname 246 host of a server address
chainText, errorChain 250-262 SimpleX error: <type chain>: <scalar fields>, details at most 500 characters
fullDomainName 272 SimplexDomain to name.simplex or name.testing

3. State

3.1 Memory

Item Holder Written by Read by
Actions Actions.actions map (actions.ts:128) create, remove, expireDue tools, Control
Open calls of an action ActionRecord.open (:44) actionResult (tools.ts:637, :646) list, expireDue
Collected replies ConnectAction.messages, SendAction.responses (:56, :87) ChatItemsWatch.collect connect, send results
Item listeners ChatItemsWatch.listeners (messages.ts:87) collect event dispatch
Downloads AgentTools.downloads, one per file ID (tools.ts:480) downloadTo file events, checks, /delete CHAT files
Forwards AgentTools.forwarding, the copy for the post per file ID (tools.ts:481) forwardFile downloadFile, before it moves a forwarded file
Rejected files AgentTools.rejectedFiles (tools.ts:482) downloadResult (:1013-1014) the file field; download, which then skips the files approval (:975)
Count of file descriptions AgentTools.describedFiles (tools.ts:483-484) onFileDescription fileServers
Members of the control chat Control.members (control.ts:82) refreshMembers (:463-465) approver checks
Mode, expiry, owners list, stop wait ServeOptions of the run CLI all modules

3.2 Chat database custom data

Item Entity Value Written by Read by
Control marker and counter agent control chat {simplexMcp: {control: true, nextAction: N}} createControlChat, saveCounter findControlGroup, controlDataOf
Standing approval of messages contact or group of the agent {...other keys, simplexMcp: {approvedBy, approvedById, approvedAt}} approveChat, revoke standingApprover
Files approval contact or group of the agent {...other keys, simplexMcpFiles: {approvedBy, approvedById, approvedAt}} approveChat, revoke standingApprover

The profile, chats, items, members and file transfers are stored by the core in DATA/simplex_chat.db; connections and queues in DATA/simplex_agent.db (file prefix DATA/simplex, serve.ts:72).

3.3 Files

Path Contents Mode Written Read
DATA/ data folder 0700, set at every start except on Windows files.ts:12-15
DATA/token base64url of 32 random bytes 0600, set again at every write except on Windows first start, --new-token (:30-36) every start
DATA/headers.json {"Authorization":"Bearer <token>"} 0600, set again at every write except on Windows every start (:42-44) Claude Code headersHelper
DATA/server.json {name, port, expireHours} 0600, set again at every write except on Windows every start, after the HTTP listener is bound (serve.ts:118) register
DATA/lock PID and a newline 0600 start (files.ts:64-72) next start
DATA/tmp/ the core's temp folder: files in transfer 0700, set at every start serve.ts:93, :205-209 the core
DATA/files/group-N/, DATA/files/contact-N/ downloaded files and files received without a download, named by file ID and extension folder 0700 the core at the download path (tools.ts:1029-1030); moves (:1500-1510) download results, /forward, /leave, /delete CHAT files
DATA/forwarded/group-N/, DATA/forwarded/contact-N/ files /forward downloaded, named as above, until a download moves them to DATA/files/ folder 0700 the core at the forwarded path (tools.ts:532-539, :1029-1030) /forward; moved by download (:522-530); deleted by leave, /leave and /delete CHAT files (:909-927, :950-958)
DATA/files/sent/ resized copies of sent images, and copies of the files /forward posts, one folder per copy folder 0700; a resized copy 0600 tools.ts:885-902, :929-933 the core; deleted at sndFileCompleteXFTP, sndFileError and at start (serve.ts:94, :156-160)

The core library is downloaded once into CACHE/core-7.1.0.20/sqlite, and the Node.js add-on into CACHE/nodejs-addon-cce06d8/, each with a Downloading ... line on stderr. CACHE is $XDG_CACHE_HOME/simplex-chat or ~/.cache/simplex-chat; on macOS ~/Library/Caches/simplex-chat; on Windows %LOCALAPPDATA%\simplex-chat (libs.ts:41-48, :89, :109, :116, :129).


4. Process lifecycle

4.1 Start

  1. Data folder, lock, token and headers file are prepared (serve.ts:67-71).
  2. libsimplex for SQLite is loaded by ChatApi.init, and the databases are migrated with confirmation yesUp (serve.ts:72, api.ts:117-126).
  3. Three log lines: version and NAME, data folder, mode (serve.ts:73-75).
  4. The active user is read, or created with display name --display-name or NAME, empty full name and peer type agent (serve.ts:90).
  5. The network config is read and set with smpProxyMode always and smpProxyFallback allowProtected (:91-92).
  6. DATA/tmp is set as the core's temp folder, and DATA/files/sent is deleted (:93-94).
  7. Events are buffered, and the chat is started (:95-96).
  8. A --display-name different from the profile, or a peer type other than agent, is applied with one apiUpdateProfile (:97, :211-216).
  9. The agent control chat is found by its marker, created with counter 1, or reset with --reset-owners (:99, :218-223).
  10. Actions, ChatItemsWatch, AgentTools and Control are created; the counter is taken from the marker (:100-109).
  11. Control.load: for a lost control chat, Actions.lost is set; otherwise the group name and command menu are updated when they differ, and members are read (control.ts:102-109).
  12. The HTTP server listens; with the port in use, the process exits with code 1 (serve.ts:117, :225-232). Until step 14 ends, requests to /mcp that pass the token check get 503 with Retry-After: 1 (http.ts:23, serve.ts:116-117, :125).
  13. server.json is written (serve.ts:118).
  14. The server runs the owner setup, Control.start; after an error, HTTP is closed and the error is rethrown (serve.ts:119-124).
  15. Listening on http://127.0.0.1:PORT/mcp (:126).
  16. Buffered events are replayed, then live events are dispatched (:129, :234-248).

After an error inside startAgent (steps 4-16) the chat is closed; after any error once the lock is taken, the lock is released (serve.ts:69-85).

4.2 Event dispatch

Each onAny receiver is awaited by the library before the next event is read (api.ts:195-202). The dispatcher in serve.ts:129-165:

Event Condition Handler
newChatItems item of the control group, main scope Control.onMessage
newChatItems every item AgentTools.onNewItems, then ChatItemsWatch.deliver
chatItemUpdated AgentTools.onFile, then ChatItemsWatch.update
chatItemsDeleted ChatItemsWatch.remove of each deletedChatItem
contactConnected AgentTools.onContactConnected
userJoinedGroup AgentTools.onUserJoinedGroup
rcvFileComplete AgentTools.onFileComplete
rcvFileSndCancelled AgentTools.onFileCancelled
rcvFileError AgentTools.onFileError
rcvFileDescrReady AgentTools.onFileDescription
sndFileCompleteXFTP AgentTools.onSentFile
sndFileError AgentTools.onSentFile, then Control.fileNotSent
joinedGroupMember
joinedGroupMemberConnecting
connectedToGroupMember
memberRole
leftMember
deletedMember
deletedMemberUser
groupDeleted
event of the control group Control.onEvent, queued one at a time
any event after stop() started ignored

Owner commands are queued in a second chain, independent of the membership chain (control.ts:177-180).

4.3 Stop

On SIGINT or SIGTERM the server runs one stop; a later signal ends its wait for cancelled cards (serve.ts:47-63). RunningServer.stop (serve.ts:171-181):

  1. Events are ignored from now on.
  2. Actions.stop starts: new actions are refused with The server is stopping.; the expiry timer is cleared; cards being posted are awaited; pending and performing actions are marked interrupted; the stop signal of the performs is aborted, which ends the wait of a connect for its connection; their cards are edited, or posted when they have no card; outcome edits already in progress are awaited (actions.ts:240-250).
  3. In parallel with step 2, the HTTP server is closed with all connections, and open calls are aborted (http.ts:48-52).
  4. Cancelled K action(s). when K > 0 (serve.ts:176).
  5. When cards were edited or posted at the stop and the control chat is not lost, the server waits --stop-wait seconds, 10 by default, for them to be sent; a second signal ends the wait (serve.ts:177, :189-203).
  6. chat.close(): the chat is stopped, then the store is closed (api.ts:165-170).
  7. The lock is removed, Stopped. is logged, and the process exits with code 0, or 1 after an error (serve.ts:179-180, :53-59).

Performs in progress are not awaited; when one ends, its card stays as edited at the stop and no line is logged, because the action is interrupted (actions.ts:280).

4.4 The lock

acquireLock(DATA):                                   files.ts:17
  create DATA/lock exclusively (flag wx), PID       :64-72
  exists:
    PID = number in the file                        :74-77
    PID valid, other than ours, running:            :21, :79-86
      exit 1 "Another server uses DATA (PID N). If no server runs, remove DATA/lock."
    remove the file; create it exclusively again    :22-23
      exists again: exit 1 with the same message
releaseLock(DATA): remove DATA/lock                  :26-28

isRunning: true when kill(pid, 0) succeeds or fails with EPERM (files.ts:79-86).


5. Approval model

5.1 Modes

The mode is set by --approve for one run (cli.ts:76-78).

Mode State of a new action Card _meta of connect, send, download, leave
requests pending
performing for a send under a standing approval, and for a download under a files approval
card with commands
outcome as a new message after a standing send; none after a download without a card
none
host performing outcome as a new message after the perform; none for a download {"anthropic/requiresUserInteraction": true}

Sources: actions.ts:166, tools.ts:494, control.ts:145-152, :495-497. In both modes a new action is refused unless the control chat has an approver (control.ts:124-127).

5.2 Owners and approvers

Term Rule Code
human member other than the agent's membership, status connected or complete control.ts:484-486, tools.ts:1394-1396
human owner human with role owner control.ts:471-473
approver, --owners absent member with role owner control.ts:488-492
approver, --owners without a list none control.ts:491
approver, --owners ID:NAME,... member with role owner whose groupMemberId and displayName, folded to one line by oneLine, match an entry control.ts:491, :758-760

The command sender's member record from the chat item is checked (control.ts:176, :204). The approver count for refusals and standing approvals is taken from Control.members, refreshed at load and at each membership event (control.ts:187, :463-465).

Owner setup at start (control.ts:111-117):

--owners Start First join by the link
absent a human owner exists: register hint
otherwise: No owners in the agent control chat. when humans exist, then a link with role member
link deleted, role owner, greeting, log line, register hint
without a list no humans: link with role observer
humans exist: their IDs and the restart command, in the log and the chat
link deleted, the joined member and the restart command announced
ID:NAME,... each entry matched to a human, or exit 2
role owner set where missing
other humans removed
link deleted
Owners: ... and register hint

Sources: startOwners, startObserver, startListed, ownerJoined, observerJoined.

Membership changes at run time (control.ts:183-198):

Change --owners Server reaction
deletedMemberUser, groupDeleted, or the agent's role below owner any Actions.loseControl: pending actions set to failed; later cards left unedited; log line
approvers drop to zero after a human owner existed absent No owners in the agent control chat., new link with role member
a human becomes owner outside the list list <name> (member N) is now an owner and can't approve actions yet. Restart the server with --owners ...
a listed owner stops being a human owner list <name> (member N) is no longer an owner. with the restart command, or No owners remain: ...

5.3 Action state machine

Transitions, one per row:

Event From To Code
call creates the action, mode requests pending actions.ts:166
call creates the action, mode host performing actions.ts:166
send call under a standing approval, or download call under a files approval performing actions.ts:166
counter write or card post fails pending
performing
failed actions.ts:252-263
stop arrives while the counter of a performing action is written performing pending, interrupted actions.ts:265-270
control chat lost pending failed actions.ts:232-238
/reject, or /leave CHAT for a pending action of CHAT with a card pending rejected actions.ts:225-230, control.ts:291-293
expiry time passes pending expired actions.ts:196-199
/approve N, /approve CHAT N and /approve CHAT all for other actions; /receive N, /receive CHAT N and /receive CHAT N all for downloads pending performing actions.ts:219-223, control.ts:229-238
perform throws performing failed actions.ts:276-279
perform ends performing done actions.ts:274-275

create (actions.ts:155-186):

create(key, fields, standingApprover?):
  state = mode host or standing ? performing : pending
  refusal, first match:
    stopping                      "The server is stopping."
    control chat lost             lostMessage
    20 actions pending            pendingMessage
    20 actions performing         performingMessage
    connect: link texts cut to the bytes its largest card leaves
    send, connect or download card over 15000 bytes    cardTooLong
    no approver                   no-owner message
  refusal: throw ToolError        counter unchanged
  N = next; next += 1; store; log "Action N: ..."
  post:
    saveCounter(next)
    state pending: card = postCard()
    either fails: state failed; log "Action N: failed: ERROR."
  after post:
    posted and performing:
      stopping: state = pending
      otherwise: perform
stateDiagram-v2
  [*] --> pending: mode requests
  [*] --> performing: mode host, or standing
  pending --> performing: /approve, /receive
  pending --> rejected: /reject, /leave CHAT
  pending --> expired: expiry
  pending --> failed: counter or card fails, control chat lost
  performing --> done: perform ends
  performing --> failed: perform throws, counter fails
  performing --> pending: stop during the counter write

An action is removed from memory when its result is returned to a call (tools.ts:643), or at an expiry tick when it is decided, its open call count is 0 and its expiry time has passed (actions.ts:200-201). A download still performing at the end of wait is returned as performing and kept (tools.ts:640).

5.4 Cards

The server posts a card when a pending action is created, and edits it with apiUpdateChatItem at the outcome (actions.ts:255, control.ts:145-152). For a connect, send or leave without a card (mode host, standing sends) the server posts the outcome as a new message; a download without a card gets none (control.ts:495-497). The text per state is built by cardText; a card of an image or video file is an image or video message with the file's preview (control.ts:441-461); forms are listed in API: cards.

Outcomes are posted after done and failed (actions.ts:282), after rejected and expired (:229, :199), and at stop (:247). They are skipped after the control chat is lost (:247, :282, :291).

5.5 Standing approvals

Aspect Messages Files Code
grant /approve CHAT all from an approver in mode requests; pending sends of CHAT with a card are approved /receive CHAT N all from an approver, on the card of download N; N is performed, and the other pending downloads of CHAT stay pending control.ts:237, :243-254
mode host nothing stored; reply Approvals apply in mode requests; this server runs in mode host. downloads get no card, so the command has none to act on control.ts:213
removal /revoke CHAT: the simplexMcp key is removed, other keys kept /receive CHAT off: the simplexMcpFiles key is removed control.ts:256-263
use mode requests, and approvedById equal to the groupMemberId of a current approver the same control.ts:130-133
scope send download of a file whose servers are all known tools.ts:796, :975
decider shown safeName(approvedBy) the same control.ts:132
storage the chat's custom data; removed with the chat by leave the same tools.ts:950-958

5.6 Expiry

Rule Code
expires = created + expireHours × 3600000, expireHours 1 to 23, default 23 actions.ts:163, cli.ts:59, :82-84
check every 60 s on an unreferenced timer, and before each owner command actions.ts:129, control.ts:201
pending and due: expired, log Action N expired., card edited to Expired. actions.ts:196-199
decided, no open call, due: removed from memory actions.ts:200-201
connect wait for the other side: at most until created + 24 h - 10 min tools.ts:143, :689

Date.now() is compared with expires, so due actions are expired at the first tick after a sleep of the computer.

5.7 Action counter

Rule Code
initial value: nextAction of the control marker, or 1 serve.ts:100
a new action takes next, then next grows by 1 actions.ts:156, :178
the new next is written before the card is posted actions.ts:254-255
one write at a time, also after a failed write control.ts:135-139
--reset-owners copies the value into the new control chat serve.ts:221-222, control.ts:522-527
a refused call leaves the counter unchanged actions.ts:177

6. Security boundaries

6.1 HTTP

Checks in order of the request listener (http.ts:19-28):

Check Rule Failure Code
bind address 127.0.0.1 only http.ts:41
Host hostname localhost, 127.0.0.1 or [::1], any port 403, JSON-RPC error node index.cjs:26-48, server index.cjs:1520-1558
Origin absent or empty passes; otherwise the same hostnames, any port 403, JSON-RPC error node index.cjs:74-96, server index.cjs:1599-1644
token Authorization equal to Bearer <token>, same length, timingSafeEqual 401, empty body http.ts:34-37
path pathname /mcp 404, empty body http.ts:22
start the agent control chat is started 503, Retry-After: 1, empty body http.ts:23
body size 4 MiB 413 node index.cjs:395-401
content type of POST application/json 415 server index.cjs:1464-1467
tool arguments input schema isError: true result mcp-BumlNux0.cjs:1812-1817
tool results output schema isError: true result mcp-BumlNux0.cjs:1822-1829

Node limits: requestTimeout 0, timeout 0, headersTimeout 60 s, keepAliveTimeout 5 s (http.ts:29-32). When the client closes the response stream, the call is aborted: the request signal is aborted in toNodeHandler on close, the per-request transport is closed, and ctx.mcpReq.signal is aborted by the protocol layer (node index.cjs:299-303, server index.cjs:86-90, src-BhnGYybN.cjs:6410-6427). A result produced after the abort is discarded (src-BhnGYybN.cjs:6536).

6.2 Token and headersHelper

  • The token is created once and kept in DATA/token; --new-token replaces it (files.ts:30-36).
  • DATA/headers.json is rewritten at every start (files.ts:42-44).
  • The Claude Code entry gets the command cat <DATA>/headers.json, or type "<DATA>\headers.json" on Windows, with the path quoted for the shell (register.ts:50, log.ts:24-31). Only this command is written to the entry and to the claude mcp add-json arguments (register.test.ts:50-61).
  • token, headers.json and server.json are set to mode 0600 at every write, and the data folder to 0700 at every start, except on Windows (files.ts:12-15, :55-58).

6.3 Peer-written text

Destination Rule Code
card and reply names folded to one line; a valid display name is plain, others a code span safeName
card text lines one code span per line; backticks become '; a leading or trailing space becomes U+00A0; blank lines empty codeLines, codeSpan
file names and paths on cards and posts folded to one line, one code span control.ts:585, :598, :166-168
reply quote on a send card whitespace runs to one space, first 100 characters (code points), one code span control.ts:586
link texts on a connect card code span lines in the bytes that the largest card form leaves; the line that does not fit is cut and ends in … inside its code span; later lines left out control.ts:644-658
mention names valid name: @name or @'name'; others a code span mentionText
errors on cards folded to one line, one code span control.ts:565
--owners part of restart lines in the chat one code span control.ts:79, :782-784
terminal names and errors of actions folded to one line actions.ts:180-181, :333-341, control.ts:78
tool results returned as data; schema descriptions and instructions mark text, from, name, file names, downloaded files, description, welcome and commands with their labels and params as data, not instructions; the instructions take a chat's messages as prompts only when the user said so in the session, and limit what the agent shares about its user tools.ts:184, :188, :193, :218-219, :229-234, :397, :439

The server writes the tappable form /'...' only in the card's command line (control.ts:603-616), in /'revoke CHAT' of standing cards and replies (:563, :253), and in /'delete CHAT files' of the kept-files message (:301).

6.4 Chat references

Rule Code
^[@#][1-9]\d{0,14}$: 1 to 15 digits; the core reads #(2^64 + N) as #N messages.ts:150-152, scenarios.test.ts:491-503
the control chat is refused by send, receive, download, leave and the owner commands with CHAT, and left out of list and /list tools.ts:1116, :1160-1177, control.ts:328-335
a store error of the chat read is reported as No chat CHAT. tools.ts:1121, :1175
the agent's own links are refused by connect (ownLink plans) tools.ts:1279, :1297, :1319

6.5 Owner commands

Rule Code
source: groupRcv text items in the main scope of the control group serve.ts:134, control.ts:173
form: one line /keyword params, leading spaces allowed control.ts:545-550
the help reply is sent to any sender; other commands are accepted from approvers only control.ts:203-204
commands run one at a time in arrival order control.ts:177-180
/approve, /reject, /receive and /forward need a pending action with a card, and the action's chat when CHAT is given; /receive and /forward take downloads only, /approve the other actions only control.ts:227-237

6.6 Files

Rule Code
send refuses files named id_*, *.pem, *.key, *.p12 or *.pfx, and files under ~/.ssh, ~/.gnupg, the data folder and ~/.simplex-mcp except the agent's own DATA/files/, each by its real path; the refusal is posted to the control chat and logged tools.ts:862-876, :1465-1484, control.ts:154-158
the file of a send is sent only when its real path, size, inode and change time at the perform are those of the request tools.ts:878-883
a sent JPEG or PNG is a resized copy made by the core tools.ts:885-896
ffmpeg and ffprobe read a video as MP4 or QuickTime, local files only, without standard input, 30 s and 64 MiB of output each tools.ts:1534-1552
a download is saved only at DATA/files/group-N/ or DATA/files/contact-N/, or for /forward at the same name under DATA/forwarded/, named by its file ID and an extension of 1 to 16 ASCII letters and digits messages.ts:215-223
a forwarded file is outside DATA/files/, so send refuses it; a download moves it into DATA/files/ after the copy for the post is made tools.ts:522-539, :871-876
a file on unknown servers gets a card that names them, also under a files approval; relays are approved only for such a card, and for every other download, and in mode host, the core checks the complete description and downloads a file on unknown servers only when the IP address is protected tools.ts:975, :545, :1030, control.ts:611-614
an accepted file that the core returns to rcvInvitation fails its download at the next check tools.ts:1043
a received file is moved into the chat's folder at its completion, unless it is at the forwarded path, also from a member chat of a group, which has a download in memory as the main conversation does; a file without a download in memory, such as one whose transfer started before a restart, only when it has its full size; a partial file is deleted only for a download in memory, when it is no larger than the file tools.ts:1082-1091, :1486-1492
  • The join link is a group link of the control chat with role member, or observer with --owners; it is printed with a QR code on stdout and deleted at the first join event (control.ts:406-416, :372, :381).
  • At every start the server sets private message routing: smpProxyMode always, smpProxyFallback allowProtected (serve.ts:91-92).
  • With incognito: true the join methods connect incognito (tools.ts:721); the core then uses a random profile for that chat (PLAN, Participants).

7. Limits

Limit Value Code
pending actions 20 actions.ts:11
actions being performed 20 actions.ts:12
expiry 1 to 23 h, default 23 cli.ts:59, :82-84
connect wait for the other side created + 23 h 50 min tools.ts:143
send, connect and download card 15000 bytes JSON-escaped, in its largest form control.ts:26, :622-630
error assumed in the largest card 600 × € control.ts:30
message as the core sends it 15602 bytes, counted with 600 bytes of envelope; send refuses a longer one, and the post of /forward leaves out its quote of the card tools.ts:153-154, :1433-1436, control.ts:265-281
send.text 1 to 12000 characters tools.ts:256
connect.message 1 to 2000 characters tools.ts:244
send.mention 1 to 3 unique member IDs tools.ts:260
wait 0 to 86400 s tools.ts:245, :261, :273, :296
idle 1 to 300 s, default 30 tools.ts:246, :262, :149
limit 1 to 100, default 50 tools.ts:263, :274, :150
connect snapshot last 50 items tools.ts:151
collected items per action or call 100, then their IDs kept as overflow until deleted messages.ts:10, :109-110
unread page 100 items messages.ts:9
chat list read one read of 1000000 chats tools.ts:142
download wait, default 60 s tools.ts:146
download check every 60 s tools.ts:147
download without progress 30 min, then cancelled tools.ts:148
ffmpeg, ffprobe 30 s, 64 MiB of output tools.ts:152, :156
stop wait 0 to 60 s, default 10 cli.ts:64, :85-87
error details 500 characters log.ts:4
progress interval 60 s messages.ts:7
expiry tick 60 s actions.ts:10
HTTP body 4 MiB server index.cjs:254
SSE keep-alive 15 s mcp-BumlNux0.cjs:64
agent NAME 1 to 32 characters cli.ts:150
display name 50 characters, mkValidName rules messages.ts:11-12, :337-359
port 1 to 65535, default 5226 cli.ts:62, :94-96