@flomenco/claude-plugin-mcp 0.1.0 → 0.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flomenco/claude-plugin-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Flo Claude Co-Work plugin bridge for Claude Desktop/Code",
5
5
  "private": false,
6
6
  "license": "UNLICENSED",
@@ -22,6 +22,7 @@
22
22
  "type": "module",
23
23
  "files": [
24
24
  "src",
25
+ "tools",
25
26
  "README.md"
26
27
  ],
27
28
  "bin": {
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
5
  import {
@@ -0,0 +1,238 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { execFileSync } from "node:child_process";
5
+
6
+ const SERVER_KEY = "flo-plugin";
7
+
8
+ function defaultDesktopConfigPath() {
9
+ if (process.platform === "darwin") {
10
+ return path.join(
11
+ os.homedir(),
12
+ "Library",
13
+ "Application Support",
14
+ "Claude",
15
+ "claude_desktop_config.json"
16
+ );
17
+ }
18
+ if (process.platform === "win32") {
19
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
20
+ return path.join(appData, "Claude", "claude_desktop_config.json");
21
+ }
22
+ return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
23
+ }
24
+
25
+ async function readJson(filePath) {
26
+ try {
27
+ const raw = await readFile(filePath, "utf8");
28
+ return JSON.parse(raw);
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+
34
+ function normalizedPluginEnv() {
35
+ const raw = (process.env.FLO_PLUGIN_ENV || "dev").trim().toLowerCase();
36
+ if (raw === "prod" || raw === "production") {
37
+ return "prod";
38
+ }
39
+ if (raw === "stg" || raw === "stage" || raw === "staging") {
40
+ return "stg";
41
+ }
42
+ return "dev";
43
+ }
44
+
45
+ function defaultInvocationUrlForEnv() {
46
+ const env = normalizedPluginEnv();
47
+ if (env === "prod") {
48
+ return "https://plugin.floapp.co/invocations";
49
+ }
50
+ if (env === "stg") {
51
+ return "https://plugin.stg.floapp.co/invocations";
52
+ }
53
+ return "https://plugin.dev.floapp.co/invocations";
54
+ }
55
+
56
+ function defaultUserPoolNameForEnv() {
57
+ const env = normalizedPluginEnv();
58
+ if (env === "prod") {
59
+ return "flo-prod";
60
+ }
61
+ if (env === "stg") {
62
+ return "flo-stg";
63
+ }
64
+ return "flo-dev";
65
+ }
66
+
67
+ function defaultPluginClientNameForEnv() {
68
+ const env = normalizedPluginEnv();
69
+ if (env === "prod") {
70
+ return "flo-prod-claude-plugin";
71
+ }
72
+ if (env === "stg") {
73
+ return "flo-stg-claude-plugin";
74
+ }
75
+ return "flo-dev-claude-plugin";
76
+ }
77
+
78
+ function defaultSettingsUrlForEnv() {
79
+ const env = normalizedPluginEnv();
80
+ if (env === "prod") {
81
+ return "https://floapp.co/settings/api";
82
+ }
83
+ if (env === "stg") {
84
+ return "https://stg.floapp.co/settings/api";
85
+ }
86
+ return "https://dev.floapp.co/settings/api";
87
+ }
88
+
89
+ function runAwsJson(args) {
90
+ const profile = (process.env.AWS_PROFILE || "").trim();
91
+ const cmdArgs = [...args];
92
+ if (profile) {
93
+ cmdArgs.push("--profile", profile);
94
+ }
95
+ const stdout = execFileSync("aws", cmdArgs, {
96
+ encoding: "utf8",
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+ return JSON.parse(stdout);
100
+ }
101
+
102
+ function discoverOauthClient() {
103
+ const explicit = (process.env.FLO_OAUTH_CLIENT_ID || "").trim();
104
+ const region = (process.env.AWS_REGION || "us-east-1").trim();
105
+ const userPoolName = (
106
+ process.env.FLO_COGNITO_USER_POOL_NAME || defaultUserPoolNameForEnv()
107
+ ).trim();
108
+ const preferredClientName = (
109
+ process.env.FLO_COGNITO_PLUGIN_CLIENT_NAME || defaultPluginClientNameForEnv()
110
+ ).trim();
111
+ const fallbackClientName = (
112
+ process.env.FLO_COGNITO_FALLBACK_CLIENT_NAME || `${userPoolName}-spa`
113
+ ).trim();
114
+
115
+ if (explicit) {
116
+ return {
117
+ clientId: explicit,
118
+ clientName: "(manual)",
119
+ userPoolId: (process.env.FLO_OAUTH_USER_POOL_ID || "").trim(),
120
+ userPoolName,
121
+ region,
122
+ settingsUrl:
123
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
124
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
125
+ defaultSettingsUrlForEnv(),
126
+ };
127
+ }
128
+
129
+ const pools = runAwsJson([
130
+ "cognito-idp",
131
+ "list-user-pools",
132
+ "--region",
133
+ region,
134
+ "--max-results",
135
+ "60",
136
+ "--output",
137
+ "json",
138
+ ]);
139
+ const userPool = (pools.UserPools || []).find((p) => p?.Name === userPoolName);
140
+ if (!userPool?.Id) {
141
+ throw new Error(
142
+ `Could not find Cognito user pool "${userPoolName}". Set FLO_OAUTH_CLIENT_ID manually.`
143
+ );
144
+ }
145
+
146
+ const clients = runAwsJson([
147
+ "cognito-idp",
148
+ "list-user-pool-clients",
149
+ "--region",
150
+ region,
151
+ "--user-pool-id",
152
+ userPool.Id,
153
+ "--max-results",
154
+ "60",
155
+ "--output",
156
+ "json",
157
+ ]);
158
+ const list = clients.UserPoolClients || [];
159
+ const preferred = list.find((c) => c?.ClientName === preferredClientName);
160
+ const fallback = list.find((c) => c?.ClientName === fallbackClientName);
161
+ const picked = preferred || fallback;
162
+ if (!picked?.ClientId) {
163
+ throw new Error(
164
+ `Could not find Cognito app client "${preferredClientName}" (or "${fallbackClientName}") in pool "${userPoolName}". Set FLO_OAUTH_CLIENT_ID manually.`
165
+ );
166
+ }
167
+ return {
168
+ clientId: picked.ClientId,
169
+ clientName: picked.ClientName || preferredClientName,
170
+ userPoolId: userPool.Id,
171
+ userPoolName,
172
+ region,
173
+ settingsUrl:
174
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
175
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
176
+ defaultSettingsUrlForEnv(),
177
+ };
178
+ }
179
+
180
+ function resolveInvocationUrl() {
181
+ if ((process.env.FLO_INTERFACE_AGENT_INVOCATION_URL || "").trim()) {
182
+ return process.env.FLO_INTERFACE_AGENT_INVOCATION_URL.trim();
183
+ }
184
+ return defaultInvocationUrlForEnv();
185
+ }
186
+
187
+ async function resolveServerConfig() {
188
+ const rootDir = path.resolve(process.cwd());
189
+ const serverPath = path.join(rootDir, "packages", "flo-claude-plugin-mcp", "src", "index.js");
190
+
191
+ const invocationUrl = resolveInvocationUrl();
192
+ const authorizeUrl =
193
+ process.env.FLO_OAUTH_AUTHORIZE_URL ||
194
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/authorize";
195
+ const tokenUrl =
196
+ process.env.FLO_OAUTH_TOKEN_URL ||
197
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/token";
198
+ const oauthClient = discoverOauthClient();
199
+
200
+ return {
201
+ command: "node",
202
+ args: [serverPath],
203
+ env: {
204
+ FLO_INTERFACE_AGENT_INVOCATION_URL: invocationUrl,
205
+ FLO_PLUGIN_ENV: normalizedPluginEnv(),
206
+ FLO_OAUTH_AUTHORIZE_URL: authorizeUrl,
207
+ FLO_OAUTH_TOKEN_URL: tokenUrl,
208
+ FLO_OAUTH_CLIENT_ID: oauthClient.clientId,
209
+ FLO_OAUTH_USER_POOL_ID: oauthClient.userPoolId,
210
+ FLO_OAUTH_USER_POOL_NAME: oauthClient.userPoolName,
211
+ FLO_OAUTH_EXPECTED_CLIENT_NAME: oauthClient.clientName,
212
+ FLO_OAUTH_SETTINGS_URL: oauthClient.settingsUrl,
213
+ FLO_OAUTH_REDIRECT_URI:
214
+ process.env.FLO_OAUTH_REDIRECT_URI || "http://127.0.0.1:8787/callback",
215
+ FLO_OAUTH_SCOPES: process.env.FLO_OAUTH_SCOPES || "openid email profile",
216
+ },
217
+ };
218
+ }
219
+
220
+ async function main() {
221
+ const configPath = process.env.CLAUDE_DESKTOP_CONFIG_PATH || defaultDesktopConfigPath();
222
+ const config = await readJson(configPath);
223
+ config.mcpServers = config.mcpServers || {};
224
+ config.mcpServers[SERVER_KEY] = await resolveServerConfig();
225
+
226
+ await mkdir(path.dirname(configPath), { recursive: true });
227
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
228
+
229
+ console.log(`Installed "${SERVER_KEY}" into ${configPath}`);
230
+ const env = config.mcpServers[SERVER_KEY].env;
231
+ console.log(`OAuth client ID: ${env.FLO_OAUTH_CLIENT_ID}`);
232
+ console.log(`OAuth settings URL: ${env.FLO_OAUTH_SETTINGS_URL}`);
233
+ }
234
+
235
+ main().catch((err) => {
236
+ console.error(err instanceof Error ? err.message : String(err));
237
+ process.exitCode = 1;
238
+ });
@@ -0,0 +1,186 @@
1
+ import { execFileSync } from "node:child_process";
2
+
3
+ function normalizedPluginEnv() {
4
+ const raw = (process.env.FLO_PLUGIN_ENV || "dev").trim().toLowerCase();
5
+ if (raw === "prod" || raw === "production") {
6
+ return "prod";
7
+ }
8
+ if (raw === "stg" || raw === "stage" || raw === "staging") {
9
+ return "stg";
10
+ }
11
+ return "dev";
12
+ }
13
+
14
+ function defaultInvocationUrlForEnv() {
15
+ const env = normalizedPluginEnv();
16
+ if (env === "prod") {
17
+ return "https://plugin.floapp.co/invocations";
18
+ }
19
+ if (env === "stg") {
20
+ return "https://plugin.stg.floapp.co/invocations";
21
+ }
22
+ return "https://plugin.dev.floapp.co/invocations";
23
+ }
24
+
25
+ function defaultUserPoolNameForEnv() {
26
+ const env = normalizedPluginEnv();
27
+ if (env === "prod") {
28
+ return "flo-prod";
29
+ }
30
+ if (env === "stg") {
31
+ return "flo-stg";
32
+ }
33
+ return "flo-dev";
34
+ }
35
+
36
+ function defaultPluginClientNameForEnv() {
37
+ const env = normalizedPluginEnv();
38
+ if (env === "prod") {
39
+ return "flo-prod-claude-plugin";
40
+ }
41
+ if (env === "stg") {
42
+ return "flo-stg-claude-plugin";
43
+ }
44
+ return "flo-dev-claude-plugin";
45
+ }
46
+
47
+ function defaultSettingsUrlForEnv() {
48
+ const env = normalizedPluginEnv();
49
+ if (env === "prod") {
50
+ return "https://floapp.co/settings/api";
51
+ }
52
+ if (env === "stg") {
53
+ return "https://stg.floapp.co/settings/api";
54
+ }
55
+ return "https://dev.floapp.co/settings/api";
56
+ }
57
+
58
+ function runAwsJson(args) {
59
+ const profile = (process.env.AWS_PROFILE || "").trim();
60
+ const cmdArgs = [...args];
61
+ if (profile) {
62
+ cmdArgs.push("--profile", profile);
63
+ }
64
+ const stdout = execFileSync("aws", cmdArgs, {
65
+ encoding: "utf8",
66
+ stdio: ["ignore", "pipe", "pipe"],
67
+ });
68
+ return JSON.parse(stdout);
69
+ }
70
+
71
+ function discoverOauthClient() {
72
+ const explicit = (process.env.FLO_OAUTH_CLIENT_ID || "").trim();
73
+ const region = (process.env.AWS_REGION || "us-east-1").trim();
74
+ const userPoolName = (
75
+ process.env.FLO_COGNITO_USER_POOL_NAME || defaultUserPoolNameForEnv()
76
+ ).trim();
77
+ const preferredClientName = (
78
+ process.env.FLO_COGNITO_PLUGIN_CLIENT_NAME || defaultPluginClientNameForEnv()
79
+ ).trim();
80
+ const fallbackClientName = (
81
+ process.env.FLO_COGNITO_FALLBACK_CLIENT_NAME || `${userPoolName}-spa`
82
+ ).trim();
83
+ if (explicit) {
84
+ return {
85
+ clientId: explicit,
86
+ clientName: "(manual)",
87
+ userPoolId: (process.env.FLO_OAUTH_USER_POOL_ID || "").trim(),
88
+ userPoolName,
89
+ region,
90
+ settingsUrl:
91
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
92
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
93
+ defaultSettingsUrlForEnv(),
94
+ };
95
+ }
96
+
97
+ const pools = runAwsJson([
98
+ "cognito-idp",
99
+ "list-user-pools",
100
+ "--region",
101
+ region,
102
+ "--max-results",
103
+ "60",
104
+ "--output",
105
+ "json",
106
+ ]);
107
+ const userPool = (pools.UserPools || []).find((p) => p?.Name === userPoolName);
108
+ if (!userPool?.Id) {
109
+ throw new Error(`Unable to find user pool "${userPoolName}"`);
110
+ }
111
+
112
+ const clients = runAwsJson([
113
+ "cognito-idp",
114
+ "list-user-pool-clients",
115
+ "--region",
116
+ region,
117
+ "--user-pool-id",
118
+ userPool.Id,
119
+ "--max-results",
120
+ "60",
121
+ "--output",
122
+ "json",
123
+ ]);
124
+ const list = clients.UserPoolClients || [];
125
+ const preferred = list.find((c) => c?.ClientName === preferredClientName);
126
+ const fallback = list.find((c) => c?.ClientName === fallbackClientName);
127
+ const picked = preferred || fallback;
128
+ if (!picked?.ClientId) {
129
+ throw new Error(
130
+ `Unable to find app client "${preferredClientName}" or "${fallbackClientName}"`
131
+ );
132
+ }
133
+ return {
134
+ clientId: picked.ClientId,
135
+ clientName: picked.ClientName || preferredClientName,
136
+ userPoolId: userPool.Id,
137
+ userPoolName,
138
+ region,
139
+ settingsUrl:
140
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
141
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
142
+ defaultSettingsUrlForEnv(),
143
+ };
144
+ }
145
+
146
+ function resolveInvocationUrl() {
147
+ if ((process.env.FLO_INTERFACE_AGENT_INVOCATION_URL || "").trim()) {
148
+ return process.env.FLO_INTERFACE_AGENT_INVOCATION_URL.trim();
149
+ }
150
+ return defaultInvocationUrlForEnv();
151
+ }
152
+
153
+ async function main() {
154
+ const invocationUrl = resolveInvocationUrl();
155
+ const authorizeUrl =
156
+ process.env.FLO_OAUTH_AUTHORIZE_URL ||
157
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/authorize";
158
+ const tokenUrl =
159
+ process.env.FLO_OAUTH_TOKEN_URL ||
160
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/token";
161
+ const oauthClient = discoverOauthClient();
162
+ const redirectUri =
163
+ process.env.FLO_OAUTH_REDIRECT_URI || "http://127.0.0.1:8787/callback";
164
+ const scopes = process.env.FLO_OAUTH_SCOPES || "openid email profile";
165
+
166
+ const json = {
167
+ invocationUrl,
168
+ oauthAuthorizeUrl: authorizeUrl,
169
+ oauthTokenUrl: tokenUrl,
170
+ oauthClientId: oauthClient.clientId,
171
+ oauthClientName: oauthClient.clientName,
172
+ oauthUserPoolId: oauthClient.userPoolId,
173
+ oauthUserPoolName: oauthClient.userPoolName,
174
+ oauthSettingsUrl: oauthClient.settingsUrl,
175
+ oauthRedirectUri: redirectUri,
176
+ oauthScopes: scopes,
177
+ };
178
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
179
+ }
180
+
181
+ try {
182
+ await main();
183
+ } catch (err) {
184
+ console.error(err instanceof Error ? err.message : String(err));
185
+ process.exitCode = 1;
186
+ }
@@ -0,0 +1,48 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+
5
+ const SERVER_KEY = "flo-plugin";
6
+
7
+ function defaultDesktopConfigPath() {
8
+ if (process.platform === "darwin") {
9
+ return path.join(
10
+ os.homedir(),
11
+ "Library",
12
+ "Application Support",
13
+ "Claude",
14
+ "claude_desktop_config.json"
15
+ );
16
+ }
17
+ if (process.platform === "win32") {
18
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
19
+ return path.join(appData, "Claude", "claude_desktop_config.json");
20
+ }
21
+ return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
22
+ }
23
+
24
+ async function main() {
25
+ const configPath = process.env.CLAUDE_DESKTOP_CONFIG_PATH || defaultDesktopConfigPath();
26
+ let raw;
27
+ try {
28
+ raw = await readFile(configPath, "utf8");
29
+ } catch {
30
+ console.log(`No Claude config found at ${configPath}; nothing to uninstall.`);
31
+ return;
32
+ }
33
+
34
+ const config = JSON.parse(raw);
35
+ if (!config.mcpServers || !config.mcpServers[SERVER_KEY]) {
36
+ console.log(`"${SERVER_KEY}" not present in ${configPath}; nothing to uninstall.`);
37
+ return;
38
+ }
39
+
40
+ delete config.mcpServers[SERVER_KEY];
41
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
42
+ console.log(`Removed "${SERVER_KEY}" from ${configPath}`);
43
+ }
44
+
45
+ main().catch((err) => {
46
+ console.error(err instanceof Error ? err.message : String(err));
47
+ process.exitCode = 1;
48
+ });