@infigure/mcp-bridge 1.0.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +456 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Infigure
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @infigure/mcp-bridge
|
|
2
|
+
|
|
3
|
+
A local stdin/stdout JSON-RPC proxy for the Infigure MCP server.
|
|
4
|
+
|
|
5
|
+
Since local AI clients (such as Claude Code, Claude Desktop, or Codex) communicate via stdio streams, this bridge translates stdio JSON-RPC payloads into HTTP requests forwarded to the remote Infigure API.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
### 1. Configure Credentials
|
|
12
|
+
Run the interactive setup to configure target host URL, Personal Access Token (PAT), and default Team ID:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @infigure/mcp-bridge configure
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This saves configurations securely to `~/.infigure/credentials` with `0o600` permissions.
|
|
19
|
+
|
|
20
|
+
### 2. Verify Connection
|
|
21
|
+
Verify the connection and authentication state by running:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @infigure/mcp-bridge whoami
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 3. Automatically Install to AI Clients
|
|
28
|
+
Register the server config with your client config file automatically:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Register with Claude Code (~/.claude.json)
|
|
32
|
+
npx @infigure/mcp-bridge install claude-code
|
|
33
|
+
|
|
34
|
+
# Register with Claude Desktop (macOS, Windows, or Linux configs)
|
|
35
|
+
npx @infigure/mcp-bridge install claude-desktop
|
|
36
|
+
|
|
37
|
+
# Register with Codex (~/.codex/config.toml)
|
|
38
|
+
npx @infigure/mcp-bridge install codex
|
|
39
|
+
|
|
40
|
+
# Register with ALL available clients
|
|
41
|
+
npx @infigure/mcp-bridge install all
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Manual Integration Configs
|
|
47
|
+
|
|
48
|
+
If you prefer to update client configs manually, use the following configurations:
|
|
49
|
+
|
|
50
|
+
### Claude Desktop
|
|
51
|
+
Target file: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"infigure": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": [
|
|
59
|
+
"-y",
|
|
60
|
+
"@infigure/mcp-bridge"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Cursor
|
|
68
|
+
Add new MCP server:
|
|
69
|
+
* **Name**: `infigure`
|
|
70
|
+
* **Type**: `stdio`
|
|
71
|
+
* **Command**: `npx -y @infigure/mcp-bridge`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Command Interface
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
infigure-mcp [command] [options]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Commands
|
|
82
|
+
* **`serve`** (default): Starts standard input/output JSON-RPC proxy.
|
|
83
|
+
* **`configure`**: Prompts for credentials.
|
|
84
|
+
* **`whoami`**: Validates credentials with the server.
|
|
85
|
+
* **`install <name>`**: Configures client. Options: `claude-code`, `claude-desktop`, `codex`, `all`.
|
|
86
|
+
|
|
87
|
+
### Options
|
|
88
|
+
* `-u, --url <url>`: Target URL (default: `https://www.infigured.com`).
|
|
89
|
+
* `-t, --token <token>`: Personal Access Token (PAT).
|
|
90
|
+
* `-m, --team-id <id>`: Workspace Team ID.
|
|
91
|
+
* `-v, --verbose`: Log JSON-RPC traffic on stderr.
|
|
92
|
+
* `-h, --help`: Output help.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Debugging
|
|
97
|
+
|
|
98
|
+
To trace JSON-RPC messages and check API status logs, run:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx @infigure/mcp-bridge --verbose
|
|
102
|
+
```
|
|
103
|
+
*Note: All diagnostics and connection states are logged to `stderr` to prevent JSON-RPC stream corruption.*
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
const CREDENTIALS_PATH = path.join(os.homedir(), ".infigure", "credentials");
|
|
7
|
+
const DEFAULT_URL = "https://www.infigured.com";
|
|
8
|
+
const USAGE = `Infigure MCP Bridge
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
npx @infigure/mcp-bridge [command] [options]
|
|
12
|
+
infigure-mcp [command] [options]
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
serve Start stdio-to-HTTP JSON-RPC proxy (default)
|
|
16
|
+
configure Set up ~/.infigure/credentials file
|
|
17
|
+
whoami Verify target URL and authentication status
|
|
18
|
+
install <name> Register config with local client (claude-code | claude-desktop | codex | all)
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-u, --url <url> API URL (default: ${DEFAULT_URL})
|
|
22
|
+
-t, --token <token> Personal Access Token (PAT)
|
|
23
|
+
-m, --team-id <id> Team/Workspace ID
|
|
24
|
+
-v, --verbose Log JSON-RPC traffic to stderr
|
|
25
|
+
-h, --help Show help
|
|
26
|
+
`;
|
|
27
|
+
function parseArgs() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const config = {
|
|
30
|
+
command: "serve",
|
|
31
|
+
installClient: "",
|
|
32
|
+
url: "",
|
|
33
|
+
token: "",
|
|
34
|
+
teamId: "",
|
|
35
|
+
verbose: false,
|
|
36
|
+
help: false,
|
|
37
|
+
};
|
|
38
|
+
if (args[0] === "configure") {
|
|
39
|
+
config.command = "configure";
|
|
40
|
+
args.shift();
|
|
41
|
+
}
|
|
42
|
+
else if (args[0] === "whoami") {
|
|
43
|
+
config.command = "whoami";
|
|
44
|
+
args.shift();
|
|
45
|
+
}
|
|
46
|
+
else if (args[0] === "install") {
|
|
47
|
+
config.command = "install";
|
|
48
|
+
args.shift();
|
|
49
|
+
config.installClient = args[0] || "";
|
|
50
|
+
if (config.installClient && !config.installClient.startsWith("-")) {
|
|
51
|
+
args.shift();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (args[0] === "serve") {
|
|
55
|
+
config.command = "serve";
|
|
56
|
+
args.shift();
|
|
57
|
+
}
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const arg = args[i];
|
|
60
|
+
if (arg === "--help" || arg === "-h") {
|
|
61
|
+
config.help = true;
|
|
62
|
+
}
|
|
63
|
+
else if (arg === "--verbose" || arg === "-v") {
|
|
64
|
+
config.verbose = true;
|
|
65
|
+
}
|
|
66
|
+
else if (arg.startsWith("--url=")) {
|
|
67
|
+
config.url = arg.substring(6);
|
|
68
|
+
}
|
|
69
|
+
else if (arg === "--url" || arg === "-u") {
|
|
70
|
+
config.url = args[++i] || "";
|
|
71
|
+
}
|
|
72
|
+
else if (arg.startsWith("--token=")) {
|
|
73
|
+
config.token = arg.substring(8);
|
|
74
|
+
}
|
|
75
|
+
else if (arg === "--token" || arg === "-t") {
|
|
76
|
+
config.token = args[++i] || "";
|
|
77
|
+
}
|
|
78
|
+
else if (arg.startsWith("--team-id=")) {
|
|
79
|
+
config.teamId = arg.substring(10);
|
|
80
|
+
}
|
|
81
|
+
else if (arg === "--team-id" || arg === "-m") {
|
|
82
|
+
config.teamId = args[++i] || "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return config;
|
|
86
|
+
}
|
|
87
|
+
function loadCredentialsFile() {
|
|
88
|
+
if (fs.existsSync(CREDENTIALS_PATH)) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf8"));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.error(`[Infigure MCP] Warning: Failed to parse credentials: ${e}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
function resolveCredentials(cliConfig) {
|
|
99
|
+
const envBaseUrl = process.env.INFIGURE_BASE_URL;
|
|
100
|
+
const envToken = process.env.INFIGURE_TOKEN;
|
|
101
|
+
const envTeamId = process.env.INFIGURE_TEAM_ID;
|
|
102
|
+
const fileData = loadCredentialsFile();
|
|
103
|
+
const baseUrl = cliConfig.url || envBaseUrl || fileData.base_url || DEFAULT_URL;
|
|
104
|
+
const token = cliConfig.token || envToken || fileData.token || "";
|
|
105
|
+
const teamId = cliConfig.teamId || envTeamId || fileData.team_id || "";
|
|
106
|
+
return {
|
|
107
|
+
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
108
|
+
token,
|
|
109
|
+
teamId,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function ensureCredentialsOrExit(creds) {
|
|
113
|
+
if (!creds.token || !creds.teamId) {
|
|
114
|
+
console.error("Error: Credentials or Team ID not configured.\n\n" +
|
|
115
|
+
"Provide them via args, env vars, or run:\n" +
|
|
116
|
+
" infigure-mcp configure");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function askQuestion(query) {
|
|
121
|
+
const rl = readline.createInterface({
|
|
122
|
+
input: process.stdin,
|
|
123
|
+
output: process.stdout,
|
|
124
|
+
});
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
rl.question(query, (ans) => {
|
|
127
|
+
rl.close();
|
|
128
|
+
resolve(ans.trim());
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async function runConfigure() {
|
|
133
|
+
const current = loadCredentialsFile();
|
|
134
|
+
const baseUrl = await askQuestion(`Infigure URL [${current.base_url || DEFAULT_URL}]: `);
|
|
135
|
+
const token = await askQuestion(`Personal Access Token [${current.token ? "********" : "none"}]: `);
|
|
136
|
+
const teamId = await askQuestion(`Default Team ID [${current.team_id || "none"}]: `);
|
|
137
|
+
const finalUrl = baseUrl || current.base_url || DEFAULT_URL;
|
|
138
|
+
const finalToken = token || current.token || "";
|
|
139
|
+
const finalTeamId = teamId || current.team_id || "";
|
|
140
|
+
if (!finalToken || !finalTeamId) {
|
|
141
|
+
console.error("Error: Token and Team ID are required.");
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const data = {
|
|
145
|
+
base_url: finalUrl.replace(/\/$/, ""),
|
|
146
|
+
token: finalToken,
|
|
147
|
+
team_id: finalTeamId,
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
const parentDir = path.dirname(CREDENTIALS_PATH);
|
|
151
|
+
if (!fs.existsSync(parentDir)) {
|
|
152
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(data, null, 2) + "\n", {
|
|
155
|
+
mode: 0o600,
|
|
156
|
+
});
|
|
157
|
+
try {
|
|
158
|
+
fs.chmodSync(CREDENTIALS_PATH, 0o600);
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
console.log(`Configuration saved to ${CREDENTIALS_PATH}`);
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
console.error(`Error: Failed to write credentials file: ${err.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function runWhoAmI(creds) {
|
|
169
|
+
ensureCredentialsOrExit(creds);
|
|
170
|
+
const checkUrl = `${creds.baseUrl}/api/agent/connect?teamId=${encodeURIComponent(creds.teamId)}`;
|
|
171
|
+
const userAgent = `infigure-mcp-bridge/1.0.0 (Node.js ${process.version}; ${process.platform}; ${process.arch})`;
|
|
172
|
+
console.log(`URL: ${creds.baseUrl}`);
|
|
173
|
+
console.log(`Team ID: ${creds.teamId}`);
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(checkUrl, {
|
|
176
|
+
headers: {
|
|
177
|
+
"Authorization": `Bearer ${creds.token}`,
|
|
178
|
+
"Accept": "application/json",
|
|
179
|
+
"User-Agent": userAgent,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
console.error(`Authentication failed: HTTP ${response.status} (${response.statusText})`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const payload = (await response.json());
|
|
187
|
+
if (payload.authenticated) {
|
|
188
|
+
console.log("Authentication successful.");
|
|
189
|
+
if (payload.readiness) {
|
|
190
|
+
console.log(`Status: ${payload.readiness.ok ? "Ready" : "Checks failing"}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.error("Authentication failed: Invalid token or unauthorized team membership.");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error(`Connection error: ${err.message}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function resolveInstallTargets(client) {
|
|
204
|
+
const targets = [];
|
|
205
|
+
const normalized = client.toLowerCase().trim();
|
|
206
|
+
if (normalized === "claude-code" || normalized === "all") {
|
|
207
|
+
targets.push({
|
|
208
|
+
name: "Claude Code",
|
|
209
|
+
path: path.join(os.homedir(), ".claude.json"),
|
|
210
|
+
format: "json",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (normalized === "claude-desktop" || normalized === "all") {
|
|
214
|
+
let desktopPath = "";
|
|
215
|
+
if (process.platform === "win32") {
|
|
216
|
+
desktopPath = process.env.APPDATA
|
|
217
|
+
? path.join(process.env.APPDATA, "Claude", "claude_desktop_config.json")
|
|
218
|
+
: "";
|
|
219
|
+
}
|
|
220
|
+
else if (process.platform === "darwin") {
|
|
221
|
+
desktopPath = path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
desktopPath = path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
225
|
+
}
|
|
226
|
+
if (desktopPath) {
|
|
227
|
+
targets.push({
|
|
228
|
+
name: "Claude Desktop",
|
|
229
|
+
path: desktopPath,
|
|
230
|
+
format: "json",
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (normalized === "codex" || normalized === "all") {
|
|
235
|
+
targets.push({
|
|
236
|
+
name: "Codex",
|
|
237
|
+
path: path.join(os.homedir(), ".codex", "config.toml"),
|
|
238
|
+
format: "toml",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return targets;
|
|
242
|
+
}
|
|
243
|
+
async function runInstall(client) {
|
|
244
|
+
if (!client) {
|
|
245
|
+
console.error("Usage: infigure-mcp install [claude-code | claude-desktop | codex | all]");
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
const dummyConfig = {
|
|
249
|
+
command: "install",
|
|
250
|
+
installClient: client,
|
|
251
|
+
url: "",
|
|
252
|
+
token: "",
|
|
253
|
+
teamId: "",
|
|
254
|
+
verbose: false,
|
|
255
|
+
help: false,
|
|
256
|
+
};
|
|
257
|
+
const creds = resolveCredentials(dummyConfig);
|
|
258
|
+
ensureCredentialsOrExit(creds);
|
|
259
|
+
const targets = resolveInstallTargets(client);
|
|
260
|
+
if (targets.length === 0) {
|
|
261
|
+
console.error(`Error: Unknown target "${client}".`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
for (const target of targets) {
|
|
265
|
+
console.log(`Writing config to ${target.path}...`);
|
|
266
|
+
try {
|
|
267
|
+
fs.mkdirSync(path.dirname(target.path), { recursive: true });
|
|
268
|
+
if (target.format === "json") {
|
|
269
|
+
let configData = {};
|
|
270
|
+
if (fs.existsSync(target.path)) {
|
|
271
|
+
try {
|
|
272
|
+
configData = JSON.parse(fs.readFileSync(target.path, "utf8"));
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
console.error(` Warning: Failed to parse ${target.path}: ${e}. Overwriting.`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!configData.mcpServers || typeof configData.mcpServers !== "object") {
|
|
279
|
+
configData.mcpServers = {};
|
|
280
|
+
}
|
|
281
|
+
configData.mcpServers["infigure"] = {
|
|
282
|
+
command: "npx",
|
|
283
|
+
args: ["-y", "@infigure/mcp-bridge"],
|
|
284
|
+
env: {
|
|
285
|
+
INFIGURE_BASE_URL: creds.baseUrl,
|
|
286
|
+
INFIGURE_TOKEN: creds.token,
|
|
287
|
+
INFIGURE_TEAM_ID: creds.teamId,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
fs.writeFileSync(target.path, JSON.stringify(configData, null, 2) + "\n", "utf8");
|
|
291
|
+
console.log(" Registered successfully.");
|
|
292
|
+
}
|
|
293
|
+
else if (target.format === "toml") {
|
|
294
|
+
let content = "";
|
|
295
|
+
if (fs.existsSync(target.path)) {
|
|
296
|
+
content = fs.readFileSync(target.path, "utf8");
|
|
297
|
+
}
|
|
298
|
+
const entry = `\n[mcp_servers.infigure]\n` +
|
|
299
|
+
`command = "npx"\n` +
|
|
300
|
+
`args = ["-y", "@infigure/mcp-bridge"]\n` +
|
|
301
|
+
`[mcp_servers.infigure.env]\n` +
|
|
302
|
+
`INFIGURE_BASE_URL = "${creds.baseUrl}"\n` +
|
|
303
|
+
`INFIGURE_TOKEN = "${creds.token}"\n` +
|
|
304
|
+
`INFIGURE_TEAM_ID = "${creds.teamId}"\n`;
|
|
305
|
+
if (content.includes("[mcp_servers.infigure]")) {
|
|
306
|
+
console.log(` Already registered.`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
fs.writeFileSync(target.path, content + entry, "utf8");
|
|
310
|
+
console.log(" Registered successfully.");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
console.error(` Error installing: ${err.message}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function handleJsonRpc(payload, creds, cliConfig, userAgent) {
|
|
320
|
+
const mcpUrl = `${creds.baseUrl}/api/agent/mcp`;
|
|
321
|
+
if (payload.params &&
|
|
322
|
+
typeof payload.params === "object" &&
|
|
323
|
+
!Array.isArray(payload.params)) {
|
|
324
|
+
if (payload.params.teamId === undefined || payload.params.teamId === null) {
|
|
325
|
+
payload.params.teamId = creds.teamId;
|
|
326
|
+
}
|
|
327
|
+
if (payload.method === "tools/call") {
|
|
328
|
+
const args = payload.params.arguments;
|
|
329
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
330
|
+
if (args.teamId === undefined || args.teamId === null) {
|
|
331
|
+
args.teamId = creds.teamId;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const controller = new AbortController();
|
|
337
|
+
const timeoutId = setTimeout(() => controller.abort(), 60000);
|
|
338
|
+
try {
|
|
339
|
+
const response = await fetch(mcpUrl, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: {
|
|
342
|
+
"Authorization": `Bearer ${creds.token}`,
|
|
343
|
+
"Content-Type": "application/json",
|
|
344
|
+
"Accept": "application/json",
|
|
345
|
+
"User-Agent": userAgent,
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify(payload),
|
|
348
|
+
signal: controller.signal,
|
|
349
|
+
});
|
|
350
|
+
const responseText = await response.text();
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
try {
|
|
353
|
+
const parsed = JSON.parse(responseText);
|
|
354
|
+
if (parsed.error) {
|
|
355
|
+
console.log(responseText.trim());
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch { }
|
|
360
|
+
const errResp = {
|
|
361
|
+
jsonrpc: "2.0",
|
|
362
|
+
id: payload.id ?? null,
|
|
363
|
+
error: {
|
|
364
|
+
code: -32603,
|
|
365
|
+
message: `HTTP error ${response.status}: ${response.statusText}`,
|
|
366
|
+
data: { body: responseText },
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
console.log(JSON.stringify(errResp));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.log(responseText.trim());
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
if (cliConfig.verbose) {
|
|
376
|
+
console.error(`[Infigure MCP] Connection error (id: ${payload.id}): ${err.message}`);
|
|
377
|
+
}
|
|
378
|
+
const isTimeout = err.name === "AbortError";
|
|
379
|
+
const errResp = {
|
|
380
|
+
jsonrpc: "2.0",
|
|
381
|
+
id: payload.id ?? null,
|
|
382
|
+
error: {
|
|
383
|
+
code: isTimeout ? -32000 : -32603,
|
|
384
|
+
message: isTimeout
|
|
385
|
+
? "Request timed out"
|
|
386
|
+
: `Connection error: ${err.message}`,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
console.log(JSON.stringify(errResp));
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
clearTimeout(timeoutId);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function runServe(creds, cliConfig) {
|
|
396
|
+
ensureCredentialsOrExit(creds);
|
|
397
|
+
const mcpUrl = `${creds.baseUrl}/api/agent/mcp`;
|
|
398
|
+
const userAgent = `infigure-mcp-bridge/1.0.0 (Node.js ${process.version}; ${process.platform}; ${process.arch})`;
|
|
399
|
+
if (cliConfig.verbose) {
|
|
400
|
+
console.error(`[Infigure MCP] Proxying to ${mcpUrl}`);
|
|
401
|
+
}
|
|
402
|
+
const rl = readline.createInterface({
|
|
403
|
+
input: process.stdin,
|
|
404
|
+
output: process.stdout,
|
|
405
|
+
terminal: false,
|
|
406
|
+
});
|
|
407
|
+
for await (const line of rl) {
|
|
408
|
+
const trimmed = line.trim();
|
|
409
|
+
if (!trimmed)
|
|
410
|
+
continue;
|
|
411
|
+
let payload;
|
|
412
|
+
try {
|
|
413
|
+
payload = JSON.parse(trimmed);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
const errResp = {
|
|
417
|
+
jsonrpc: "2.0",
|
|
418
|
+
id: null,
|
|
419
|
+
error: {
|
|
420
|
+
code: -32700,
|
|
421
|
+
message: `Parse error: ${err.message}`,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
console.log(JSON.stringify(errResp));
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
void handleJsonRpc(payload, creds, cliConfig, userAgent);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function main() {
|
|
431
|
+
const cliConfig = parseArgs();
|
|
432
|
+
if (cliConfig.help) {
|
|
433
|
+
console.log(USAGE);
|
|
434
|
+
process.exit(0);
|
|
435
|
+
}
|
|
436
|
+
const creds = resolveCredentials(cliConfig);
|
|
437
|
+
switch (cliConfig.command) {
|
|
438
|
+
case "configure":
|
|
439
|
+
await runConfigure();
|
|
440
|
+
break;
|
|
441
|
+
case "whoami":
|
|
442
|
+
await runWhoAmI(creds);
|
|
443
|
+
break;
|
|
444
|
+
case "install":
|
|
445
|
+
await runInstall(cliConfig.installClient);
|
|
446
|
+
break;
|
|
447
|
+
case "serve":
|
|
448
|
+
default:
|
|
449
|
+
await runServe(creds, cliConfig);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
main().catch((err) => {
|
|
454
|
+
console.error("[Infigure MCP] Fatal error:", err);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@infigure/mcp-bridge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Local stdio-to-HTTP Model Context Protocol (MCP) bridge for Infigure.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"infigure-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"infigure",
|
|
23
|
+
"ai",
|
|
24
|
+
"agent"
|
|
25
|
+
],
|
|
26
|
+
"author": "Infigure team",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.19.43",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|