myhotlunchbox-mcp 0.3.0 → 0.4.0
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/README.md +3 -1
- package/dist/bundle.js +183 -2
- package/dist/index.js +2 -0
- package/dist/tools/health.js +65 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for My Hot Lunchbox — order school lunches, manage students, and track payments via natural language",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.4.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "My Hot Lunchbox",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for My Hot Lunchbox — school lunch calendar, ordering, and payments. Signs in server-side with the parent account credentials.",
|
|
18
|
-
"version": "0.
|
|
18
|
+
"version": "0.4.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myhotlunchbox-mcp",
|
|
3
3
|
"displayName": "My Hot Lunchbox",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "MCP server for My Hot Lunchbox — order school lunches, manage students, and track payments via natural language",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/README.md
CHANGED
|
@@ -38,10 +38,12 @@ still works; the configuration error surfaces on the first tool call.
|
|
|
38
38
|
|
|
39
39
|
## Tools
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
35 tools, all prefixed `mhlb_`. All 20 read tools are verified live against a real parent account (`node scripts/verify-reads.mjs`); the 14 write tools are not — see below.
|
|
42
42
|
|
|
43
43
|
**Account** — `mhlb_whoami`, `mhlb_session_reset`
|
|
44
44
|
|
|
45
|
+
**Health** — `mhlb_healthcheck` (is this connector working? reports whether the credential resolved, whether My Hot Lunchbox accepted it, and what to fix — unlike `mhlb_whoami`, which throws instead of answering)
|
|
46
|
+
|
|
45
47
|
**Students** — `mhlb_list_students`, `mhlb_get_student_form`,
|
|
46
48
|
`mhlb_new_student_form`, `mhlb_create_student`, `mhlb_update_student`,
|
|
47
49
|
`mhlb_delete_student`
|
package/dist/bundle.js
CHANGED
|
@@ -31343,6 +31343,11 @@ function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
|
31343
31343
|
return redacted;
|
|
31344
31344
|
return `${redacted.slice(0, max)}\u2026 [truncated]`;
|
|
31345
31345
|
}
|
|
31346
|
+
function messageOf(err) {
|
|
31347
|
+
if (err instanceof Error)
|
|
31348
|
+
return err.message;
|
|
31349
|
+
return String(err);
|
|
31350
|
+
}
|
|
31346
31351
|
|
|
31347
31352
|
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
31348
31353
|
function textResult(data) {
|
|
@@ -32240,7 +32245,7 @@ var MhlbClient = class {
|
|
|
32240
32245
|
};
|
|
32241
32246
|
|
|
32242
32247
|
// src/version.ts
|
|
32243
|
-
var VERSION = "0.
|
|
32248
|
+
var VERSION = "0.4.0";
|
|
32244
32249
|
|
|
32245
32250
|
// src/tools/_shared.ts
|
|
32246
32251
|
function preview(action, request, notes) {
|
|
@@ -32919,6 +32924,181 @@ function registerReportTools(server, client2) {
|
|
|
32919
32924
|
);
|
|
32920
32925
|
}
|
|
32921
32926
|
|
|
32927
|
+
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
32928
|
+
function statusOf(err) {
|
|
32929
|
+
if (typeof err !== "object" || err === null)
|
|
32930
|
+
return void 0;
|
|
32931
|
+
const s = err.status ?? err.statusCode;
|
|
32932
|
+
return typeof s === "number" ? s : void 0;
|
|
32933
|
+
}
|
|
32934
|
+
var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
32935
|
+
"ok",
|
|
32936
|
+
"no_credential",
|
|
32937
|
+
"credential_rejected",
|
|
32938
|
+
"timeout",
|
|
32939
|
+
"http",
|
|
32940
|
+
"transport",
|
|
32941
|
+
"unknown"
|
|
32942
|
+
]);
|
|
32943
|
+
function isArm(kind) {
|
|
32944
|
+
return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
|
|
32945
|
+
}
|
|
32946
|
+
function credentialHint(arm, prefix, hostLabel, source) {
|
|
32947
|
+
switch (arm) {
|
|
32948
|
+
case "ok":
|
|
32949
|
+
return `Credential from '${source}' works: ${hostLabel} accepted an authenticated request. If a real tool still fails, the problem is that tool, not auth.`;
|
|
32950
|
+
case "no_credential":
|
|
32951
|
+
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
32952
|
+
case "credential_rejected":
|
|
32953
|
+
return `${hostLabel} rejected the credential from '${source}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
32954
|
+
case "timeout":
|
|
32955
|
+
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
32956
|
+
case "http":
|
|
32957
|
+
return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
|
|
32958
|
+
case "transport":
|
|
32959
|
+
return `Could not reach ${hostLabel} at all. Check network egress; the credential itself was never judged.`;
|
|
32960
|
+
default:
|
|
32961
|
+
return `Unexpected failure \u2014 see error.message.`;
|
|
32962
|
+
}
|
|
32963
|
+
}
|
|
32964
|
+
function registerCredentialHealthcheckTool(args) {
|
|
32965
|
+
const { server, prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
|
|
32966
|
+
const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
|
|
32967
|
+
server.registerTool(`${prefix}_healthcheck`, {
|
|
32968
|
+
title: "Verify credentials and upstream reachability",
|
|
32969
|
+
description: `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only; never returns the credential itself.`,
|
|
32970
|
+
annotations: {
|
|
32971
|
+
title: "Verify credentials and upstream reachability",
|
|
32972
|
+
readOnlyHint: true,
|
|
32973
|
+
idempotentHint: true,
|
|
32974
|
+
openWorldHint: true
|
|
32975
|
+
},
|
|
32976
|
+
inputSchema: {}
|
|
32977
|
+
}, async () => {
|
|
32978
|
+
let probeStarted = 0;
|
|
32979
|
+
let state;
|
|
32980
|
+
try {
|
|
32981
|
+
state = await resolveCredential();
|
|
32982
|
+
} catch (e) {
|
|
32983
|
+
const classified = classifyThrown?.(e);
|
|
32984
|
+
const result2 = {
|
|
32985
|
+
ok: false,
|
|
32986
|
+
// Still false, and still no source: a classification explains WHY
|
|
32987
|
+
// nothing resolved, it does not invent a credential that did.
|
|
32988
|
+
credential: { source: null, resolved: false },
|
|
32989
|
+
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
32990
|
+
probe: { elapsed_ms: 0 },
|
|
32991
|
+
error: {
|
|
32992
|
+
kind: classified?.kind ?? "no_credential",
|
|
32993
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
32994
|
+
...classified?.detail !== void 0 ? { detail: classified.detail } : {}
|
|
32995
|
+
},
|
|
32996
|
+
// The hint must follow the KIND beside it. Falling back to
|
|
32997
|
+
// `no_credential`'s copy under a classified kind would state a cause
|
|
32998
|
+
// the kind contradicts — the same disagreement this path exists to
|
|
32999
|
+
// remove. So: an inline hint wins; else the classified arm's own
|
|
33000
|
+
// copy (consumer override first); else, for a kind this module has
|
|
33001
|
+
// no copy for, the neutral `unknown` text rather than one that
|
|
33002
|
+
// asserts a cause; else the unclassified `no_credential` default.
|
|
33003
|
+
hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
|
|
33004
|
+
};
|
|
33005
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
33006
|
+
}
|
|
33007
|
+
const credential = {
|
|
33008
|
+
source: state.source,
|
|
33009
|
+
resolved: state.source !== null,
|
|
33010
|
+
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
33011
|
+
};
|
|
33012
|
+
if (!credential.resolved) {
|
|
33013
|
+
const result2 = {
|
|
33014
|
+
ok: false,
|
|
33015
|
+
credential,
|
|
33016
|
+
probe: { elapsed_ms: 0 },
|
|
33017
|
+
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
33018
|
+
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
33019
|
+
};
|
|
33020
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
33021
|
+
}
|
|
33022
|
+
let arm = "ok";
|
|
33023
|
+
let error51;
|
|
33024
|
+
let status;
|
|
33025
|
+
let customHint;
|
|
33026
|
+
probeStarted = Date.now();
|
|
33027
|
+
try {
|
|
33028
|
+
await probeFn();
|
|
33029
|
+
} catch (e) {
|
|
33030
|
+
status = statusOf(e);
|
|
33031
|
+
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
33032
|
+
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
33033
|
+
let kind = arm;
|
|
33034
|
+
let detail;
|
|
33035
|
+
const custom2 = classifyThrown?.(e);
|
|
33036
|
+
if (custom2) {
|
|
33037
|
+
kind = custom2.kind;
|
|
33038
|
+
customHint = custom2.hint;
|
|
33039
|
+
detail = custom2.detail;
|
|
33040
|
+
}
|
|
33041
|
+
error51 = {
|
|
33042
|
+
kind,
|
|
33043
|
+
// Redacted AND bounded before it reaches the result: an upstream
|
|
33044
|
+
// failure routinely quotes what it was sent, and a healthcheck is
|
|
33045
|
+
// the tool people paste into a chat when something is broken.
|
|
33046
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
33047
|
+
...detail !== void 0 ? { detail } : {}
|
|
33048
|
+
};
|
|
33049
|
+
}
|
|
33050
|
+
const result = {
|
|
33051
|
+
ok: error51 === void 0,
|
|
33052
|
+
credential,
|
|
33053
|
+
probe: {
|
|
33054
|
+
...probeUrl ? { url: probeUrl } : {},
|
|
33055
|
+
elapsed_ms: Date.now() - probeStarted,
|
|
33056
|
+
...status !== void 0 ? { status } : {}
|
|
33057
|
+
},
|
|
33058
|
+
...error51 ? { error: error51 } : {},
|
|
33059
|
+
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
33060
|
+
};
|
|
33061
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
33062
|
+
});
|
|
33063
|
+
}
|
|
33064
|
+
|
|
33065
|
+
// src/tools/health.ts
|
|
33066
|
+
var NOT_CONFIGURED = "credentials are not configured";
|
|
33067
|
+
function classifyMhlbError(err) {
|
|
33068
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33069
|
+
if (msg.includes(NOT_CONFIGURED)) return { kind: "no_credential" };
|
|
33070
|
+
if (msg.includes("Could not reach My Hot Lunchbox")) {
|
|
33071
|
+
return {
|
|
33072
|
+
kind: "unreachable",
|
|
33073
|
+
hint: "Could not reach My Hot Lunchbox at all, so the credential was never tested. Check network connectivity, or MYHOTLUNCHBOX_BASE_URL if the app has moved."
|
|
33074
|
+
};
|
|
33075
|
+
}
|
|
33076
|
+
if (msg.includes("rejected the sign-in")) {
|
|
33077
|
+
return {
|
|
33078
|
+
kind: "credential_rejected",
|
|
33079
|
+
hint: "My Hot Lunchbox rejected the sign-in. Check MYHOTLUNCHBOX_USERNAME / MYHOTLUNCHBOX_PASSWORD against https://ordernow.myhotlunchbox.com \u2014 but do NOT re-run this with guesses: repeated failures can lock the account or force a CAPTCHA that blocks server-side sign-in entirely."
|
|
33080
|
+
};
|
|
33081
|
+
}
|
|
33082
|
+
return void 0;
|
|
33083
|
+
}
|
|
33084
|
+
function registerHealthcheckTools(server, client2, readConfig = () => loadConfig()) {
|
|
33085
|
+
registerCredentialHealthcheckTool({
|
|
33086
|
+
server,
|
|
33087
|
+
prefix: "mhlb",
|
|
33088
|
+
hostLabel: "ordernow.myhotlunchbox.com",
|
|
33089
|
+
probePath: "/api/auth/userinfo",
|
|
33090
|
+
resolveCredential: async () => {
|
|
33091
|
+
const { username, password, baseUrl } = readConfig();
|
|
33092
|
+
const source = username && password ? "MYHOTLUNCHBOX_USERNAME+MYHOTLUNCHBOX_PASSWORD" : null;
|
|
33093
|
+
return { source, detail: { base_url: baseUrl } };
|
|
33094
|
+
},
|
|
33095
|
+
// The same authenticated read `mhlb_whoami` makes: cheap, and it changes
|
|
33096
|
+
// nothing — no order is placed, no balance moved.
|
|
33097
|
+
probeFn: () => client2.get("/auth/userinfo"),
|
|
33098
|
+
classifyThrown: classifyMhlbError
|
|
33099
|
+
});
|
|
33100
|
+
}
|
|
33101
|
+
|
|
32922
33102
|
// src/index.ts
|
|
32923
33103
|
await loadDotenvSafely();
|
|
32924
33104
|
var client = new MhlbClient();
|
|
@@ -32934,6 +33114,7 @@ await runMcp({
|
|
|
32934
33114
|
registerOrderTools,
|
|
32935
33115
|
registerBillingTools,
|
|
32936
33116
|
registerCheckoutTools,
|
|
32937
|
-
registerReportTools
|
|
33117
|
+
registerReportTools,
|
|
33118
|
+
registerHealthcheckTools
|
|
32938
33119
|
]
|
|
32939
33120
|
});
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { registerOrderTools } from './tools/orders.js';
|
|
|
9
9
|
import { registerBillingTools } from './tools/billing.js';
|
|
10
10
|
import { registerCheckoutTools } from './tools/checkout.js';
|
|
11
11
|
import { registerReportTools } from './tools/reports.js';
|
|
12
|
+
import { registerHealthcheckTools } from './tools/health.js';
|
|
12
13
|
await loadDotenvSafely();
|
|
13
14
|
// Built here, in the caller, so the deferred-config-error pattern holds: the
|
|
14
15
|
// server still boots (and answers the host's install-time tools/list probe)
|
|
@@ -27,5 +28,6 @@ await runMcp({
|
|
|
27
28
|
registerBillingTools,
|
|
28
29
|
registerCheckoutTools,
|
|
29
30
|
registerReportTools,
|
|
31
|
+
registerHealthcheckTools,
|
|
30
32
|
],
|
|
31
33
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { registerCredentialHealthcheckTool } from '@chrischall/mcp-utils/healthcheck';
|
|
2
|
+
import { loadConfig } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* `mhlb_healthcheck` — the one call that answers "is this connector
|
|
5
|
+
* working?", and the only tool here that reports a failure as DATA rather
|
|
6
|
+
* than throwing.
|
|
7
|
+
*
|
|
8
|
+
* My Hot Lunchbox had none. `mhlb_whoami` looks like one — its own
|
|
9
|
+
* description says "Start here to confirm the session works" — and is not: it
|
|
10
|
+
* is a plain data read that THROWS when auth fails, so the caller gets an
|
|
11
|
+
* exception, not an answer, and cannot tell a bad password from an
|
|
12
|
+
* unreachable host from a lockout.
|
|
13
|
+
*
|
|
14
|
+
* The lockout is why this matters more here than elsewhere. This account
|
|
15
|
+
* counts failed sign-ins and will lock or force a CAPTCHA that blocks
|
|
16
|
+
* server-side sign-in entirely, so a `credential_rejected` hint that invites
|
|
17
|
+
* retrying is actively harmful. {@link classifyMhlbError} carries that
|
|
18
|
+
* warning instead.
|
|
19
|
+
*/
|
|
20
|
+
const NOT_CONFIGURED = 'credentials are not configured';
|
|
21
|
+
export function classifyMhlbError(err) {
|
|
22
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23
|
+
if (msg.includes(NOT_CONFIGURED))
|
|
24
|
+
return { kind: 'no_credential' };
|
|
25
|
+
// The host is unreachable, or MYHOTLUNCHBOX_BASE_URL points somewhere wrong.
|
|
26
|
+
// Nothing here says the credential is bad — do not send anyone to change it.
|
|
27
|
+
if (msg.includes('Could not reach My Hot Lunchbox')) {
|
|
28
|
+
return {
|
|
29
|
+
kind: 'unreachable',
|
|
30
|
+
hint: 'Could not reach My Hot Lunchbox at all, so the credential was never tested. ' +
|
|
31
|
+
'Check network connectivity, or MYHOTLUNCHBOX_BASE_URL if the app has moved.',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (msg.includes('rejected the sign-in')) {
|
|
35
|
+
return {
|
|
36
|
+
kind: 'credential_rejected',
|
|
37
|
+
hint: 'My Hot Lunchbox rejected the sign-in. Check MYHOTLUNCHBOX_USERNAME / MYHOTLUNCHBOX_PASSWORD against ' +
|
|
38
|
+
'https://ordernow.myhotlunchbox.com — but do NOT re-run this with guesses: repeated failures can lock ' +
|
|
39
|
+
'the account or force a CAPTCHA that blocks server-side sign-in entirely.',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
export function registerHealthcheckTools(server, client,
|
|
45
|
+
/** Seam: injectable so tests need no process env. */
|
|
46
|
+
readConfig = () => loadConfig()) {
|
|
47
|
+
registerCredentialHealthcheckTool({
|
|
48
|
+
server,
|
|
49
|
+
prefix: 'mhlb',
|
|
50
|
+
hostLabel: 'ordernow.myhotlunchbox.com',
|
|
51
|
+
probePath: '/api/auth/userinfo',
|
|
52
|
+
resolveCredential: async () => {
|
|
53
|
+
const { username, password, baseUrl } = readConfig();
|
|
54
|
+
// Both halves or nothing: a username with no password cannot sign in,
|
|
55
|
+
// and sending it to the API would spend a failed attempt against an
|
|
56
|
+
// account that locks.
|
|
57
|
+
const source = username && password ? 'MYHOTLUNCHBOX_USERNAME+MYHOTLUNCHBOX_PASSWORD' : null;
|
|
58
|
+
return { source, detail: { base_url: baseUrl } };
|
|
59
|
+
},
|
|
60
|
+
// The same authenticated read `mhlb_whoami` makes: cheap, and it changes
|
|
61
|
+
// nothing — no order is placed, no balance moved.
|
|
62
|
+
probeFn: () => client.get('/auth/userinfo'),
|
|
63
|
+
classifyThrown: classifyMhlbError,
|
|
64
|
+
});
|
|
65
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Single source of truth for the server version; release-please rewrites it. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.4.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myhotlunchbox-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/myhotlunchbox-mcp",
|
|
5
5
|
"description": "My Hot Lunchbox MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"capture:writes": "node scripts/capture-writes.mjs"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^0.
|
|
48
|
+
"@chrischall/mcp-utils": "^0.19.3",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
50
|
"dotenv": "^17.4.0",
|
|
51
51
|
"zod": "^4.4.2"
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/myhotlunchbox-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.4.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "myhotlunchbox-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.4.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|