@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.js +129 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.4.40",
3
+ "version": "0.4.42",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "A-Team MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
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. 'github' (DEFAULT) — read from and write to the tenant's GitHub repo (GitHub is master; the normal path). 'local' read from and write to the Builder FS store (no GitHub repo required). Use 'local' ONLY for a repo-less bootstrap tenant (e.g. freshly onboarded from a template, before GitHub is connected). This is a DEDICATED, EXPLICIT switch never a fallback. Redeploy is local in both modes.",
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
- " • Inspect arbitrary branch/tag/commit comparisons (override base/head)",
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
- const isLocal = source === "local";
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
- const readResult = await get(`/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent(filePath)}`, sid);
3267
- current = JSON.parse(readResult.content);
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
- ? { after_state: patched }
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
- return get(`/deploy/solutions/${solution_id}/github/read?${qs.toString()}`, sid);
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) =>