@ateam-ai/mcp 0.3.24 → 0.3.26

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
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",
@@ -0,0 +1,248 @@
1
+ /**
2
+ * CLAUDE.md agent-onboarding doc renderer.
3
+ *
4
+ * Produces a per-solution markdown file that auto-loads into Claude Code
5
+ * sessions when the developer clones the solution repo. Closes the gap where
6
+ * a fresh agent has no idea how to iterate, which tools to use, or what
7
+ * pitfalls to avoid.
8
+ *
9
+ * Structure:
10
+ * - Auto-generated header (above SENTINEL) — rewritten on every regen
11
+ * - SENTINEL line — marks the boundary
12
+ * - Solution-specific notes (below SENTINEL) — preserved across regens
13
+ *
14
+ * Callers:
15
+ * - ateam_write_agent_doc({ solution_id, overwrite? }) — explicit backfill
16
+ * - ateam_build_and_run — auto-invoked post-deploy (non-fatal)
17
+ */
18
+
19
+ export const AGENT_DOC_SENTINEL = "<!-- SOLUTION-SPECIFIC NOTES BELOW — not auto-regenerated -->";
20
+
21
+ /**
22
+ * Render the auto-generated section of CLAUDE.md.
23
+ *
24
+ * @param {object} params
25
+ * @param {object} params.solution - Full solution definition
26
+ * @param {Array} params.skills - Array of skill definitions
27
+ * @param {Array} [params.connectors] - Solution-connector metadata
28
+ * @returns {string} markdown
29
+ */
30
+ export function renderAgentDocHeader({ solution, skills = [], connectors = [] }) {
31
+ const solId = solution?.id || "unknown";
32
+ const solName = solution?.name || solId;
33
+ const solDesc = solution?.description || "";
34
+ const orchestratorId = solution?.orchestrator_skill || null;
35
+ const tenantHint = inferTenantFromId(solId);
36
+
37
+ const skillsList = skills.map((s) => {
38
+ const role = s.role || (s.id === orchestratorId ? "orchestrator" : "worker");
39
+ const desc = s.description || s.problem?.statement || "";
40
+ return `- **${s.id}** _(${role})_ — ${oneLine(desc)}`;
41
+ }).join("\n") || "_(no skills defined)_";
42
+
43
+ const platformConnectorIds = (solution?.platform_connectors || []).map((c) => c.id);
44
+ const solutionConnectorIds = [
45
+ ...new Set(connectors.map((c) => c.id).filter(Boolean)),
46
+ ];
47
+ const connectorTable = buildConnectorTable({
48
+ platform: platformConnectorIds,
49
+ solution: solutionConnectorIds,
50
+ });
51
+
52
+ const uiPlugins = (solution?.ui_plugins || []).map((p) => {
53
+ const mode = p.render?.mode || "?";
54
+ return `- **${p.name || p.id}** (\`${p.id}\`, ${mode})`;
55
+ }).join("\n") || "_(none)_";
56
+
57
+ const tools = buildToolTable();
58
+ const pitfalls = buildUniversalPitfalls();
59
+ const repoLayout = buildRepoLayout({ skills, connectors });
60
+
61
+ return `# ${solName} — Agent Onboarding
62
+
63
+ > This file is auto-generated by A-Team on every deploy.
64
+ > Everything ABOVE the sentinel line is regenerated.
65
+ > Solution-specific notes go BELOW the sentinel — they are preserved.
66
+
67
+ You are working on A-Team solution **${solId}**. Read this entire file before your first tool call.
68
+
69
+ ---
70
+
71
+ ## 1. What this solution is
72
+
73
+ ${solDesc || "_(no description set — add one to solution.json)_"}
74
+
75
+ ${tenantHint ? `- **Tenant:** \`${tenantHint}\`` : ""}
76
+ - **Orchestrator skill:** ${orchestratorId ? `\`${orchestratorId}\`` : "_(none set)_"}
77
+
78
+ ---
79
+
80
+ ## 2. Skills
81
+
82
+ ${skillsList}
83
+
84
+ ---
85
+
86
+ ## 3. Connectors
87
+
88
+ ${connectorTable}
89
+
90
+ ---
91
+
92
+ ## 4. UI plugins
93
+
94
+ ${uiPlugins}
95
+
96
+ ---
97
+
98
+ ## 5. Repo layout
99
+
100
+ \`\`\`
101
+ ${repoLayout}
102
+ \`\`\`
103
+
104
+ ---
105
+
106
+ ## 6. Dev workflow (single-branch, GitHub-first)
107
+
108
+ Everything lands on \`main\` directly. Checkpoints via \`safe-*\` tags.
109
+
110
+ \`\`\`
111
+ 1. git clone <this repo>
112
+ 2. edit code in your IDE / agent
113
+ 3. git commit && git push origin main
114
+ 4. ateam_build_and_run(solution_id: "${solId}", github: true)
115
+ 5. ateam_test_skill / ateam_test_connector — verify
116
+ 6. ateam_github_promote(solution_id) — checkpoint when green
117
+ \`\`\`
118
+
119
+ Or edit remotely via \`ateam_github_patch\` / \`ateam_github_write\` — same effect.
120
+
121
+ **First tool call every session:** \`ateam_auth(api_key: "adas_${tenantHint || "<tenant>"}_<hex>")\`.
122
+
123
+ ---
124
+
125
+ ## 7. Which MCP tool for what
126
+
127
+ ${tools}
128
+
129
+ **Rule:** prefer \`ateam_patch\` over \`ateam_github_patch\` + \`ateam_build_and_run\` when changing skill definitions — it's one call and always redeploys.
130
+
131
+ ---
132
+
133
+ ## 8. Universal pitfalls (apply to every A-Team solution)
134
+
135
+ ${pitfalls}
136
+
137
+ ---
138
+
139
+ ## 9. Spec & examples
140
+
141
+ - \`ateam_get_spec(topic: "skill")\` — full skill schema
142
+ - \`ateam_get_spec(topic: "solution")\` — solution schema
143
+ - \`ateam_get_examples(type: "skill" | "connector" | "connector-ui" | "solution")\`
144
+ - \`ateam_get_workflows()\` — builder workflow state machines
145
+
146
+ Read these on your first edit into an area you haven't touched.
147
+
148
+ ---
149
+
150
+ ${AGENT_DOC_SENTINEL}
151
+
152
+ `;
153
+ }
154
+
155
+ /**
156
+ * Merge the newly-rendered header with an existing CLAUDE.md, preserving
157
+ * anything below the sentinel (solution-specific notes).
158
+ *
159
+ * @param {string} freshHeader - Output of renderAgentDocHeader
160
+ * @param {string|null} existing - Current CLAUDE.md contents (or null if new file)
161
+ * @returns {string} merged markdown
162
+ */
163
+ export function mergeAgentDoc(freshHeader, existing) {
164
+ if (!existing) return freshHeader;
165
+ const idx = existing.indexOf(AGENT_DOC_SENTINEL);
166
+ if (idx === -1) {
167
+ // Existing file has no sentinel — treat the whole file as manual notes,
168
+ // prepend the auto-generated header, separate by sentinel.
169
+ return freshHeader + "\n" + existing.trimStart();
170
+ }
171
+ const tail = existing.slice(idx + AGENT_DOC_SENTINEL.length);
172
+ return freshHeader + tail;
173
+ }
174
+
175
+ // ──────────────────────────────────────────────────────────────────────────
176
+
177
+ function oneLine(s) {
178
+ return String(s || "").replace(/\s+/g, " ").trim().slice(0, 200);
179
+ }
180
+
181
+ function inferTenantFromId() {
182
+ // Can't reliably infer from solution_id alone; caller can override by
183
+ // setting tenant on the rendered header via post-processing if needed.
184
+ return null;
185
+ }
186
+
187
+ function buildConnectorTable({ platform, solution }) {
188
+ const rows = [];
189
+ if (solution.length) {
190
+ rows.push("**Solution connectors** (live in this repo under `connectors/`):");
191
+ for (const id of solution) rows.push(` - \`${id}\``);
192
+ rows.push("");
193
+ }
194
+ if (platform.length) {
195
+ rows.push("**Platform connectors** (shared infrastructure, not in this repo):");
196
+ for (const id of platform) rows.push(` - \`${id}\``);
197
+ rows.push("");
198
+ }
199
+ if (!rows.length) rows.push("_(no connectors declared)_");
200
+ return rows.join("\n");
201
+ }
202
+
203
+ function buildToolTable() {
204
+ return [
205
+ "| Task | Tool |",
206
+ "|------|------|",
207
+ "| Edit connector source code | `ateam_github_patch` / `ateam_github_write` → `ateam_build_and_run(github:true)` |",
208
+ "| Edit skill definition (persona, tools, intents, guardrails) | `ateam_patch(target:\"skill\", skill_id, updates)` |",
209
+ "| Edit solution definition | `ateam_patch(target:\"solution\", updates)` |",
210
+ "| First deploy / redeploy from GitHub | `ateam_build_and_run(github:true)` |",
211
+ "| Run a skill end-to-end | `ateam_test_skill(solution_id, skill_id, message)` |",
212
+ "| Call one connector tool in isolation | `ateam_test_connector(solution_id, connector_id, tool, args)` |",
213
+ "| Inspect deployed state | `ateam_get_solution(solution_id, view: \"definition\"|\"skills\"|\"health\")` |",
214
+ "| Read file from repo | `ateam_github_read(solution_id, path)` |",
215
+ "| Checkpoint | `ateam_github_promote(solution_id, label)` |",
216
+ "| Rollback | `ateam_github_rollback(solution_id, tag)` |",
217
+ "| List checkpoints | `ateam_github_list_versions(solution_id)` |",
218
+ ].join("\n");
219
+ }
220
+
221
+ function buildUniversalPitfalls() {
222
+ return [
223
+ "- **`ateam_test_connector` runs as `_system_service`.** Core strips user-provided `_adas_actor` when you call via the test harness. Use it for connector-level bugs; use `ateam_test_skill` / `ateam_conversation` for per-user-actor flows.",
224
+ "- **`.ateam/export.json` is auto-generated.** Never hand-edit. Deploys read it, so stale copies silently break things.",
225
+ "- **Prefer `ateam_patch` over `github_patch` + `build_and_run`** for skill-definition edits. One call, guaranteed redeploy, no drift.",
226
+ "- **Always refetch dynamic ids.** Corpus ids, job ids, actor ids change. Call `docs.corpus.list` / `ateam_list_solutions` / etc. in the current job — don't reuse ids from memory or previous sessions.",
227
+ "- **MCP tool param names are strict.** `top_k` (not `k`), `corpus_id` (not `corpus`). Passing wrong names silently fails zod validation.",
228
+ "- **Don't count `{ok:false}` as success.** When calling one MCP from another, always check `result.ok` before incrementing a success counter.",
229
+ "- **Connectors are stdio, not HTTP.** Use `StdioServerTransport`. Never `app.listen()`. Log with `console.error` (stdout is the JSON-RPC channel).",
230
+ "- **Shared-tenant resources need `shared: true`.** When a resource (corpus, bind, etc.) is used across users in a tenant, pass `shared: true` at create time — otherwise per-user actor isolation blocks cross-user access.",
231
+ ].map((s) => "- " + s.replace(/^- /, "")).join("\n");
232
+ }
233
+
234
+ function buildRepoLayout({ skills, connectors }) {
235
+ const skillDirs = skills.map((s) => ` ${s.id}/skill.json`).join("\n") || " _(no skills)_";
236
+ const connDirs = connectors.length
237
+ ? connectors.map((c) => ` ${c.id}/\n server.js\n package.json`).join("\n")
238
+ : " _(no solution connectors)_";
239
+ return [
240
+ "solution.json # full solution definition",
241
+ "skills/",
242
+ skillDirs,
243
+ "connectors/ # solution connectors (stdio MCPs)",
244
+ connDirs,
245
+ ".ateam/export.json # auto-generated deploy bundle (don't hand-edit)",
246
+ "CLAUDE.md # this file — auto-regenerated above sentinel",
247
+ ].join("\n");
248
+ }
package/src/tools.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  getCredentials, parseApiKey, touchSession, getSessionContext,
15
15
  setAuthOverride, switchTenant, isMasterMode, listTenants,
16
16
  } from "./api.js";
17
+ import { renderAgentDocHeader, mergeAgentDoc, AGENT_DOC_SENTINEL } from "./agentDoc.js";
17
18
 
18
19
  // ─── Tool definitions ───────────────────────────────────────────────
19
20
 
@@ -921,6 +922,24 @@ export const tools = [
921
922
  required: ["solution_id", "path"],
922
923
  },
923
924
  },
925
+ {
926
+ name: "ateam_write_agent_doc",
927
+ core: false,
928
+ description:
929
+ "Render + write (or refresh) CLAUDE.md in the solution's GitHub repo. Auto-generates the onboarding header from the deployed solution/skill/connector definitions and preserves any solution-specific notes below the sentinel line. " +
930
+ "Normally called automatically on every ateam_build_and_run so CLAUDE.md stays in sync — use this tool directly to backfill existing solutions or to force a refresh.",
931
+ inputSchema: {
932
+ type: "object",
933
+ properties: {
934
+ solution_id: { type: "string", description: "The solution ID" },
935
+ overwrite: {
936
+ type: "boolean",
937
+ description: "If true, rewrite the whole file (discards any solution-specific notes below the sentinel). Default false — preserves notes.",
938
+ },
939
+ },
940
+ required: ["solution_id"],
941
+ },
942
+ },
924
943
  {
925
944
  name: "ateam_github_write",
926
945
  core: true,
@@ -1697,6 +1716,25 @@ const handlers = {
1697
1716
  }
1698
1717
  }
1699
1718
 
1719
+ // Auto-seed / refresh the agent onboarding doc. Non-fatal — any failure
1720
+ // here must not break a successful deploy. The doc renders from the
1721
+ // deployed definition, so only attempt after the repo is present.
1722
+ let agent_doc_result = null;
1723
+ if (github_result && !github_result.error) {
1724
+ try {
1725
+ agent_doc_result = await handlers.ateam_write_agent_doc({ solution_id: solutionId }, sid);
1726
+ phases.push({
1727
+ phase: "agent_doc",
1728
+ status: "done",
1729
+ created: agent_doc_result?.created || false,
1730
+ preserved_notes: agent_doc_result?.preserved_notes || false,
1731
+ });
1732
+ } catch (err) {
1733
+ agent_doc_result = { error: err.message };
1734
+ phases.push({ phase: "agent_doc", status: "error", error: err.message });
1735
+ }
1736
+ }
1737
+
1700
1738
  return {
1701
1739
  ok: true,
1702
1740
  solution_id: solutionId,
@@ -1711,6 +1749,7 @@ const handlers = {
1711
1749
  health,
1712
1750
  ...(test_result && { test_result }),
1713
1751
  ...(github_result && !github_result.error && !github_result.skipped && { github: github_result }),
1752
+ ...(agent_doc_result && !agent_doc_result.error && { agent_doc: agent_doc_result }),
1714
1753
  ...(validation.warnings?.length > 0 && { validation_warnings: validation.warnings }),
1715
1754
  _status: '✅ Deployed to Core + pushed to main.',
1716
1755
  _next: 'Create a checkpoint before making more changes: ateam_github_promote(solution_id)',
@@ -1947,7 +1986,39 @@ const handlers = {
1947
1986
  return post(`/deploy/mcp-store/${connector_id}`, { files: resolved }, sid);
1948
1987
  },
1949
1988
 
1950
- ateam_list_solutions: async (_args, sid) => get("/deploy/solutions", sid),
1989
+ ateam_list_solutions: async (_args, sid) => {
1990
+ const raw = await get("/deploy/solutions", sid);
1991
+ // Enrich each solution with GitHub metadata (repo_url, branch, CLAUDE.md)
1992
+ // so an agent sees everything it needs to clone + onboard in one call.
1993
+ // Fetches run in parallel; failures are non-fatal (fall back to the raw row).
1994
+ const solutions = Array.isArray(raw?.solutions) ? raw.solutions : Array.isArray(raw) ? raw : [];
1995
+ const enriched = await Promise.all(solutions.map(async (s) => {
1996
+ const out = { ...s };
1997
+ try {
1998
+ const gh = await get(`/deploy/solutions/${s.id}/github/status`, sid);
1999
+ if (gh?.exists && gh.repo_url) {
2000
+ out.repo_url = gh.repo_url;
2001
+ out.github_full_name = gh.full_name || null;
2002
+ out.default_branch = gh.default_branch || "main";
2003
+ out.latest_commit_sha = gh.latest_commit?.sha || null;
2004
+ // Probe for agent-onboarding doc; swallow 404 etc.
2005
+ try {
2006
+ const probe = await get(`/deploy/solutions/${s.id}/github/read?path=CLAUDE.md`, sid);
2007
+ out.has_claude_md = Boolean(probe?.content);
2008
+ } catch { out.has_claude_md = false; }
2009
+ out.local_dev_quickstart = {
2010
+ _note: "Share these 3 lines with a developer (or their agent). They will clone the repo and, if CLAUDE.md is present, their agent sees the full onboarding on session start.",
2011
+ clone: `git clone ${gh.repo_url}`,
2012
+ cd: `cd ${(gh.full_name || "").split("/").pop() || s.id}`,
2013
+ auth_in_new_session: `ateam_auth(api_key: "adas_<tenant>_<hex>")`,
2014
+ needs_github_collaborator_access: !gh.repo_url.includes("public") ? true : false,
2015
+ };
2016
+ }
2017
+ } catch { /* non-fatal — leave the row as-is */ }
2018
+ return out;
2019
+ }));
2020
+ return { ...raw, solutions: enriched };
2021
+ },
1951
2022
 
1952
2023
  ateam_get_solution: async ({ solution_id, view, skill_id }, sid) => {
1953
2024
  const base = `/deploy/solutions/${solution_id}`;
@@ -2031,6 +2102,55 @@ const handlers = {
2031
2102
  ateam_get_connector_source: async ({ solution_id, connector_id }, sid) =>
2032
2103
  get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid),
2033
2104
 
2105
+ // Render + write CLAUDE.md into the solution's GitHub repo.
2106
+ // Preserves content below the sentinel unless overwrite=true.
2107
+ // Swallows errors internally when invoked non-interactively (see _writeAgentDocSafe).
2108
+ ateam_write_agent_doc: async ({ solution_id, overwrite = false }, sid) => {
2109
+ if (!solution_id) throw new Error("solution_id required");
2110
+ // Gather source material: the deployed solution definition + skills list.
2111
+ // These come straight from Core, so the doc always reflects what's running.
2112
+ const def = await get(`/deploy/solutions/${solution_id}/definition`, sid);
2113
+ const solution = def?.solution || def;
2114
+ let skills = [];
2115
+ try {
2116
+ const skillsRes = await get(`/deploy/solutions/${solution_id}/skills`, sid);
2117
+ skills = Array.isArray(skillsRes?.skills) ? skillsRes.skills : Array.isArray(skillsRes) ? skillsRes : [];
2118
+ } catch { /* no skills yet — render anyway */ }
2119
+ const connectors = Array.isArray(solution?.connectors) ? solution.connectors : [];
2120
+
2121
+ const freshHeader = renderAgentDocHeader({ solution, skills, connectors });
2122
+ let existing = null;
2123
+ if (!overwrite) {
2124
+ try {
2125
+ const r = await get(
2126
+ `/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent("CLAUDE.md")}`,
2127
+ sid,
2128
+ );
2129
+ existing = r?.content || null;
2130
+ } catch { /* file doesn't exist yet — treat as fresh create */ }
2131
+ }
2132
+ const merged = overwrite ? freshHeader : mergeAgentDoc(freshHeader, existing);
2133
+
2134
+ const res = await post(
2135
+ `/deploy/solutions/${solution_id}/github/patch`,
2136
+ {
2137
+ path: "CLAUDE.md",
2138
+ content: merged,
2139
+ message: existing ? "CLAUDE.md: refresh auto-generated header" : "CLAUDE.md: seed agent onboarding doc",
2140
+ },
2141
+ sid,
2142
+ );
2143
+ return {
2144
+ ok: Boolean(res?.ok ?? true),
2145
+ solution_id,
2146
+ created: !existing,
2147
+ preserved_notes: Boolean(existing && existing.includes(AGENT_DOC_SENTINEL)),
2148
+ bytes: merged.length,
2149
+ commit_url: res?.commit_url || null,
2150
+ commit_sha: res?.commit_sha || null,
2151
+ };
2152
+ },
2153
+
2034
2154
  ateam_get_metrics: async ({ solution_id, job_id, skill_id }, sid) => {
2035
2155
  const qs = new URLSearchParams();
2036
2156
  if (job_id) qs.set("job_id", job_id);
@@ -2335,6 +2455,30 @@ export async function handleToolCall(name, args, sessionId) {
2335
2455
  last_tool_used: ctx.lastToolName || null,
2336
2456
  };
2337
2457
  }
2458
+ // If authenticated, attach a tenant onboarding block so the agent can
2459
+ // discover existing solutions + their repo URLs without extra round-trips.
2460
+ // This is what lets a fresh agent clone the right repo on first greet.
2461
+ try {
2462
+ const creds = getCredentials(sessionId);
2463
+ if (creds?.apiKey || creds?.masterKey) {
2464
+ const listed = await handlers.ateam_list_solutions({}, sessionId);
2465
+ const solutions = Array.isArray(listed?.solutions) ? listed.solutions : [];
2466
+ if (solutions.length > 0) {
2467
+ result.tenant_onboarding = {
2468
+ _note: "The authed key can see these solutions. For LOCAL development: clone the repo_url and open any Claude-Code-compatible agent in that directory — it will auto-load CLAUDE.md on session start. For REMOTE-only work: call ateam_github_read(solution_id, 'CLAUDE.md') to fetch the onboarding doc. If `git clone` returns 403, ask the solution owner to add your GitHub account as a collaborator on the repo (GitHub access is separate from the A-Team API key).",
2469
+ tenant: creds.tenant || null,
2470
+ solutions: solutions.map((s) => ({
2471
+ id: s.id,
2472
+ name: s.name,
2473
+ repo_url: s.repo_url || null,
2474
+ default_branch: s.default_branch || "main",
2475
+ has_claude_md: s.has_claude_md ?? null,
2476
+ clone_command: s.repo_url ? `git clone ${s.repo_url}` : null,
2477
+ })),
2478
+ };
2479
+ }
2480
+ }
2481
+ } catch { /* non-fatal — unauthed sessions or API blips shouldn't break bootstrap */ }
2338
2482
  }
2339
2483
 
2340
2484
  return {