@pharaoh-so/mcp 0.2.4 → 0.2.6
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/.claude-plugin/plugin.json +1 -1
- package/dist/helpers.js +1 -1
- package/dist/index.js +15 -3
- package/dist/inspect.js +12 -1
- package/dist/install-skills.js +63 -26
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pharaoh",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Codebase knowledge graph with 23 development workflow skills. Query architecture, dependencies, blast radius, and more instead of reading files one at a time.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pharaoh",
|
package/dist/helpers.js
CHANGED
|
@@ -40,7 +40,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
40
40
|
return { server, logout };
|
|
41
41
|
}
|
|
42
42
|
export function printUsage() {
|
|
43
|
-
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear stored credentials and exit", " --install-skills
|
|
43
|
+
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear stored credentials and exit", " --install-skills Force reinstall Pharaoh skills (Claude Code + OpenClaw)", " --help, -h Show this help", "", "Add to Claude Code:", " claude mcp add pharaoh -- npx @pharaoh-so/mcp", "");
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Validate that a server-supplied SSE URL shares the same origin as the configured server.
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* - Interactive (TTY): authenticate, print setup instructions, exit.
|
|
11
11
|
* - Proxy (pipe): require pre-existing credentials, bridge stdio ↔ SSE.
|
|
12
12
|
*/
|
|
13
|
-
import { printActivationPrompt, printAuthSuccess,
|
|
13
|
+
import { pollForToken, printActivationPrompt, printAuthSuccess, requestDeviceCode, } from "./auth.js";
|
|
14
14
|
import { deleteCredentials, isExpired, readCredentials, writeCredentials } from "./credentials.js";
|
|
15
15
|
import { formatIdentity, formatTtl, parseArgs, printLines, printSetupInstructions, printUsage, resolveSseUrl, tokenToCredentials, } from "./helpers.js";
|
|
16
|
-
import {
|
|
16
|
+
import { runInstallSkills } from "./install-skills.js";
|
|
17
|
+
import { startProxy, TenantSuspendedError, TokenExpiredError } from "./proxy.js";
|
|
17
18
|
async function main() {
|
|
18
19
|
const args = process.argv.slice(2);
|
|
19
20
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -46,6 +47,8 @@ async function main() {
|
|
|
46
47
|
if (isInteractive) {
|
|
47
48
|
if (creds && !isExpired(creds)) {
|
|
48
49
|
printLines(`Pharaoh: authenticated as ${formatIdentity(creds)} — token valid for ${formatTtl(creds.expires_at)}, ${creds.repos.length} repo${creds.repos.length === 1 ? "" : "s"} connected`);
|
|
50
|
+
// Ensure skills are installed/up-to-date on every interactive run
|
|
51
|
+
runInstallSkills();
|
|
49
52
|
printSetupInstructions();
|
|
50
53
|
process.exit(0);
|
|
51
54
|
}
|
|
@@ -61,6 +64,8 @@ async function main() {
|
|
|
61
64
|
const newCreds = tokenToCredentials(token, sseUrl);
|
|
62
65
|
writeCredentials(newCreds);
|
|
63
66
|
printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
|
|
67
|
+
// Auto-install skills to detected platforms (Claude Code, OpenClaw)
|
|
68
|
+
runInstallSkills();
|
|
64
69
|
printSetupInstructions();
|
|
65
70
|
process.exit(0);
|
|
66
71
|
}
|
|
@@ -70,7 +75,14 @@ async function main() {
|
|
|
70
75
|
printLines("Pharaoh: no valid credentials — cannot start proxy.", "Run this command first to authenticate:", " npx @pharaoh-so/mcp", "");
|
|
71
76
|
process.exit(1);
|
|
72
77
|
}
|
|
73
|
-
// Valid credentials —
|
|
78
|
+
// Valid credentials — ensure skills are installed before starting proxy
|
|
79
|
+
// Silently installs/updates on first run or package update; fast no-op if current.
|
|
80
|
+
try {
|
|
81
|
+
runInstallSkills();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// ALLOWED-CATCH: skill install failure must not block MCP proxy startup
|
|
85
|
+
}
|
|
74
86
|
printLines(`Pharaoh: token valid for ${formatTtl(creds.expires_at)} — connecting`);
|
|
75
87
|
try {
|
|
76
88
|
await startProxy(creds.sse_url, creds.access_token);
|
package/dist/inspect.js
CHANGED
|
@@ -14,6 +14,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
14
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
15
|
const INSPECT_MSG = "This is Pharaoh's inspection server for directory listings. " +
|
|
16
16
|
"Connect to https://mcp.pharaoh.so for actual usage.";
|
|
17
|
+
/** Read version from package.json (single source of truth). */
|
|
18
|
+
function getPackageVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const pkgPath = join(import.meta.dirname, "..", "package.json");
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
22
|
+
return pkg.version ?? "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "0.0.0";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
export async function runInspect() {
|
|
18
29
|
// Load pre-generated schemas (shipped in the npm package)
|
|
19
30
|
const schemasPath = join(import.meta.dirname, "..", "inspect-tools.json");
|
|
@@ -25,7 +36,7 @@ export async function runInspect() {
|
|
|
25
36
|
process.stderr.write("Pharaoh: inspect-tools.json not found — rebuild the package.\n");
|
|
26
37
|
process.exit(1);
|
|
27
38
|
}
|
|
28
|
-
const server = new McpServer({ name: "pharaoh", version:
|
|
39
|
+
const server = new McpServer({ name: "pharaoh", version: getPackageVersion() }, {
|
|
29
40
|
instructions: "Pharaoh turns codebases into knowledge graphs that AI agents can think with. " +
|
|
30
41
|
"Connect at https://mcp.pharaoh.so for full functionality.",
|
|
31
42
|
});
|
package/dist/install-skills.js
CHANGED
|
@@ -14,6 +14,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
14
14
|
const PKG_ROOT = join(__dirname, "..");
|
|
15
15
|
/** Path to the bundled skills directory. */
|
|
16
16
|
const BUNDLED_SKILLS_DIR = join(PKG_ROOT, "skills");
|
|
17
|
+
/** Read the package version from package.json (single source of truth). */
|
|
18
|
+
function getPackageVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
|
|
21
|
+
return pkg.version ?? "0.0.0";
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "0.0.0";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
17
27
|
// ── Detection ───────────────────────────────────────────────────────
|
|
18
28
|
/**
|
|
19
29
|
* Detect whether Claude Code is installed by checking for ~/.claude/.
|
|
@@ -59,50 +69,75 @@ function installClaudeCodePlugin(home = homedir()) {
|
|
|
59
69
|
return -1;
|
|
60
70
|
}
|
|
61
71
|
// Copy skills/
|
|
72
|
+
let skillCount = 0;
|
|
62
73
|
if (existsSync(BUNDLED_SKILLS_DIR)) {
|
|
63
74
|
cpSync(BUNDLED_SKILLS_DIR, join(pluginDir, "skills"), { recursive: true, force: true });
|
|
75
|
+
const entries = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true });
|
|
76
|
+
skillCount = entries.filter((e) => e.isDirectory()).length;
|
|
64
77
|
}
|
|
65
78
|
// Copy .mcp.json
|
|
66
79
|
const mcpSrc = join(PKG_ROOT, ".mcp.json");
|
|
67
80
|
if (existsSync(mcpSrc)) {
|
|
68
81
|
cpSync(mcpSrc, join(pluginDir, ".mcp.json"), { force: true });
|
|
69
82
|
}
|
|
70
|
-
|
|
71
|
-
const entries = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true });
|
|
72
|
-
return entries.filter((e) => e.isDirectory()).length;
|
|
83
|
+
return skillCount;
|
|
73
84
|
}
|
|
74
85
|
/**
|
|
75
|
-
* Register Pharaoh in Claude Code's installed_plugins.json.
|
|
76
|
-
*
|
|
86
|
+
* Register Pharaoh in Claude Code's installed_plugins.json (v2 format).
|
|
87
|
+
* Adds a new entry or updates the version/lastUpdated of an existing one.
|
|
88
|
+
* Preserves all other plugin entries.
|
|
77
89
|
*
|
|
78
90
|
* @param home - Home directory override (defaults to os.homedir()).
|
|
79
|
-
* @returns
|
|
91
|
+
* @returns "added" if new entry created, "updated" if version bumped, "current" if already up-to-date, "error" on failure.
|
|
80
92
|
*/
|
|
81
93
|
function registerClaudeCodePlugin(home = homedir()) {
|
|
82
94
|
const registryPath = join(home, ".claude", "plugins", "installed_plugins.json");
|
|
83
95
|
const pluginKey = "pharaoh@pharaoh-so";
|
|
84
|
-
|
|
96
|
+
const pluginDir = join(home, ".claude", "plugins", "data", "pharaoh");
|
|
97
|
+
const version = getPackageVersion();
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
let registry = { version: 2, plugins: {} };
|
|
85
100
|
if (existsSync(registryPath)) {
|
|
86
101
|
try {
|
|
87
|
-
|
|
102
|
+
const raw = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
103
|
+
// Handle both v2 format (with plugins key) and any other shape
|
|
104
|
+
if (raw.version === 2 && raw.plugins && typeof raw.plugins === "object") {
|
|
105
|
+
registry = raw;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Non-v2 file — preserve as-is, add v2 structure around it
|
|
109
|
+
registry = { version: 2, plugins: {}, ...raw };
|
|
110
|
+
if (!registry.plugins)
|
|
111
|
+
registry.plugins = {};
|
|
112
|
+
}
|
|
88
113
|
}
|
|
89
114
|
catch {
|
|
90
115
|
process.stderr.write("Pharaoh: installed_plugins.json exists but is not valid JSON — skipping registration.\n");
|
|
91
|
-
return
|
|
116
|
+
return "error";
|
|
92
117
|
}
|
|
93
118
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
pluginRoot: join(home, ".claude", "plugins", "data", "pharaoh"),
|
|
102
|
-
installedAt: new Date().toISOString(),
|
|
119
|
+
const existing = registry.plugins[pluginKey];
|
|
120
|
+
const entry = {
|
|
121
|
+
scope: "user",
|
|
122
|
+
installPath: pluginDir,
|
|
123
|
+
version,
|
|
124
|
+
installedAt: now,
|
|
125
|
+
lastUpdated: now,
|
|
103
126
|
};
|
|
127
|
+
if (existing && existing.length > 0) {
|
|
128
|
+
const current = existing[0];
|
|
129
|
+
if (current.version === version) {
|
|
130
|
+
return "current";
|
|
131
|
+
}
|
|
132
|
+
// Version changed — update in place, preserve original installedAt
|
|
133
|
+
entry.installedAt = current.installedAt || now;
|
|
134
|
+
registry.plugins[pluginKey] = [entry];
|
|
135
|
+
writeFileSync(registryPath, JSON.stringify(registry, null, "\t"));
|
|
136
|
+
return "updated";
|
|
137
|
+
}
|
|
138
|
+
registry.plugins[pluginKey] = [entry];
|
|
104
139
|
writeFileSync(registryPath, JSON.stringify(registry, null, "\t"));
|
|
105
|
-
return
|
|
140
|
+
return "added";
|
|
106
141
|
}
|
|
107
142
|
// ── OpenClaw installer ──────────────────────────────────────────────
|
|
108
143
|
/**
|
|
@@ -181,17 +216,19 @@ export function runInstallSkills(home = homedir()) {
|
|
|
181
216
|
if (hasClaudeCode) {
|
|
182
217
|
const count = installClaudeCodePlugin(home);
|
|
183
218
|
if (count >= 0) {
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
: "Plugin
|
|
219
|
+
const regResult = registerClaudeCodePlugin(home);
|
|
220
|
+
const regMessages = {
|
|
221
|
+
added: "Plugin registered in installed_plugins.json",
|
|
222
|
+
updated: "Plugin version updated in installed_plugins.json",
|
|
223
|
+
current: "Plugin already registered and up-to-date",
|
|
224
|
+
error: "Could not update installed_plugins.json",
|
|
225
|
+
};
|
|
188
226
|
process.stderr.write([
|
|
189
227
|
`Pharaoh: installed ${count} skills as Claude Code plugin`,
|
|
190
228
|
` → ~/.claude/plugins/data/pharaoh/`,
|
|
191
|
-
` → ${
|
|
192
|
-
"",
|
|
193
|
-
"Restart Claude Code to pick up the new skills.",
|
|
229
|
+
` → ${regMessages[regResult]}`,
|
|
194
230
|
"",
|
|
231
|
+
...(regResult === "added" ? ["Restart Claude Code to pick up the new skills.", ""] : []),
|
|
195
232
|
].join("\n"));
|
|
196
233
|
installed = true;
|
|
197
234
|
}
|
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.2.
|
|
4
|
+
"version": "0.2.6",
|
|
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",
|