lensmcp 1.18.0 → 1.18.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/bundled/dashboard.js +6 -0
- package/bundled/main.js +6 -0
- package/bundled/shim.js +49 -7
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/bundled/dashboard.js
CHANGED
|
@@ -17435,9 +17435,14 @@ var MAX_ERRORS = 50;
|
|
|
17435
17435
|
var STATE_BY_KIND = {
|
|
17436
17436
|
"service-starting": "starting",
|
|
17437
17437
|
"service-respawn": "starting",
|
|
17438
|
+
// A lens frontend's vite has been SPAWNED — started, not yet proven to serve.
|
|
17439
|
+
// The gateway's liveness probe emits `service-up` once its port accepts, so
|
|
17440
|
+
// this must claim `starting` and never `up`.
|
|
17441
|
+
"lens-app-up": "starting",
|
|
17438
17442
|
"service-up": "up",
|
|
17439
17443
|
"cold-start": "up",
|
|
17440
17444
|
"service-down": "down",
|
|
17445
|
+
"idle-kill": "idle",
|
|
17441
17446
|
"service-recycle": "wedged",
|
|
17442
17447
|
"pod-recycle": "wedged",
|
|
17443
17448
|
"service-crash-loop": "crash-looping"
|
|
@@ -17494,6 +17499,7 @@ var ClusterHealth = class {
|
|
|
17494
17499
|
down: byState("down"),
|
|
17495
17500
|
wedged: byState("wedged"),
|
|
17496
17501
|
crashLooping: byState("crash-looping"),
|
|
17502
|
+
idle: byState("idle"),
|
|
17497
17503
|
buildFailed: services.filter((s) => s.buildFailed).map((s) => s.project),
|
|
17498
17504
|
restarts: services.reduce((n, s) => n + s.restarts, 0),
|
|
17499
17505
|
errors: this.errors.slice()
|
package/bundled/main.js
CHANGED
|
@@ -17437,9 +17437,14 @@ var MAX_ERRORS = 50;
|
|
|
17437
17437
|
var STATE_BY_KIND = {
|
|
17438
17438
|
"service-starting": "starting",
|
|
17439
17439
|
"service-respawn": "starting",
|
|
17440
|
+
// A lens frontend's vite has been SPAWNED — started, not yet proven to serve.
|
|
17441
|
+
// The gateway's liveness probe emits `service-up` once its port accepts, so
|
|
17442
|
+
// this must claim `starting` and never `up`.
|
|
17443
|
+
"lens-app-up": "starting",
|
|
17440
17444
|
"service-up": "up",
|
|
17441
17445
|
"cold-start": "up",
|
|
17442
17446
|
"service-down": "down",
|
|
17447
|
+
"idle-kill": "idle",
|
|
17443
17448
|
"service-recycle": "wedged",
|
|
17444
17449
|
"pod-recycle": "wedged",
|
|
17445
17450
|
"service-crash-loop": "crash-looping"
|
|
@@ -17496,6 +17501,7 @@ var ClusterHealth = class {
|
|
|
17496
17501
|
down: byState("down"),
|
|
17497
17502
|
wedged: byState("wedged"),
|
|
17498
17503
|
crashLooping: byState("crash-looping"),
|
|
17504
|
+
idle: byState("idle"),
|
|
17499
17505
|
buildFailed: services.filter((s) => s.buildFailed).map((s) => s.project),
|
|
17500
17506
|
restarts: services.reduce((n, s) => n + s.restarts, 0),
|
|
17501
17507
|
errors: this.errors.slice()
|
package/bundled/shim.js
CHANGED
|
@@ -8493,6 +8493,9 @@ async function ensureSharedServer(deps) {
|
|
|
8493
8493
|
};
|
|
8494
8494
|
if (await deps.probe())
|
|
8495
8495
|
return live("already-running");
|
|
8496
|
+
const taken = await deps.foreignPortOwner?.();
|
|
8497
|
+
if (taken)
|
|
8498
|
+
return { outcome: "unavailable", reason: taken, waitedMs: elapsed() };
|
|
8496
8499
|
let holdsLock = deps.tryCreateLock(JSON.stringify({ pid: process.pid, at: deps.now() }));
|
|
8497
8500
|
if (!holdsLock) {
|
|
8498
8501
|
const existing = parseSpawnLock(deps.readLock());
|
|
@@ -8624,6 +8627,9 @@ function startSharedServerWatched(opts) {
|
|
|
8624
8627
|
}
|
|
8625
8628
|
}
|
|
8626
8629
|
|
|
8630
|
+
// servers/lensmcp-mcp/dist/shim/probe.js
|
|
8631
|
+
import { connect } from "node:net";
|
|
8632
|
+
|
|
8627
8633
|
// servers/lensmcp-mcp/dist/shim/workspace.js
|
|
8628
8634
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
8629
8635
|
import { basename, dirname, join as join2 } from "node:path";
|
|
@@ -8779,7 +8785,7 @@ async function probeShared(port, timeoutMs = 2e3) {
|
|
|
8779
8785
|
if (res.ok) {
|
|
8780
8786
|
const identity = parseServerIdentity(await res.json().catch(() => void 0));
|
|
8781
8787
|
if (identity)
|
|
8782
|
-
return { alive: true, identity };
|
|
8788
|
+
return { alive: true, identity, owner: "lensmcp" };
|
|
8783
8789
|
} else {
|
|
8784
8790
|
await res.text().catch(() => void 0);
|
|
8785
8791
|
}
|
|
@@ -8788,8 +8794,22 @@ async function probeShared(port, timeoutMs = 2e3) {
|
|
|
8788
8794
|
clearTimeout(timer);
|
|
8789
8795
|
}
|
|
8790
8796
|
if (!answered)
|
|
8791
|
-
return { alive: false };
|
|
8792
|
-
|
|
8797
|
+
return { alive: false, owner: "none" };
|
|
8798
|
+
const legacy = await probeMcp(sharedMcpUrl(port), timeoutMs);
|
|
8799
|
+
return { alive: legacy, owner: legacy ? "lensmcp" : "foreign" };
|
|
8800
|
+
}
|
|
8801
|
+
function tcpAccepts(port, timeoutMs = 750) {
|
|
8802
|
+
return new Promise((resolve2) => {
|
|
8803
|
+
const socket = connect({ port, host: "127.0.0.1" });
|
|
8804
|
+
const settle = (accepted) => {
|
|
8805
|
+
socket.destroy();
|
|
8806
|
+
resolve2(accepted);
|
|
8807
|
+
};
|
|
8808
|
+
socket.setTimeout(timeoutMs);
|
|
8809
|
+
socket.once("connect", () => settle(true));
|
|
8810
|
+
socket.once("timeout", () => settle(false));
|
|
8811
|
+
socket.once("error", () => settle(false));
|
|
8812
|
+
});
|
|
8793
8813
|
}
|
|
8794
8814
|
|
|
8795
8815
|
// servers/lensmcp-mcp/dist/shim/proxy.js
|
|
@@ -8976,9 +8996,9 @@ async function main() {
|
|
|
8976
8996
|
const scope = resolveWorkspaceScope(cwd);
|
|
8977
8997
|
const url2 = sharedMcpUrl(scope.mcpHttpPort);
|
|
8978
8998
|
log(`[lensmcp] shim \u2192 workspace "${scope.key}" (${scope.root}) \u2192 ${url2}`);
|
|
8979
|
-
let seen = { alive: false };
|
|
8999
|
+
let seen = { alive: false, owner: "none" };
|
|
8980
9000
|
let warnedUnknown = false;
|
|
8981
|
-
let
|
|
9001
|
+
let explained = false;
|
|
8982
9002
|
const verifyIdentity = () => {
|
|
8983
9003
|
const mismatch = describeIdentityMismatch(scope, seen.identity);
|
|
8984
9004
|
if (!mismatch) {
|
|
@@ -8989,7 +9009,7 @@ async function main() {
|
|
|
8989
9009
|
return void 0;
|
|
8990
9010
|
}
|
|
8991
9011
|
const theirs = seen.identity;
|
|
8992
|
-
|
|
9012
|
+
explained = true;
|
|
8993
9013
|
log("");
|
|
8994
9014
|
log(`[lensmcp] REFUSING the shared MCP server on 127.0.0.1:${scope.mcpHttpPort} \u2014 ${mismatch}.`);
|
|
8995
9015
|
log(`[lensmcp] this session's workspace : "${scope.key}" ${scope.root}`);
|
|
@@ -9002,6 +9022,27 @@ async function main() {
|
|
|
9002
9022
|
log("");
|
|
9003
9023
|
return `the shared MCP port is served by workspace "${theirs?.key}", not "${scope.key}"`;
|
|
9004
9024
|
};
|
|
9025
|
+
const foreignPortOwner = async () => {
|
|
9026
|
+
const port = scope.mcpHttpPort;
|
|
9027
|
+
const nonHttp = seen.owner === "foreign" ? false : await tcpAccepts(port);
|
|
9028
|
+
if (seen.owner !== "foreign" && !nonHttp)
|
|
9029
|
+
return void 0;
|
|
9030
|
+
explained = true;
|
|
9031
|
+
log("");
|
|
9032
|
+
log(`[lensmcp] the shared MCP port 127.0.0.1:${port} is TAKEN by another process.`);
|
|
9033
|
+
log(`[lensmcp] It ${nonHttp ? "accepts connections but does not speak HTTP" : "answers HTTP but is not a lensmcp MCP server"}, so this session cannot attach to it.`);
|
|
9034
|
+
log("[lensmcp] Not starting a server on it: on macOS a second bind SUCCEEDS on the");
|
|
9035
|
+
log("[lensmcp] wildcard address while the kernel keeps routing 127.0.0.1 to the");
|
|
9036
|
+
log("[lensmcp] process already there \u2014 the new server would be unreachable, and");
|
|
9037
|
+
log("[lensmcp] would keep running after this session ends.");
|
|
9038
|
+
log("[lensmcp] Fix EITHER by freeing the port:");
|
|
9039
|
+
log(`[lensmcp] lsof -nP -iTCP:${port} -sTCP:LISTEN`);
|
|
9040
|
+
log(`[lensmcp] OR by pinning a different one in ${join3(scope.root, ".lensmcp", "config.json")}:`);
|
|
9041
|
+
log(`[lensmcp] { "ports": { "mcpHttp": ${port + 1} } }`);
|
|
9042
|
+
log("[lensmcp] then restart this session. Any free 4500-4699 port works.");
|
|
9043
|
+
log("");
|
|
9044
|
+
return `the shared MCP port ${port} is held by another process that is not a lensmcp server`;
|
|
9045
|
+
};
|
|
9005
9046
|
const ensure = () => {
|
|
9006
9047
|
let watch;
|
|
9007
9048
|
return ensureSharedServer({
|
|
@@ -9010,6 +9051,7 @@ async function main() {
|
|
|
9010
9051
|
return seen.alive;
|
|
9011
9052
|
},
|
|
9012
9053
|
verifyIdentity,
|
|
9054
|
+
foreignPortOwner,
|
|
9013
9055
|
startServer: () => {
|
|
9014
9056
|
const started = startSharedServerWatched({
|
|
9015
9057
|
serverEntry: serverEntry(),
|
|
@@ -9032,7 +9074,7 @@ async function main() {
|
|
|
9032
9074
|
};
|
|
9033
9075
|
const ensured = await ensure();
|
|
9034
9076
|
if (ensured.outcome === "unavailable") {
|
|
9035
|
-
runEmbedded(cwd, ensured.reason ?? "unknown", !
|
|
9077
|
+
runEmbedded(cwd, ensured.reason ?? "unknown", !explained);
|
|
9036
9078
|
return;
|
|
9037
9079
|
}
|
|
9038
9080
|
log(`[lensmcp] shim: shared server ${ensured.outcome} (${ensured.waitedMs}ms)`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "lensmcp",
|
|
3
3
|
"displayName": "LensMCP",
|
|
4
4
|
"description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
|
|
5
|
-
"version": "1.18.
|
|
5
|
+
"version": "1.18.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Antoon",
|
|
8
8
|
"email": "davidmantoon@gmail.com"
|