grain-tools 0.10.12 → 0.10.13
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/mcp-app.html +5 -2
- package/out/server/standalone.js +101 -16
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/mcp-app.html
CHANGED
|
@@ -148961,7 +148961,7 @@ function App() {
|
|
|
148961
148961
|
var _a3, _b3;
|
|
148962
148962
|
if (!isReady || !activeTab) return;
|
|
148963
148963
|
const tabId = activeTab.id;
|
|
148964
|
-
updateTab(tabId, { isLoading: true, error: null });
|
|
148964
|
+
updateTab(tabId, { isLoading: true, error: null, cancelled: false, errorDetails: null });
|
|
148965
148965
|
try {
|
|
148966
148966
|
const callResult = await callServerTool("run_query", { sql, session: "mcp-app-manual" });
|
|
148967
148967
|
if (callResult.isError) {
|
|
@@ -149105,7 +149105,10 @@ function App() {
|
|
|
149105
149105
|
data: result,
|
|
149106
149106
|
domLayout: "autoHeight"
|
|
149107
149107
|
}
|
|
149108
|
-
) }) : /* @__PURE__ */ jsxRuntimeExports.
|
|
149108
|
+
) }) : activeTab.cancelled ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-cancelled-bar sp-empty", children: [
|
|
149109
|
+
"Query cancelled",
|
|
149110
|
+
activeTab.errorDetails ? `: ${activeTab.errorDetails}` : ""
|
|
149111
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-empty", children: "No results yet." }) })
|
|
149109
149112
|
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ConnectionsManager, { theme: theme2 })
|
|
149110
149113
|
] })
|
|
149111
149114
|
] });
|
package/out/server/standalone.js
CHANGED
|
@@ -19277,13 +19277,27 @@ var init_StdioBridge = __esm({
|
|
|
19277
19277
|
}
|
|
19278
19278
|
}
|
|
19279
19279
|
async forwardAgentMessage(message, fatal) {
|
|
19280
|
+
const isInitialize = "method" in message && message.method === "initialize";
|
|
19281
|
+
const isHandshake = isInitialize || "method" in message && message.method === "notifications/initialized";
|
|
19282
|
+
if (!isInitialize && this.sessionOpening) {
|
|
19283
|
+
await this.sessionOpening;
|
|
19284
|
+
}
|
|
19280
19285
|
if (this.reconnecting) {
|
|
19281
|
-
|
|
19286
|
+
if (!isHandshake) {
|
|
19287
|
+
this.queuedAgentMessages.push(message);
|
|
19288
|
+
}
|
|
19282
19289
|
return;
|
|
19283
19290
|
}
|
|
19291
|
+
const sending = this.daemonTransport.send(message);
|
|
19292
|
+
if (isInitialize) {
|
|
19293
|
+
this.sessionOpening = sending.catch(() => void 0);
|
|
19294
|
+
}
|
|
19284
19295
|
try {
|
|
19285
|
-
await
|
|
19296
|
+
await sending;
|
|
19286
19297
|
} catch {
|
|
19298
|
+
if (!isHandshake) {
|
|
19299
|
+
this.queuedAgentMessages.push(message);
|
|
19300
|
+
}
|
|
19287
19301
|
void this.reconnect(fatal);
|
|
19288
19302
|
}
|
|
19289
19303
|
}
|
|
@@ -20473,7 +20487,11 @@ var init_TelemetrySchema = __esm({
|
|
|
20473
20487
|
duration_bucket: "durationBucket",
|
|
20474
20488
|
error_kind: "errorKind"
|
|
20475
20489
|
},
|
|
20476
|
-
ledger_health: {
|
|
20490
|
+
ledger_health: {
|
|
20491
|
+
entries_bucket: "rowCountBucket",
|
|
20492
|
+
write_failures_bucket: "rowCountBucket",
|
|
20493
|
+
outcome: "safeString"
|
|
20494
|
+
},
|
|
20477
20495
|
ledger_verify_completed: {
|
|
20478
20496
|
outcome: "safeString",
|
|
20479
20497
|
entries_checked_bucket: "rowCountBucket",
|
|
@@ -21816,7 +21834,7 @@ var require_package = __commonJS({
|
|
|
21816
21834
|
name: "grain",
|
|
21817
21835
|
displayName: "Grain SQL",
|
|
21818
21836
|
description: "Run SQL in VS Code and expose safe, visible MCP database tools for Claude, Cursor, and other AI agents.",
|
|
21819
|
-
version: "0.10.
|
|
21837
|
+
version: "0.10.13",
|
|
21820
21838
|
license: "UNLICENSED",
|
|
21821
21839
|
mcpName: "io.github.pattrnlabs/grain",
|
|
21822
21840
|
publisher: "pattrnlabs",
|
|
@@ -101556,13 +101574,26 @@ var init_QueryOperationRunner = __esm({
|
|
|
101556
101574
|
sql
|
|
101557
101575
|
};
|
|
101558
101576
|
}
|
|
101577
|
+
getNextResultNumber(session) {
|
|
101578
|
+
let maxCount = 0;
|
|
101579
|
+
session.tabs.forEach((tab) => {
|
|
101580
|
+
const match = tab.title.match(/^Result (\d+)$/);
|
|
101581
|
+
if (match && match[1]) {
|
|
101582
|
+
const count2 = parseInt(match[1], 10);
|
|
101583
|
+
if (count2 > maxCount) {
|
|
101584
|
+
maxCount = count2;
|
|
101585
|
+
}
|
|
101586
|
+
}
|
|
101587
|
+
});
|
|
101588
|
+
return maxCount + 1;
|
|
101589
|
+
}
|
|
101559
101590
|
resolveTab(session, providedTabId, shouldCreateNewTab, providedTitle) {
|
|
101560
101591
|
if (providedTabId) {
|
|
101561
101592
|
const tab = session.tabs.get(providedTabId);
|
|
101562
101593
|
return {
|
|
101563
101594
|
tab,
|
|
101564
101595
|
tabId: providedTabId,
|
|
101565
|
-
tabTitle: providedTitle || tab?.title || `Result ${session
|
|
101596
|
+
tabTitle: providedTitle || tab?.title || `Result ${this.getNextResultNumber(session)}`
|
|
101566
101597
|
};
|
|
101567
101598
|
}
|
|
101568
101599
|
if (!shouldCreateNewTab && session.activeTabId) {
|
|
@@ -101578,7 +101609,7 @@ var init_QueryOperationRunner = __esm({
|
|
|
101578
101609
|
return {
|
|
101579
101610
|
tab: void 0,
|
|
101580
101611
|
tabId: `t${Math.random().toString(36).substring(2, 10)}`,
|
|
101581
|
-
tabTitle: providedTitle || `Result ${session
|
|
101612
|
+
tabTitle: providedTitle || `Result ${this.getNextResultNumber(session)}`
|
|
101582
101613
|
};
|
|
101583
101614
|
}
|
|
101584
101615
|
async executeAndStore(sessionId, tabId, sql, connectionId, connectionProfile, authHeader, controller, origin2, loadAllRows, enforceReadOnly, clientConsent, workspaceRoot) {
|
|
@@ -183984,13 +184015,25 @@ This link expires at ${new Date(
|
|
|
183984
184015
|
};
|
|
183985
184016
|
}
|
|
183986
184017
|
async handleLedgerStats(clientConsent) {
|
|
183987
|
-
if (!this.ledger)
|
|
184018
|
+
if (!this.ledger) {
|
|
184019
|
+
this.options.telemetry?.capture(
|
|
184020
|
+
"ledger_health",
|
|
184021
|
+
{
|
|
184022
|
+
state: "daemon",
|
|
184023
|
+
outcome: "disabled",
|
|
184024
|
+
entries_bucket: "0",
|
|
184025
|
+
write_failures_bucket: "0"
|
|
184026
|
+
},
|
|
184027
|
+
clientConsent
|
|
184028
|
+
);
|
|
183988
184029
|
return textToolResult("Ledger disabled.");
|
|
184030
|
+
}
|
|
183989
184031
|
const stats = await this.ledger.stats();
|
|
183990
184032
|
this.options.telemetry?.capture(
|
|
183991
184033
|
"ledger_health",
|
|
183992
184034
|
{
|
|
183993
184035
|
state: "daemon",
|
|
184036
|
+
outcome: "enabled",
|
|
183994
184037
|
entries_bucket: bucketRowCount(stats.persisted_entries),
|
|
183995
184038
|
write_failures_bucket: bucketRowCount(stats.write_failures)
|
|
183996
184039
|
},
|
|
@@ -183999,8 +184042,19 @@ This link expires at ${new Date(
|
|
|
183999
184042
|
return { content: [{ type: "text", text: JSON.stringify(stats) }], structuredContent: stats };
|
|
184000
184043
|
}
|
|
184001
184044
|
async handleVerifyLedger(clientConsent) {
|
|
184002
|
-
if (!this.ledger)
|
|
184045
|
+
if (!this.ledger) {
|
|
184046
|
+
this.options.telemetry?.capture(
|
|
184047
|
+
"ledger_verify_completed",
|
|
184048
|
+
{
|
|
184049
|
+
state: "daemon",
|
|
184050
|
+
outcome: "disabled",
|
|
184051
|
+
entries_checked_bucket: "0",
|
|
184052
|
+
duration_bucket: "<100ms"
|
|
184053
|
+
},
|
|
184054
|
+
clientConsent
|
|
184055
|
+
);
|
|
184003
184056
|
return textToolResult("Ledger disabled.");
|
|
184057
|
+
}
|
|
184004
184058
|
const startedAt = Date.now();
|
|
184005
184059
|
const result = await verifyLedger(this.ledger);
|
|
184006
184060
|
this.options.telemetry?.capture(
|
|
@@ -251308,7 +251362,14 @@ function confirmsExpectedInstance(expected, identity2) {
|
|
|
251308
251362
|
if (expected.token && identity2.pid === void 0) {
|
|
251309
251363
|
return false;
|
|
251310
251364
|
}
|
|
251311
|
-
|
|
251365
|
+
if (expected.pid === void 0 || expected.startedAt === void 0) {
|
|
251366
|
+
return true;
|
|
251367
|
+
}
|
|
251368
|
+
return classifyRecordInstance(
|
|
251369
|
+
{ pid: expected.pid, startedAt: expected.startedAt },
|
|
251370
|
+
identity2,
|
|
251371
|
+
Date.now()
|
|
251372
|
+
) !== "mismatch";
|
|
251312
251373
|
}
|
|
251313
251374
|
function probeSharedDaemonOnce(port, timeoutMs, expected) {
|
|
251314
251375
|
return new Promise((resolve11) => {
|
|
@@ -251363,10 +251424,33 @@ function probeSharedDaemonOnce(port, timeoutMs, expected) {
|
|
|
251363
251424
|
});
|
|
251364
251425
|
});
|
|
251365
251426
|
}
|
|
251427
|
+
function isTokenAccepted(port, token, timeoutMs) {
|
|
251428
|
+
return new Promise((resolve11) => {
|
|
251429
|
+
const request2 = http.get(
|
|
251430
|
+
{
|
|
251431
|
+
host: "127.0.0.1",
|
|
251432
|
+
port,
|
|
251433
|
+
path: "/bridge-token-check",
|
|
251434
|
+
timeout: timeoutMs,
|
|
251435
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
251436
|
+
},
|
|
251437
|
+
(response) => {
|
|
251438
|
+
response.resume();
|
|
251439
|
+
resolve11(response.statusCode !== 401 && response.statusCode !== 403);
|
|
251440
|
+
}
|
|
251441
|
+
);
|
|
251442
|
+
request2.on("timeout", () => {
|
|
251443
|
+
request2.destroy();
|
|
251444
|
+
resolve11(true);
|
|
251445
|
+
});
|
|
251446
|
+
request2.on("error", () => resolve11(true));
|
|
251447
|
+
});
|
|
251448
|
+
}
|
|
251366
251449
|
|
|
251367
251450
|
// src/server/standalone.ts
|
|
251368
251451
|
init_StdioBridge();
|
|
251369
251452
|
init_DaemonAuthToken();
|
|
251453
|
+
init_AgentToken();
|
|
251370
251454
|
init_ConfigLoader();
|
|
251371
251455
|
|
|
251372
251456
|
// src/server/cli/subcommands.ts
|
|
@@ -253035,12 +253119,12 @@ async function main() {
|
|
|
253035
253119
|
const configDir = defaultDaemonConfigDir();
|
|
253036
253120
|
const { address, source, record: record2 } = resolveDaemonAddress({ args, env: process.env, configDir });
|
|
253037
253121
|
if (address) {
|
|
253038
|
-
const
|
|
253039
|
-
|
|
253040
|
-
|
|
253041
|
-
|
|
253042
|
-
|
|
253043
|
-
const probe =
|
|
253122
|
+
const accessToken = readDaemonAuthToken(configDir);
|
|
253123
|
+
const expected = record2 ? { pid: record2.pid, startedAt: record2.startedAt, token: accessToken } : accessToken ? { token: accessToken } : void 0;
|
|
253124
|
+
const probed = await probeSharedDaemon(address.port, 500, expected);
|
|
253125
|
+
const agentToken = readAgentToken(configDir);
|
|
253126
|
+
const foreignReason = !probed.identity ? void 0 : !agentToken ? "no-agent-token" : await isTokenAccepted(address.port, agentToken, 500) ? void 0 : "agent-token-rejected";
|
|
253127
|
+
const probe = foreignReason ? { attempts: probed.attempts, failureReason: foreignReason } : probed;
|
|
253044
253128
|
if (probe.identity) {
|
|
253045
253129
|
console.error(
|
|
253046
253130
|
`[grain] bridging stdio to shared daemon v${probe.identity.version ?? "?"} on ${address.host}:${address.port} (via ${source}, RFC-082) after ${probe.attempts} attempt(s)`
|
|
@@ -253050,8 +253134,9 @@ async function main() {
|
|
|
253050
253134
|
return;
|
|
253051
253135
|
}
|
|
253052
253136
|
const reasonStr = probe.failureReason === "stale-record" ? `stale-record pid ${expected?.pid}` : probe.failureReason;
|
|
253137
|
+
const detail = probe.failureReason === "no-agent-token" ? `answered, but ${configDir} has no agent-token for this bridge to present` : probe.failureReason === "wrong-instance" || probe.failureReason === "agent-token-rejected" ? `answered, but it does not accept the tokens in ${configDir}, so it belongs to a different Grain home` : void 0;
|
|
253053
253138
|
console.error(
|
|
253054
|
-
`[grain] no shared daemon at ${address.host}:${address.port} (via ${source}) after ${probe.attempts} attempt(s) (${reasonStr}); starting embedded stdio daemon \u2014 this session's queries will not appear in the extension's results panel`
|
|
253139
|
+
(detail ? `[grain] the daemon at ${address.host}:${address.port} (via ${source}) ${detail} (${reasonStr}); check GRAIN_HOME in this MCP server's config. Starting embedded stdio ` : `[grain] no shared daemon at ${address.host}:${address.port} (via ${source}) after ${probe.attempts} attempt(s) (${reasonStr}); starting embedded stdio `) + `daemon \u2014 this session's queries will not appear in the extension's results panel`
|
|
253055
253140
|
);
|
|
253056
253141
|
notSetUpMessage = resolveNotSetUpMessage(Boolean(process.env["GRAIN_CONNECTIONS"]));
|
|
253057
253142
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grain-tools",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.13",
|
|
4
4
|
"mcpName": "io.github.pattrnlabs/grain",
|
|
5
5
|
"description": "Standalone Grain MCP database server for VS Code, Claude Desktop, Cursor, and other MCP clients.",
|
|
6
6
|
"main": "out/server/standalone.js",
|
package/server.json
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"source": "github",
|
|
9
9
|
"id": "1348511592"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.10.
|
|
11
|
+
"version": "0.10.13",
|
|
12
12
|
"websiteUrl": "https://grain.tools",
|
|
13
13
|
"packages": [
|
|
14
14
|
{
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
17
17
|
"identifier": "grain-tools",
|
|
18
|
-
"version": "0.10.
|
|
18
|
+
"version": "0.10.13",
|
|
19
19
|
"runtimeHint": "npx",
|
|
20
20
|
"runtimeArguments": [
|
|
21
21
|
{
|