@ateam-ai/mcp 0.3.25 → 0.3.27

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.25",
3
+ "version": "0.3.27",
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,24 @@ const handlers = {
1697
1716
  }
1698
1717
  }
1699
1718
 
1719
+ // Auto-seed / refresh the agent onboarding doc. Non-fatal — any failure
1720
+ // here is swallowed so it can't break a successful deploy. The tool is
1721
+ // idempotent: if the rendered doc is byte-identical to what's in the
1722
+ // repo, it returns unchanged:true and writes no commit.
1723
+ let agent_doc_result = null;
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: agent_doc_result?.unchanged ? "unchanged" : "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: "skipped", reason: err.message });
1735
+ }
1736
+
1700
1737
  return {
1701
1738
  ok: true,
1702
1739
  solution_id: solutionId,
@@ -1711,6 +1748,7 @@ const handlers = {
1711
1748
  health,
1712
1749
  ...(test_result && { test_result }),
1713
1750
  ...(github_result && !github_result.error && !github_result.skipped && { github: github_result }),
1751
+ ...(agent_doc_result && !agent_doc_result.error && { agent_doc: agent_doc_result }),
1714
1752
  ...(validation.warnings?.length > 0 && { validation_warnings: validation.warnings }),
1715
1753
  _status: '✅ Deployed to Core + pushed to main.',
1716
1754
  _next: 'Create a checkpoint before making more changes: ateam_github_promote(solution_id)',
@@ -2063,6 +2101,68 @@ const handlers = {
2063
2101
  ateam_get_connector_source: async ({ solution_id, connector_id }, sid) =>
2064
2102
  get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid),
2065
2103
 
2104
+ // Render + write CLAUDE.md into the solution's GitHub repo.
2105
+ // Preserves content below the sentinel unless overwrite=true.
2106
+ // Swallows errors internally when invoked non-interactively (see _writeAgentDocSafe).
2107
+ ateam_write_agent_doc: async ({ solution_id, overwrite = false }, sid) => {
2108
+ if (!solution_id) throw new Error("solution_id required");
2109
+ // Gather source material: the deployed solution definition + skills list.
2110
+ // These come straight from Core, so the doc always reflects what's running.
2111
+ const def = await get(`/deploy/solutions/${solution_id}/definition`, sid);
2112
+ const solution = def?.solution || def;
2113
+ let skills = [];
2114
+ try {
2115
+ const skillsRes = await get(`/deploy/solutions/${solution_id}/skills`, sid);
2116
+ skills = Array.isArray(skillsRes?.skills) ? skillsRes.skills : Array.isArray(skillsRes) ? skillsRes : [];
2117
+ } catch { /* no skills yet — render anyway */ }
2118
+ const connectors = Array.isArray(solution?.connectors) ? solution.connectors : [];
2119
+
2120
+ const freshHeader = renderAgentDocHeader({ solution, skills, connectors });
2121
+ let existing = null;
2122
+ try {
2123
+ const r = await get(
2124
+ `/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent("CLAUDE.md")}`,
2125
+ sid,
2126
+ );
2127
+ existing = r?.content || null;
2128
+ } catch { /* file doesn't exist yet — treat as fresh create */ }
2129
+ const merged = overwrite ? freshHeader : mergeAgentDoc(freshHeader, existing);
2130
+
2131
+ // Idempotent write: if the merged content is byte-identical to what's
2132
+ // already committed, skip the patch entirely. Prevents a noise commit on
2133
+ // every build_and_run when nothing meaningful changed.
2134
+ if (existing && existing === merged) {
2135
+ return {
2136
+ ok: true,
2137
+ solution_id,
2138
+ unchanged: true,
2139
+ created: false,
2140
+ preserved_notes: existing.includes(AGENT_DOC_SENTINEL),
2141
+ bytes: merged.length,
2142
+ };
2143
+ }
2144
+
2145
+ const res = await post(
2146
+ `/deploy/solutions/${solution_id}/github/patch`,
2147
+ {
2148
+ path: "CLAUDE.md",
2149
+ content: merged,
2150
+ message: existing ? "CLAUDE.md: refresh auto-generated header" : "CLAUDE.md: seed agent onboarding doc",
2151
+ },
2152
+ sid,
2153
+ );
2154
+ return {
2155
+ ok: Boolean(res?.ok ?? true),
2156
+ solution_id,
2157
+ unchanged: false,
2158
+ created: !existing,
2159
+ preserved_notes: Boolean(existing && existing.includes(AGENT_DOC_SENTINEL)),
2160
+ bytes: merged.length,
2161
+ commit_url: res?.commit_url || null,
2162
+ commit_sha: res?.commit_sha || null,
2163
+ };
2164
+ },
2165
+
2066
2166
  ateam_get_metrics: async ({ solution_id, job_id, skill_id }, sid) => {
2067
2167
  const qs = new URLSearchParams();
2068
2168
  if (job_id) qs.set("job_id", job_id);