vibo-mcp 1.3.0 → 1.3.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 +2 -2
- package/README.md +1 -1
- package/dist/bundle.js +164 -19
- package/dist/session-store.js +23 -21
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/server.json +2 -2
- /package/{SKILL.md → skills/vibo-mcp/SKILL.md} +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Vibo (vibodj.com) — plan & manage event music, song requests, ideas, guests, and playlists via natural language",
|
|
10
|
-
"version": "1.3.
|
|
10
|
+
"version": "1.3.2"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Vibo",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Vibo — browse & manage events, timeline, songs, the DJ song ideas/questions, guests, and exports to Spotify/Apple Music",
|
|
18
|
-
"version": "1.3.
|
|
18
|
+
"version": "1.3.2",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibo-mcp",
|
|
3
3
|
"displayName": "Vibo",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.2",
|
|
5
5
|
"description": "MCP server for Vibo (vibodj.com) — plan & manage event music, song requests, ideas, guests, and playlists via natural language",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"music",
|
|
19
19
|
"mcp"
|
|
20
20
|
],
|
|
21
|
-
"skills": "./
|
|
21
|
+
"skills": "./skills/",
|
|
22
22
|
"mcp": "./.mcp.json"
|
|
23
23
|
}
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ confirm-gated: without `confirm: true` it returns a dry-run preview and makes no
|
|
|
48
48
|
network call.
|
|
49
49
|
|
|
50
50
|
See [docs/VIBO-API.md](docs/VIBO-API.md) for the reverse-engineered API notes
|
|
51
|
-
and [SKILL.md](SKILL.md) for the full tool list.
|
|
51
|
+
and [skills/vibo-mcp/SKILL.md](skills/vibo-mcp/SKILL.md) for the full tool list.
|
|
52
52
|
|
|
53
53
|
## Development
|
|
54
54
|
|
package/dist/bundle.js
CHANGED
|
@@ -31040,8 +31040,12 @@ var API_KEY_RE = new RegExp([
|
|
|
31040
31040
|
// webhook signing secret (Stripe-style)
|
|
31041
31041
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
31042
31042
|
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
31043
|
+
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
31044
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
31045
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
31046
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
31043
31047
|
function redactSecrets(text) {
|
|
31044
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
31048
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
31045
31049
|
}
|
|
31046
31050
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
31047
31051
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -31116,7 +31120,7 @@ function toolAnnotations(opts = {}) {
|
|
|
31116
31120
|
}
|
|
31117
31121
|
|
|
31118
31122
|
// src/version.ts
|
|
31119
|
-
var VERSION = "1.3.
|
|
31123
|
+
var VERSION = "1.3.2";
|
|
31120
31124
|
|
|
31121
31125
|
// src/client.ts
|
|
31122
31126
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -31124,18 +31128,162 @@ import { fileURLToPath } from "url";
|
|
|
31124
31128
|
|
|
31125
31129
|
// src/session-store.ts
|
|
31126
31130
|
import { homedir } from "os";
|
|
31127
|
-
import {
|
|
31128
|
-
|
|
31129
|
-
|
|
31130
|
-
|
|
31131
|
+
import { join } from "path";
|
|
31132
|
+
|
|
31133
|
+
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
31134
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync } from "node:fs";
|
|
31135
|
+
import { dirname } from "node:path";
|
|
31136
|
+
function normalizeOrigin(input) {
|
|
31137
|
+
try {
|
|
31138
|
+
return new URL(input).origin.replace(/\/$/, "");
|
|
31139
|
+
} catch {
|
|
31140
|
+
return input.replace(/\/$/, "");
|
|
31141
|
+
}
|
|
31142
|
+
}
|
|
31143
|
+
var SessionStore = class {
|
|
31144
|
+
sessions = /* @__PURE__ */ new Map();
|
|
31145
|
+
mostRecentKey = null;
|
|
31146
|
+
filePath;
|
|
31147
|
+
keyOf;
|
|
31148
|
+
normalizeKey;
|
|
31149
|
+
constructor(opts) {
|
|
31150
|
+
this.filePath = opts.filePath;
|
|
31151
|
+
this.keyOf = opts.keyOf;
|
|
31152
|
+
this.normalizeKey = opts.normalizeKey ?? normalizeOrigin;
|
|
31153
|
+
this.loadFromDisk();
|
|
31154
|
+
}
|
|
31155
|
+
loadFromDisk() {
|
|
31156
|
+
if (!existsSync(this.filePath))
|
|
31157
|
+
return;
|
|
31158
|
+
try {
|
|
31159
|
+
this.sessions = this.deserialize(readFileSync(this.filePath, "utf8"));
|
|
31160
|
+
const keys = Array.from(this.sessions.keys());
|
|
31161
|
+
this.mostRecentKey = keys[keys.length - 1] ?? null;
|
|
31162
|
+
} catch (err) {
|
|
31163
|
+
const backupPath = this.preserveCorruptFile();
|
|
31164
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
31165
|
+
console.error(`[mcp-utils] SessionStore: failed to parse ${this.filePath} (${detail}); ` + (backupPath !== null ? `preserved the corrupt file at ${backupPath}; ` : "could not preserve the corrupt file; ") + "starting with an empty store.");
|
|
31166
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
31167
|
+
this.mostRecentKey = null;
|
|
31168
|
+
}
|
|
31169
|
+
}
|
|
31170
|
+
/**
|
|
31171
|
+
* Move a corrupt store file to `<filePath>.corrupt` (or `.corrupt-<n>` if a
|
|
31172
|
+
* previous backup exists) so a subsequent save cannot overwrite the only
|
|
31173
|
+
* copy of the prior credentials. Best-effort: returns the backup path, or
|
|
31174
|
+
* `null` if the rename failed.
|
|
31175
|
+
*/
|
|
31176
|
+
preserveCorruptFile() {
|
|
31177
|
+
try {
|
|
31178
|
+
let candidate = `${this.filePath}.corrupt`;
|
|
31179
|
+
for (let n = 1; existsSync(candidate) && n <= 100; n++) {
|
|
31180
|
+
candidate = `${this.filePath}.corrupt-${n}`;
|
|
31181
|
+
}
|
|
31182
|
+
if (existsSync(candidate))
|
|
31183
|
+
return null;
|
|
31184
|
+
renameSync(this.filePath, candidate);
|
|
31185
|
+
return candidate;
|
|
31186
|
+
} catch {
|
|
31187
|
+
return null;
|
|
31188
|
+
}
|
|
31189
|
+
}
|
|
31190
|
+
/** Serialize the store to its on-disk JSON form (array, insertion order). */
|
|
31191
|
+
serialize() {
|
|
31192
|
+
return JSON.stringify(Array.from(this.sessions.values()), null, 2);
|
|
31193
|
+
}
|
|
31194
|
+
/** Parse on-disk JSON back into a keyed `Map`; empty map on invalid input. */
|
|
31195
|
+
deserialize(body) {
|
|
31196
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
31197
|
+
const arr = JSON.parse(body);
|
|
31198
|
+
if (!Array.isArray(arr))
|
|
31199
|
+
return map2;
|
|
31200
|
+
for (const raw of arr) {
|
|
31201
|
+
if (raw && typeof raw === "object") {
|
|
31202
|
+
const s = raw;
|
|
31203
|
+
const key = this.normalizeKey(this.keyOf(s));
|
|
31204
|
+
map2.set(key, s);
|
|
31205
|
+
}
|
|
31206
|
+
}
|
|
31207
|
+
return map2;
|
|
31208
|
+
}
|
|
31209
|
+
saveToDisk() {
|
|
31210
|
+
const dir = dirname(this.filePath);
|
|
31211
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
31212
|
+
if (existsSync(this.filePath)) {
|
|
31213
|
+
try {
|
|
31214
|
+
chmodSync(this.filePath, 384);
|
|
31215
|
+
} catch {
|
|
31216
|
+
}
|
|
31217
|
+
}
|
|
31218
|
+
writeFileSync(this.filePath, this.serialize(), { mode: 384 });
|
|
31219
|
+
try {
|
|
31220
|
+
chmodSync(this.filePath, 384);
|
|
31221
|
+
chmodSync(dir, 448);
|
|
31222
|
+
} catch {
|
|
31223
|
+
}
|
|
31224
|
+
}
|
|
31225
|
+
/** Insert or replace a record, normalizing its key and marking it active. */
|
|
31226
|
+
add(session) {
|
|
31227
|
+
const key = this.normalizeKey(this.keyOf(session));
|
|
31228
|
+
this.sessions.set(key, session);
|
|
31229
|
+
this.mostRecentKey = key;
|
|
31230
|
+
this.saveToDisk();
|
|
31231
|
+
}
|
|
31232
|
+
/** Look up by key; with no key, returns the active (most-recent) session. */
|
|
31233
|
+
get(key) {
|
|
31234
|
+
if (key !== void 0)
|
|
31235
|
+
return this.sessions.get(this.normalizeKey(key)) ?? null;
|
|
31236
|
+
if (this.mostRecentKey !== null)
|
|
31237
|
+
return this.sessions.get(this.mostRecentKey) ?? null;
|
|
31238
|
+
return null;
|
|
31239
|
+
}
|
|
31240
|
+
/** The most-recently-added session, or `null`. */
|
|
31241
|
+
getActiveSession() {
|
|
31242
|
+
return this.get();
|
|
31243
|
+
}
|
|
31244
|
+
/** All sessions in insertion order. */
|
|
31245
|
+
list() {
|
|
31246
|
+
return Array.from(this.sessions.values());
|
|
31247
|
+
}
|
|
31248
|
+
/** Remove a session; fixes up the active pointer. Returns whether it existed. */
|
|
31249
|
+
remove(key) {
|
|
31250
|
+
const normalized = this.normalizeKey(key);
|
|
31251
|
+
const had = this.sessions.delete(normalized);
|
|
31252
|
+
if (had) {
|
|
31253
|
+
if (this.mostRecentKey === normalized) {
|
|
31254
|
+
const keys = Array.from(this.sessions.keys());
|
|
31255
|
+
this.mostRecentKey = keys[keys.length - 1] ?? null;
|
|
31256
|
+
}
|
|
31257
|
+
this.saveToDisk();
|
|
31258
|
+
}
|
|
31259
|
+
return had;
|
|
31260
|
+
}
|
|
31261
|
+
/** Clear in-memory state without touching disk. Test helper. */
|
|
31262
|
+
resetForTest() {
|
|
31263
|
+
this.sessions.clear();
|
|
31264
|
+
this.mostRecentKey = null;
|
|
31265
|
+
}
|
|
31266
|
+
};
|
|
31267
|
+
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
31268
|
+
|
|
31269
|
+
// src/session-store.ts
|
|
31270
|
+
var SESSION_KEY = "vibo";
|
|
31271
|
+
function openStore() {
|
|
31272
|
+
return new SessionStore({
|
|
31273
|
+
filePath: readEnvVar("VIBO_SESSION_FILE") ?? join(homedir(), ".vibo-mcp", "session.json"),
|
|
31274
|
+
keyOf: (s) => typeof s.key === "string" && s.key ? s.key : SESSION_KEY,
|
|
31275
|
+
normalizeKey: (k) => k
|
|
31276
|
+
// fixed single key — no origin normalization
|
|
31277
|
+
});
|
|
31131
31278
|
}
|
|
31132
31279
|
function loadSession() {
|
|
31133
31280
|
try {
|
|
31134
|
-
const
|
|
31135
|
-
if (
|
|
31136
|
-
|
|
31137
|
-
|
|
31138
|
-
|
|
31281
|
+
const rec = openStore().get(SESSION_KEY);
|
|
31282
|
+
if (rec && typeof rec.accessToken === "string" && rec.accessToken) {
|
|
31283
|
+
return {
|
|
31284
|
+
accessToken: rec.accessToken,
|
|
31285
|
+
refreshToken: typeof rec.refreshToken === "string" ? rec.refreshToken : null
|
|
31286
|
+
};
|
|
31139
31287
|
}
|
|
31140
31288
|
return null;
|
|
31141
31289
|
} catch {
|
|
@@ -31143,14 +31291,11 @@ function loadSession() {
|
|
|
31143
31291
|
}
|
|
31144
31292
|
}
|
|
31145
31293
|
function saveSession(session) {
|
|
31146
|
-
|
|
31147
|
-
|
|
31148
|
-
|
|
31149
|
-
|
|
31150
|
-
|
|
31151
|
-
{ mode: 384 }
|
|
31152
|
-
);
|
|
31153
|
-
chmodSync(file2, 384);
|
|
31294
|
+
openStore().add({
|
|
31295
|
+
key: SESSION_KEY,
|
|
31296
|
+
accessToken: session.accessToken,
|
|
31297
|
+
refreshToken: session.refreshToken ?? null
|
|
31298
|
+
});
|
|
31154
31299
|
}
|
|
31155
31300
|
|
|
31156
31301
|
// src/client.ts
|
package/dist/session-store.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
|
-
import {
|
|
3
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, chmodSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
4
3
|
import { readEnvVar } from '@chrischall/mcp-utils';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import { SessionStore } from '@chrischall/mcp-utils/session';
|
|
5
|
+
const SESSION_KEY = 'vibo';
|
|
6
|
+
// Constructed per call (it re-reads disk) so VIBO_SESSION_FILE is honored
|
|
7
|
+
// dynamically, matching the previous read-env-on-every-call behaviour.
|
|
8
|
+
function openStore() {
|
|
9
|
+
return new SessionStore({
|
|
10
|
+
filePath: readEnvVar('VIBO_SESSION_FILE') ?? join(homedir(), '.vibo-mcp', 'session.json'),
|
|
11
|
+
keyOf: (s) => (typeof s.key === 'string' && s.key ? s.key : SESSION_KEY),
|
|
12
|
+
normalizeKey: (k) => k, // fixed single key — no origin normalization
|
|
13
|
+
});
|
|
9
14
|
}
|
|
10
15
|
/** Load a previously-captured session, or null if none / unreadable. */
|
|
11
16
|
export function loadSession() {
|
|
12
17
|
try {
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const rec = openStore().get(SESSION_KEY);
|
|
19
|
+
if (rec && typeof rec.accessToken === 'string' && rec.accessToken) {
|
|
20
|
+
return {
|
|
21
|
+
accessToken: rec.accessToken,
|
|
22
|
+
refreshToken: typeof rec.refreshToken === 'string' ? rec.refreshToken : null,
|
|
23
|
+
};
|
|
19
24
|
}
|
|
20
25
|
return null;
|
|
21
26
|
}
|
|
@@ -25,19 +30,16 @@ export function loadSession() {
|
|
|
25
30
|
}
|
|
26
31
|
/** Persist a captured/refreshed token pair (0600 file in a 0700 dir). */
|
|
27
32
|
export function saveSession(session) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
chmodSync(file, 0o600);
|
|
33
|
+
openStore().add({
|
|
34
|
+
key: SESSION_KEY,
|
|
35
|
+
accessToken: session.accessToken,
|
|
36
|
+
refreshToken: session.refreshToken ?? null,
|
|
37
|
+
});
|
|
34
38
|
}
|
|
35
39
|
/** Remove any persisted session. */
|
|
36
40
|
export function clearSession() {
|
|
37
41
|
try {
|
|
38
|
-
|
|
39
|
-
if (existsSync(file))
|
|
40
|
-
rmSync(file);
|
|
42
|
+
openStore().remove(SESSION_KEY);
|
|
41
43
|
}
|
|
42
44
|
catch {
|
|
43
45
|
// best-effort
|
package/dist/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// literal on the line carrying the release marker; every manifest and the MCP
|
|
3
3
|
// server banner import VERSION from here, so there is exactly one place to keep
|
|
4
4
|
// in sync (and one release-please extra-files entry).
|
|
5
|
-
export const VERSION = '1.3.
|
|
5
|
+
export const VERSION = '1.3.2'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibo-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"mcpName": "io.github.chrischall/vibo-mcp",
|
|
5
5
|
"description": "Vibo (vibodj.com) MCP server for Claude — host/couple event music planning & management. Developed and maintained by AI (Claude Code).",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
33
|
".claude-plugin",
|
|
34
|
-
"
|
|
34
|
+
"skills",
|
|
35
35
|
".mcp.json",
|
|
36
36
|
"server.json"
|
|
37
37
|
],
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"test:coverage": "vitest run --coverage"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
47
|
+
"@chrischall/mcp-utils": "^0.12.0",
|
|
48
48
|
"@fetchproxy/bootstrap": "^1.3.4",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
50
|
"dotenv": "^17.4.0",
|
|
51
51
|
"zod": "^4.4.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@types/node": "^
|
|
54
|
+
"@types/node": "^26.0.0",
|
|
55
55
|
"@vitest/coverage-v8": "^4.1.2",
|
|
56
56
|
"esbuild": "^0.28.0",
|
|
57
57
|
"typescript": "^6.0.2",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/vibo-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.3.
|
|
9
|
+
"version": "1.3.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "vibo-mcp",
|
|
14
|
-
"version": "1.3.
|
|
14
|
+
"version": "1.3.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
File without changes
|