@ateam-ai/mcp 0.3.25 → 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 +1 -1
- package/src/agentDoc.js +248 -0
- package/src/tools.js +88 -0
package/package.json
CHANGED
package/src/agentDoc.js
ADDED
|
@@ -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)',
|
|
@@ -2063,6 +2102,55 @@ const handlers = {
|
|
|
2063
2102
|
ateam_get_connector_source: async ({ solution_id, connector_id }, sid) =>
|
|
2064
2103
|
get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid),
|
|
2065
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
|
+
|
|
2066
2154
|
ateam_get_metrics: async ({ solution_id, job_id, skill_id }, sid) => {
|
|
2067
2155
|
const qs = new URLSearchParams();
|
|
2068
2156
|
if (job_id) qs.set("job_id", job_id);
|