@pharaoh-so/mcp 0.3.51 → 0.3.52
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/.mcp.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +2 -1
- package/dist/backend.d.ts +31 -0
- package/dist/backend.js +49 -1
- package/dist/helpers.js +1 -1
- package/dist/index.js +5 -0
- package/dist/install-skills.d.ts +3 -3
- package/dist/install-skills.js +34 -4
- package/dist/proxy-server.d.ts +1 -0
- package/dist/proxy-server.js +19 -2
- package/package.json +1 -1
- package/commands/sessions.md +0 -101
- package/skills/parallel/SKILL.md +0 -75
- package/skills/sessions/SKILL.md +0 -102
package/.mcp.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to `@pharaoh-so/mcp` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.52] - 2026-10-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- The proxy tells Pharaoh which client it serves (its `initialize` name and version) and whether Pharaoh's Claude Code plugin is enabled in `~/.claude/settings.json`, once per upstream session. Pharaoh stores it with that session, to compare adoption with and without the plugin. Nothing else is sent.
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
- The `sessions` and `parallel` skills, the `/sessions` slash command and their MCP prompts (`session-decomposition`, `parallel-agents`). Claude Code and Codex now run subagents and parallel sessions on their own.
|
|
15
|
+
- The installer deletes the `~/.claude/commands/sessions.md` it wrote, only while it carries the `# managed-by: pharaoh-mcp` marker. A file of your own with that name, or a symlink, is kept.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- `npx @pharaoh-so/mcp --version` (or `-v`) prints the version and exits, as README § Custom Server says. It used to start setup: in a terminal it deleted saved credentials and signed in again against mcp.pharaoh.so; on a pipe it started the proxy.
|
|
19
|
+
|
|
8
20
|
## [0.3.51] - 2026-10-23
|
|
9
21
|
|
|
10
22
|
### Added
|
package/README.md
CHANGED
|
@@ -166,9 +166,10 @@ claude mcp add --scope user pharaoh -- npx @pharaoh-so/mcp@<version> --server ht
|
|
|
166
166
|
| `~/.pharaoh/` | `0700`, set again on every start | Always | Credentials and the Claude Code plugin marketplace |
|
|
167
167
|
| `~/.pharaoh/credentials.json` | `0600` | After sign-in | Access token, refresh token, expiry |
|
|
168
168
|
| `~/.pharaoh/claude-marketplace/` | — | `~/.claude/` exists | The one-plugin marketplace Claude Code installs as `pharaoh@pharaoh`: the skills and the Grep routing hook. Rewritten when the package version changes |
|
|
169
|
-
| `~/.claude/commands/plan.md`, `review.md
|
|
169
|
+
| `~/.claude/commands/plan.md`, `review.md` | — | `~/.claude/` exists | Slash commands. A file is written only when it is absent or is Pharaoh's: it carries the front-matter comment `# managed-by: pharaoh-mcp`, or has the exact header and `$ARGUMENTS` footer of a Pharaoh command from 0.3.46 or earlier. Any other file with that name is yours: it is kept, a symlink is never followed, and the setup output names it. Rename your file to keep both. 0.3.51 and earlier also wrote `sessions.md`; later versions delete it while it carries the marker |
|
|
170
170
|
| `~/.claude.json` | — | Interactive setup | Removes earlier `pharaoh` entries, then `claude mcp add --scope user pharaoh -- npx @pharaoh-so/mcp@<version>` |
|
|
171
171
|
| `~/.claude/settings.json` | — | Interactive setup, and only if the file already exists | Adds to `permissions.allow`, each by full name: `mcp__pharaoh__get_codebase_map`, `mcp__pharaoh__get_module_context`, `mcp__pharaoh__search_functions`, `mcp__pharaoh__get_blast_radius`, `mcp__pharaoh__get_unused_code`, `mcp__pharaoh__pharaoh_recon`, `mcp__pharaoh__pharaoh_account`. The setup prints a line saying what it did. The proxy never creates this file and never touches it on a start |
|
|
172
|
+
| `~/.agents/skills/pharaoh-review/` | — | `~/.codex/` exists | The review skill for Codex (`$pharaoh-review`), rewritten on every start. Other tools share `~/.agents/skills/`, so nothing else there is touched |
|
|
172
173
|
| `~/.openclaw/skills/`, `~/.openclaw/openclaw.json` | — | `~/.openclaw/` exists | Skills prefixed `pharaoh-`, and an `mcpServers.pharaoh` entry running `npx @pharaoh-so/mcp@<version>`. An entry that is not an `npx @pharaoh-so/mcp` command is left alone |
|
|
173
174
|
|
|
174
175
|
Versions 0.3.46 and earlier wrote one rule, `mcp__pharaoh`, on every proxy start. That prefix auto-approved every Pharaoh tool, including account deletion and `pharaoh_admin`, so a prompt injection could run them without a human prompt. The next interactive setup replaces that rule with the list above; account deletion, `pharaoh_admin` and any tool not listed prompt before they run. To take the blanket rule out by hand, delete `mcp__pharaoh` from `permissions.allow`.
|
package/dist/backend.d.ts
CHANGED
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { CallToolResult, ClientCapabilities, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
19
19
|
import type { DeviceFlowGateway } from "./gateway.js";
|
|
20
|
+
/**
|
|
21
|
+
* Notification that tells Pharaoh which client the proxy serves. Pharaoh sees
|
|
22
|
+
* the proxy itself as `pharaoh-proxy`; this lands on the session's row.
|
|
23
|
+
* Mirrors PROXY_CLIENT_NOTIFICATION in src/mcp/server.ts.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CLIENT_NOTIFICATION = "notifications/pharaoh/client";
|
|
26
|
+
/** What the proxy reports about the client behind it. */
|
|
27
|
+
export type DownstreamClient = {
|
|
28
|
+
/** The client's own `initialize` clientInfo (e.g. claude-code 2.1.3). */
|
|
29
|
+
client: {
|
|
30
|
+
name?: string;
|
|
31
|
+
version?: string;
|
|
32
|
+
};
|
|
33
|
+
/** Pharaoh's Claude Code plugin as ~/.claude/settings.json lists it. */
|
|
34
|
+
plugin: "enabled" | "disabled" | "absent";
|
|
35
|
+
};
|
|
20
36
|
/** The contract ProxyServer uses to delegate tool handling. */
|
|
21
37
|
export interface Backend {
|
|
22
38
|
readonly name: "auth-required" | "sse";
|
|
@@ -34,6 +50,8 @@ export interface Backend {
|
|
|
34
50
|
waitForExit?(): Promise<void>;
|
|
35
51
|
/** Return an unstarted replacement only if the negotiated UI support changed. */
|
|
36
52
|
withClientCapabilities?(capabilities: ClientCapabilities): Backend;
|
|
53
|
+
/** Report the client behind the proxy to every upstream session, reconnects included. */
|
|
54
|
+
identifyClient?(downstream: DownstreamClient): void;
|
|
37
55
|
}
|
|
38
56
|
/**
|
|
39
57
|
* Pre-auth backend. Every tool call triggers a device-flow state step via the
|
|
@@ -78,7 +96,20 @@ export declare class SseProxyBackend implements Backend {
|
|
|
78
96
|
private stopped;
|
|
79
97
|
private reconnectPromise;
|
|
80
98
|
private readonly clientCapabilities;
|
|
99
|
+
private downstream;
|
|
100
|
+
/** The client whose `connect` has finished; null while connecting or after its transport closed. */
|
|
101
|
+
private connectedClient;
|
|
102
|
+
/** Set once a report fails; see reportClient. */
|
|
103
|
+
private reportsOff;
|
|
81
104
|
constructor(sseUrl: string, token: string, capabilities?: ClientCapabilities);
|
|
105
|
+
identifyClient(downstream: DownstreamClient): void;
|
|
106
|
+
/**
|
|
107
|
+
* Tell the connected session which client the proxy serves. Never awaited.
|
|
108
|
+
* The SDK also hands a failed POST to the transport's onerror, which
|
|
109
|
+
* reconnects, so after one failure this backend stops reporting: a server
|
|
110
|
+
* that rejects reports costs one reconnect, not a loop.
|
|
111
|
+
*/
|
|
112
|
+
private reportClient;
|
|
82
113
|
withClientCapabilities(capabilities: ClientCapabilities): Backend;
|
|
83
114
|
start(): Promise<void>;
|
|
84
115
|
/**
|
package/dist/backend.js
CHANGED
|
@@ -21,6 +21,12 @@ import { loadBundledCatalog } from "./catalog.js";
|
|
|
21
21
|
import { classifySSEError, TenantSuspendedError, TokenExpiredError } from "./proxy.js";
|
|
22
22
|
import { TRACE_MIME_TYPE } from "./trace-resource.js";
|
|
23
23
|
import { getPackageVersion } from "./version.js";
|
|
24
|
+
/**
|
|
25
|
+
* Notification that tells Pharaoh which client the proxy serves. Pharaoh sees
|
|
26
|
+
* the proxy itself as `pharaoh-proxy`; this lands on the session's row.
|
|
27
|
+
* Mirrors PROXY_CLIENT_NOTIFICATION in src/mcp/server.ts.
|
|
28
|
+
*/
|
|
29
|
+
export const CLIENT_NOTIFICATION = "notifications/pharaoh/client";
|
|
24
30
|
/**
|
|
25
31
|
* Pre-auth backend. Every tool call triggers a device-flow state step via the
|
|
26
32
|
* gateway; returns a structured auth-required response. Once the gateway
|
|
@@ -145,6 +151,11 @@ export class SseProxyBackend {
|
|
|
145
151
|
stopped = false;
|
|
146
152
|
reconnectPromise = null;
|
|
147
153
|
clientCapabilities;
|
|
154
|
+
downstream = null;
|
|
155
|
+
/** The client whose `connect` has finished; null while connecting or after its transport closed. */
|
|
156
|
+
connectedClient = null;
|
|
157
|
+
/** Set once a report fails; see reportClient. */
|
|
158
|
+
reportsOff = false;
|
|
148
159
|
constructor(sseUrl, token, capabilities = {}) {
|
|
149
160
|
this.sseUrl = sseUrl;
|
|
150
161
|
this.token = token;
|
|
@@ -156,6 +167,29 @@ export class SseProxyBackend {
|
|
|
156
167
|
? { extensions: { "io.modelcontextprotocol/ui": { mimeTypes: [TRACE_MIME_TYPE] } } }
|
|
157
168
|
: {};
|
|
158
169
|
}
|
|
170
|
+
identifyClient(downstream) {
|
|
171
|
+
this.downstream = downstream;
|
|
172
|
+
void this.reportClient();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Tell the connected session which client the proxy serves. Never awaited.
|
|
176
|
+
* The SDK also hands a failed POST to the transport's onerror, which
|
|
177
|
+
* reconnects, so after one failure this backend stops reporting: a server
|
|
178
|
+
* that rejects reports costs one reconnect, not a loop.
|
|
179
|
+
*/
|
|
180
|
+
async reportClient() {
|
|
181
|
+
const client = this.connectedClient;
|
|
182
|
+
if (!client || !this.downstream || this.reportsOff)
|
|
183
|
+
return;
|
|
184
|
+
await client
|
|
185
|
+
.notification({ method: CLIENT_NOTIFICATION, params: this.downstream })
|
|
186
|
+
.catch((error) => {
|
|
187
|
+
// ALLOWED-CATCH: telemetry; reportsOff bounds the reconnect a failed POST triggers
|
|
188
|
+
this.reportsOff = true;
|
|
189
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
190
|
+
process.stderr.write(`Pharaoh: client report not sent, not retrying (${msg})\n`);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
159
193
|
withClientCapabilities(capabilities) {
|
|
160
194
|
const next = new SseProxyBackend(this.sseUrl, this.token, capabilities);
|
|
161
195
|
return JSON.stringify(next.clientCapabilities) === JSON.stringify(this.clientCapabilities)
|
|
@@ -207,6 +241,13 @@ export class SseProxyBackend {
|
|
|
207
241
|
classifySSEError(error);
|
|
208
242
|
throw error;
|
|
209
243
|
}
|
|
244
|
+
// A stop or a newer connect while this one was in flight owns the backend now.
|
|
245
|
+
if (this.stopped || this.client !== client)
|
|
246
|
+
return;
|
|
247
|
+
this.connectedClient = client;
|
|
248
|
+
// Every new upstream session (reconnects too) hears which client this is,
|
|
249
|
+
// without holding up startup or reconnect supervision.
|
|
250
|
+
void this.reportClient();
|
|
210
251
|
}
|
|
211
252
|
/** Wait for the current transport to close, then decide what to do. */
|
|
212
253
|
waitForClose() {
|
|
@@ -216,8 +257,14 @@ export class SseProxyBackend {
|
|
|
216
257
|
return;
|
|
217
258
|
}
|
|
218
259
|
const sse = this.transport;
|
|
219
|
-
|
|
260
|
+
// The client is gone the moment its transport closes or errors,
|
|
261
|
+
// whichever way the loop then leaves (backoff or typed recovery).
|
|
262
|
+
sse.onclose = () => {
|
|
263
|
+
this.connectedClient = null;
|
|
264
|
+
resolve();
|
|
265
|
+
};
|
|
220
266
|
sse.onerror = (err) => {
|
|
267
|
+
this.connectedClient = null;
|
|
221
268
|
try {
|
|
222
269
|
classifySSEError(err);
|
|
223
270
|
}
|
|
@@ -302,6 +349,7 @@ export class SseProxyBackend {
|
|
|
302
349
|
}
|
|
303
350
|
async stop() {
|
|
304
351
|
this.stopped = true;
|
|
352
|
+
this.connectedClient = null;
|
|
305
353
|
if (this.transport) {
|
|
306
354
|
await this.transport.close().catch(() => { });
|
|
307
355
|
this.transport = null;
|
package/dist/helpers.js
CHANGED
|
@@ -42,7 +42,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
42
42
|
return { server, logout };
|
|
43
43
|
}
|
|
44
44
|
export function printUsage() {
|
|
45
|
-
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config and the Claude Code plugin, then exit", " --clean Remove Pharaoh's MCP entries and plugin from Claude Code config", " --install-skills Reinstall the Claude Code plugin and OpenClaw skills", " --help, -h Show this help", "", "Get started:", ` ${NPX_COMMAND}`, "");
|
|
45
|
+
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config and the Claude Code plugin, then exit", " --clean Remove Pharaoh's MCP entries and plugin from Claude Code config", " --install-skills Reinstall the Claude Code plugin and OpenClaw skills", " --version, -v Print the version", " --help, -h Show this help", "", "Get started:", ` ${NPX_COMMAND}`, "");
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Validate that a server-supplied SSE URL shares the same origin as the configured server.
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,11 @@ async function main() {
|
|
|
37
37
|
printUsage();
|
|
38
38
|
process.exit(0);
|
|
39
39
|
}
|
|
40
|
+
// Stdout, alone on its line, so a script can capture the version to pin
|
|
41
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
42
|
+
process.stdout.write(`${getPackageVersion()}\n`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
40
45
|
// Inspect mode — serve tool schemas over stdio for Glama directory inspection
|
|
41
46
|
if (args.includes("--inspect")) {
|
|
42
47
|
const { runInspect } = await import("./inspect.js");
|
package/dist/install-skills.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ClaudeCli } from "./setup.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Pharaoh's Claude Code plugin as ~/.claude/settings.json lists it. The proxy
|
|
4
|
+
* reports this to Pharaoh, so adoption can be compared with and without it.
|
|
4
5
|
*
|
|
5
6
|
* @param home - Home directory override (defaults to os.homedir()).
|
|
6
|
-
* @returns True if ~/.openclaw/ exists.
|
|
7
7
|
*/
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function claudePluginState(home?: string): "enabled" | "disabled" | "absent";
|
|
9
9
|
/**
|
|
10
10
|
* Uninstall Pharaoh's Claude Code plugin: through the CLI, then the local
|
|
11
11
|
* marketplace directory and any pre-marketplace install.
|
package/dist/install-skills.js
CHANGED
|
@@ -63,7 +63,7 @@ function detectCodex(home = homedir()) {
|
|
|
63
63
|
* @param home - Home directory override (defaults to os.homedir()).
|
|
64
64
|
* @returns True if ~/.openclaw/ exists.
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
function detectOpenClaw(home = homedir()) {
|
|
67
67
|
return existsSync(join(home, ".openclaw"));
|
|
68
68
|
}
|
|
69
69
|
// ── Claude Code plugin ──────────────────────────────────────────────
|
|
@@ -151,6 +151,18 @@ function pluginSetting(home) {
|
|
|
151
151
|
return undefined;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Pharaoh's Claude Code plugin as ~/.claude/settings.json lists it. The proxy
|
|
156
|
+
* reports this to Pharaoh, so adoption can be compared with and without it.
|
|
157
|
+
*
|
|
158
|
+
* @param home - Home directory override (defaults to os.homedir()).
|
|
159
|
+
*/
|
|
160
|
+
export function claudePluginState(home = homedir()) {
|
|
161
|
+
const setting = pluginSetting(home);
|
|
162
|
+
if (setting === undefined)
|
|
163
|
+
return "absent";
|
|
164
|
+
return setting ? "enabled" : "disabled";
|
|
165
|
+
}
|
|
154
166
|
/**
|
|
155
167
|
* Install the plugin through Claude Code's CLI (both commands are idempotent).
|
|
156
168
|
* Skipped when it's already enabled, and when the user disabled it: that choice stands.
|
|
@@ -408,6 +420,13 @@ function cleanupRemovedSkills(srcDir, dstDir, oldManifest) {
|
|
|
408
420
|
* and is left alone (security review SC-7).
|
|
409
421
|
*/
|
|
410
422
|
export const PHARAOH_COMMAND_MARKER = "# managed-by: pharaoh-mcp";
|
|
423
|
+
/**
|
|
424
|
+
* Commands an earlier installer wrote that this version no longer ships. Each
|
|
425
|
+
* is deleted only while it is still Pharaoh's marked file; a user's own file
|
|
426
|
+
* or a symlink with the name stays. A pre-marker copy (0.3.46 and earlier) is
|
|
427
|
+
* left too: without the bundled file there is nothing to prove it is ours.
|
|
428
|
+
*/
|
|
429
|
+
const RETIRED_COMMANDS = ["sessions.md"];
|
|
411
430
|
/** The full marker line, with the one thing a reader of the file needs to know. */
|
|
412
431
|
const COMMAND_MARKER_LINE = `${PHARAOH_COMMAND_MARKER} (rewritten by npx ${PACKAGE_NAME} on upgrade; rename this file to keep your own version)`;
|
|
413
432
|
/** The front-matter block of a bundled command, `---` to `---` inclusive. */
|
|
@@ -438,13 +457,14 @@ function isPharaohCommand(existing, bundled) {
|
|
|
438
457
|
* Install the bundled slash commands (`.md` files in the package's `commands/`
|
|
439
458
|
* directory) to `~/.claude/commands/`. A destination that is missing or is
|
|
440
459
|
* Pharaoh's own file is written; anything else (the user's file, or a symlink,
|
|
441
|
-
* which is never followed) is kept and reported.
|
|
460
|
+
* which is never followed) is kept and reported. Pharaoh's copies of
|
|
461
|
+
* `RETIRED_COMMANDS` are removed.
|
|
442
462
|
*
|
|
443
463
|
* @param home - Home directory override (defaults to os.homedir()).
|
|
444
|
-
* @returns How many commands were written, and the names of the files kept.
|
|
464
|
+
* @returns How many commands were written, and the names of the files kept and removed.
|
|
445
465
|
*/
|
|
446
466
|
function installClaudeCodeCommands(home) {
|
|
447
|
-
const result = { installed: 0, kept: [] };
|
|
467
|
+
const result = { installed: 0, kept: [], removed: [] };
|
|
448
468
|
if (!existsSync(BUNDLED_COMMANDS_DIR))
|
|
449
469
|
return result;
|
|
450
470
|
const commandsDst = join(home, ".claude", "commands");
|
|
@@ -463,6 +483,15 @@ function installClaudeCodeCommands(home) {
|
|
|
463
483
|
writeFileSync(dst, markCommand(bundled));
|
|
464
484
|
result.installed++;
|
|
465
485
|
}
|
|
486
|
+
for (const name of RETIRED_COMMANDS) {
|
|
487
|
+
const dst = join(commandsDst, name);
|
|
488
|
+
if (!lstatSync(dst, { throwIfNoEntry: false })?.isFile())
|
|
489
|
+
continue;
|
|
490
|
+
if (!readFileSync(dst, "utf-8").includes(PHARAOH_COMMAND_MARKER))
|
|
491
|
+
continue;
|
|
492
|
+
rmSync(dst);
|
|
493
|
+
result.removed.push(name);
|
|
494
|
+
}
|
|
466
495
|
return result;
|
|
467
496
|
}
|
|
468
497
|
/**
|
|
@@ -691,6 +720,7 @@ export function runInstallSkills(home = homedir(), options) {
|
|
|
691
720
|
]
|
|
692
721
|
: []),
|
|
693
722
|
...(keptLine ? [` → ${keptLine}`] : []),
|
|
723
|
+
...commands.removed.map((name) => ` → Removed retired ~/.claude/commands/${name}`),
|
|
694
724
|
"",
|
|
695
725
|
].join("\n"));
|
|
696
726
|
}
|
package/dist/proxy-server.d.ts
CHANGED
package/dist/proxy-server.js
CHANGED
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
25
25
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
26
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
27
|
-
import { AuthRequiredBackend, createSseBackend } from "./backend.js";
|
|
27
|
+
import { AuthRequiredBackend, createSseBackend, } from "./backend.js";
|
|
28
28
|
import { loadBundledCatalog } from "./catalog.js";
|
|
29
29
|
import { createGateway } from "./gateway.js";
|
|
30
|
+
import { claudePluginState } from "./install-skills.js";
|
|
30
31
|
import { TenantSuspendedError, TokenExpiredError } from "./proxy.js";
|
|
31
32
|
import { registerTraceResource } from "./trace-resource.js";
|
|
32
33
|
import { getPackageVersion } from "./version.js";
|
|
@@ -42,6 +43,7 @@ export class ProxyServer {
|
|
|
42
43
|
stopping = false;
|
|
43
44
|
clientCapabilities = {};
|
|
44
45
|
clientInitialized = false;
|
|
46
|
+
downstream = null;
|
|
45
47
|
capabilityUpdate = Promise.resolve();
|
|
46
48
|
transportClosed;
|
|
47
49
|
transportClosedResolve;
|
|
@@ -64,10 +66,21 @@ export class ProxyServer {
|
|
|
64
66
|
if (this.clientInitialized)
|
|
65
67
|
return;
|
|
66
68
|
this.clientInitialized = true;
|
|
69
|
+
const info = this.server.getClientVersion();
|
|
70
|
+
const downstream = {
|
|
71
|
+
client: { name: info?.name, version: info?.version },
|
|
72
|
+
plugin: claudePluginState(),
|
|
73
|
+
};
|
|
74
|
+
this.downstream = downstream;
|
|
67
75
|
this.clientCapabilities = this.server.getClientCapabilities() ?? {};
|
|
68
76
|
const next = this.backend.withClientCapabilities?.(this.clientCapabilities) ?? this.backend;
|
|
69
|
-
|
|
77
|
+
// Only the backend that serves this client is told about it: a warm
|
|
78
|
+
// session replaced below never takes a tool call. setBackend names the
|
|
79
|
+
// client to its replacement once that has started.
|
|
80
|
+
if (next === this.backend) {
|
|
81
|
+
this.backend.identifyClient?.(downstream);
|
|
70
82
|
return;
|
|
83
|
+
}
|
|
71
84
|
// Warm startup happens before the frontend initializes. Replace that
|
|
72
85
|
// conservative connection only when real UI support is negotiated.
|
|
73
86
|
this.capabilityUpdate = this.setBackend(next).catch((error) => {
|
|
@@ -75,6 +88,7 @@ export class ProxyServer {
|
|
|
75
88
|
throw error;
|
|
76
89
|
// UI negotiation is optional: keep the already-started plain-text
|
|
77
90
|
// backend healthy when its replacement hits a transient failure.
|
|
91
|
+
this.backend.identifyClient?.(downstream);
|
|
78
92
|
process.stderr.write("Pharaoh: compact UI negotiation unavailable; continuing with text responses.\n");
|
|
79
93
|
});
|
|
80
94
|
this.capabilityUpdate.catch(() => {
|
|
@@ -201,6 +215,9 @@ export class ProxyServer {
|
|
|
201
215
|
return;
|
|
202
216
|
}
|
|
203
217
|
this.backend = next;
|
|
218
|
+
// Read the client after start: it may have initialized during the await.
|
|
219
|
+
if (this.downstream)
|
|
220
|
+
next.identifyClient?.(this.downstream);
|
|
204
221
|
this.capabilityUpdate = Promise.resolve();
|
|
205
222
|
// Fire the notification after the swap so a client that races a list
|
|
206
223
|
// request against it sees the new catalog.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pharaoh-so/mcp",
|
|
3
3
|
"mcpName": "so.pharaoh/pharaoh",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.52",
|
|
5
5
|
"description": "MCP proxy for Pharaoh — maps codebases into queryable knowledge graphs for AI agents. Enables Claude Code in headless environments (VPS, SSH, CI) via device flow auth.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
package/commands/sessions.md
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Decompose work into parallel, isolated sessions using git worktrees
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Session Decomposition
|
|
6
|
-
|
|
7
|
-
Break large tasks into parallel, isolated work sessions. Each session runs in its own git worktree with fresh context, focused scope, and atomic commits.
|
|
8
|
-
|
|
9
|
-
## When to Use
|
|
10
|
-
|
|
11
|
-
- Task is too large for a single context window
|
|
12
|
-
- Work has 3+ independent sub-tasks that don't touch the same files
|
|
13
|
-
- You need to preserve context quality across a multi-hour effort
|
|
14
|
-
- Multiple features or fixes can proceed in parallel
|
|
15
|
-
|
|
16
|
-
## Do Not Use When
|
|
17
|
-
|
|
18
|
-
- Sub-tasks share files or state
|
|
19
|
-
- Work is sequential (each step depends on the previous)
|
|
20
|
-
- Task fits comfortably in one session
|
|
21
|
-
|
|
22
|
-
## Step 1: Reconnaissance
|
|
23
|
-
|
|
24
|
-
If Pharaoh MCP tools are available, call `get_codebase_map` and `get_module_context` on affected modules to understand the current landscape before decomposing.
|
|
25
|
-
|
|
26
|
-
## Step 2: Decompose
|
|
27
|
-
|
|
28
|
-
Break the task into sessions. Each session must:
|
|
29
|
-
|
|
30
|
-
- Have a clear, narrow goal (one feature, one fix, one module)
|
|
31
|
-
- Touch a distinct set of files — no overlap between sessions
|
|
32
|
-
- Be independently verifiable (tests pass, build succeeds)
|
|
33
|
-
- Produce atomic commits that make sense on their own
|
|
34
|
-
|
|
35
|
-
## Step 3: Write Session Prompts
|
|
36
|
-
|
|
37
|
-
For each session, write a complete prompt containing:
|
|
38
|
-
|
|
39
|
-
- **Goal:** what this session produces (1-2 sentences)
|
|
40
|
-
- **Scope:** which files/modules to touch (explicit list)
|
|
41
|
-
- **Constraints:** what NOT to change
|
|
42
|
-
- **Verification:** how to confirm the work is correct
|
|
43
|
-
- **Context:** any architectural decisions or patterns to follow
|
|
44
|
-
|
|
45
|
-
## Step 4: Present for Review (MANDATORY — do NOT skip)
|
|
46
|
-
|
|
47
|
-
**STOP. Paste every session prompt into the chat as a numbered list.**
|
|
48
|
-
|
|
49
|
-
For each session, show:
|
|
50
|
-
1. The session name
|
|
51
|
-
2. The full prompt text
|
|
52
|
-
3. Which sessions need `/plan` review (flag anything non-trivial)
|
|
53
|
-
|
|
54
|
-
**Wait for the user to approve, modify, add, remove, or reorder sessions before proceeding.** Do not create worktrees or execute any work until the user explicitly approves the decomposition.
|
|
55
|
-
|
|
56
|
-
If the user says "looks good" or similar, proceed. If they request changes, update the prompts and present again.
|
|
57
|
-
|
|
58
|
-
## Step 5: Create Worktrees
|
|
59
|
-
|
|
60
|
-
Only after user approval. For each session, create an isolated worktree:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
git worktree add .worktrees/<session-name> -b <branch-name>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Install dependencies in each worktree. Verify clean baseline (tests pass).
|
|
67
|
-
|
|
68
|
-
## Step 6: Execute Sessions
|
|
69
|
-
|
|
70
|
-
Run each session independently. Sessions should not reference each other's work-in-progress — they operate on the same base commit.
|
|
71
|
-
|
|
72
|
-
## Step 7: Integrate
|
|
73
|
-
|
|
74
|
-
After all sessions complete:
|
|
75
|
-
|
|
76
|
-
1. Verify each branch independently (tests pass, build succeeds)
|
|
77
|
-
2. Merge branches sequentially into the target branch
|
|
78
|
-
3. Resolve any conflicts (rare if decomposition was clean)
|
|
79
|
-
4. Run full verification on the integrated result
|
|
80
|
-
|
|
81
|
-
## Decomposition Rules
|
|
82
|
-
|
|
83
|
-
| Good decomposition | Bad decomposition |
|
|
84
|
-
|---|---|
|
|
85
|
-
| Session A: auth module, Session B: billing module | Session A: backend, Session B: frontend (likely share types) |
|
|
86
|
-
| Session A: new feature, Session B: unrelated bugfix | Session A: write code, Session B: write tests (coupled) |
|
|
87
|
-
| Session A: parser, Session B: renderer (clear interface) | Session A: first half of file, Session B: second half |
|
|
88
|
-
|
|
89
|
-
## Key Principles
|
|
90
|
-
|
|
91
|
-
- **No shared files** — if two sessions touch the same file, merge them into one
|
|
92
|
-
- **Fresh context per session** — don't carry state between sessions
|
|
93
|
-
- **Atomic commits** — each session's output should be a coherent, reviewable unit
|
|
94
|
-
- **Verify before integrating** — never merge a session that doesn't pass its own checks
|
|
95
|
-
- **Decomposition is the hard part** — spend time getting boundaries right before starting work
|
|
96
|
-
- **The user reviews before execution** — always present prompts, never skip to building
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
Here is the task to decompose into sessions:
|
|
100
|
-
|
|
101
|
-
$ARGUMENTS
|
package/skills/parallel/SKILL.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: parallel
|
|
3
|
-
prompt-name: parallel-agents
|
|
4
|
-
description: "Dispatch 2+ independent subagent tasks that run concurrently. Each agent gets focused scope, clear goal, constraints, and expected output. No shared state between agents. Review and integrate results after all complete."
|
|
5
|
-
version: 0.2.0
|
|
6
|
-
homepage: https://pharaoh.so
|
|
7
|
-
user-invocable: true
|
|
8
|
-
metadata: {"emoji": "☥", "tags": ["parallel", "subagents", "concurrency", "delegation", "efficiency"]}
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Parallel Dispatch
|
|
12
|
-
|
|
13
|
-
Delegate independent tasks to specialized agents running concurrently. Each agent gets isolated context and focused scope. Never share your session history — construct exactly what each agent needs.
|
|
14
|
-
|
|
15
|
-
## When to Use
|
|
16
|
-
|
|
17
|
-
- 2+ independent tasks with no shared state
|
|
18
|
-
- Multiple failures across different subsystems
|
|
19
|
-
- Each problem can be understood without context from others
|
|
20
|
-
- Agents won't edit the same files
|
|
21
|
-
|
|
22
|
-
## Do Not Use When
|
|
23
|
-
|
|
24
|
-
- Failures are related (fixing one might fix others)
|
|
25
|
-
- Tasks require understanding full system state
|
|
26
|
-
- Agents would edit the same files or resources
|
|
27
|
-
- You don't yet know what's broken (investigate first)
|
|
28
|
-
|
|
29
|
-
## The Pattern
|
|
30
|
-
|
|
31
|
-
### 1. Identify Independent Domains
|
|
32
|
-
|
|
33
|
-
Group work by what's independent:
|
|
34
|
-
- Different test files with different root causes
|
|
35
|
-
- Different modules with unrelated issues
|
|
36
|
-
- Different features with no shared code
|
|
37
|
-
|
|
38
|
-
### 2. Craft Agent Prompts
|
|
39
|
-
|
|
40
|
-
Each agent gets:
|
|
41
|
-
|
|
42
|
-
- **Specific scope:** one file, one module, one subsystem
|
|
43
|
-
- **Clear goal:** what success looks like
|
|
44
|
-
- **Constraints:** what NOT to change
|
|
45
|
-
- **Context:** error messages, relevant code paths, architectural notes
|
|
46
|
-
- **Expected output:** summary of findings and changes
|
|
47
|
-
|
|
48
|
-
### 3. Dispatch
|
|
49
|
-
|
|
50
|
-
Launch all agents simultaneously. They run concurrently with no coordination needed.
|
|
51
|
-
|
|
52
|
-
### 4. Review and Integrate
|
|
53
|
-
|
|
54
|
-
When agents return:
|
|
55
|
-
|
|
56
|
-
1. Read each summary — understand what changed
|
|
57
|
-
2. Check for conflicts — did agents edit overlapping code?
|
|
58
|
-
3. Run full test suite — verify all fixes work together
|
|
59
|
-
4. Spot-check results — agents can make systematic errors
|
|
60
|
-
|
|
61
|
-
## Prompt Quality
|
|
62
|
-
|
|
63
|
-
| Bad | Good |
|
|
64
|
-
|-----|------|
|
|
65
|
-
| "Fix all the tests" | "Fix the 3 failures in agent-abort.test.ts" |
|
|
66
|
-
| "Fix the race condition" | "Fix timing in abort test — here are the error messages: ..." |
|
|
67
|
-
| No constraints | "Do NOT change production code, fix tests only" |
|
|
68
|
-
| "Fix it" | "Return: root cause summary + what you changed" |
|
|
69
|
-
|
|
70
|
-
## Iron Rules
|
|
71
|
-
|
|
72
|
-
- **One task per agent** — focused agents produce better results than broad ones
|
|
73
|
-
- **No shared state** — agents must not depend on each other's output
|
|
74
|
-
- **Never trust agent reports** — verify changes independently before integrating
|
|
75
|
-
- **Construct context, don't inherit** — give each agent exactly what it needs, nothing more
|
package/skills/sessions/SKILL.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sessions
|
|
3
|
-
prompt-name: session-decomposition
|
|
4
|
-
description: "Decompose work into parallel, isolated sessions using git worktrees. Each session gets fresh context, a narrow scope, and produces atomic commits. Presents session prompts for user review before execution."
|
|
5
|
-
version: 0.3.0
|
|
6
|
-
homepage: https://pharaoh.so
|
|
7
|
-
user-invocable: true
|
|
8
|
-
metadata: {"emoji": "☥", "tags": ["sessions", "worktrees", "parallel-work", "context-management", "decomposition"]}
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Session Decomposition
|
|
12
|
-
|
|
13
|
-
Break large tasks into parallel, isolated work sessions. Each session runs in its own git worktree with fresh context, focused scope, and atomic commits.
|
|
14
|
-
|
|
15
|
-
## When to Use
|
|
16
|
-
|
|
17
|
-
- Task is too large for a single context window
|
|
18
|
-
- Work has 3+ independent sub-tasks that don't touch the same files
|
|
19
|
-
- You need to preserve context quality across a multi-hour effort
|
|
20
|
-
- Multiple features or fixes can proceed in parallel
|
|
21
|
-
|
|
22
|
-
## Do Not Use When
|
|
23
|
-
|
|
24
|
-
- Sub-tasks share files or state
|
|
25
|
-
- Work is sequential (each step depends on the previous)
|
|
26
|
-
- Task fits comfortably in one session
|
|
27
|
-
|
|
28
|
-
## Step 1: Reconnaissance
|
|
29
|
-
|
|
30
|
-
If Pharaoh MCP tools are available, call `get_codebase_map` and `get_module_context` on affected modules to understand the current landscape before decomposing.
|
|
31
|
-
|
|
32
|
-
## Step 2: Decompose
|
|
33
|
-
|
|
34
|
-
Break the task into sessions. Each session must:
|
|
35
|
-
|
|
36
|
-
- Have a clear, narrow goal (one feature, one fix, one module)
|
|
37
|
-
- Touch a distinct set of files — no overlap between sessions
|
|
38
|
-
- Be independently verifiable (tests pass, build succeeds)
|
|
39
|
-
- Produce atomic commits that make sense on their own
|
|
40
|
-
|
|
41
|
-
## Step 3: Write Session Prompts
|
|
42
|
-
|
|
43
|
-
For each session, write a complete prompt containing:
|
|
44
|
-
|
|
45
|
-
- **Goal:** what this session produces (1-2 sentences)
|
|
46
|
-
- **Scope:** which files/modules to touch (explicit list)
|
|
47
|
-
- **Constraints:** what NOT to change
|
|
48
|
-
- **Verification:** how to confirm the work is correct
|
|
49
|
-
- **Context:** any architectural decisions or patterns to follow
|
|
50
|
-
|
|
51
|
-
## Step 4: Present for Review (MANDATORY — do NOT skip)
|
|
52
|
-
|
|
53
|
-
**STOP. Paste every session prompt into the chat as a numbered list.**
|
|
54
|
-
|
|
55
|
-
For each session, show:
|
|
56
|
-
1. The session name
|
|
57
|
-
2. The full prompt text
|
|
58
|
-
3. Which sessions need `/plan` review (flag anything non-trivial)
|
|
59
|
-
|
|
60
|
-
**Wait for the user to approve, modify, add, remove, or reorder sessions before proceeding.** Do not create worktrees or execute any work until the user explicitly approves the decomposition.
|
|
61
|
-
|
|
62
|
-
If the user says "looks good" or similar, proceed. If they request changes, update the prompts and present again.
|
|
63
|
-
|
|
64
|
-
## Step 5: Create Worktrees
|
|
65
|
-
|
|
66
|
-
Only after user approval. For each session, create an isolated worktree:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
git worktree add .worktrees/<session-name> -b <branch-name>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Install dependencies in each worktree. Verify clean baseline (tests pass).
|
|
73
|
-
|
|
74
|
-
## Step 6: Execute Sessions
|
|
75
|
-
|
|
76
|
-
Run each session independently. Sessions should not reference each other's work-in-progress — they operate on the same base commit.
|
|
77
|
-
|
|
78
|
-
## Step 7: Integrate
|
|
79
|
-
|
|
80
|
-
After all sessions complete:
|
|
81
|
-
|
|
82
|
-
1. Verify each branch independently (tests pass, build succeeds)
|
|
83
|
-
2. Merge branches sequentially into the target branch
|
|
84
|
-
3. Resolve any conflicts (rare if decomposition was clean)
|
|
85
|
-
4. Run full verification on the integrated result
|
|
86
|
-
|
|
87
|
-
## Decomposition Rules
|
|
88
|
-
|
|
89
|
-
| Good decomposition | Bad decomposition |
|
|
90
|
-
|---|---|
|
|
91
|
-
| Session A: auth module, Session B: billing module | Session A: backend, Session B: frontend (likely share types) |
|
|
92
|
-
| Session A: new feature, Session B: unrelated bugfix | Session A: write code, Session B: write tests (coupled) |
|
|
93
|
-
| Session A: parser, Session B: renderer (clear interface) | Session A: first half of file, Session B: second half |
|
|
94
|
-
|
|
95
|
-
## Key Principles
|
|
96
|
-
|
|
97
|
-
- **No shared files** — if two sessions touch the same file, merge them into one
|
|
98
|
-
- **Fresh context per session** — don't carry state between sessions
|
|
99
|
-
- **Atomic commits** — each session's output should be a coherent, reviewable unit
|
|
100
|
-
- **Verify before integrating** — never merge a session that doesn't pass its own checks
|
|
101
|
-
- **Decomposition is the hard part** — spend time getting boundaries right before starting work
|
|
102
|
-
- **The user reviews before execution** — always present prompts, never skip to building
|