@loopops/mcp-server 2.0.1 → 2.0.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/dist/api-client.js +31 -2
- package/dist/tools/eng.js +2 -20
- package/package.json +1 -1
package/dist/api-client.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* updater helper, and also keep the latest rotated token in memory
|
|
21
21
|
* in case Claude Desktop doesn't restart before next use.
|
|
22
22
|
*/
|
|
23
|
-
import { homedir } from "node:os";
|
|
23
|
+
import { homedir, platform } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
26
26
|
const apiUrl = process.env.API_URL;
|
|
@@ -152,11 +152,37 @@ function isRetryable(err) {
|
|
|
152
152
|
return true;
|
|
153
153
|
return false;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* OS-specific location Claude Desktop reads its config from. We have
|
|
157
|
+
* to write here in addition to ~/.mcp.json, because Claude Desktop
|
|
158
|
+
* does NOT read ~/.mcp.json on macOS (it reads Application Support
|
|
159
|
+
* instead). If we only update ~/.mcp.json + ~/.claude/settings.json,
|
|
160
|
+
* Claude Desktop falls behind after every rotation and breaks with
|
|
161
|
+
* "invalid_grant" on its next cold start.
|
|
162
|
+
*/
|
|
163
|
+
function claudeDesktopConfigPath() {
|
|
164
|
+
const home = homedir();
|
|
165
|
+
switch (platform()) {
|
|
166
|
+
case "darwin":
|
|
167
|
+
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
168
|
+
case "win32": {
|
|
169
|
+
const appdata = process.env.APPDATA;
|
|
170
|
+
return appdata
|
|
171
|
+
? join(appdata, "Claude", "claude_desktop_config.json")
|
|
172
|
+
: null;
|
|
173
|
+
}
|
|
174
|
+
case "linux":
|
|
175
|
+
return join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
176
|
+
default:
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
155
180
|
/**
|
|
156
181
|
* Persist a rotated refresh token across every config location we know
|
|
157
182
|
* users store MCP settings in:
|
|
158
|
-
* - ~/.mcp.json (Claude Desktop)
|
|
183
|
+
* - ~/.mcp.json (CLI canonical, also legacy Claude Desktop path)
|
|
159
184
|
* - ~/.claude/settings.json (Claude Code)
|
|
185
|
+
* - OS-specific Claude Desktop config (macOS/Windows/Linux)
|
|
160
186
|
*
|
|
161
187
|
* We update whichever files have the loop-operations stanza. Missing
|
|
162
188
|
* files are skipped silently. This mirrors @loopops/mcp-cli's
|
|
@@ -171,6 +197,9 @@ function persistRotatedRefreshToken(newToken) {
|
|
|
171
197
|
join(homedir(), ".mcp.json"),
|
|
172
198
|
join(homedir(), ".claude", "settings.json"),
|
|
173
199
|
];
|
|
200
|
+
const desktop = claudeDesktopConfigPath();
|
|
201
|
+
if (desktop)
|
|
202
|
+
paths.push(desktop);
|
|
174
203
|
for (const path of paths) {
|
|
175
204
|
try {
|
|
176
205
|
if (!existsSync(path))
|
package/dist/tools/eng.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
import { trpcMutation, trpcQuery } from "../api-client.js";
|
|
2
|
+
import { trpcQuery } from "../api-client.js";
|
|
4
3
|
import { safeTool } from "./_helpers.js";
|
|
5
4
|
import { rangeSchema } from "./_schemas.js";
|
|
6
5
|
export function registerEngTools(server, allowed) {
|
|
@@ -11,25 +10,8 @@ export function registerEngTools(server, allowed) {
|
|
|
11
10
|
.describe("Time window. Short ranges (1h, 6h, 24h, 7d) are most useful. Default: 24h."),
|
|
12
11
|
}, safeTool(async ({ range }) => trpcQuery("mcp.systemDiagnostics", { range })));
|
|
13
12
|
}
|
|
14
|
-
if (allowed.has("revoke_api_key")) {
|
|
15
|
-
server.tool("revoke_api_key", "Immediately revoke every active MCP API key for a user. The user's loop_sk_* tokens in Claude Desktop stop working on the next tRPC call (401). Use for urgent access cutoff (suspected compromise, termination not yet processed in Okta). Routine offboarding should just deactivate the user in Okta — the 5-min reconciler picks that up.", {
|
|
16
|
-
email: z
|
|
17
|
-
.string()
|
|
18
|
-
.email()
|
|
19
|
-
.describe("Email of the Loop user to revoke."),
|
|
20
|
-
reason: z
|
|
21
|
-
.string()
|
|
22
|
-
.min(8)
|
|
23
|
-
.describe("Why you're revoking. Required — recorded in audit_log."),
|
|
24
|
-
}, safeTool(async ({ email, reason }) => trpcMutation("mcp.revokeApiKey", { email, reason })));
|
|
25
|
-
}
|
|
26
13
|
if (allowed.has("access_review")) {
|
|
27
|
-
server.tool("access_review", "Produce a user-access snapshot for SOC/security review. Lists every Loop user with role, status, SF linkage,
|
|
28
|
-
includeRevoked: z
|
|
29
|
-
.boolean()
|
|
30
|
-
.optional()
|
|
31
|
-
.describe("If true, also include users whose API keys have been revoked (for full historical picture). Default: false (active access only)."),
|
|
32
|
-
}, safeTool(async ({ includeRevoked }) => trpcQuery("mcp.accessReview", { includeRevoked })));
|
|
14
|
+
server.tool("access_review", "Produce a user-access snapshot for SOC/security review. Lists every active Loop user with role, status, SF linkage, and last-active date. The review itself is audited. Intended for quarterly access reviews. For token-level detail (issued/rotated/revoked), query the Okta system log directly.", {}, safeTool(async () => trpcQuery("mcp.accessReview")));
|
|
33
15
|
}
|
|
34
16
|
if (allowed.has("sync_local")) {
|
|
35
17
|
server.tool("sync_local", "Pull the latest changes from the remote repo into your local working tree. Runs `git pull --ff-only` in the repo at $LOOP_REPO_PATH. Use this after update_config / deploy_config writes commits directly to the remote branch so your local clone catches up.", {}, safeTool(async () => {
|