@ateam-ai/mcp 0.4.88 → 0.4.89

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 +2 -2
  2. package/src/tools.js +189 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.4.88",
3
+ "version": "0.4.89",
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",
@@ -13,7 +13,7 @@
13
13
  "start:http": "node src/index.js --http",
14
14
  "dev": "node --watch src/index.js",
15
15
  "dev:http": "node --watch src/index.js --http",
16
- "test": "node test/session-isolation.test.mjs && node test/widget-protocol.test.mjs && node test/actor-binding.test.mjs && node test/spec-topics.test.mjs && node test/example-types.test.mjs && node --test test/deploy-status-truth.test.mjs"
16
+ "test": "node test/session-isolation.test.mjs && node test/widget-protocol.test.mjs && node test/actor-binding.test.mjs && node test/spec-topics.test.mjs && node test/example-types.test.mjs && node --test test/deploy-status-truth.test.mjs && node --test test/connector-source-provenance.test.mjs"
17
17
  },
18
18
  "keywords": [
19
19
  "mcp",
package/src/tools.js CHANGED
@@ -1856,7 +1856,9 @@ export const tools = [
1856
1856
  name: "ateam_get_connector_source",
1857
1857
  core: true,
1858
1858
  description:
1859
- `Read the source code files of a deployed MCP connector. Returns all files (server.js, package.json, etc.) stored in the mcp_store for this connector. Use this BEFORE patching or rewriting a connector — always read the current code first so you can make surgical fixes instead of blind full rewrites.`,
1859
+ `Read a connector's AUTHORED source — the code the Builder holds as the source of record (its authored store, or GitHub). Returns a file manifest, or one file's content with path:'<file>'. Use this BEFORE patching or rewriting a connector, so you make surgical fixes instead of blind full rewrites. Every answer carries `+
1860
+ `\`provenance\` (authored_fs | github) so you know which store you are reading.\n\n` +
1861
+ `This does NOT return what Core is currently RUNNING. Those are different questions and used to share one answer: a connector could be deployed and healthy while no authored copy of it existed anywhere, and this tool would hand back the runtime bytes as though they were the source. If there is no authored source you get AUTHORED_SOURCE_MISSING, not a silent substitute. To see the deployed copy use ateam_get_deployed_connector_source; to adopt it as authored source use ateam_recover_connector_source.`,
1860
1862
  inputSchema: {
1861
1863
  type: "object",
1862
1864
  properties: {
@@ -1876,6 +1878,45 @@ export const tools = [
1876
1878
  required: ["solution_id", "connector_id"],
1877
1879
  },
1878
1880
  },
1881
+ {
1882
+ name: "ateam_get_deployed_connector_source",
1883
+ core: true,
1884
+ description:
1885
+ `Read the DEPLOYED copy of a connector — the files ADAS Core is actually running. This is a runtime projection of the last successful deploy, NOT the source of record: it can differ from the authored source, and it may exist for a connector the Builder cannot reproduce at all. Answers carry authored_source_of_record:false so that is never in doubt.\n\n` +
1886
+ `Use it to diagnose ("what is actually running?"), to compare against ateam_get_connector_source, or to inspect a connector whose authored source is missing before deciding whether to adopt it with ateam_recover_connector_source. Do NOT copy bytes out of here and re-upload them as if you had authored them — that launders a runtime copy into the source of record and hides the fact that the real source was lost.`,
1887
+ inputSchema: {
1888
+ type: "object",
1889
+ properties: {
1890
+ solution_id: { type: "string", description: "The solution ID" },
1891
+ connector_id: { type: "string", description: "The connector ID to inspect" },
1892
+ path: {
1893
+ type: "string",
1894
+ description: "Optional. Read ONE file. Omit for a manifest (paths + sizes) — a whole connector exceeds the output limit and truncates.",
1895
+ },
1896
+ },
1897
+ required: ["solution_id", "connector_id"],
1898
+ },
1899
+ },
1900
+ {
1901
+ name: "ateam_recover_connector_source",
1902
+ core: true,
1903
+ description:
1904
+ `ADOPT the deployed copy of a connector as its AUTHORED source. The only sanctioned Core→Builder direction, and deliberately explicit: it is never part of a deploy.\n\n` +
1905
+ `Use it when a connector is running in Core but has no authored source (ateam_get_connector_source returns AUTHORED_SOURCE_MISSING) — the running bytes may be the only surviving copy of real work. The recovered files are STAMPED as recovered_from_core with a timestamp, so a reconstruction is never later mistaken for code someone wrote. Refuses with AUTHORED_SOURCE_EXISTS if authored source is already present; pass force:true only after comparing both copies and deciding the deployed one is the keeper.\n\n` +
1906
+ `Binaries and files over 512KB cannot round-trip and are reported under not_recovered — they stay missing. Push the result to GitHub afterwards so the recovered source is not held in one place only.`,
1907
+ inputSchema: {
1908
+ type: "object",
1909
+ properties: {
1910
+ solution_id: { type: "string", description: "The solution ID" },
1911
+ connector_id: { type: "string", description: "The connector ID to recover" },
1912
+ force: {
1913
+ type: "boolean",
1914
+ description: "Overwrite EXISTING authored source with Core's deployed copy. Default false. Only after comparing the two — the deployed copy may be older than what was authored.",
1915
+ },
1916
+ },
1917
+ required: ["solution_id", "connector_id"],
1918
+ },
1919
+ },
1879
1920
  {
1880
1921
  name: "ateam_get_metrics",
1881
1922
  monitoring: { safe: true, cost: "cheap", latency_ms_p95: 1000, output: "bounded", poll_interval_s: 30 },
@@ -2574,6 +2615,8 @@ const TENANT_TOOLS = new Set([
2574
2615
  "ateam_chain_status",
2575
2616
  "ateam_get_widget_catalog",
2576
2617
  "ateam_get_connector_source",
2618
+ "ateam_get_deployed_connector_source",
2619
+ "ateam_recover_connector_source",
2577
2620
  "ateam_get_metrics",
2578
2621
  "ateam_diff",
2579
2622
  "ateam_verify_consistency",
@@ -3127,7 +3170,12 @@ function chainTreeOf(resp) {
3127
3170
  };
3128
3171
  }
3129
3172
 
3130
- const handlers = {
3173
+ // Exported for tests. handleToolCall below is the runtime entry point and stays
3174
+ // the only one production code should use; reaching a handler directly lets a
3175
+ // test EXECUTE it instead of asserting against this file's source text, which
3176
+ // is the difference between proving behaviour and matching a string that a
3177
+ // rename would quietly satisfy.
3178
+ export const handlers = {
3131
3179
  ateam_bootstrap: async () => ({
3132
3180
  runtime: {
3133
3181
  ateam_mcp_version: MCP_VERSION,
@@ -3272,7 +3320,7 @@ const handlers = {
3272
3320
  },
3273
3321
  advanced_tools: {
3274
3322
  _note: "These tools are available but hidden from the default tool list. Call them by name when you need fine-grained control.",
3275
- debugging: ["ateam_get_execution_logs", "ateam_connector_logs", "ateam_get_metrics", "ateam_diff", "ateam_get_connector_source"],
3323
+ debugging: ["ateam_get_execution_logs", "ateam_connector_logs", "ateam_get_metrics", "ateam_diff", "ateam_get_connector_source", "ateam_get_deployed_connector_source"],
3276
3324
  manual_lifecycle: ["ateam_validate_skill", "ateam_validate_solution", "ateam_deploy_solution", "ateam_deploy_skill", "ateam_deploy_connector", "ateam_update", "ateam_redeploy"],
3277
3325
  async_testing: ["ateam_test_status", "ateam_test_abort"],
3278
3326
  other: ["ateam_upload_connector_files", "ateam_solution_chat"],
@@ -3646,6 +3694,12 @@ const handlers = {
3646
3694
  if (!mcp_store) {
3647
3695
  try {
3648
3696
  const ghStatus = await get(`/deploy/solutions/${solutionId}/github/status`, sid);
3697
+ // repo_url only says a REPO EXISTS. It has never said the repo carries
3698
+ // this solution's connector source, and treating the two as the same
3699
+ // claim is how a connector with nothing in the repo used to slip
3700
+ // through Phase 0 and then vanish from connectors[] below. The real
3701
+ // per-connector answer comes from pull-bundle, and it is acted on
3702
+ // there — this stays a cheap "is GitHub worth asking at all?" gate.
3649
3703
  if (ghStatus?.repo_url) {
3650
3704
  github = true;
3651
3705
  }
@@ -3690,13 +3744,30 @@ const handlers = {
3690
3744
  // shape to the connector id and dedupe, so a mis-keyed mcp_store can never
3691
3745
  // manufacture file-path connectors. (Core also rejects "/"-bearing ids at
3692
3746
  // its boundary as defense-in-depth.)
3693
- if (!connectors?.length && Object.keys(effectiveMcpStore).length > 0) {
3694
- const connIds = [...new Set(
3695
- Object.keys(effectiveMcpStore).map((k) => {
3747
+ //
3748
+ // B5 A CONNECTOR WITH NO SOURCE IN THE REPO MUST NOT VANISH.
3749
+ // Synthesizing purely from mcp_store keys means a declared connector the
3750
+ // repo does not carry is simply ABSENT from connectors[], so nothing
3751
+ // validates it, nothing reports it, and the deploy proceeds as though it
3752
+ // were never part of the solution. Phase 0 made this likelier by
3753
+ // treating "the repo exists" (repo_url) as "the repo has the source" —
3754
+ // two different claims.
3755
+ //
3756
+ // pull-bundle now answers per connector, so union those ids in. A
3757
+ // connector listed here still deploys when its authored source lives in
3758
+ // the Builder's store; what it can no longer do is disappear.
3759
+ const missingSource = Array.isArray(pullResult.connectors_missing_source)
3760
+ ? pullResult.connectors_missing_source : [];
3761
+ const unreadable = Array.isArray(pullResult.connectors_unreadable)
3762
+ ? pullResult.connectors_unreadable : [];
3763
+ if (!connectors?.length && (Object.keys(effectiveMcpStore).length > 0 || missingSource.length > 0)) {
3764
+ const connIds = [...new Set([
3765
+ ...Object.keys(effectiveMcpStore).map((k) => {
3696
3766
  const m = String(k).match(/^connectors\/([^/]+)\//);
3697
3767
  return m ? m[1] : k;
3698
- })
3699
- )];
3768
+ }),
3769
+ ...missingSource.map((c) => (typeof c === "string" ? c : c?.id)).filter(Boolean),
3770
+ ])];
3700
3771
  connectors = connIds.map((id) => ({
3701
3772
  id,
3702
3773
  name: id,
@@ -3710,6 +3781,12 @@ const handlers = {
3710
3781
  connectors_found: pullResult.connectors_found || 0,
3711
3782
  files_loaded: pullResult.files_loaded || 0,
3712
3783
  connectors_synthesized: connectors?.length || 0,
3784
+ // Named, not swallowed. The repo existing said nothing about these.
3785
+ ...(missingSource.length > 0 && {
3786
+ connectors_missing_source: missingSource,
3787
+ note: "These connectors are declared but the repo carries no source for them. They are still deployed if the Builder holds their authored source; if it does not, validation refuses and ateam_get_connector_source / ateam_recover_connector_source tell you which.",
3788
+ }),
3789
+ ...(unreadable.length > 0 && { connectors_unreadable: unreadable }),
3713
3790
  });
3714
3791
  } catch (err) {
3715
3792
  return {
@@ -5247,8 +5324,46 @@ const handlers = {
5247
5324
  },
5248
5325
 
5249
5326
  ateam_get_connector_source: async ({ solution_id, connector_id, path }, sid) => {
5250
- const data = await get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid);
5327
+ let data;
5328
+ try {
5329
+ data = await get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid);
5330
+ } catch (err) {
5331
+ // AUTHORED_SOURCE_MISSING is a real, actionable answer — not a lookup
5332
+ // failure. Returning the raw 404 would send a caller hunting for a wrong
5333
+ // solution_id, when the true state is "Core may be running this connector
5334
+ // and nobody can reproduce it". Say that, and name the two tools that act
5335
+ // on it, instead of leaving the agent to improvise a rewrite.
5336
+ let parsed = null;
5337
+ try { parsed = JSON.parse(err.body || "{}"); } catch { /* not JSON */ }
5338
+ if (err.status === 404 && parsed?.code === "AUTHORED_SOURCE_MISSING") {
5339
+ return {
5340
+ ok: false,
5341
+ code: "AUTHORED_SOURCE_MISSING",
5342
+ connector_id,
5343
+ error: parsed.error,
5344
+ deployed_in_core: parsed.deployed_in_core === true,
5345
+ next: parsed.deployed_in_core
5346
+ ? [
5347
+ `ateam_get_deployed_connector_source(solution_id:'${solution_id}', connector_id:'${connector_id}') — see what Core is actually running`,
5348
+ `ateam_recover_connector_source(solution_id:'${solution_id}', connector_id:'${connector_id}') — adopt it as authored source, stamped as recovered`,
5349
+ ]
5350
+ : [`ateam_create_connector — nothing has been authored for this connector yet`],
5351
+ warning: parsed.deployed_in_core
5352
+ ? "Do NOT write a replacement from memory. A running connector's code is recoverable; an improvised rewrite silently replaces working code with a guess."
5353
+ : undefined,
5354
+ };
5355
+ }
5356
+ throw err;
5357
+ }
5251
5358
  const files = Array.isArray(data?.files) ? data.files : [];
5359
+ // Provenance travels with every answer. Without it a caller cannot tell the
5360
+ // authored store from GitHub — and historically could not tell either from
5361
+ // Core's runtime copy, which is how a lost source looked like a present one.
5362
+ const prov = {
5363
+ provenance: data?.provenance,
5364
+ ...(data?.scheme && { scheme: data.scheme }),
5365
+ authored_source_of_record: data?.authored_source_of_record !== false,
5366
+ };
5252
5367
  // A whole connector's source easily exceeds the ~50KB tool-output ceiling and
5253
5368
  // truncates (you couldn't read the file you needed). So: no `path` → return a
5254
5369
  // FILE MANIFEST (paths + sizes, no content — small); with `path` → return just
@@ -5257,6 +5372,7 @@ const handlers = {
5257
5372
  return {
5258
5373
  ok: true,
5259
5374
  connector_id,
5375
+ ...prov,
5260
5376
  files: files.map((f) => ({ path: f.path, bytes: (f.content || "").length, encoding: f.encoding || "utf8" })),
5261
5377
  total_bytes: files.reduce((n, f) => n + (f.content || "").length, 0),
5262
5378
  hint: "Large source is not returned inline. Call again with path:'<file>' to read one file (e.g. path:'server.js').",
@@ -5265,9 +5381,71 @@ const handlers = {
5265
5381
  const norm = String(path).replace(/^\.?\//, "");
5266
5382
  const file = files.find((f) => f.path === path || f.path === norm || f.path.replace(/^\.?\//, "") === norm);
5267
5383
  if (!file) {
5268
- return { ok: false, connector_id, error: `file '${path}' not found`, available: files.map((f) => f.path) };
5384
+ return { ok: false, connector_id, ...prov, error: `file '${path}' not found`, available: files.map((f) => f.path) };
5385
+ }
5386
+ return { ok: true, connector_id, ...prov, path: file.path, encoding: file.encoding || "utf8", content: file.content };
5387
+ },
5388
+
5389
+ ateam_get_deployed_connector_source: async ({ solution_id, connector_id, path }, sid) => {
5390
+ const data = await get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/deployed-source`, sid);
5391
+ const files = Array.isArray(data?.files) ? data.files : [];
5392
+ // The label rides on EVERY response shape, including the error one. A
5393
+ // caller that reads one file out of here must not be able to forget which
5394
+ // store it came from — that forgetting is the whole defect this splits.
5395
+ const label = {
5396
+ provenance: data?.provenance || "core_runtime",
5397
+ authored_source_of_record: false,
5398
+ note: "DEPLOYED copy from Core, not authored source. It reflects the last successful deploy and may differ from what the Builder can reproduce.",
5399
+ };
5400
+ if (!path) {
5401
+ return {
5402
+ ok: true,
5403
+ connector_id,
5404
+ ...label,
5405
+ files: files.map((f) => ({ path: f.path, bytes: (f.content || "").length, encoding: f.encoding || "utf8" })),
5406
+ total_bytes: files.reduce((n, f) => n + (f.content || "").length, 0),
5407
+ hint: "Call again with path:'<file>' to read one file.",
5408
+ };
5409
+ }
5410
+ const norm = String(path).replace(/^\.?\//, "");
5411
+ const file = files.find((f) => f.path === path || f.path === norm || f.path.replace(/^\.?\//, "") === norm);
5412
+ if (!file) {
5413
+ return { ok: false, connector_id, ...label, error: `file '${path}' not found`, available: files.map((f) => f.path) };
5414
+ }
5415
+ return { ok: true, connector_id, ...label, path: file.path, encoding: file.encoding || "utf8", content: file.content };
5416
+ },
5417
+
5418
+ ateam_recover_connector_source: async ({ solution_id, connector_id, force = false }, sid) => {
5419
+ try {
5420
+ const data = await post(
5421
+ `/deploy/solutions/${solution_id}/connectors/${connector_id}/recover-from-core`,
5422
+ { force: force === true },
5423
+ sid,
5424
+ );
5425
+ return data;
5426
+ } catch (err) {
5427
+ // The refusal is the point of the tool, so report it as a decision the
5428
+ // caller has to make rather than as a failure it should retry past.
5429
+ let parsed = null;
5430
+ try { parsed = JSON.parse(err.body || "{}"); } catch { /* not JSON */ }
5431
+ if (err.status === 409 && parsed?.code === "AUTHORED_SOURCE_EXISTS") {
5432
+ return {
5433
+ ok: false,
5434
+ code: "AUTHORED_SOURCE_EXISTS",
5435
+ connector_id,
5436
+ error: parsed.error,
5437
+ next: [
5438
+ `ateam_get_connector_source(solution_id:'${solution_id}', connector_id:'${connector_id}') — the authored copy`,
5439
+ `ateam_get_deployed_connector_source(solution_id:'${solution_id}', connector_id:'${connector_id}') — the deployed copy`,
5440
+ "Compare them. Only if the deployed copy is the one to keep, call again with force:true.",
5441
+ ],
5442
+ };
5443
+ }
5444
+ if (err.status === 404 && parsed?.code === "NOTHING_TO_RECOVER") {
5445
+ return { ok: false, code: "NOTHING_TO_RECOVER", connector_id, error: parsed.error };
5446
+ }
5447
+ throw err;
5269
5448
  }
5270
- return { ok: true, connector_id, path: file.path, encoding: file.encoding || "utf8", content: file.content };
5271
5449
  },
5272
5450
 
5273
5451
  // Render + write CLAUDE.md into the solution's GitHub repo.