* packages: simplex-chat MCP server * update * standing approvals per chat, leave tool, simplify * fixes * add error messages * LICENSE * specification * migration * update simplex-chat, tests, fixes * update simplex-chat libraries * use agent peer type * better server and tool descriptions * short readme * reference * images * update readme * update readme * update * change links to master * fix links to point to master branch --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
42 KiB
SimpleX MCP server: architecture
System structure, modules, state, process lifecycle, approval model and security boundaries of
@simplex-chat/mcp0.1.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
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.12"]
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 --> lib
control --> lib
serve --> lib
lib --> core
core --> db[("DATA:<br/>simplex_chat.db<br/>simplex_agent.db")]
core <-->|"SMP<br/>private routing"| smp["SMP 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:
- Claude Code runs the
headersHelpercommand of its entry (register.ts:47) and sends theAuthorizationheader it prints (Reference). - The server checks Host, Origin, token and path, then passes the request to the SDK handler (
http.ts:18-26). - An
McpServeris built from the factory for each request, and the arguments are validated against the input schema by the SDK (serve.ts:108-112,mcp-BumlNux0.cjs:1777-1791). - The handler result is returned by
AgentTools.callasstructuredContentand as JSON text (tools.ts:367-374). - The server sends chat commands through
ChatApito the core (messages.ts:169-173); actions are created inActionsand shown to the owner byControl(actions.ts:119-146). - The core emits events; the server dispatches them in one handler of
serve.tsto the item watch, the connect actions and the agent control chat (serve.ts:123-144).
2. Modules
2.1 cli.ts
Entry point of the simplex-mcp binary.
| Symbol | Line | Contents |
|---|---|---|
helpText |
10 | --help output |
Command |
37 | help, serve with ServeOptions, register with RegisterOptions |
parseCommand |
42 | command, NAME and options to a Command |
serveOptions |
51 | defaults and option switch of serve |
registerOptions |
103 | defaults and option switch of register |
OptionReader |
120 | value: the next argument, requiredoptionalValue: the next argument, absent when missing or starting with -- |
validAgentName |
142 | ^[a-z0-9](-?[a-z0-9])*-?$, at most 32 characters |
usageError |
187 | ExitError with code 2 |
main |
191 | help to stdout, serve, register |
| top-level catch | 204-213 | 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 |
|---|---|---|
ServeOptions |
26 | name, data folder and its dataGiven flag, display name, mode, owners, expiry, reset and token flags, port, QR code size |
OwnerEntry |
21 | {memberId, name} of --owners |
RunningServer |
40 | token, ChatApi, user, Actions, stop |
serve |
48 | startServer, then SIGINT and SIGTERM handlers with one shared stop promise |
startServer |
64 | data folder, lock, token, headers file, ChatApi.init, startAgent; cleanup on error |
startAgent |
87 | user, network config, control group, wiring of Actions, AgentTools, Control, HTTP, event handler, stop |
registerHint |
164 | the register command line printed after the owner setup |
deliveries |
168 | wait for pending deliveries of the control chat at stop |
controlGroup |
194 | found, created or reset agent control chat |
listen |
201 | startHttp; EADDRINUSE to ExitError code 1 |
bufferEvents |
210 | 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 |
startHttp |
13 | http.Server on 127.0.0.1:port: guards, SDK handler, timeouts |
authorized |
32 | length check and timingSafeEqual against Bearer <token> |
stopHttp |
46 | server.close and closeAllConnections |
2.4 tools.ts
Tool definitions, call handlers, connect plans and perform steps.
| Symbol | Line | Contents |
|---|---|---|
ConnectInput, SendInput, ReceiveInput, LeaveInput |
31-57 | tool inputs |
PlanSummary |
63 | card kind, chat type, name, SimpleX name, peer type, description, welcome, commands, takesMessage |
ChatFields |
75 | chat fields of connect and list results |
Agent |
89 | ChatApi, user, Actions, ChatItemsWatch, control chat reference, standing approver lookup |
JoinMethod |
98 | prepared, prepareChannel, viaAddress, prepareContact, connect |
| schemas | 119-299 | input and output JSON schemas |
descriptions, instructions |
301-332 | tool descriptions and server instructions |
definitions |
342 | name, description, schemas and action flag of the five tools |
AgentTools.register |
355 | registerTool for each definition; _meta in mode host |
AgentTools.call |
367 | handler result to CallToolResult; errors to isError |
AgentTools.perform |
376 | perform step of an approved action |
onContactConnected, onUserJoinedGroup |
387-395 | completion of connect actions by events |
actionResult |
431 | wait for the decision, result fields, removal |
agentChats, knownChat |
698-721 | the agent's chats; a checked chat reference |
classifyPlan |
811 | connectionPlan response to a join plan or a known chat |
chatSubject, isConnected, isActive, memberCurrent |
911-964 | chat and member predicates shared with control.ts |
toolCall |
1011 | 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 |
ToolError |
16 | errors the model can correct; text returned as is |
Subject |
18 | kind, name, SimpleX name, incognito flag of a card |
ConnRef |
25 | pending connection, contact or group of a connect action |
ActionRecord |
31 | number, key, times, change signal, standing flag, state, card, decider, open calls, interrupted flag, error |
ConnectAction, SendAction, LeaveAction |
47-79 | per-tool fields |
ActionHooks |
85 | refusal, saveCounter, postCard, perform, outcome |
Actions |
93 | action map, expiry timer, posting and outcome promises |
create |
119 | refusal checks, number, log line, counter write, card |
decided |
166 | wait with progress until a decided state or the abort signal |
stop |
200 | cancellation of pending and performing actions |
summary, performedText, progressText |
268-304 | log and progress texts |
Hooks are bound in serve.ts:97-103: refusal, saveCounter, postCard and outcome to Control; perform to AgentTools.
2.6 control.ts
| Symbol | Line | Contents |
|---|---|---|
ControlData |
31 | {control: true, nextAction} |
ChatApproval |
48 | {approvedBy, approvedById, approvedAt} |
OwnerRequest |
54 | parsed owner command |
Control.load |
88 | lost check, group profile and menu, member list |
Control.start |
97 | owner setup per --owners |
Control.refusal |
105 | card size of a send, approver presence |
Control.standingApprover |
113 | approver name of a chat's standing approval |
Control.saveCounter |
118 | queued counter writes |
Control.postCard, Control.outcome |
124-132 | card post and card edit |
Control.onMessage |
134 | owner commands, queued one at a time |
Control.onEvent |
146 | membership changes and loss of the control chat |
Control.ownerCommand |
163 | command dispatch and replies |
findControlGroup, createControlChat, resetControlChat |
364-392 | control group lookup, creation, reset |
isControlEvent |
394 | membership events of the control group |
parseCommand |
410 | one-line /keyword params |
cardText |
417 | card text per state and mode |
largestCard, encodedLength |
476-484 | card size check |
codeSpan, safeName, mentionText |
494-506 | quoting of peer text |
ownerRequest |
521 | command words to an OwnerRequest |
qrCodeText |
532 | terminal QR code |
losesControl, isLost |
548-563 | loss of the control chat |
entryText, restartText |
581-591 | --owners entries in restart lines |
2.7 messages.ts
| Symbol | Line | Contents |
|---|---|---|
Message |
11 | message item of tool results |
ToolCall |
29 | abort signal and progress callback of one call |
Collected |
34 | collected items, change signal, overflow count, time of the last change |
Changes |
41 | set of wake callbacks |
ChatItemsWatch |
58 | listeners per chat reference; received message items gathered by collect |
chatRef, isChatRef |
104-117 | @N and #N references |
toMessage |
135 | chat item to Message |
chatCommand, getChat, markRead |
169-181 | raw chat commands with response type checks |
unreadItems |
183 | unread items of a chat, paged |
reportProgress, nextChange, awaitResponses |
200-230 | waits |
validName, viewName |
236-262 | display name rules and @name form |
fullDomainName |
264-267 | SimplexDomain to name.simplex or name.testing |
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 |
30 | claude mcp remove, then claude mcp add-json |
registryEntry |
43 | {type, url, headersHelper, timeout} |
manualEntryText |
52 | entry text when claude is missing |
projectRoot |
57 | project key of --scope local |
runCommand |
83 | spawnSync with ENOENT detection |
2.10 log.ts
| Symbol | Line | Contents |
|---|---|---|
ExitError |
12 | message and exit code |
shellWord, shellPath |
18-25 | POSIX single quotes or Windows double quotes |
oneLine |
27 | runs of \p{Cc}, \p{Cf}, \p{Zl}, \p{Zp} to one space |
log, logError |
31-37 | stdout lines; stderr lines |
errorText, chatErrorText |
39-47 | error text, folded to one line: the apps' wording for known error shapes, otherwise the type chain |
appErrorText |
49-58 | wording of the apps' alerts for chat, agent and store errors |
chainText, errorChain |
193-205 | SimpleX error: <type chain>: <scalar fields>, details at most 500 characters |
3. State
3.1 Memory
| Item | Holder | Written by | Read by |
|---|---|---|---|
| Actions | Actions.actions map (actions.ts:96) |
create, remove, expireDue |
tools, Control |
| Open calls of an action | ActionRecord.open (:41) |
actionResult (tools.ts:432, :440) |
list, expireDue |
| Collected replies | ConnectAction.messages, SendAction.responses (:52, :69) |
ChatItemsWatch.collect |
connect, send results |
| Item listeners | ChatItemsWatch.listeners (messages.ts:59) |
collect |
event dispatch |
| Members of the control chat | Control.members (control.ts:68) |
refreshMembers (:332) |
approver checks |
| Mode, expiry, owners list | 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 | contact or group of the agent | {...other keys, simplexMcp: {approvedBy, approvedById, approvedAt}} |
approveChat, revoke |
standingApprover |
The profile, chats, items and members are stored by the core in DATA/simplex_chat.db; connections and queues in DATA/simplex_agent.db (file prefix DATA/simplex, serve.ts:71).
3.3 Files
| Path | Contents | Mode | Written | Read |
|---|---|---|---|---|
DATA/ |
data folder | 0700, set at every start except on Windows | files.ts:12 | |
DATA/token |
base64url of 32 random bytes | 0600 at creation | first start, --new-token (:30) |
every start |
DATA/headers.json |
{"Authorization":"Bearer <token>"} |
0600 at creation | every start (:42) | Claude Code headersHelper |
DATA/server.json |
{name, port, expireHours} |
0600 at creation | every start, after the user exists (serve.ts:90) | register |
DATA/lock |
PID and a newline | 0600 | start (files.ts:57) | next start |
The core library is downloaded once into CACHE/core-7.1.0.12/sqlite, and the Node.js add-on into CACHE/<add-on release>/, 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:39-46, :87, :107, :114, :127).
4. Process lifecycle
4.1 Start
- Data folder, lock, token and headers file are prepared (serve.ts:66-70).
- libsimplex for SQLite is loaded by
ChatApi.init, and the databases are migrated with confirmationyesUp(serve.ts:71, api.ts:114-123). - Three log lines: version and NAME, data folder, mode (serve.ts:72-74).
- The active user is read, or created with display name
--display-nameor NAME, empty full name and peer typeagent(serve.ts:89). server.jsonis written (:90).- The network config is set:
/network socks=off smp-proxy=always smp-proxy-fallback=protected(:91). - Events are buffered, and the chat is started (:92-93).
- A
--display-namedifferent from the profile, or a peer type other thanagent, is applied with oneapiUpdateProfile(:94, :187-192). - The agent control chat is found by its marker, created with counter 1, or reset with
--reset-owners(:96, :194-199). Actions,ChatItemsWatch,AgentToolsandControlare created; the counter is taken from the marker (:97-106).Control.load: for a lost control chat,Actions.lostis set; otherwise the group name and command menu are updated when they differ, and members are read (control.ts:88-95).- The HTTP server listens; with the port in use, the process exits with code 1 (serve.ts:113, :201-208).
- The server runs the owner setup,
Control.start; after an error, HTTP is closed and the error is rethrown (serve.ts:114-119). Listening on http://127.0.0.1:PORT/mcp(:120).- Buffered events are replayed, then live events are dispatched (:123, :210-224).
After an error inside startAgent (steps 4-15) the chat is closed; after any error once the lock is taken, the lock is released (serve.ts:68-84).
4.2 Event dispatch
Each onAny receiver is awaited by the library before the next event is read (api.ts:192-198). The dispatcher in serve.ts:123-144:
| Event | Condition | Handler |
|---|---|---|
newChatItems |
item of the control group, main scope | Control.onMessage |
newChatItems |
every item | ChatItemsWatch.deliver |
chatItemUpdated |
ChatItemsWatch.update |
|
chatItemsDeleted |
ChatItemsWatch.remove of each deletedChatItem |
|
contactConnected |
AgentTools.onContactConnected |
|
userJoinedGroup |
AgentTools.onUserJoinedGroup |
|
joinedGroupMemberjoinedGroupMemberConnectingconnectedToGroupMembermemberRoleleftMemberdeletedMemberdeletedMemberUsergroupDeleted |
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:140-143).
4.3 Stop
On SIGINT or SIGTERM the server runs one stop, shared by all later signals (serve.ts:50-61). RunningServer.stop (serve.ts:150-160):
- Events are ignored from now on.
Actions.stopstarts: new actions are refused withThe server is stopping.; the expiry timer is cleared; cards being posted are awaited; pending and performing actions are marked interrupted, and their cards are edited, or posted when they have no card; outcome edits already in progress are awaited (actions.ts:200-209).- In parallel with step 2, the HTTP server is closed with all connections, and open calls are aborted (http.ts:46-50).
Cancelled K action(s).when K > 0 (serve.ts:155).- Pending deliveries of the control chat's member connections are polled with
/sql chatand/sql agentevery 100 ms, for at most 10 s (serve.ts:168-176). chat.close():/_stop, then the store is closed (api.ts:162-167).- The lock is removed,
Stopped.is logged, and the process exits with code 0, or 1 after an error (serve.ts:158-159, :52-58).
Performs in progress are not awaited; when one ends, its card stays as edited at the stop, because the action is interrupted (actions.ts:239).
4.4 The lock
acquireLock(DATA): files.ts:17
create DATA/lock exclusively (flag wx), PID :57
exists:
PID = number in the file
PID valid, other than ours, running: :21, :70-77
exit 1 "Another server uses DATA (PID N)."
remove the file; create it exclusively again :22-23
exists again: exit 1 with the same message
releaseLock(DATA): remove DATA/lock :26
isRunning: true when kill(pid, 0) succeeds or fails with EPERM (files.ts:70-77).
5. Approval model
5.1 Modes
The mode is set by --approve for one run (cli.ts:72-74).
| Mode | State of a new action | Card | _meta of connect, send, leave |
|---|---|---|---|
requests |
pendingperforming for a send under a standing approval |
card with commands outcome card after a standing send |
none |
host |
performing |
outcome card after the perform | {"anthropic/requiresUserInteraction": true} |
Sources: actions.ts:130, tools.ts:361. In both modes a new action is refused unless the control chat has an approver (control.ts:107-110).
5.2 Owners and approvers
| Term | Rule | Code |
|---|---|---|
| human | member other than the agent's membership, status connected or complete |
control.ts:353-355, tools.ts:949-951 |
| human owner | human with role owner |
control.ts:340-342 |
approver, --owners absent |
member with role owner |
control.ts:357-361 |
approver, --owners without a list |
none | control.ts:360 |
approver, --owners ID:NAME,... |
member with role owner whose groupMemberId and displayName match an entry |
control.ts:360, :565-567 |
The command sender's member record from the chat item is checked (control.ts:139, :167). The approver count for refusals and standing approvals is taken from Control.members, refreshed at load and at each membership event (control.ts:344-346).
Owner setup at start (control.ts:97-103):
--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 observerhumans 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 missingother humans removed link deleted Owners: ... and register hint |
Sources: startOwners, startObserver, startListed, ownerJoined, observerJoined.
Membership changes at run time (control.ts:146-161):
| 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:130 | |
call creates the action, mode host |
performing |
actions.ts:130 | |
send call creates the action under a standing approval |
performing |
actions.ts:130 | |
| counter write or card post fails | pendingperforming |
failed |
actions.ts:211-221 |
stop arrives while the counter of a performing action is written |
performing |
pending, interrupted |
actions.ts:226 |
| control chat lost | pending |
failed |
actions.ts:192-198 |
/reject |
pending |
rejected |
actions.ts:185-190 |
| expiry time passes | pending |
expired |
actions.ts:156-159 |
/approve N, /approve CHAT N, /approve CHAT all |
pending |
performing |
actions.ts:179-183 |
| perform throws | performing |
failed |
actions.ts:234-237 |
| perform ends | performing |
done |
actions.ts:232-233 |
create (actions.ts:119-146):
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
send 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()
after post:
posted and performing:
stopping: state = pending
otherwise: perform
An action is removed from memory when its result is returned to a call (tools.ts:437), or at an expiry tick when it is decided, its open call count is 0 and its expiry time has passed (actions.ts:160-161).
5.4 Cards
The server posts a card when a pending action is created, and edits it with /_update item at the outcome (control.ts:124-132). For an action without a card (mode host, standing sends) the server posts the outcome as a new message. The text per state is built by cardText; forms are listed in API: cards.
Outcomes are posted after done and failed (actions.ts:239), after rejected and expired (:189, :159), and at stop (:206). They are skipped after the control chat is lost (:206, :239, :248).
5.5 Standing approvals
| Aspect | Rule | Code |
|---|---|---|
| grant | /approve CHAT all from an approver: custom data of CHAT gets simplexMcp: {approvedBy, approvedById, approvedAt}, other keys kept; pending sends of CHAT with a card are approved |
control.ts:192-203 |
| removal | /revoke CHAT: the simplexMcp key is removed, other keys kept |
control.ts:205-211 |
| use | mode requests, and approvedById equal to the groupMemberId of a current approver |
control.ts:113-116 |
| scope | send only |
tools.ts:586 |
| decider shown | safeName(approvedBy) |
control.ts:115 |
| storage | chat database; removed with the chat by leave |
tools.ts:646-651 |
5.6 Expiry
| Rule | Code |
|---|---|
expires = created + expireHours × 3600000, expireHours 1 to 23, default 23 |
actions.ts:127, cli.ts:79 |
| check every 60 s on an unreferenced timer, and before each owner command | actions.ts:97, control.ts:164 |
pending and due: expired, log Action N expired., card edited to Expired. |
actions.ts:156-159 |
| decided, no open call, due: removed from memory | actions.ts:160-161 |
connect wait for the other side: at most until created + 24 h - 10 min |
tools.ts:112, :483 |
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:97 |
a new action takes next, then next grows by 1 |
actions.ts:120, :138 |
the new next is written before the card is posted |
actions.ts:213-214 |
| one write at a time, also after a failed write | control.ts:118-122 |
--reset-owners copies the value into the new control chat |
serve.ts:197-198, control.ts:387-392 |
| a refused call leaves the counter unchanged | actions.ts:137 |
6. Security boundaries
6.1 HTTP
Checks in order of the request listener (http.ts:18-26):
| Check | Rule | Failure | Code |
|---|---|---|---|
| bind address | 127.0.0.1 only |
http.ts:39 | |
| 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:32-35 |
| path | pathname /mcp |
404, empty body | http.ts:21 |
| 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, Ajv | isError: true result |
mcp-BumlNux0.cjs:1812-1817 |
| tool results | output schema, Ajv | isError: true result |
mcp-BumlNux0.cjs:1822-1829 |
Node limits: requestTimeout 0, timeout 0, headersTimeout 60 s, keepAliveTimeout 5 s (http.ts:27-30). 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-tokenreplaces it (files.ts:30-36). DATA/headers.jsonis rewritten at every start (files.ts:42-44).- The Claude Code entry gets the command
cat <DATA>/headers.json, ortype "<DATA>\headers.json"on Windows, with the path quoted for the shell (register.ts:47, log.ts:18-25). Only this command is written to the entry and to theclaude mcp add-jsonarguments (register.test.ts:56). - File modes 0600 apply when a file is created; the data folder is set to 0700 at every start, except on Windows (files.ts:12-15).
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 |
| reply quote on a send card | whitespace runs to one space, first 100 UTF-16 units, one code span | control.ts:450 |
| mention names | valid name: @name or @'name'; others a code span |
mentionText |
| errors on cards | folded to one line, one code span | control.ts:430 |
--owners part of restart lines in the chat |
one code span | control.ts:65, :589-591 |
| terminal names and errors of actions | folded to one line | actions.ts:141, :285-287, control.ts:64 |
| tool results | returned as data; schema descriptions and instructions mark text, from, name, 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:125-129, :146-147, :157-161, :330 |
The server writes the tappable form /'...' only in the card's command line, /'approve ...' and /'reject ...' (control.ts:461-470), and in /'revoke CHAT' of standing cards and replies (:428, :202).
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:115-117, scenarios.test.ts:320-332 |
the control chat is refused by send, receive, leave and /approve CHAT all, and left out of list and /list |
tools.ts:655, :708-711, :715, control.ts:223-230 |
a store error of the chat read is reported as No chat CHAT. |
tools.ts:660, :719 |
the agent's own links are refused by connect (ownLink plans) |
tools.ts:830, :848, :870 |
6.5 Owner commands
| Rule | Code |
|---|---|
source: groupRcv text items in the main scope of the control group |
serve.ts:128, control.ts:136 |
form: one line /keyword params, leading spaces allowed |
control.ts:410-415 |
| the help reply is sent to any sender; other commands are accepted from approvers only | control.ts:165-167 |
| commands run one at a time in arrival order | control.ts:140-143 |
/approve and /reject need a pending action with a card, and the action's chat when CHAT is given |
control.ts:181-183 |
6.6 Link and network
- The join link is a group link of the control chat with role
member, orobserverwith--owners; it is printed with a QR code on stdout and deleted at the first join event (control.ts:301-311, :267, :276). - At every start the server sets private message routing:
smp-proxy=always,smp-proxy-fallback=protected,socks=off(serve.ts:91). - With
incognito: truethe server sendsincognito=onwith the connect command (tools.ts:516); the core then uses a random profile for that chat (PLAN, Participants).
7. Limits
| Limit | Value | Code |
|---|---|---|
| pending actions | 20 | actions.ts:10 |
| expiry | 1 to 23 h, default 23 | cli.ts:79 |
| connect wait for the other side | created + 23 h 50 min |
tools.ts:112 |
| send card | 15000 bytes JSON-escaped, in its largest form | control.ts:17, :476-484 |
| error assumed in the largest card | 600 × € |
control.ts:18 |
send.text |
1 to 12000 characters | tools.ts:184 |
connect.message |
1 to 2000 characters | tools.ts:172 |
send.mention |
1 to 3 unique member IDs | tools.ts:186 |
wait |
0 to 86400 s | tools.ts:173, :187, :199 |
idle |
1 to 300 s, default 30 | tools.ts:174, :188, :115 |
limit |
1 to 100, default 50 | tools.ts:189, :200, :116 |
connect snapshot |
last 50 items | tools.ts:117 |
| collected items per action or call | 100, then counted as overflow | messages.ts:7, :78-79 |
| unread page | 100 items | messages.ts:6 |
| chat list page | 100 chats | tools.ts:111 |
| error details | 500 characters | log.ts:5 |
| progress interval | 60 s | messages.ts:4 |
| expiry tick | 60 s | actions.ts:9 |
| delivery wait at stop | 10 s, polled every 100 ms | serve.ts:16-17 |
| 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:143 |
| display name | 50 characters, mkValidName rules |
messages.ts:8-9, :236-258 |
| port | 1 to 65535, default 5226 | cli.ts:88, :59 |