promptgraph-mcp 1.5.19 → 1.5.21

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.
Files changed (3) hide show
  1. package/index.js +16 -11
  2. package/marketplace.js +52 -11
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -1,15 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
- import { search, getContext, getCallers, getCallees, getImpact, listAll } from './search.js';
6
- import { indexAll } from './indexer.js';
7
- import { startWatcher } from './watcher.js';
8
- import { promptConfig } from './config.js';
9
- import { importFromGitHub } from './github-import.js';
10
- import { detectPlatforms, PLATFORMS } from './platform.js';
11
- import { browseMarketplace, installSkill, publishSkill, getTopRated, recordUse, recordSuccess, recordFail, browseBundles, installBundle } from './marketplace.js';
12
-
2
+ // Only lightweight imports at top. Heavy modules (fastembed/ONNX, vectra,
3
+ // better-sqlite3) are dynamically imported inside the command that needs them,
4
+ // so fast CLI commands (help, marketplace) start instantly.
13
5
  import { colors, banner, success, error, info, section, table } from './cli.js';
14
6
  import boxen from 'boxen';
15
7
  import chalk from 'chalk';
@@ -222,11 +214,13 @@ if (args[0] === 'validate') {
222
214
  }
223
215
 
224
216
  if (args[0] === 'import') {
217
+ const { importFromGitHub } = await import('./github-import.js');
225
218
  await importFromGitHub(args[1]);
226
219
  process.exit(0);
227
220
  }
228
221
 
229
222
  if (args[0] === 'setup') {
223
+ const { detectPlatforms, PLATFORMS } = await import('./platform.js');
230
224
  const platformId = args[1];
231
225
  if (!platformId) {
232
226
  section('Detected platforms');
@@ -243,6 +237,8 @@ if (args[0] === 'setup') {
243
237
  }
244
238
 
245
239
  if (args[0] === 'init') {
240
+ const { promptConfig } = await import('./config.js');
241
+ const { indexAll } = await import('./indexer.js');
246
242
  const config = await promptConfig();
247
243
  await indexAll();
248
244
  console.log();
@@ -257,10 +253,19 @@ if (args[0] === 'init') {
257
253
  }
258
254
 
259
255
  if (args[0] === 'reindex') {
256
+ const { indexAll } = await import('./indexer.js');
260
257
  await indexAll();
261
258
  process.exit(0);
262
259
  }
263
260
 
261
+ // ── MCP server mode (no CLI command) ──
262
+ const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
263
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
264
+ const { CallToolRequestSchema, ListToolsRequestSchema } = await import('@modelcontextprotocol/sdk/types.js');
265
+ const { search, getContext, getCallers, getCallees, getImpact, listAll } = await import('./search.js');
266
+ const { startWatcher } = await import('./watcher.js');
267
+ const { browseMarketplace, installSkill, publishSkill, getTopRated, recordUse, recordSuccess, recordFail, browseBundles, installBundle } = await import('./marketplace.js');
268
+
264
269
  const server = new Server(
265
270
  { name: 'promptgraph', version: '1.0.0' },
266
271
  { capabilities: { tools: {} } }
package/marketplace.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import https from 'https';
5
+ import { createHash } from 'crypto';
5
6
  import { spawnSync } from 'child_process';
6
7
  import { getDb } from './db.js';
7
8
  import { validateSkill } from './validator.js';
@@ -9,10 +10,16 @@ import { validateSkill } from './validator.js';
9
10
  const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
10
11
  const SKILLS_DIR = path.join(os.homedir(), '.claude', 'skills-store', 'marketplace');
11
12
 
12
- // Robust fetch: try undici fetch, fall back to node:https (works where undici fails on Windows)
13
+ // Deterministic short code from an id. Same id always yields the same code,
14
+ // so codes auto-generate — no need to assign them by hand.
15
+ export function codeFor(id) {
16
+ return 'pg-' + createHash('md5').update(String(id)).digest('hex').slice(0, 6);
17
+ }
18
+
19
+ // node:https GET — reliable and fast on Windows (undici fetch can hang ~10s there).
13
20
  function httpGet(url) {
14
21
  return new Promise((resolve, reject) => {
15
- https.get(url, { headers: { 'User-Agent': 'promptgraph-mcp' } }, (res) => {
22
+ const req = https.get(url, { headers: { 'User-Agent': 'promptgraph-mcp' }, timeout: 8000, family: 4 }, (res) => {
16
23
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
17
24
  return httpGet(res.headers.location).then(resolve, reject);
18
25
  }
@@ -24,26 +31,58 @@ function httpGet(url) {
24
31
  res.setEncoding('utf8');
25
32
  res.on('data', c => data += c);
26
33
  res.on('end', () => resolve(data));
27
- }).on('error', reject);
34
+ });
35
+ req.on('timeout', () => { req.destroy(new Error('request timed out')); });
36
+ req.on('error', reject);
28
37
  });
29
38
  }
30
39
 
31
- async function fetchText(url) {
40
+ // Primary path is httpGet (fast/reliable on Windows); undici fetch only as fallback.
41
+ async function rawFetch(url) {
32
42
  try {
43
+ return await httpGet(url);
44
+ } catch {
33
45
  const res = await fetch(url, { headers: { 'User-Agent': 'promptgraph-mcp' } });
34
46
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
35
47
  return await res.text();
36
- } catch {
37
- return await httpGet(url);
38
48
  }
39
49
  }
40
50
 
51
+ // Disk cache for the registry (network to GitHub raw can be slow on some networks).
52
+ const CACHE_DIR = path.join(os.homedir(), '.claude', '.promptgraph');
53
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
54
+
55
+ async function fetchText(url) {
56
+ const cacheFile = path.join(CACHE_DIR, 'registry-cache.json');
57
+ const isRegistry = url === REGISTRY_URL;
58
+
59
+ if (isRegistry && fs.existsSync(cacheFile)) {
60
+ try {
61
+ const stat = fs.statSync(cacheFile);
62
+ if (Date.now() - stat.mtimeMs < CACHE_TTL) {
63
+ return fs.readFileSync(cacheFile, 'utf8');
64
+ }
65
+ } catch {}
66
+ }
67
+
68
+ const text = await rawFetch(url);
69
+
70
+ if (isRegistry) {
71
+ try {
72
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
73
+ fs.writeFileSync(cacheFile, text);
74
+ } catch {}
75
+ }
76
+ return text;
77
+ }
78
+
41
79
  export async function browseMarketplace(topK = 20) {
42
80
  try {
43
81
  const text = await fetchText(REGISTRY_URL);
44
82
  const registry = JSON.parse(text);
45
83
  if (!Array.isArray(registry.skills)) return { error: 'Invalid registry format' };
46
84
  return registry.skills
85
+ .map(s => ({ ...s, code: s.code || codeFor(s.id) })) // auto-fill code
47
86
  .sort((a, b) => (b.stars || 0) - (a.stars || 0))
48
87
  .slice(0, topK);
49
88
  } catch (e) {
@@ -56,15 +95,16 @@ export async function installSkill(query) {
56
95
  const text = await fetchText(REGISTRY_URL);
57
96
  const registry = JSON.parse(text);
58
97
  const q = String(query).trim().toLowerCase();
59
- // match by code, id, or name (case-insensitive)
98
+ // match by code (stored OR auto-generated), id, or name
60
99
  const skill = registry.skills?.find(s =>
61
- s.code?.toLowerCase() === q ||
100
+ (s.code || codeFor(s.id)).toLowerCase() === q ||
62
101
  s.id?.toLowerCase() === q ||
63
102
  s.name?.toLowerCase() === q
64
103
  );
65
104
  if (!skill) {
66
- // maybe it's a bundle code/id hint the user
67
- const bundle = (registry.bundles || []).find(b => b.code?.toLowerCase() === q || b.id?.toLowerCase() === q);
105
+ const bundle = (registry.bundles || []).find(b =>
106
+ (b.code || codeFor(b.id)).toLowerCase() === q || b.id?.toLowerCase() === q
107
+ );
68
108
  if (bundle) return { error: `"${query}" is a bundle. Use pg_bundle_install("${bundle.id}") instead.` };
69
109
  return { error: `No skill matching "${query}" (try a code like pg-xxxxxx, an id, or a name)` };
70
110
  }
@@ -89,6 +129,7 @@ export async function browseBundles(topK = 20) {
89
129
  const registry = JSON.parse(text);
90
130
  const bundles = registry.bundles || [];
91
131
  return bundles
132
+ .map(b => ({ ...b, code: b.code || codeFor(b.id) }))
92
133
  .sort((a, b) => (b.stars || 0) - (a.stars || 0))
93
134
  .slice(0, topK);
94
135
  } catch (e) {
@@ -102,7 +143,7 @@ export async function installBundle(bundleId) {
102
143
  const registry = JSON.parse(text);
103
144
  const q = String(bundleId).trim().toLowerCase();
104
145
  const bundle = (registry.bundles || []).find(b =>
105
- b.code?.toLowerCase() === q || b.id?.toLowerCase() === q || b.name?.toLowerCase() === q
146
+ (b.code || codeFor(b.id)).toLowerCase() === q || b.id?.toLowerCase() === q || b.name?.toLowerCase() === q
106
147
  );
107
148
  if (!bundle) return { error: `No bundle matching "${bundleId}"` };
108
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "1.5.19",
3
+ "version": "1.5.21",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {