@ateam-ai/mcp 0.4.40 → 0.4.42
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/tools.js +129 -8
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -87,6 +87,63 @@ async function pollDeployJob(jobId, sid, { label = 'deploy', maxMs = 15 * 60_000
|
|
|
87
87
|
//
|
|
88
88
|
// Returns null when the solution declares no widgets (nothing to check), else
|
|
89
89
|
// { ok, checked, healthy, plugins[], issues[]?, hint? }.
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
// Authored-source representation marker
|
|
92
|
+
//
|
|
93
|
+
// A skill/solution definition read from GitHub is NOT the runtime. It is a
|
|
94
|
+
// mirror that drifts: on solution 'ada' (2026-08-04) the `dev` copy declared 29
|
|
95
|
+
// tools while production was running 66, because deploy-time connector imports
|
|
96
|
+
// are regenerated on every deploy and only mirrored to `main`. An agent that
|
|
97
|
+
// answers "what tools does this skill have?" from a repo read gets a wrong
|
|
98
|
+
// answer today, and would get an emptier one once generated data leaves the
|
|
99
|
+
// committed file.
|
|
100
|
+
//
|
|
101
|
+
// So every read of a definition path carries an ADDITIVE `_ateam_representation`
|
|
102
|
+
// telling the caller what it is holding and which tool returns the live view.
|
|
103
|
+
// Additive on purpose — wrapping or reshaping the existing response would break
|
|
104
|
+
// callers that expect the raw payload.
|
|
105
|
+
//
|
|
106
|
+
// `kind` reports what the file ACTUALLY is right now, not what we intend it to
|
|
107
|
+
// become: files without `source_schema_version >= 2` still carry generated data,
|
|
108
|
+
// so calling them "authored_source" today would be a lie.
|
|
109
|
+
// See Docs/WIP/SKILL_JSON_SPLIT_PLAN_2026-08-04.md (Phase C).
|
|
110
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
111
|
+
const _SKILL_JSON_RE = /^skills\/([^/]+)\/skill\.json$/;
|
|
112
|
+
|
|
113
|
+
function _representationFor(filePath, content, solution_id) {
|
|
114
|
+
const p = String(filePath || "");
|
|
115
|
+
const skillMatch = p.match(_SKILL_JSON_RE);
|
|
116
|
+
if (p !== "solution.json" && !skillMatch) return null;
|
|
117
|
+
|
|
118
|
+
let schemaVersion = null;
|
|
119
|
+
try {
|
|
120
|
+
const parsed = typeof content === "string" ? JSON.parse(content) : content;
|
|
121
|
+
schemaVersion = parsed?.source_schema_version ?? null;
|
|
122
|
+
} catch { /* not JSON, or truncated — fall through to the v1 wording */ }
|
|
123
|
+
|
|
124
|
+
const authoredOnly = typeof schemaVersion === "number" && schemaVersion >= 2;
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
kind: authoredOnly ? "authored_source" : "git_mirror_v1",
|
|
128
|
+
is_runtime_state: false,
|
|
129
|
+
runtime_state_may_differ: true,
|
|
130
|
+
may_include_generated_fields: !authoredOnly,
|
|
131
|
+
...(authoredOnly
|
|
132
|
+
? { generated_fields_omitted: ["auto_imported_tools", "deployment_timestamps"] }
|
|
133
|
+
: { contains_generated_fields: ["auto_imported_tools", "deployment_timestamps"] }),
|
|
134
|
+
warning: authoredOnly
|
|
135
|
+
? "Authored source only. Connector-imported tools are NOT in this file — they are regenerated at deploy time. Do not answer capability questions from it."
|
|
136
|
+
: "This is a git mirror, not runtime state. Its tools[] and timestamps are a snapshot from the last write to this branch and may not match what is deployed. Do not answer capability questions from it.",
|
|
137
|
+
live_state_tool: {
|
|
138
|
+
name: "ateam_get_solution",
|
|
139
|
+
arguments: {
|
|
140
|
+
solution_id,
|
|
141
|
+
...(skillMatch ? { skill_id: skillMatch[1], section: "tools" } : {}),
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
90
147
|
// Compress a skill/solution definition to a small, non-truncating summary for
|
|
91
148
|
// tool results — enough to confirm the shape without the 10s-of-KB full doc.
|
|
92
149
|
function _summarizeDef(def) {
|
|
@@ -773,7 +830,7 @@ export const tools = [
|
|
|
773
830
|
type: "string",
|
|
774
831
|
enum: ["github", "local"],
|
|
775
832
|
description:
|
|
776
|
-
"Where the solution/skill definition lives.
|
|
833
|
+
"Where the solution/skill definition lives. Omit (DEFAULT) — prefer the tenant's GitHub repo (GitHub is master), but AUTO-DEGRADE to the Builder FS store if the tenant hasn't connected a repo, so a simple def patch always succeeds (it's pushed to GitHub once connected). 'github' — force GitHub; fails loud if not connected (use when you specifically require the repo write). 'local' — force the Builder FS store, no GitHub (repo-less bootstrap tenant). Redeploy is local in all modes.",
|
|
777
834
|
},
|
|
778
835
|
include_definition: {
|
|
779
836
|
type: "boolean",
|
|
@@ -1610,7 +1667,11 @@ export const tools = [
|
|
|
1610
1667
|
core: true,
|
|
1611
1668
|
description:
|
|
1612
1669
|
"Read any file from a solution's GitHub repo. Returns the file content. Use this to read connector source code, skill definitions, or any versioned file. " +
|
|
1613
|
-
"Default reads from `main` (deployed/prod state). Pass `ref: 'dev'` to read in-progress work
|
|
1670
|
+
"Default reads from `main` (deployed/prod state). Pass `ref: 'dev'` to read in-progress work.\n\n" +
|
|
1671
|
+
"⚠️ NOT RUNTIME STATE. For `solution.json` and `skills/<id>/skill.json` this returns a git MIRROR, not what is deployed. " +
|
|
1672
|
+
"Connector-imported tools are regenerated at deploy time, so a repo copy's `tools[]` can differ from production (on one solution `dev` showed 29 tools while production ran 66). " +
|
|
1673
|
+
"Reads of those paths carry an `_ateam_representation` field saying what you are holding. " +
|
|
1674
|
+
"To answer \"what can this skill actually do?\", call ateam_get_solution(solution_id, skill_id, section:'tools') — never this tool.",
|
|
1614
1675
|
inputSchema: {
|
|
1615
1676
|
type: "object",
|
|
1616
1677
|
properties: {
|
|
@@ -1764,7 +1825,9 @@ export const tools = [
|
|
|
1764
1825
|
"Use this when you want to:\n" +
|
|
1765
1826
|
" • Review changes before promoting to prod\n" +
|
|
1766
1827
|
" • See if dev is ahead of main at all (returns ahead_by: 0 if nothing to promote)\n" +
|
|
1767
|
-
" •
|
|
1828
|
+
" • **Diagnose a failed promote** — check `behind_by` and `status`. `status: 'diverged'` (behind_by > 0) means main holds commits dev never received, which is what makes ateam_github_promote return 409 Merge conflict. ALWAYS call this after a promote failure, before reporting anything to the user.\n" +
|
|
1829
|
+
" • Inspect arbitrary branch/tag/commit comparisons (override base/head)\n\n" +
|
|
1830
|
+
"Note: `files[]` lists what DIFFERS, not what conflicts. For solution.json and skills/*/skill.json the difference is often deploy-generated data (regenerated connector tools, timestamps) rather than authored change — see ateam_github_read's `_ateam_representation`.",
|
|
1768
1831
|
inputSchema: {
|
|
1769
1832
|
type: "object",
|
|
1770
1833
|
properties: {
|
|
@@ -3240,7 +3303,15 @@ const handlers = {
|
|
|
3240
3303
|
// yet connected). GitHub is still master overall; local is a temporary
|
|
3241
3304
|
// bootstrap until the tenant connects a repo (then local is pushed → GitHub).
|
|
3242
3305
|
// Redeploy (Phase 4) is local (Builder FS → Core) in BOTH modes.
|
|
3243
|
-
|
|
3306
|
+
let isLocal = source === "local";
|
|
3307
|
+
// Was a source EXPLICITLY chosen? An unspecified source is the default ("github")
|
|
3308
|
+
// and MAY auto-degrade to local when the tenant hasn't connected a repo — a simple
|
|
3309
|
+
// def patch must work FS-only (Arie's rule: create/patch allowed offline; only
|
|
3310
|
+
// GitHub-native ops refuse). An explicit source:'github' is honored as-is (the
|
|
3311
|
+
// caller asked for GitHub → it fails loud if not connected), and explicit 'local'
|
|
3312
|
+
// stays local.
|
|
3313
|
+
const sourceExplicit = source === "github" || source === "local";
|
|
3314
|
+
let degradedToLocal = false;
|
|
3244
3315
|
|
|
3245
3316
|
// Phase 1: Read current state (or create scaffold if new skill)
|
|
3246
3317
|
let current;
|
|
@@ -3263,8 +3334,39 @@ const handlers = {
|
|
|
3263
3334
|
throw new Error(`Local ${filePath} not found (empty definition)`);
|
|
3264
3335
|
}
|
|
3265
3336
|
} else {
|
|
3266
|
-
|
|
3267
|
-
|
|
3337
|
+
try {
|
|
3338
|
+
const readResult = await get(`/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent(filePath)}`, sid);
|
|
3339
|
+
current = JSON.parse(readResult.content);
|
|
3340
|
+
} catch (ghErr) {
|
|
3341
|
+
// Auto-degrade a DEFAULT-source patch to the Builder FS when the tenant
|
|
3342
|
+
// hasn't connected GitHub. A github/read failure alone is ambiguous (could
|
|
3343
|
+
// be a wrong solution_id or a transient Core hiccup — both must still fail
|
|
3344
|
+
// loud), so disambiguate with the definitive /github/connected probe and
|
|
3345
|
+
// only degrade on a genuine "not connected". Explicit source:'github' never
|
|
3346
|
+
// degrades. The local write below reconciles to GitHub once connected.
|
|
3347
|
+
let connected = true;
|
|
3348
|
+
if (!sourceExplicit) {
|
|
3349
|
+
try {
|
|
3350
|
+
const probe = await get(`/deploy/solutions/${solution_id}/github/connected`, sid);
|
|
3351
|
+
connected = probe?.connected !== false && probe?.enabled !== false;
|
|
3352
|
+
} catch { connected = true; /* probe failed → don't mask the real read error */ }
|
|
3353
|
+
}
|
|
3354
|
+
if (!sourceExplicit && !connected) {
|
|
3355
|
+
isLocal = true;
|
|
3356
|
+
degradedToLocal = true;
|
|
3357
|
+
const r = target === "skill" && skill_id
|
|
3358
|
+
? await get(`/deploy/solutions/${solution_id}/skills/${encodeURIComponent(skill_id)}`, sid)
|
|
3359
|
+
: await get(`/deploy/solutions/${solution_id}/definition?raw=1`, sid);
|
|
3360
|
+
current = target === "skill" && skill_id
|
|
3361
|
+
? (r.skill || r.definition || r)
|
|
3362
|
+
: (r.solution || r);
|
|
3363
|
+
if (!current || typeof current !== "object") {
|
|
3364
|
+
throw new Error(`Local ${filePath} not found (empty definition)`);
|
|
3365
|
+
}
|
|
3366
|
+
} else {
|
|
3367
|
+
throw ghErr; // connected (real error) or explicit github → surface it
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3268
3370
|
}
|
|
3269
3371
|
} catch (err) {
|
|
3270
3372
|
// OPEN-31 guard: only scaffold-create when the skill is GENUINELY ABSENT.
|
|
@@ -3496,7 +3598,17 @@ const handlers = {
|
|
|
3496
3598
|
// output cap and truncates the rest of the result. Return a compact
|
|
3497
3599
|
// summary by default; pass include_definition:true for the whole thing.
|
|
3498
3600
|
...(include_definition
|
|
3499
|
-
? {
|
|
3601
|
+
? {
|
|
3602
|
+
after_state: patched,
|
|
3603
|
+
// The base of this after-state came from a git read, so it inherits
|
|
3604
|
+
// that copy's staleness — notably regenerated connector tools. Say so
|
|
3605
|
+
// rather than letting an agent treat it as runtime truth.
|
|
3606
|
+
_ateam_representation: _representationFor(
|
|
3607
|
+
target === "skill" && skill_id ? `skills/${skill_id}/skill.json` : "solution.json",
|
|
3608
|
+
patched,
|
|
3609
|
+
solution_id,
|
|
3610
|
+
),
|
|
3611
|
+
}
|
|
3500
3612
|
: { after_state_summary: _summarizeDef(patched) }),
|
|
3501
3613
|
would_write,
|
|
3502
3614
|
would_write_bytes: JSON.stringify(patched, null, 2).length,
|
|
@@ -3646,6 +3758,10 @@ const handlers = {
|
|
|
3646
3758
|
ok: true,
|
|
3647
3759
|
solution_id,
|
|
3648
3760
|
source: isLocal ? "local" : "github",
|
|
3761
|
+
...(degradedToLocal && {
|
|
3762
|
+
degraded_to_local: true,
|
|
3763
|
+
_degrade_note: `GitHub isn't connected for this tenant, so this patch was saved to the Builder store only (the edit succeeded). It will be pushed to GitHub automatically once the tenant connects a repo (Tenant Admin → GitHub). GitHub-native actions (promote, connector-source patch, github-sourced deploy) will refuse until then.`,
|
|
3764
|
+
}),
|
|
3649
3765
|
...(isLocal ? {} : {
|
|
3650
3766
|
branch: writeBranch,
|
|
3651
3767
|
// Be explicit: a github write lands on `dev`, not prod. The change is
|
|
@@ -4394,7 +4510,12 @@ const handlers = {
|
|
|
4394
4510
|
ateam_github_read: async ({ solution_id, path: filePath, ref }, sid) => {
|
|
4395
4511
|
const qs = new URLSearchParams({ path: filePath });
|
|
4396
4512
|
if (ref) qs.set('branch', ref);
|
|
4397
|
-
|
|
4513
|
+
const result = await get(`/deploy/solutions/${solution_id}/github/read?${qs.toString()}`, sid);
|
|
4514
|
+
// Additive only — never reshape `result`, callers depend on the raw payload.
|
|
4515
|
+
const rep = _representationFor(filePath, result?.content, solution_id);
|
|
4516
|
+
return rep && result && typeof result === "object"
|
|
4517
|
+
? { ...result, _ateam_representation: rep }
|
|
4518
|
+
: result;
|
|
4398
4519
|
},
|
|
4399
4520
|
|
|
4400
4521
|
ateam_github_patch: async ({ solution_id, path: filePath, content, search, replace, message, ref }, sid) =>
|