untappd-mcp 2.5.1 → 2.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +39 -11
- package/dist/cache/db.js +62 -14
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Untappd — beers, breweries, venues, check-ins, wishlists, and your friend feed",
|
|
10
|
-
"version": "2.5.
|
|
10
|
+
"version": "2.5.2"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Untappd",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Untappd — search beers/breweries/venues, read profiles/check-ins/wishlists, and post check-ins, toasts, and comments",
|
|
18
|
-
"version": "2.5.
|
|
18
|
+
"version": "2.5.2",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
3
|
"displayName": "Untappd",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.2",
|
|
5
5
|
"description": "MCP server for Untappd — search beers/breweries/venues, read check-ins and wishlists, and post check-ins, toasts, and comments",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/dist/bundle.js
CHANGED
|
@@ -59906,7 +59906,7 @@ function toolAnnotations(opts = {}) {
|
|
|
59906
59906
|
}
|
|
59907
59907
|
|
|
59908
59908
|
// src/version.ts
|
|
59909
|
-
var VERSION = "2.5.
|
|
59909
|
+
var VERSION = "2.5.2";
|
|
59910
59910
|
|
|
59911
59911
|
// src/client.ts
|
|
59912
59912
|
import { dirname as dirname2, join as join4 } from "path";
|
|
@@ -66702,7 +66702,7 @@ var NodeSqlDriver = class {
|
|
|
66702
66702
|
return this.prepared(sql).all(...params);
|
|
66703
66703
|
}
|
|
66704
66704
|
transaction(fn) {
|
|
66705
|
-
this.db.exec("BEGIN IMMEDIATE");
|
|
66705
|
+
retryBusy(() => this.db.exec("BEGIN IMMEDIATE"));
|
|
66706
66706
|
try {
|
|
66707
66707
|
fn();
|
|
66708
66708
|
this.db.exec("COMMIT");
|
|
@@ -66728,19 +66728,25 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
|
|
|
66728
66728
|
static open(path) {
|
|
66729
66729
|
if (path !== ":memory:") preparePrivateFile(path);
|
|
66730
66730
|
const db = new DatabaseSync(path);
|
|
66731
|
-
|
|
66732
|
-
|
|
66733
|
-
|
|
66734
|
-
|
|
66731
|
+
try {
|
|
66732
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
66733
|
+
if (path !== ":memory:") {
|
|
66734
|
+
retryBusy(() => {
|
|
66735
|
+
if (journalModeOf(db) !== "wal") db.exec("PRAGMA journal_mode = WAL");
|
|
66736
|
+
});
|
|
66737
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
66738
|
+
}
|
|
66739
|
+
const cache = new _CheckinCache(db, path);
|
|
66740
|
+
if (path !== ":memory:") tightenSidecars(path);
|
|
66741
|
+
return cache;
|
|
66742
|
+
} catch (e) {
|
|
66743
|
+
db.close();
|
|
66744
|
+
throw e;
|
|
66735
66745
|
}
|
|
66736
|
-
const cache = new _CheckinCache(db, path);
|
|
66737
|
-
if (path !== ":memory:") tightenSidecars(path);
|
|
66738
|
-
return cache;
|
|
66739
66746
|
}
|
|
66740
66747
|
/** The SQLite journal mode actually in effect (`wal` for a file, `memory` for :memory:). */
|
|
66741
66748
|
journalMode() {
|
|
66742
|
-
|
|
66743
|
-
return String(row?.journal_mode ?? "unknown");
|
|
66749
|
+
return journalModeOf(this.db);
|
|
66744
66750
|
}
|
|
66745
66751
|
/** On-disk footprint: the db file plus its -wal/-shm side files. */
|
|
66746
66752
|
fileInfo() {
|
|
@@ -66779,6 +66785,28 @@ function bestEffortChmod(path, mode) {
|
|
|
66779
66785
|
}
|
|
66780
66786
|
}
|
|
66781
66787
|
var BUSY_TIMEOUT_MS = 5e3;
|
|
66788
|
+
function journalModeOf(db) {
|
|
66789
|
+
const row = db.prepare("PRAGMA journal_mode").get();
|
|
66790
|
+
return String(row?.journal_mode ?? "unknown");
|
|
66791
|
+
}
|
|
66792
|
+
function isBusy(e) {
|
|
66793
|
+
const code2 = e?.errcode;
|
|
66794
|
+
return typeof code2 === "number" && ((code2 & 255) === 5 || (code2 & 255) === 6);
|
|
66795
|
+
}
|
|
66796
|
+
var sleepCell = new Int32Array(new SharedArrayBuffer(4));
|
|
66797
|
+
function retryBusy(fn, timeoutMs = BUSY_TIMEOUT_MS) {
|
|
66798
|
+
const deadline = Date.now() + timeoutMs;
|
|
66799
|
+
for (let attempt = 0; ; attempt++) {
|
|
66800
|
+
try {
|
|
66801
|
+
return fn();
|
|
66802
|
+
} catch (e) {
|
|
66803
|
+
const left = deadline - Date.now();
|
|
66804
|
+
if (!isBusy(e) || left <= 0) throw e;
|
|
66805
|
+
const wait = Math.min(100, 5 * 2 ** Math.min(attempt, 5)) + Math.random() * 5;
|
|
66806
|
+
Atomics.wait(sleepCell, 0, 0, Math.min(wait, left));
|
|
66807
|
+
}
|
|
66808
|
+
}
|
|
66809
|
+
}
|
|
66782
66810
|
function defaultCachePath() {
|
|
66783
66811
|
const file2 = readEnvVar("UNTAPPD_CACHE_DB");
|
|
66784
66812
|
if (file2) return file2 === ":memory:" ? file2 : absolutise(file2);
|
package/dist/cache/db.js
CHANGED
|
@@ -47,7 +47,9 @@ class NodeSqlDriver {
|
|
|
47
47
|
// IMMEDIATE takes the write lock up front. A DEFERRED transaction that
|
|
48
48
|
// reads and then writes can hit SQLITE_BUSY on the lock upgrade, which
|
|
49
49
|
// busy_timeout does NOT retry — the failure mode of two hosts open at once.
|
|
50
|
-
|
|
50
|
+
// BEGIN itself can still get a handler-less SQLITE_BUSY (e.g. while another
|
|
51
|
+
// process recovers the WAL), so it is retried too.
|
|
52
|
+
retryBusy(() => this.db.exec('BEGIN IMMEDIATE'));
|
|
51
53
|
try {
|
|
52
54
|
fn();
|
|
53
55
|
this.db.exec('COMMIT');
|
|
@@ -78,23 +80,36 @@ export class CheckinCache extends LocalCacheStore {
|
|
|
78
80
|
if (path !== ':memory:')
|
|
79
81
|
preparePrivateFile(path);
|
|
80
82
|
const db = new DatabaseSync(path);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
try {
|
|
84
|
+
// Set before the schema/migration transaction so even that waits out a
|
|
85
|
+
// second process instead of failing. WAL lets readers and one writer run
|
|
86
|
+
// concurrently (two hosts open at once); NORMAL sync is the WAL-safe default.
|
|
87
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
88
|
+
if (path !== ':memory:') {
|
|
89
|
+
// Switching a fresh file to WAL needs an exclusive lock, and when another
|
|
90
|
+
// process is opening it too SQLite answers SQLITE_BUSY WITHOUT calling
|
|
91
|
+
// the busy handler (#256) — so busy_timeout never applies and this has
|
|
92
|
+
// to retry itself. Once the file is WAL (the switch is persistent) a
|
|
93
|
+
// later open skips the switch entirely.
|
|
94
|
+
retryBusy(() => {
|
|
95
|
+
if (journalModeOf(db) !== 'wal')
|
|
96
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
97
|
+
});
|
|
98
|
+
db.exec('PRAGMA synchronous = NORMAL');
|
|
99
|
+
}
|
|
100
|
+
const cache = new CheckinCache(db, path);
|
|
101
|
+
if (path !== ':memory:')
|
|
102
|
+
tightenSidecars(path);
|
|
103
|
+
return cache;
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
db.close();
|
|
107
|
+
throw e;
|
|
88
108
|
}
|
|
89
|
-
const cache = new CheckinCache(db, path);
|
|
90
|
-
if (path !== ':memory:')
|
|
91
|
-
tightenSidecars(path);
|
|
92
|
-
return cache;
|
|
93
109
|
}
|
|
94
110
|
/** The SQLite journal mode actually in effect (`wal` for a file, `memory` for :memory:). */
|
|
95
111
|
journalMode() {
|
|
96
|
-
|
|
97
|
-
return String(row?.journal_mode ?? 'unknown');
|
|
112
|
+
return journalModeOf(this.db);
|
|
98
113
|
}
|
|
99
114
|
/** On-disk footprint: the db file plus its -wal/-shm side files. */
|
|
100
115
|
fileInfo() {
|
|
@@ -152,6 +167,39 @@ function bestEffortChmod(path, mode) {
|
|
|
152
167
|
}
|
|
153
168
|
// How long a write waits for another process's lock before failing.
|
|
154
169
|
const BUSY_TIMEOUT_MS = 5000;
|
|
170
|
+
function journalModeOf(db) {
|
|
171
|
+
const row = db.prepare('PRAGMA journal_mode').get();
|
|
172
|
+
return String(row?.journal_mode ?? 'unknown');
|
|
173
|
+
}
|
|
174
|
+
/** SQLITE_BUSY / SQLITE_LOCKED, including their extended codes (e.g. BUSY_RECOVERY). */
|
|
175
|
+
function isBusy(e) {
|
|
176
|
+
const code = e?.errcode;
|
|
177
|
+
return typeof code === 'number' && ((code & 0xff) === 5 || (code & 0xff) === 6);
|
|
178
|
+
}
|
|
179
|
+
const sleepCell = new Int32Array(new SharedArrayBuffer(4));
|
|
180
|
+
/**
|
|
181
|
+
* Run `fn`, retrying a SQLITE_BUSY that SQLite raised without consulting the
|
|
182
|
+
* busy handler, until {@link BUSY_TIMEOUT_MS} after the FIRST attempt. A BUSY
|
|
183
|
+
* that already waited out busy_timeout lands past that deadline and is thrown
|
|
184
|
+
* as-is, so the total wait never exceeds busy_timeout's. The driver is
|
|
185
|
+
* synchronous, so the backoff blocks (Atomics.wait) like busy_timeout does.
|
|
186
|
+
*/
|
|
187
|
+
export function retryBusy(fn, timeoutMs = BUSY_TIMEOUT_MS) {
|
|
188
|
+
const deadline = Date.now() + timeoutMs;
|
|
189
|
+
for (let attempt = 0;; attempt++) {
|
|
190
|
+
try {
|
|
191
|
+
return fn();
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
const left = deadline - Date.now();
|
|
195
|
+
if (!isBusy(e) || left <= 0)
|
|
196
|
+
throw e;
|
|
197
|
+
// 5, 10, 20 … ms, capped at 100, plus jitter so colliding opens spread out.
|
|
198
|
+
const wait = Math.min(100, 5 * 2 ** Math.min(attempt, 5)) + Math.random() * 5;
|
|
199
|
+
Atomics.wait(sleepCell, 0, 0, Math.min(wait, left));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
155
203
|
/**
|
|
156
204
|
* The on-disk cache path, always ABSOLUTE: `$UNTAPPD_CACHE_DB` (a file), else
|
|
157
205
|
* `$UNTAPPD_CACHE_DIR/checkins.db`, else `~/.untappd-mcp/checkins.db`.
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// json's `extra-files`), and `versionSyncTest` guards that it stays equal to
|
|
4
4
|
// package.json. Import VERSION wherever the version is needed rather than
|
|
5
5
|
// re-declaring it.
|
|
6
|
-
export const VERSION = '2.5.
|
|
6
|
+
export const VERSION = '2.5.2'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"mcpName": "io.github.chrischall/untappd-mcp",
|
|
5
5
|
"description": "Untappd MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/untappd-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.5.
|
|
9
|
+
"version": "2.5.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "untappd-mcp",
|
|
14
|
-
"version": "2.5.
|
|
14
|
+
"version": "2.5.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|