@ateam-ai/mcp 0.4.68 → 0.4.70
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/package.json +1 -1
- package/src/api.js +23 -0
- package/src/http.js +34 -0
- package/src/tools.js +38 -2
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -487,6 +487,29 @@ function formatError(method, path, status, body, baseUrl) {
|
|
|
487
487
|
503: "A-Team API is temporarily unavailable. Try again in a minute.",
|
|
488
488
|
};
|
|
489
489
|
|
|
490
|
+
// A 500 THAT NAMES A MISSING CONFIGURATION IS NOT "TRY AGAIN IN A MINUTE".
|
|
491
|
+
//
|
|
492
|
+
// /chat answered 500 {"message":"OPENAI_API_KEY is not set"} and this table
|
|
493
|
+
// labelled it "the platform may be temporarily unavailable — try again in a
|
|
494
|
+
// minute". Every part is wrong: nothing is unavailable, a minute changes
|
|
495
|
+
// nothing, and the fix is to set a key. An agent obeying that hint burns its
|
|
496
|
+
// retries on a condition that cannot clear on its own. The body already said
|
|
497
|
+
// so; only the hint disagreed. (2026-08-22, found by sweeping every tool.)
|
|
498
|
+
if (status >= 500) {
|
|
499
|
+
// `body` is what this function receives; asText is derived further down, so
|
|
500
|
+
// read the body directly here rather than a variable that is not yet in scope.
|
|
501
|
+
const bodyText = typeof body === "string"
|
|
502
|
+
? body
|
|
503
|
+
: (() => { try { return JSON.stringify(body || ""); } catch { return ""; } })();
|
|
504
|
+
const m = /\b([A-Z][A-Z0-9_]{3,})\s+is not set\b|\bmissing (?:env|environment) (?:var|variable)\s+([A-Z0-9_]+)/i.exec(bodyText);
|
|
505
|
+
if (m) {
|
|
506
|
+
const name = m[1] || m[2];
|
|
507
|
+
hints[status] =
|
|
508
|
+
`CONFIGURATION, not an outage: the server reports ${name} is not set. Retrying will not help and nothing is down — ` +
|
|
509
|
+
`this needs ${name} configured on the A-Team backend serving ${baseUrl || "this API"}. Other tools are unaffected.`;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
490
513
|
// Special-case: GitHub App not connected for this tenant. This is the wall a
|
|
491
514
|
// user hits the first time they iterate on CONNECTOR CODE (github_patch /
|
|
492
515
|
// github_write / github_push / build_and_run auto-pull). The raw
|
package/src/http.js
CHANGED
|
@@ -31,6 +31,22 @@ import {
|
|
|
31
31
|
import { mountOAuth } from "./oauth.js";
|
|
32
32
|
import { connectGithubPage } from "./pages.js";
|
|
33
33
|
|
|
34
|
+
// Read once at import: the version of the code in THIS process, and when it
|
|
35
|
+
// started. See the /health handler for why both matter.
|
|
36
|
+
const PKG_VERSION = await (async () => {
|
|
37
|
+
try {
|
|
38
|
+
const { readFileSync } = await import("node:fs");
|
|
39
|
+
const { fileURLToPath } = await import("node:url");
|
|
40
|
+
const { dirname, join } = await import("node:path");
|
|
41
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version || "unknown";
|
|
43
|
+
} catch {
|
|
44
|
+
// Never let a liveness probe fail over its own labelling.
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
})();
|
|
48
|
+
const STARTED_AT = new Date().toISOString();
|
|
49
|
+
|
|
34
50
|
// Active sessions
|
|
35
51
|
const transports = {};
|
|
36
52
|
|
|
@@ -213,10 +229,28 @@ export function startHttpServer(port = 3100) {
|
|
|
213
229
|
}
|
|
214
230
|
|
|
215
231
|
// ─── Health check ─────────────────────────────────────────────
|
|
232
|
+
//
|
|
233
|
+
// version + startedAt are the whole point of this probe, not decoration.
|
|
234
|
+
//
|
|
235
|
+
// Without them every field here was TRUE while the agent served seven-day-old
|
|
236
|
+
// code: ok, service, transport and sessions all reported correctly, and not
|
|
237
|
+
// one of them could reveal that the process had been running since Aug 15
|
|
238
|
+
// across ~10 publishes. A liveness probe that cannot answer "is this the code
|
|
239
|
+
// I shipped?" is the truthful-but-useless shape — and a stale server that
|
|
240
|
+
// ANSWERS is worse than one that is down, because its errors describe bugs
|
|
241
|
+
// that were already fixed. (2026-08-22: it returned a 401 from a code path
|
|
242
|
+
// deleted in 2ff2a34, and a session went debugging a system that was correct.)
|
|
243
|
+
//
|
|
244
|
+
// version comes from the package.json NEXT TO THIS FILE, read at import, so it
|
|
245
|
+
// describes the code actually loaded — not what npm has, and not what a
|
|
246
|
+
// container was built with.
|
|
216
247
|
app.get("/health", (_req, res) => {
|
|
217
248
|
res.json({
|
|
218
249
|
ok: true,
|
|
219
250
|
service: "ateam-mcp",
|
|
251
|
+
version: PKG_VERSION,
|
|
252
|
+
startedAt: STARTED_AT,
|
|
253
|
+
uptime_s: Math.round(process.uptime()),
|
|
220
254
|
transport: "http",
|
|
221
255
|
sessions: getSessionStats(),
|
|
222
256
|
});
|
package/src/tools.js
CHANGED
|
@@ -4303,6 +4303,18 @@ const handlers = {
|
|
|
4303
4303
|
post("/deploy/connector", { connector }, sid),
|
|
4304
4304
|
|
|
4305
4305
|
ateam_upload_connector_files: async ({ connector_id, files }, sid) => {
|
|
4306
|
+
// A MISSING ARGUMENT MUST NAME ITSELF. Omitting `files` crashed with
|
|
4307
|
+
// "files is not iterable" — a stack-trace phrase that names a JS type
|
|
4308
|
+
// problem, not the thing the caller has to change, and it reads like the
|
|
4309
|
+
// tool is broken rather than the call. Same for a non-array.
|
|
4310
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
4311
|
+
throw new Error(
|
|
4312
|
+
`ateam_upload_connector_files needs files: an ARRAY of { path, content } (content_base64 or url also accepted). ` +
|
|
4313
|
+
`Got ${files === undefined ? "nothing" : JSON.stringify(files).slice(0, 60)}. ` +
|
|
4314
|
+
`To upload a connector already in the repo, use ateam_upload_connector(connector_id, github:true) instead.`,
|
|
4315
|
+
);
|
|
4316
|
+
}
|
|
4317
|
+
if (!connector_id) throw new Error("ateam_upload_connector_files needs connector_id — the connector these files belong to.");
|
|
4306
4318
|
// Resolve content_base64 and url into plain content before sending to backend
|
|
4307
4319
|
const resolved = [];
|
|
4308
4320
|
for (const file of files) {
|
|
@@ -4697,10 +4709,34 @@ const handlers = {
|
|
|
4697
4709
|
};
|
|
4698
4710
|
},
|
|
4699
4711
|
|
|
4700
|
-
ateam_test_pipeline: async ({ solution_id, skill_id, message }, sid) =>
|
|
4701
|
-
|
|
4712
|
+
ateam_test_pipeline: async ({ solution_id, skill_id, message }, sid) => {
|
|
4713
|
+
// NEVER INTERPOLATE undefined INTO A PATH. Omitting skill_id produced a
|
|
4714
|
+
// request to /skills/undefined/test-pipeline, so the server answered about
|
|
4715
|
+
// a skill literally named "undefined" — the caller then hunts a routing
|
|
4716
|
+
// problem instead of reading "you forgot skill_id".
|
|
4717
|
+
if (!skill_id) {
|
|
4718
|
+
throw new Error(
|
|
4719
|
+
`ateam_test_pipeline needs skill_id — it tests ONE skill's intent pipeline. ` +
|
|
4720
|
+
`List them with ateam_get_solution(view:"skills"). To send a message without choosing a skill, use ateam_conversation (it auto-routes).`,
|
|
4721
|
+
);
|
|
4722
|
+
}
|
|
4723
|
+
if (!message) throw new Error("ateam_test_pipeline needs message — the utterance to run through the pipeline.");
|
|
4724
|
+
return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test-pipeline`, { message }, sid, { timeoutMs: 30_000 });
|
|
4725
|
+
},
|
|
4702
4726
|
|
|
4703
4727
|
ateam_test_voice: async ({ solution_id, messages, phone_number, skill_slug, timeout_ms }, sid) => {
|
|
4728
|
+
// Without this, omitting `messages` died on messages.length with
|
|
4729
|
+
// "Cannot read properties of undefined (reading 'length')" — an internal
|
|
4730
|
+
// crash where the caller needed one sentence about the argument. Note it
|
|
4731
|
+
// is messageS (a turn array), which is easy to miss next to every other
|
|
4732
|
+
// test tool taking a single `message`.
|
|
4733
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
4734
|
+
throw new Error(
|
|
4735
|
+
`ateam_test_voice needs messages: an ARRAY of caller turns, e.g. ["book me an appointment", "tomorrow at 3"]. ` +
|
|
4736
|
+
`Got ${messages === undefined ? "nothing" : JSON.stringify(messages).slice(0, 60)}. ` +
|
|
4737
|
+
`(It is "messages", plural — unlike ateam_test_skill/ateam_conversation, which take a single message.)`,
|
|
4738
|
+
);
|
|
4739
|
+
}
|
|
4704
4740
|
const body = { messages };
|
|
4705
4741
|
if (phone_number) body.phone_number = phone_number;
|
|
4706
4742
|
if (skill_slug) body.skill_slug = skill_slug;
|