@pacerelle/mcp-server 0.1.0-alpha.2 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,41 +1,14 @@
1
1
  # Pacerelle MCP Server
2
2
 
3
- MCP server for Pacerelle encrypted local agent relays.
3
+ A local encrypted inbox for an assistant running in an MCP host. Pacerelle
4
+ transports messages; your host remains responsible for starting the assistant,
5
+ running tools, applying permissions, and deciding when its processing loop ends.
6
+ Connecting this server alone does not wake Claude Desktop, Cursor, Codex, or Zed.
4
7
 
5
- Run it from any MCP-compatible client that can spawn a local stdio server, such
6
- as Claude Desktop, Cursor, Zed, or other MCP hosts.
8
+ ## Connect and verify a real reply
7
9
 
8
- ## Install
9
-
10
- ```bash
11
- npx -y @pacerelle/mcp-server
12
- ```
13
-
14
- ## Configuration
15
-
16
- Create an agent in Pacerelle, copy its id and token, then expose them to the MCP
17
- process:
18
-
19
- ```env
20
- PACERELLE_AGENT_ID=agent_...
21
- PACERELLE_AGENT_TOKEN=...
22
- ```
23
-
24
- The published package connects to `https://api.pacerelle.com` by default. For
25
- local development, set:
26
-
27
- ```env
28
- PACERELLE_BASE_URL=http://localhost:8080
29
- ```
30
-
31
- Optional:
32
-
33
- ```env
34
- PACERELLE_WS_URL=ws://localhost:8080
35
- PACERELLE_STORE_ROOT=.pacerelle/mcp
36
- ```
37
-
38
- ## Claude Desktop Example
10
+ Create or resume an agent in Pacerelle. Copy both its ID and authentication token
11
+ into the host's local MCP configuration:
39
12
 
40
13
  ```json
41
14
  {
@@ -52,31 +25,132 @@ PACERELLE_STORE_ROOT=.pacerelle/mcp
52
25
  }
53
26
  ```
54
27
 
55
- ## Tools
28
+ Use the configuration format supported by your host. Then start a conversation
29
+ with its assistant and select the `start_pacerelle` prompt, or ask:
56
30
 
57
- - `send_message`: send an encrypted text reply to the current or provided conversation.
58
- - `send_widget`: send a `widget.standard` v1 payload.
59
- - `get_history`: return recent plaintext messages observed by this MCP process.
31
+ > Start or resume my Pacerelle work. Read get_status and get_checkpoints, then
32
+ > wait_for_messages without a cursor. Handle each authorized request, reply
33
+ > using its sourceMessageId, and mark_processed after handling it. Save a
34
+ > checkpoint before an interruption. Keep listening while this host session can
35
+ > continue; tell me if listening stops or needs my intervention.
60
36
 
61
- ## Resources
37
+ Send a test message from Pacerelle and verify a readable reply before leaving
38
+ your computer. Keep the computer, host, and assistant loop running. Hosts may
39
+ stop at a turn limit, require approval, or not support continuous waiting. For
40
+ unattended execution, use the SDK with your own continuously running agent.
41
+ MCP resource notifications or sampling are not a universal assistant wake-up API.
62
42
 
63
- - `conversation://current`: most recent conversation message seen by this process.
64
- - `widget://pending`: latest widget response observed by this process.
43
+ These instructions describe the source implementation. To test an unreleased
44
+ checkout, build `sdk-js` and `mcp-server`, then configure the host with `node`
45
+ and the absolute path to `mcp-server/dist/cli.js` instead of `npx`.
65
46
 
66
- ## CLI
47
+ ## Tools
67
48
 
68
- Environment variables are recommended, but explicit flags are supported:
49
+ | Tool | Meaning |
50
+ | --- | --- |
51
+ | `get_status` | Relay state, number of pending requests, and uncertain outgoing sends. |
52
+ | `wait_for_messages` | Return pending requests, or wait 0–30 seconds for a matching request. |
53
+ | `send_message` | Submit an encrypted reply; use `sourceMessageId` from the incoming request. |
54
+ | `send_widget` | Submit a `widget.standard` v1 payload to the same source target. |
55
+ | `mark_processed` | Record that a received request has been handled. Reading does not do this. |
56
+ | `get_history` | Recent locally retained incoming requests and outgoing replies. |
57
+ | `save_checkpoint` | Save summary, next action, and optional host session label for a received request. |
58
+ | `get_checkpoints` | Retrieve active saved work; optionally include completed work. |
59
+
60
+ `wait_for_messages` accepts `conversationId`, `afterCursor`, `limit` (1–50), and
61
+ `timeoutMs` (0–30000, default 25000). Its result includes `messages`, `cursor`,
62
+ `has_more`, and `connection`. Canceling the MCP request cancels its wait. Reading,
63
+ waiting, and sending replies never mark an incoming request processed. Use a
64
+ single processing loop for each agent.
65
+
66
+ A cursor skips earlier requests, including unfinished ones. Omit it when
67
+ resuming a session; use it only to paginate a batch that your loop has tracked.
68
+ Pending requests are retained until explicitly processed. The local history
69
+ retains 50 recent processed messages, 50 outgoing replies, and source messages
70
+ needed by retained checkpoints. Duplicate IDs are ignored while retained.
71
+
72
+ Replies using `sourceMessageId` stay attached to the original conversation,
73
+ device, or group even if another message arrives meanwhile. Unknown sources and
74
+ conflicting targets are rejected. Without a source, the conversation target must
75
+ be unambiguous; explicit `conversationId` and `to` remain available for advanced
76
+ use. `conversation://current` is merely the latest observation, not ownership of
77
+ an assistant session. `widget://pending` is the latest unprocessed widget response.
78
+
79
+ ## Resume in another assistant session
80
+
81
+ Before an interruption, the host can call:
69
82
 
70
- ```bash
71
- npx -y @pacerelle/mcp-server \
72
- --agent-id "$PACERELLE_AGENT_ID" \
73
- --token "$PACERELLE_AGENT_TOKEN"
83
+ ```json
84
+ {
85
+ "sourceMessageId": "received-message-id",
86
+ "summary": "Draft complete. Figures checked. Waiting for the user's approval.",
87
+ "nextAction": "Read the approval response before publishing the report.",
88
+ "hostSessionLabel": "Report preparation",
89
+ "status": "active"
90
+ }
91
+ ```
92
+
93
+ The next session calls `get_checkpoints`, `wait_for_messages` without a cursor,
94
+ and `get_history` for the selected conversation. It receives the saved working
95
+ context and remaining requests without asking you to copy a transcript. Update
96
+ the checkpoint with `status: "completed"` when its work ends. Checkpoint updates
97
+ do not automatically mark messages processed, and processing a message does not
98
+ automatically complete a longer task.
99
+
100
+ This preserves context explicitly saved by the host. It does not access private
101
+ Codex, Claude, or Cursor transcripts, and cannot recover unsaved model reasoning.
102
+ The host's permissions and approval rules continue to apply after resumption.
103
+
104
+ ## Local state and connection recovery
105
+
106
+ The default directory is `~/.pacerelle/mcp`; override it with
107
+ `PACERELLE_STORE_ROOT`. Use the same directory when moving between host sessions.
108
+ Each agent and gateway has its own AES-256-GCM encrypted file containing the
109
+ Signal snapshot, inbox, replies, and checkpoints. Writes use a temporary file,
110
+ flush, and atomic replacement. A separate random local key is needed to decrypt
111
+ it. Back up the key and encrypted file together. Do not copy live state between
112
+ two running processes.
113
+
114
+ Directory mode is `0700` and files use `0600` on POSIX. On Windows, filesystem
115
+ protection depends on the account's directory ACLs; use your private user profile
116
+ directory. Encryption with a locally stored key does not protect against another
117
+ process already running as your account. Tokens are not saved by this server.
118
+
119
+ Only one local MCP process can own an agent's state on a gateway. Close its
120
+ Pacerelle server in the old host before opening it in another. Normal process
121
+ crash locks are reclaimed when the recorded PID no longer exists. A crash during
122
+ the brief lock-opening operation can leave an `.opening` gate; the startup error
123
+ identifies it, and it must only be removed after checking no bridge is starting.
124
+ This local lock does not prevent an independent process on another computer
125
+ from using the same agent credentials.
126
+
127
+ The CLI reconnects with increasing delays (up to 30 seconds), creating a fresh
128
+ SDK client with the latest saved Signal state. The old production snapshot is
129
+ migrated into encrypted storage before its plaintext file is removed. A legacy
130
+ snapshot from an unknown gateway requires gateway verification instead of
131
+ silently creating a new identity. A lost key or corrupt state stops startup.
132
+
133
+ An outgoing `submitted` result means SDK submission, not verified execution or
134
+ recipient display. If a send or interruption leaves the outcome uncertain,
135
+ `get_status.uncertain_sends` and history expose it. Check the actual outcome
136
+ before repeating consequential work. The bridge does not guarantee exactly-once
137
+ external actions or atomicity between Signal decryption and inbox persistence.
138
+
139
+ ## Local development
140
+
141
+ ```env
142
+ PACERELLE_AGENT_ID=agent_...
143
+ PACERELLE_AGENT_TOKEN=...
144
+ PACERELLE_BASE_URL=http://localhost:8080
145
+ PACERELLE_STORE_ROOT=/absolute/private/directory
74
146
  ```
75
147
 
76
- Local development:
148
+ `PACERELLE_WS_URL` optionally overrides the websocket URL. Corresponding CLI
149
+ flags are `--agent-id`, `--token`, `--base-url`, `--ws-url`, and `--store-root`.
150
+ The default gateway is `https://api.pacerelle.com`. Keep E2EE enabled;
151
+ `--no-e2ee` exists for local transport debugging only.
77
152
 
78
153
  ```bash
79
- npx -y @pacerelle/mcp-server \
80
- --base-url "http://localhost:8080" \
81
- --ws-url "ws://localhost:8080"
154
+ npm run build --workspace sdk-js
155
+ npm test --workspace mcp-server
82
156
  ```
package/dist/bridge.d.ts CHANGED
@@ -3,36 +3,150 @@ export interface MessageSender {
3
3
  sendMessage(options: {
4
4
  conversationId: string;
5
5
  to: string;
6
+ toDeviceId?: string;
6
7
  text: string;
7
8
  replyToMessageId?: string;
8
9
  }): Promise<void>;
9
10
  }
11
+ export interface InboxMessage {
12
+ id: string;
13
+ direction: "incoming";
14
+ sequence: number;
15
+ conversation_id: string;
16
+ from: string;
17
+ from_device_id?: string;
18
+ reply_target: string;
19
+ text: string;
20
+ encrypted: boolean;
21
+ reply_to_message_id: string | null;
22
+ attachments: AgentMessage["attachments"];
23
+ widget_response: AgentMessage["widgetResponse"] | null;
24
+ widget_update: AgentMessage["widgetUpdate"] | null;
25
+ recorded_at: string;
26
+ processed_at: string | null;
27
+ processing_note?: string;
28
+ }
29
+ interface ReplyRecord {
30
+ local_id: string;
31
+ direction: "outgoing";
32
+ conversation_id: string;
33
+ to: string;
34
+ text: string;
35
+ source_message_id?: string;
36
+ recorded_at: string;
37
+ delivery_status: "sending" | "submitted" | "unknown";
38
+ }
39
+ export interface BridgeState {
40
+ version: 1;
41
+ epoch: string;
42
+ nextSequence: number;
43
+ inbox: InboxMessage[];
44
+ replies: ReplyRecord[];
45
+ checkpoints: WorkCheckpoint[];
46
+ }
47
+ export interface WorkCheckpoint {
48
+ source_message_id: string;
49
+ conversation_id: string;
50
+ summary: string;
51
+ next_action: string;
52
+ host_session_label?: string;
53
+ status: "active" | "completed";
54
+ updated_at: string;
55
+ }
56
+ interface ReplyTarget {
57
+ sourceMessageId?: string;
58
+ conversationId?: string;
59
+ to?: string;
60
+ }
61
+ export interface WaitOptions {
62
+ conversationId?: string;
63
+ afterCursor?: string;
64
+ limit?: number;
65
+ timeoutMs?: number;
66
+ signal?: AbortSignal;
67
+ }
68
+ export type ConnectionState = "connecting" | "connected" | "reconnecting" | "disconnected" | "stopped";
69
+ /** Reading/waiting never acknowledges application work. Persistence precedes visibility. */
10
70
  export declare class PacerelleMcpBridge {
11
71
  private readonly client;
12
72
  private readonly historyLimit;
13
- private readonly messages;
14
- private current;
15
- private pendingWidget;
16
- constructor(client: MessageSender, historyLimit?: number);
73
+ private readonly options;
74
+ private state;
75
+ private connection;
76
+ private readonly listeners;
77
+ constructor(client: MessageSender, historyLimit?: number, options?: {
78
+ state?: BridgeState;
79
+ onStateChange?: (state: BridgeState) => void;
80
+ });
17
81
  recordMessage(message: AgentMessage): void;
18
- sendMessage(args: {
19
- text: string;
82
+ setConnectionState(state: ConnectionState): void;
83
+ getStatus(): {
84
+ connection: ConnectionState;
85
+ pending_count: number;
86
+ cursor: string;
87
+ host_action: string;
88
+ uncertain_sends: ReplyRecord[];
89
+ };
90
+ waitForMessages(args?: WaitOptions): Promise<{
91
+ status: string;
92
+ messages: InboxMessage[];
93
+ cursor: string;
94
+ has_more: boolean;
95
+ connection: ConnectionState;
96
+ }>;
97
+ markProcessed(args: {
98
+ messageId: string;
99
+ note?: string;
100
+ }): {
101
+ ok: boolean;
102
+ message_id: string;
103
+ pending_count: number;
104
+ };
105
+ saveCheckpoint(args: {
106
+ sourceMessageId: string;
107
+ summary: string;
108
+ nextAction: string;
109
+ hostSessionLabel?: string;
110
+ status?: "active" | "completed";
111
+ }): WorkCheckpoint;
112
+ getCheckpoints(args?: {
20
113
  conversationId?: string;
21
- to?: string;
114
+ includeCompleted?: boolean;
115
+ }): WorkCheckpoint[];
116
+ sendMessage(args: ReplyTarget & {
117
+ text: string;
22
118
  replyToMessageId?: string;
23
- }): Promise<Record<string, unknown>>;
24
- sendWidget(args: {
119
+ }): Promise<{
120
+ ok: boolean;
121
+ conversation_id: string;
122
+ to: string;
123
+ delivery_status: string;
124
+ }>;
125
+ sendWidget(args: ReplyTarget & {
25
126
  widgetJson: string | Record<string, unknown>;
26
- conversationId?: string;
27
- to?: string;
28
- }): Promise<Record<string, unknown>>;
127
+ }): Promise<{
128
+ ok: boolean;
129
+ widget_id: unknown;
130
+ kind: unknown;
131
+ conversation_id: string;
132
+ to: string;
133
+ delivery_status: string;
134
+ }>;
29
135
  getHistory(args: {
30
136
  conversationId?: string;
31
137
  limit?: number;
32
- }): Array<Record<string, unknown>>;
33
- currentConversation(): Record<string, unknown>;
138
+ }): (InboxMessage | ReplyRecord)[];
139
+ currentConversation(): InboxMessage | {
140
+ status: string;
141
+ };
34
142
  pendingWidgetResponse(): Record<string, unknown>;
35
143
  attach(client: AgentGatewayClient): void;
36
- private requireCurrent;
144
+ private commit;
145
+ private notify;
146
+ private cursor;
147
+ private parseCursor;
148
+ private resolveTarget;
149
+ private submitReply;
37
150
  private parseWidget;
38
151
  }
152
+ export {};