applaunchflow 0.3.8 → 0.3.9
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 +33 -0
- package/build/cli-core.js +25 -0
- package/build/cli-core.test.js +31 -1
- package/build/cli.js +38 -1
- package/build/cli.test.js +57 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,23 @@ codex mcp get applaunchflow
|
|
|
31
31
|
codex mcp remove applaunchflow
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
### Claude Code
|
|
35
|
+
|
|
36
|
+
The same one-command setup is available for Claude Code. It replaces a legacy
|
|
37
|
+
AppLaunchFlow entry when necessary, adds the hosted HTTP connector, and opens
|
|
38
|
+
OAuth sign-in:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx -y applaunchflow connect claude
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The equivalent native Claude Code commands are:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
claude mcp add --transport http applaunchflow https://mcp.applaunchflow.com/mcp
|
|
48
|
+
claude mcp login applaunchflow
|
|
49
|
+
```
|
|
50
|
+
|
|
34
51
|
### ChatGPT
|
|
35
52
|
|
|
36
53
|
```bash
|
|
@@ -48,6 +65,22 @@ Use this Streamable HTTP endpoint and enable OAuth when prompted:
|
|
|
48
65
|
https://mcp.applaunchflow.com/mcp
|
|
49
66
|
```
|
|
50
67
|
|
|
68
|
+
### Claude Code plugin
|
|
69
|
+
|
|
70
|
+
This repository is also a distributable Claude Code plugin. Its
|
|
71
|
+
`.claude-plugin/plugin.json` manifest bundles the hosted OAuth connector from
|
|
72
|
+
`.mcp.json`, so users do not need to copy a server configuration manually.
|
|
73
|
+
|
|
74
|
+
To validate or try the plugin directly from a clone:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
claude plugin validate . --strict
|
|
78
|
+
claude --plugin-dir .
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Claude Code starts the hosted connector when the plugin is enabled and opens
|
|
82
|
+
the AppLaunchFlow OAuth flow when authentication is required.
|
|
83
|
+
|
|
51
84
|
## Hosted service
|
|
52
85
|
|
|
53
86
|
The public Streamable HTTP service uses OAuth 2.1 authorization code flow with
|
package/build/cli-core.js
CHANGED
|
@@ -30,3 +30,28 @@ export function isHostedCodexConfig(value) {
|
|
|
30
30
|
export function codexDisconnectArgs() {
|
|
31
31
|
return ["mcp", "remove", APPLAUNCHFLOW_MCP_NAME];
|
|
32
32
|
}
|
|
33
|
+
export function claudeAddArgs() {
|
|
34
|
+
return [
|
|
35
|
+
"mcp",
|
|
36
|
+
"add",
|
|
37
|
+
"--transport",
|
|
38
|
+
"http",
|
|
39
|
+
APPLAUNCHFLOW_MCP_NAME,
|
|
40
|
+
APPLAUNCHFLOW_MCP_URL,
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
export function claudeLoginArgs() {
|
|
44
|
+
return ["mcp", "login", APPLAUNCHFLOW_MCP_NAME];
|
|
45
|
+
}
|
|
46
|
+
export function claudeStatusArgs() {
|
|
47
|
+
return ["mcp", "get", APPLAUNCHFLOW_MCP_NAME];
|
|
48
|
+
}
|
|
49
|
+
export function claudeDisconnectArgs() {
|
|
50
|
+
return ["mcp", "remove", APPLAUNCHFLOW_MCP_NAME];
|
|
51
|
+
}
|
|
52
|
+
export function isHostedClaudeConfig(output) {
|
|
53
|
+
const normalized = output.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
54
|
+
const hasHttpTransport = /^\s*Type:\s*http\s*$/im.test(normalized);
|
|
55
|
+
const configuredUrl = normalized.match(/^\s*URL:\s*(\S+)\s*$/im)?.[1];
|
|
56
|
+
return hasHttpTransport && configuredUrl === APPLAUNCHFLOW_MCP_URL;
|
|
57
|
+
}
|
package/build/cli-core.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import { APPLAUNCHFLOW_MCP_NAME, APPLAUNCHFLOW_MCP_URL, codexAddArgs, codexDisconnectArgs, codexInspectArgs, codexLoginArgs, codexStatusArgs, isHostedCodexConfig, } from "./cli-core.js";
|
|
3
|
+
import { APPLAUNCHFLOW_MCP_NAME, APPLAUNCHFLOW_MCP_URL, claudeAddArgs, claudeDisconnectArgs, claudeLoginArgs, claudeStatusArgs, codexAddArgs, codexDisconnectArgs, codexInspectArgs, codexLoginArgs, codexStatusArgs, isHostedClaudeConfig, isHostedCodexConfig, } from "./cli-core.js";
|
|
4
4
|
test("Codex convenience commands use the hosted OAuth connector", () => {
|
|
5
5
|
assert.deepEqual(codexAddArgs(), [
|
|
6
6
|
"mcp",
|
|
@@ -23,6 +23,36 @@ test("Codex convenience commands use the hosted OAuth connector", () => {
|
|
|
23
23
|
"applaunchflow",
|
|
24
24
|
]);
|
|
25
25
|
});
|
|
26
|
+
test("Claude Code convenience commands use the hosted OAuth connector", () => {
|
|
27
|
+
assert.deepEqual(claudeAddArgs(), [
|
|
28
|
+
"mcp",
|
|
29
|
+
"add",
|
|
30
|
+
"--transport",
|
|
31
|
+
"http",
|
|
32
|
+
APPLAUNCHFLOW_MCP_NAME,
|
|
33
|
+
APPLAUNCHFLOW_MCP_URL,
|
|
34
|
+
]);
|
|
35
|
+
assert.deepEqual(claudeLoginArgs(), ["mcp", "login", "applaunchflow"]);
|
|
36
|
+
assert.deepEqual(claudeStatusArgs(), ["mcp", "get", "applaunchflow"]);
|
|
37
|
+
assert.deepEqual(claudeDisconnectArgs(), [
|
|
38
|
+
"mcp",
|
|
39
|
+
"remove",
|
|
40
|
+
"applaunchflow",
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
test("detects whether Claude Code already uses the hosted connector", () => {
|
|
44
|
+
assert.equal(isHostedClaudeConfig(`applaunchflow:
|
|
45
|
+
Scope: Local config
|
|
46
|
+
Type: http
|
|
47
|
+
URL: ${APPLAUNCHFLOW_MCP_URL}`), true);
|
|
48
|
+
assert.equal(isHostedClaudeConfig(`applaunchflow:
|
|
49
|
+
Type: stdio
|
|
50
|
+
Command: npx -y @applaunchflow/mcp@latest`), false);
|
|
51
|
+
assert.equal(isHostedClaudeConfig(`applaunchflow:
|
|
52
|
+
Type: http
|
|
53
|
+
URL: https://example.com/mcp`), false);
|
|
54
|
+
assert.equal(isHostedClaudeConfig(`\u001b[32mType: http\u001b[0m\nURL: ${APPLAUNCHFLOW_MCP_URL}`), true);
|
|
55
|
+
});
|
|
26
56
|
test("detects whether Codex already uses the hosted connector", () => {
|
|
27
57
|
assert.equal(isHostedCodexConfig({
|
|
28
58
|
transport: {
|
package/build/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { APPLAUNCHFLOW_MCP_URL, codexAddArgs, codexDisconnectArgs, codexInspectArgs, codexLoginArgs, codexStatusArgs, isHostedCodexConfig, } from "./cli-core.js";
|
|
3
|
+
import { APPLAUNCHFLOW_MCP_URL, claudeAddArgs, claudeDisconnectArgs, claudeLoginArgs, claudeStatusArgs, codexAddArgs, codexDisconnectArgs, codexInspectArgs, codexLoginArgs, codexStatusArgs, isHostedClaudeConfig, isHostedCodexConfig, } from "./cli-core.js";
|
|
4
4
|
function printHelp() {
|
|
5
5
|
console.log(`AppLaunchFlow MCP
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
8
|
applaunchflow connect codex
|
|
9
|
+
applaunchflow connect claude
|
|
9
10
|
applaunchflow connect chatgpt
|
|
10
11
|
applaunchflow status
|
|
11
12
|
applaunchflow disconnect
|
|
@@ -20,6 +21,15 @@ function runCodex(args) {
|
|
|
20
21
|
throw new Error(`Codex exited with status ${result.status ?? "unknown"}`);
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
function runClaude(args) {
|
|
25
|
+
const result = spawnSync("claude", args, { stdio: "inherit" });
|
|
26
|
+
if (result.error) {
|
|
27
|
+
throw new Error(`Could not run Claude Code: ${result.error.message}`);
|
|
28
|
+
}
|
|
29
|
+
if (result.status !== 0) {
|
|
30
|
+
throw new Error(`Claude Code exited with status ${result.status ?? "unknown"}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
23
33
|
function getCodexConfiguration() {
|
|
24
34
|
const result = spawnSync("codex", codexInspectArgs(), {
|
|
25
35
|
encoding: "utf8",
|
|
@@ -51,6 +61,29 @@ function connectCodex() {
|
|
|
51
61
|
}
|
|
52
62
|
runCodex(codexLoginArgs());
|
|
53
63
|
}
|
|
64
|
+
function getClaudeConfiguration() {
|
|
65
|
+
const result = spawnSync("claude", claudeStatusArgs(), {
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
68
|
+
});
|
|
69
|
+
if (result.error) {
|
|
70
|
+
throw new Error(`Could not run Claude Code: ${result.error.message}`);
|
|
71
|
+
}
|
|
72
|
+
if (result.status !== 0)
|
|
73
|
+
return "missing";
|
|
74
|
+
return isHostedClaudeConfig(result.stdout) ? "hosted" : "legacy";
|
|
75
|
+
}
|
|
76
|
+
function connectClaude() {
|
|
77
|
+
const configuration = getClaudeConfiguration();
|
|
78
|
+
if (configuration === "legacy") {
|
|
79
|
+
console.log("Replacing the legacy local AppLaunchFlow MCP configuration...");
|
|
80
|
+
runClaude(claudeDisconnectArgs());
|
|
81
|
+
}
|
|
82
|
+
if (configuration !== "hosted") {
|
|
83
|
+
runClaude(claudeAddArgs());
|
|
84
|
+
}
|
|
85
|
+
runClaude(claudeLoginArgs());
|
|
86
|
+
}
|
|
54
87
|
function connectChatGpt() {
|
|
55
88
|
console.log(`Add a custom MCP connector in ChatGPT using this URL:\n${APPLAUNCHFLOW_MCP_URL}`);
|
|
56
89
|
}
|
|
@@ -60,6 +93,10 @@ function main() {
|
|
|
60
93
|
connectCodex();
|
|
61
94
|
return;
|
|
62
95
|
}
|
|
96
|
+
if (command === "connect" && target === "claude") {
|
|
97
|
+
connectClaude();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
63
100
|
if (command === "connect" && target === "chatgpt") {
|
|
64
101
|
connectChatGpt();
|
|
65
102
|
return;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { APPLAUNCHFLOW_MCP_URL } from "./cli-core.js";
|
|
9
|
+
async function runConnectClaude(getOutput, getStatus = 0) {
|
|
10
|
+
const directory = await mkdtemp(join(tmpdir(), "applaunchflow-cli-"));
|
|
11
|
+
const executable = join(directory, "claude");
|
|
12
|
+
const logPath = join(directory, "claude.log");
|
|
13
|
+
await writeFile(executable, `#!/bin/sh
|
|
14
|
+
printf '%s\\n' "$*" >> "$CLAUDE_TEST_LOG"
|
|
15
|
+
if [ "$*" = "mcp get applaunchflow" ]; then
|
|
16
|
+
printf '%s' "$CLAUDE_GET_OUTPUT"
|
|
17
|
+
exit "$CLAUDE_GET_STATUS"
|
|
18
|
+
fi
|
|
19
|
+
`);
|
|
20
|
+
await chmod(executable, 0o755);
|
|
21
|
+
try {
|
|
22
|
+
const cliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
23
|
+
const result = spawnSync(process.execPath, [cliPath, "connect", "claude"], {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
PATH: `${directory}:${process.env.PATH ?? ""}`,
|
|
28
|
+
CLAUDE_TEST_LOG: logPath,
|
|
29
|
+
CLAUDE_GET_OUTPUT: getOutput,
|
|
30
|
+
CLAUDE_GET_STATUS: String(getStatus),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
assert.equal(result.status, 0, result.stderr);
|
|
34
|
+
return (await readFile(logPath, "utf8")).trim().split("\n");
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await rm(directory, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
test("Claude helper adds a missing connector and starts OAuth", async () => {
|
|
41
|
+
assert.deepEqual(await runConnectClaude("", 1), [
|
|
42
|
+
"mcp get applaunchflow",
|
|
43
|
+
`mcp add --transport http applaunchflow ${APPLAUNCHFLOW_MCP_URL}`,
|
|
44
|
+
"mcp login applaunchflow",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
test("Claude helper replaces a legacy connector before starting OAuth", async () => {
|
|
48
|
+
assert.deepEqual(await runConnectClaude("Type: stdio\nCommand: npx legacy-server"), [
|
|
49
|
+
"mcp get applaunchflow",
|
|
50
|
+
"mcp remove applaunchflow",
|
|
51
|
+
`mcp add --transport http applaunchflow ${APPLAUNCHFLOW_MCP_URL}`,
|
|
52
|
+
"mcp login applaunchflow",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
test("Claude helper keeps the hosted connector and refreshes OAuth", async () => {
|
|
56
|
+
assert.deepEqual(await runConnectClaude(`Type: http\nURL: ${APPLAUNCHFLOW_MCP_URL}\nStatus: Connected`), ["mcp get applaunchflow", "mcp login applaunchflow"]);
|
|
57
|
+
});
|