@letoribo/mcpgql 1.1.1 → 1.2.1

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 (2) hide show
  1. package/core.ts +85 -29
  2. package/package.json +1 -1
package/core.ts CHANGED
@@ -5,22 +5,40 @@ import { stdin as input, stdout as output } from 'process';
5
5
  import * as path from 'path';
6
6
  import * as fs from 'fs';
7
7
 
8
- // --- Interfaces ---
9
8
  interface Config {
10
9
  activeEndpoints: string[];
11
10
  endpoints: Record<string, string>;
12
11
  }
13
12
 
14
- // --- Initialization ---
15
13
  const rl = readline.createInterface({ input, output });
14
+ const configPath = path.join(process.cwd(), 'mcpgql.config.json');
15
+ const headersPath = path.join(process.cwd(), 'headers.json');
16
+ const defaultConfigPath = path.join(__dirname, 'mcpgql.config.json');
16
17
 
17
- const configPath = path.join(__dirname, 'mcpgql.config.json');
18
- const config: Config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
19
- const entries: [string, string][] = Object.entries(config.endpoints);
18
+ async function initializeFiles() {
19
+ // Check for mcpgql.config.json and offer to copy default if missing
20
+ if (!fs.existsSync(configPath)) {
21
+ console.log(`[INFO] Configuration file not found at: ${configPath}`);
22
+ if (fs.existsSync(defaultConfigPath)) {
23
+ const copyChoice = await rl.question('Do you want to copy the default configuration template here? (y/n): ');
24
+ if (copyChoice.toLowerCase() === 'y') {
25
+ fs.copyFileSync(defaultConfigPath, configPath);
26
+ console.log('[INFO] Default configuration copied to your current directory.');
27
+ } else {
28
+ console.log('[INFO] Please create mcpgql.config.json manually to proceed.');
29
+ process.exit(0);
30
+ }
31
+ } else {
32
+ console.error('[ERROR] Default template not found in package.');
33
+ process.exit(1);
34
+ }
35
+ }
20
36
 
21
- // Default: first endpoint is selected
22
- const selected = new Set<string>();
23
- if (entries.length > 0) selected.add(entries[0][0]);
37
+ // Initialize headers.json with an empty object if it does not exist
38
+ if (!fs.existsSync(headersPath)) {
39
+ fs.writeFileSync(headersPath, JSON.stringify({}, null, 2));
40
+ }
41
+ }
24
42
 
25
43
  function getDistPath(): string {
26
44
  let current = process.cwd();
@@ -31,14 +49,18 @@ function getDistPath(): string {
31
49
  if (parent === current) break;
32
50
  current = parent;
33
51
  }
34
- return path.join(
35
- path.dirname(require.resolve('@letoribo/mcp-graphql-enhanced/package.json')),
36
- 'dist/index.js'
37
- );
52
+ return path.join(path.dirname(require.resolve('@letoribo/mcp-graphql-enhanced/package.json')), 'dist/index.js');
38
53
  }
39
54
 
40
- // --- Core Logic ---
41
- async function startSelection() {
55
+ async function startSelection(
56
+ entries: [string, string][],
57
+ selected: Set<string>,
58
+ allowMutations: { value: boolean },
59
+ headers: { value: string }
60
+ ) {
61
+ const mutationIdx = entries.length + 1;
62
+ const headersIdx = entries.length + 2;
63
+
42
64
  while (true) {
43
65
  console.clear();
44
66
  console.log('\n--- Select Endpoints (Enter to confirm) ---');
@@ -46,24 +68,61 @@ async function startSelection() {
46
68
  console.log(`${selected.has(key) ? ' [✓] ' : ' [ ] '} ${i + 1}. ${key}`);
47
69
  });
48
70
 
49
- const choice = await rl.question('\nEnter number to toggle (or Enter to proceed): ');
71
+ console.log('--- Environment variables ---');
72
+ const mutationStatus = allowMutations.value ? '[YES]' : '[NO]';
73
+ console.log(`${mutationStatus} ${mutationIdx}. Allow Mutations`);
50
74
 
75
+ let hasAuth = false;
76
+ try { if (Object.keys(JSON.parse(headers.value)).length > 0) hasAuth = true; } catch (e) {}
77
+ console.log(`${hasAuth ? '[OK]' : '[ ]'} ${headersIdx}. Headers (Loaded from: ${headersPath})`);
78
+
79
+ const choice = await rl.question('\nEnter number to toggle (or Enter to proceed): ');
51
80
  if (choice.trim() === '') break;
52
81
 
53
- const idx = parseInt(choice.trim()) - 1;
82
+ const choiceNum = parseInt(choice.trim());
83
+ const idx = choiceNum - 1;
84
+
54
85
  if (idx >= 0 && idx < entries.length) {
55
86
  const key = entries[idx][0];
56
87
  selected.has(key) ? selected.delete(key) : selected.add(key);
88
+ } else if (choiceNum === mutationIdx) {
89
+ allowMutations.value = !allowMutations.value;
90
+ } else if (choiceNum === headersIdx) {
91
+ console.log('\n--- Paste JSON for headers.json ---');
92
+ console.log('Current: ' + headers.value);
93
+ const input = await rl.question('Paste new JSON (e.g. {"Authorization": "..."}): ');
94
+
95
+ if (input.trim() !== '') {
96
+ try {
97
+ const parsed = JSON.parse(input);
98
+ fs.writeFileSync(headersPath, JSON.stringify(parsed, null, 2));
99
+ headers.value = JSON.stringify(parsed);
100
+ console.log('[INFO] headers.json successfully updated.');
101
+ } catch (e) {
102
+ console.log('[ERROR] Invalid JSON! Update aborted.');
103
+ }
104
+ await new Promise(resolve => setTimeout(resolve, 1000));
105
+ }
57
106
  }
58
107
  }
59
108
  }
60
109
 
61
110
  async function main() {
62
- await startSelection();
111
+ await initializeFiles();
112
+
113
+ const config: Config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
114
+ const entries: [string, string][] = Object.entries(config.endpoints);
115
+ const selected = new Set<string>();
116
+ if (entries.length > 0) selected.add(entries[0][0]);
117
+
118
+ const allowMutations = { value: false };
119
+ const headers = { value: fs.readFileSync(headersPath, 'utf-8') };
63
120
 
121
+ await startSelection(entries, selected, allowMutations, headers);
122
+
64
123
  const selectedUrls = Array.from(selected).map(k => config.endpoints[k]);
65
124
  if (selectedUrls.length === 0) {
66
- console.log('[ERROR] No endpoints selected.');
125
+ console.log('[ERROR] No endpoints selected. Exiting.');
67
126
  rl.close();
68
127
  process.exit(1);
69
128
  }
@@ -72,31 +131,28 @@ async function main() {
72
131
  console.log('2. MCP Inspector (stdio)');
73
132
  const mode = await rl.question('Select mode (1 or 2): ');
74
133
 
75
- // Environment setup
76
- const endpointString = selectedUrls.join(',');
77
134
  const spawnEnv: Record<string, string> = {
78
135
  ...(process.env as Record<string, string>),
79
- ENDPOINT: endpointString
136
+ ENDPOINT: selectedUrls.join(','),
137
+ ALLOW_MUTATIONS: String(allowMutations.value),
138
+ HEADERS: headers.value
80
139
  };
81
140
 
141
+ console.log(`[INFO] Launching with ENDPOINT=${spawnEnv.ENDPOINT}`);
82
142
  rl.close();
83
143
 
84
144
  const distPath = getDistPath();
145
+ console.log(`[DEBUG] Executing: node ${distPath}`);
146
+
85
147
  if (mode === '1') {
86
148
  spawnEnv['ENABLE_HTTP'] = 'true';
87
- console.log(`[INFO] Launching with ENDPOINT=${endpointString}`);
88
- spawn('node', [distPath], { stdio: 'inherit', cwd: process.cwd(), env: spawnEnv });
149
+ spawn('node', [distPath], { stdio: 'inherit', env: spawnEnv });
89
150
  } else {
90
- console.log('Launching in Inspector mode...');
91
151
  spawn('npx', ['-y', '@modelcontextprotocol/inspector', 'node', distPath], {
92
152
  stdio: 'inherit',
93
- cwd: process.cwd(),
94
153
  env: spawnEnv
95
154
  });
96
155
  }
97
156
  }
98
157
 
99
- main().catch(err => {
100
- console.error(err);
101
- process.exit(1);
102
- });
158
+ main().catch(err => { console.error(err); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letoribo/mcpgql",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "CLI tool for MCP GraphQL Enhanced",
5
5
  "main": "index.js",
6
6
  "bin": {