@letoribo/mcpgql 1.2.0 → 1.2.2
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 +63 -0
- package/core.ts +48 -23
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# mcpgql
|
|
2
|
+
|
|
3
|
+
**The GraphQL Overlord’s bridge to MCP.**
|
|
4
|
+
|
|
5
|
+
`mcpgql` is not just a client; it's a federated bridge connecting any GraphQL endpoint directly to the Model Context Protocol (MCP) ecosystem.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why mcpgql?
|
|
10
|
+
|
|
11
|
+
- **Zero-Config Integration**: Seamlessly connect LLMs to your graph data.
|
|
12
|
+
- **No Environment Pollution**: Unlike standard setups, no environment variables need to be declared in your `claude_desktop_config.json`. All configuration (endpoints, headers, mutation toggles) is handled dynamically in your terminal gateway.
|
|
13
|
+
- **Federated Power**: Engineered to work flawlessly with `mcp-remote` for decentralized, multi-endpoint environments.
|
|
14
|
+
- **Real-time Mutations**: Full support for write operations (like `publishToDiscord`) directly from your AI agent.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
Run the bridge via the terminal command:
|
|
19
|
+
```
|
|
20
|
+
mcpgql
|
|
21
|
+
```
|
|
22
|
+
The interactive dialog will automatically guide you through setting up your endpoints and required environment variables:
|
|
23
|
+
```
|
|
24
|
+
--- Select Endpoints (Enter to confirm) ---
|
|
25
|
+
[✓] 1. mcp
|
|
26
|
+
[ ] 2. neo4j
|
|
27
|
+
--- Environment variables ---
|
|
28
|
+
[YES] 3. Allow Mutations
|
|
29
|
+
[OK] 4. Headers (Loaded from: /home/atman/headers.json)
|
|
30
|
+
|
|
31
|
+
Enter number to toggle (or Enter to proceed):
|
|
32
|
+
|
|
33
|
+
1. Terminal (HTTP GraphQL Gateway)
|
|
34
|
+
2. MCP Inspector (stdio)
|
|
35
|
+
Select mode (1 or 2): 1
|
|
36
|
+
[INFO] Launching with ENDPOINT=https://mcp-discord.vercel.app/api/graphiql
|
|
37
|
+
[DEBUG] Executing: node /home/mcp-graphql-enhanced/dist/index.js
|
|
38
|
+
[BOOT] Initializing schema sync for: https://mcp-discord.vercel.app/api/graphiql
|
|
39
|
+
[SYSTEM] Federated Bridge active on port 6274
|
|
40
|
+
📡 MCP Endpoint: http://localhost:6274/mcp
|
|
41
|
+
🎨 GraphiQL: http://localhost:6274/graphiql
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Integrate into Claude Desktop with a minimal configuration:
|
|
45
|
+
```
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"mcpgql": {
|
|
49
|
+
"command": "npx",
|
|
50
|
+
"args": [
|
|
51
|
+
"-y",
|
|
52
|
+
"mcp-remote",
|
|
53
|
+
"http://localhost:6274/mcp"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
## Proof of Concept
|
|
60
|
+
|
|
61
|
+
- **Real-time Mutation**: Successfully executed complex GraphQL mutations (`publishToDiscord`) directly from the Claude interface.
|
|
62
|
+
- **Integration Log**: [View the live integration proof for this session](https://claude.ai/share/7053179e-8cd7-47b6-bd88-a1f43b28539f).
|
|
63
|
+
- **Success Status**: The integration verifies node querying and response transmission with 100% precision.
|
package/core.ts
CHANGED
|
@@ -13,23 +13,33 @@ interface Config {
|
|
|
13
13
|
const rl = readline.createInterface({ input, output });
|
|
14
14
|
const configPath = path.join(process.cwd(), 'mcpgql.config.json');
|
|
15
15
|
const headersPath = path.join(process.cwd(), 'headers.json');
|
|
16
|
+
const defaultConfigPath = path.join(__dirname, 'mcpgql.config.json');
|
|
17
|
+
|
|
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
|
+
}
|
|
16
36
|
|
|
17
|
-
// Initialize
|
|
18
|
-
if (!fs.existsSync(headersPath)) {
|
|
19
|
-
|
|
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
|
+
}
|
|
20
41
|
}
|
|
21
42
|
|
|
22
|
-
const config: Config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
23
|
-
const entries: [string, string][] = Object.entries(config.endpoints);
|
|
24
|
-
|
|
25
|
-
const selected = new Set<string>();
|
|
26
|
-
if (entries.length > 0) selected.add(entries[0][0]);
|
|
27
|
-
|
|
28
|
-
let allowMutations = false;
|
|
29
|
-
|
|
30
|
-
// Load headers
|
|
31
|
-
let headers = fs.readFileSync(headersPath, 'utf-8');
|
|
32
|
-
|
|
33
43
|
function getDistPath(): string {
|
|
34
44
|
let current = process.cwd();
|
|
35
45
|
while (true) {
|
|
@@ -42,7 +52,12 @@ function getDistPath(): string {
|
|
|
42
52
|
return path.join(path.dirname(require.resolve('@letoribo/mcp-graphql-enhanced/package.json')), 'dist/index.js');
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
async function startSelection(
|
|
55
|
+
async function startSelection(
|
|
56
|
+
entries: [string, string][],
|
|
57
|
+
selected: Set<string>,
|
|
58
|
+
allowMutations: { value: boolean },
|
|
59
|
+
headers: { value: string }
|
|
60
|
+
) {
|
|
46
61
|
const mutationIdx = entries.length + 1;
|
|
47
62
|
const headersIdx = entries.length + 2;
|
|
48
63
|
|
|
@@ -54,11 +69,11 @@ async function startSelection() {
|
|
|
54
69
|
});
|
|
55
70
|
|
|
56
71
|
console.log('--- Environment variables ---');
|
|
57
|
-
const mutationStatus = allowMutations ? '[YES]' : '[NO]';
|
|
72
|
+
const mutationStatus = allowMutations.value ? '[YES]' : '[NO]';
|
|
58
73
|
console.log(`${mutationStatus} ${mutationIdx}. Allow Mutations`);
|
|
59
74
|
|
|
60
75
|
let hasAuth = false;
|
|
61
|
-
try { if (Object.keys(JSON.parse(headers)).length > 0) hasAuth = true; } catch (e) {}
|
|
76
|
+
try { if (Object.keys(JSON.parse(headers.value)).length > 0) hasAuth = true; } catch (e) {}
|
|
62
77
|
console.log(`${hasAuth ? '[OK]' : '[ ]'} ${headersIdx}. Headers (Loaded from: ${headersPath})`);
|
|
63
78
|
|
|
64
79
|
const choice = await rl.question('\nEnter number to toggle (or Enter to proceed): ');
|
|
@@ -71,17 +86,17 @@ async function startSelection() {
|
|
|
71
86
|
const key = entries[idx][0];
|
|
72
87
|
selected.has(key) ? selected.delete(key) : selected.add(key);
|
|
73
88
|
} else if (choiceNum === mutationIdx) {
|
|
74
|
-
allowMutations = !allowMutations;
|
|
89
|
+
allowMutations.value = !allowMutations.value;
|
|
75
90
|
} else if (choiceNum === headersIdx) {
|
|
76
91
|
console.log('\n--- Paste JSON for headers.json ---');
|
|
77
|
-
console.log('Current: ' + headers);
|
|
92
|
+
console.log('Current: ' + headers.value);
|
|
78
93
|
const input = await rl.question('Paste new JSON (e.g. {"Authorization": "..."}): ');
|
|
79
94
|
|
|
80
95
|
if (input.trim() !== '') {
|
|
81
96
|
try {
|
|
82
97
|
const parsed = JSON.parse(input);
|
|
83
98
|
fs.writeFileSync(headersPath, JSON.stringify(parsed, null, 2));
|
|
84
|
-
headers = JSON.stringify(parsed);
|
|
99
|
+
headers.value = JSON.stringify(parsed);
|
|
85
100
|
console.log('[INFO] headers.json successfully updated.');
|
|
86
101
|
} catch (e) {
|
|
87
102
|
console.log('[ERROR] Invalid JSON! Update aborted.');
|
|
@@ -93,7 +108,17 @@ async function startSelection() {
|
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
async function main() {
|
|
96
|
-
await
|
|
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') };
|
|
120
|
+
|
|
121
|
+
await startSelection(entries, selected, allowMutations, headers);
|
|
97
122
|
|
|
98
123
|
const selectedUrls = Array.from(selected).map(k => config.endpoints[k]);
|
|
99
124
|
if (selectedUrls.length === 0) {
|
|
@@ -109,8 +134,8 @@ async function main() {
|
|
|
109
134
|
const spawnEnv: Record<string, string> = {
|
|
110
135
|
...(process.env as Record<string, string>),
|
|
111
136
|
ENDPOINT: selectedUrls.join(','),
|
|
112
|
-
ALLOW_MUTATIONS: String(allowMutations),
|
|
113
|
-
HEADERS: headers
|
|
137
|
+
ALLOW_MUTATIONS: String(allowMutations.value),
|
|
138
|
+
HEADERS: headers.value
|
|
114
139
|
};
|
|
115
140
|
|
|
116
141
|
console.log(`[INFO] Launching with ENDPOINT=${spawnEnv.ENDPOINT}`);
|