@ateam-ai/mcp 0.3.39 → 0.3.41
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/tools.js +414 -0
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -499,6 +499,70 @@ export const tools = [
|
|
|
499
499
|
},
|
|
500
500
|
},
|
|
501
501
|
|
|
502
|
+
{
|
|
503
|
+
name: "ateam_create_connector",
|
|
504
|
+
core: true,
|
|
505
|
+
description:
|
|
506
|
+
"Scaffold a new MCP connector with server.js + package.json + README. " +
|
|
507
|
+
"Eliminates ~50% of identical boilerplate (MCP server setup, tool registration, " +
|
|
508
|
+
"stdio transport). You then fill in the tool implementations. " +
|
|
509
|
+
"Set ui_capable=true to include ui.listPlugins / ui.getPlugin stubs " +
|
|
510
|
+
"(plugin source files added separately via ateam_create_plugin). " +
|
|
511
|
+
"After scaffolding, the files are uploaded to Core via the same path " +
|
|
512
|
+
"as ateam_upload_connector.",
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: "object",
|
|
515
|
+
properties: {
|
|
516
|
+
solution_id: { type: "string", description: "The solution ID" },
|
|
517
|
+
connector_id: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "Connector ID (lowercase-with-dashes, no spaces). Becomes the directory name.",
|
|
520
|
+
},
|
|
521
|
+
name: {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: "Human-readable name for the connector (e.g. 'Hue Lights'). Defaults to connector_id.",
|
|
524
|
+
},
|
|
525
|
+
ui_capable: {
|
|
526
|
+
type: "boolean",
|
|
527
|
+
description: "If true, include ui.listPlugins/ui.getPlugin handler stubs. Default: false.",
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
required: ["solution_id", "connector_id"],
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
{
|
|
535
|
+
name: "ateam_create_plugin",
|
|
536
|
+
core: true,
|
|
537
|
+
description:
|
|
538
|
+
"Scaffold a UI plugin (iframe HTML, React Native TSX, or both) inside an existing connector. " +
|
|
539
|
+
"Eliminates ~50% of identical plugin boilerplate (imports, theme/bridge hooks, " +
|
|
540
|
+
"postMessage protocol, default export shape). You then fill in the component body. " +
|
|
541
|
+
"Use kind='iframe' for web-only, 'rn' for mobile-only, 'adaptive' for both. " +
|
|
542
|
+
"Auto-discovery (Phase 5 of the strip) picks up the new plugin at next deploy " +
|
|
543
|
+
"without a manifest declaration.",
|
|
544
|
+
inputSchema: {
|
|
545
|
+
type: "object",
|
|
546
|
+
properties: {
|
|
547
|
+
solution_id: { type: "string", description: "The solution ID" },
|
|
548
|
+
connector_id: {
|
|
549
|
+
type: "string",
|
|
550
|
+
description: "Existing connector to add the plugin into (e.g. 'personal-assistant-ui-mcp')",
|
|
551
|
+
},
|
|
552
|
+
plugin_name: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Plugin name (lowercase-with-dashes). E.g. 'memories-panel'. Becomes the dir name.",
|
|
555
|
+
},
|
|
556
|
+
kind: {
|
|
557
|
+
type: "string",
|
|
558
|
+
enum: ["iframe", "rn", "adaptive"],
|
|
559
|
+
description: "Render mode. 'adaptive' (default) produces both iframe + RN scaffolds.",
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
required: ["solution_id", "connector_id", "plugin_name"],
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
|
|
502
566
|
{
|
|
503
567
|
name: "ateam_upload_connector",
|
|
504
568
|
core: true,
|
|
@@ -894,6 +958,19 @@ export const tools = [
|
|
|
894
958
|
required: ["solution_id"],
|
|
895
959
|
},
|
|
896
960
|
},
|
|
961
|
+
{
|
|
962
|
+
name: "ateam_verify_consistency",
|
|
963
|
+
core: false,
|
|
964
|
+
description:
|
|
965
|
+
"Read-only: do Builder FS and the GitHub repo agree for this solution? Returns { consistent: bool, drifts: [{path, kind}] } where kind ∈ fs_missing | content_differs | gh_missing | gh_read_error | repo_unreachable. Comparison strips ephemeral fields (timestamps, runtime/deploy-state, resolved-on-load flags) so only REAL content drift surfaces. Use this any time you're unsure whether a recent change landed on GitHub or whether your local view of the solution matches what's deployed — much faster than scrolling ateam_github_log manually. No deploy is triggered.",
|
|
966
|
+
inputSchema: {
|
|
967
|
+
type: "object",
|
|
968
|
+
properties: {
|
|
969
|
+
solution_id: { type: "string", description: "The solution ID" },
|
|
970
|
+
},
|
|
971
|
+
required: ["solution_id"],
|
|
972
|
+
},
|
|
973
|
+
},
|
|
897
974
|
|
|
898
975
|
// ═══════════════════════════════════════════════════════════════════
|
|
899
976
|
// GITHUB TOOLS — version control for solutions
|
|
@@ -1252,6 +1329,7 @@ const TENANT_TOOLS = new Set([
|
|
|
1252
1329
|
"ateam_get_connector_source",
|
|
1253
1330
|
"ateam_get_metrics",
|
|
1254
1331
|
"ateam_diff",
|
|
1332
|
+
"ateam_verify_consistency",
|
|
1255
1333
|
// GitHub operations
|
|
1256
1334
|
"ateam_github_push",
|
|
1257
1335
|
"ateam_github_pull",
|
|
@@ -1267,6 +1345,264 @@ const TENANT_TOOLS = new Set([
|
|
|
1267
1345
|
/** Small delay helper */
|
|
1268
1346
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
1269
1347
|
|
|
1348
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1349
|
+
// Phase 7 strip: connector + plugin scaffolds
|
|
1350
|
+
// ───────────────────────────────────────────────────────────────────
|
|
1351
|
+
// Pure client-side templates. ateam_create_connector / ateam_create_plugin
|
|
1352
|
+
// produce file contents + push them via the existing /deploy/.../upload
|
|
1353
|
+
// endpoint. The author writes only the unique tool implementations and
|
|
1354
|
+
// component bodies; the ~50% of boilerplate per connector/plugin (MCP
|
|
1355
|
+
// server setup, theme/bridge hooks, postMessage protocol, package.json)
|
|
1356
|
+
// is template-generated.
|
|
1357
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1358
|
+
|
|
1359
|
+
function _scaffoldConnectorFiles({ connectorId, displayName, uiCapable }) {
|
|
1360
|
+
const safeName = displayName || connectorId;
|
|
1361
|
+
const files = [];
|
|
1362
|
+
|
|
1363
|
+
// server.js — minimal stdio MCP server with one tool stub + ui handlers if ui_capable
|
|
1364
|
+
const serverJs = `#!/usr/bin/env node
|
|
1365
|
+
// ${connectorId} — stdio MCP server. Generated by ateam_create_connector.
|
|
1366
|
+
//
|
|
1367
|
+
// Fill in the tool implementations below. The MCP scaffolding
|
|
1368
|
+
// (server setup, tool registration, error handling, stdio transport)
|
|
1369
|
+
// is template-provided. You write the integration logic.
|
|
1370
|
+
|
|
1371
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1372
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1373
|
+
|
|
1374
|
+
const server = new Server(
|
|
1375
|
+
{ name: "${connectorId}", version: "1.0.0" },
|
|
1376
|
+
{ capabilities: { tools: {}${uiCapable ? ', ui: {}' : ''} } },
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
// ── Tool definitions ────────────────────────────────────────────────
|
|
1380
|
+
const TOOLS = [
|
|
1381
|
+
{
|
|
1382
|
+
name: "${connectorId}.echo",
|
|
1383
|
+
description: "Echo back the input. Replace with your real tools.",
|
|
1384
|
+
inputSchema: {
|
|
1385
|
+
type: "object",
|
|
1386
|
+
properties: { message: { type: "string" } },
|
|
1387
|
+
required: ["message"],
|
|
1388
|
+
},
|
|
1389
|
+
},
|
|
1390
|
+
];
|
|
1391
|
+
|
|
1392
|
+
server.setRequestHandler({ method: "tools/list" }, async () => ({ tools: TOOLS }));
|
|
1393
|
+
|
|
1394
|
+
server.setRequestHandler({ method: "tools/call" }, async (req) => {
|
|
1395
|
+
const { name, arguments: args } = req.params;
|
|
1396
|
+
switch (name) {
|
|
1397
|
+
case "${connectorId}.echo":
|
|
1398
|
+
return { content: [{ type: "text", text: \`Echo: \${args.message}\` }] };
|
|
1399
|
+
default:
|
|
1400
|
+
throw new Error(\`Unknown tool: \${name}\`);
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
${uiCapable ? `
|
|
1404
|
+
// ── UI plugin handlers (ui_capable connector) ──────────────────────
|
|
1405
|
+
server.setRequestHandler({ method: "ui.listPlugins" }, async () => ({
|
|
1406
|
+
plugins: [
|
|
1407
|
+
// List your plugins here. Plugin source files live in
|
|
1408
|
+
// plugins/<plugin-name>/ (RN) and ui-dist/<plugin-name>/ (iframe).
|
|
1409
|
+
// Plugin manifests are auto-discovered at deploy time per Phase 5
|
|
1410
|
+
// of the strip — no need to repeat them here unless you want overrides.
|
|
1411
|
+
],
|
|
1412
|
+
}));
|
|
1413
|
+
|
|
1414
|
+
server.setRequestHandler({ method: "ui.getPlugin" }, async (req) => {
|
|
1415
|
+
const { id } = req.params;
|
|
1416
|
+
// Return plugin manifest by id. Auto-discovery covers the default case.
|
|
1417
|
+
return { ok: false, error: \`Plugin \${id} not found\` };
|
|
1418
|
+
});
|
|
1419
|
+
` : ''}
|
|
1420
|
+
// ── Boot ────────────────────────────────────────────────────────────
|
|
1421
|
+
const transport = new StdioServerTransport();
|
|
1422
|
+
await server.connect(transport);
|
|
1423
|
+
console.error("[${connectorId}] connected via stdio");
|
|
1424
|
+
`;
|
|
1425
|
+
files.push({ path: "server.js", content: serverJs });
|
|
1426
|
+
|
|
1427
|
+
// package.json
|
|
1428
|
+
const pkg = {
|
|
1429
|
+
name: connectorId,
|
|
1430
|
+
version: "1.0.0",
|
|
1431
|
+
type: "module",
|
|
1432
|
+
description: `${safeName} — A-Team MCP connector`,
|
|
1433
|
+
main: "server.js",
|
|
1434
|
+
dependencies: {
|
|
1435
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
1436
|
+
},
|
|
1437
|
+
};
|
|
1438
|
+
files.push({ path: "package.json", content: JSON.stringify(pkg, null, 2) + "\n" });
|
|
1439
|
+
|
|
1440
|
+
// README.md
|
|
1441
|
+
const readme = `# ${safeName}
|
|
1442
|
+
|
|
1443
|
+
Connector ID: \`${connectorId}\`
|
|
1444
|
+
${uiCapable ? "UI-capable: yes" : ""}
|
|
1445
|
+
|
|
1446
|
+
## Adding tools
|
|
1447
|
+
|
|
1448
|
+
Edit \`server.js\`. Add entries to the \`TOOLS\` array, then add a
|
|
1449
|
+
matching case in the \`tools/call\` handler.
|
|
1450
|
+
|
|
1451
|
+
## Adding UI plugins (ui_capable connectors)
|
|
1452
|
+
|
|
1453
|
+
Drop iframe plugins under \`ui-dist/<plugin-name>/index.html\` and/or
|
|
1454
|
+
React Native plugins under \`plugins/<plugin-name>/index.tsx\`.
|
|
1455
|
+
Phase 5 of the strip auto-discovers them at deploy — no manifest
|
|
1456
|
+
declaration needed unless you want overrides (drop a
|
|
1457
|
+
\`manifest.json\` next to the source).
|
|
1458
|
+
|
|
1459
|
+
## Deploy
|
|
1460
|
+
|
|
1461
|
+
Use \`ateam_upload_connector\` to push the latest source to Core
|
|
1462
|
+
without a full skill redeploy.
|
|
1463
|
+
`;
|
|
1464
|
+
files.push({ path: "README.md", content: readme });
|
|
1465
|
+
|
|
1466
|
+
return files;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function _scaffoldPluginFiles({ connectorId, pluginName, kind }) {
|
|
1470
|
+
const files = [];
|
|
1471
|
+
|
|
1472
|
+
if (kind === "iframe" || kind === "adaptive") {
|
|
1473
|
+
const html = `<!DOCTYPE html>
|
|
1474
|
+
<html lang="en">
|
|
1475
|
+
<head>
|
|
1476
|
+
<meta charset="UTF-8" />
|
|
1477
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1478
|
+
<title>${pluginName}</title>
|
|
1479
|
+
<style>
|
|
1480
|
+
body { font-family: system-ui, sans-serif; padding: 16px; margin: 0; }
|
|
1481
|
+
.card { background: #f5f5f5; padding: 12px; border-radius: 8px; }
|
|
1482
|
+
</style>
|
|
1483
|
+
</head>
|
|
1484
|
+
<body>
|
|
1485
|
+
<div class="card">
|
|
1486
|
+
<h2>${pluginName}</h2>
|
|
1487
|
+
<p>Plugin body — replace with your real UI.</p>
|
|
1488
|
+
<button id="callTool">Call sample tool</button>
|
|
1489
|
+
<pre id="output"></pre>
|
|
1490
|
+
</div>
|
|
1491
|
+
<script type="module">
|
|
1492
|
+
// ── Plugin postMessage protocol scaffold ────────────────────────
|
|
1493
|
+
// 'adas-host' messages come FROM the host shell (web app / mobile).
|
|
1494
|
+
// 'adas-plugin' messages go TO the host.
|
|
1495
|
+
// Use mcpCall(tool, args) to invoke any tool the skill has access to.
|
|
1496
|
+
|
|
1497
|
+
function mcpCall(tool, args = {}, connectorId) {
|
|
1498
|
+
return new Promise((resolve, reject) => {
|
|
1499
|
+
const id = "call_" + Math.random().toString(36).slice(2);
|
|
1500
|
+
const listener = (e) => {
|
|
1501
|
+
if (e?.data?.type !== "adas-host") return;
|
|
1502
|
+
if (e?.data?.requestId !== id) return;
|
|
1503
|
+
window.removeEventListener("message", listener);
|
|
1504
|
+
if (e.data.error) reject(new Error(e.data.error));
|
|
1505
|
+
else resolve(e.data.result);
|
|
1506
|
+
};
|
|
1507
|
+
window.addEventListener("message", listener);
|
|
1508
|
+
window.parent?.postMessage({
|
|
1509
|
+
type: "adas-plugin",
|
|
1510
|
+
action: "mcpCall",
|
|
1511
|
+
requestId: id,
|
|
1512
|
+
tool, args, connectorId,
|
|
1513
|
+
}, "*");
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
document.getElementById("callTool").addEventListener("click", async () => {
|
|
1518
|
+
try {
|
|
1519
|
+
const result = await mcpCall("${connectorId}.echo", { message: "hello" });
|
|
1520
|
+
document.getElementById("output").textContent = JSON.stringify(result, null, 2);
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
document.getElementById("output").textContent = "Error: " + err.message;
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
// Tell host we're ready
|
|
1527
|
+
window.parent?.postMessage({ type: "adas-plugin", action: "ready" }, "*");
|
|
1528
|
+
</script>
|
|
1529
|
+
</body>
|
|
1530
|
+
</html>
|
|
1531
|
+
`;
|
|
1532
|
+
files.push({
|
|
1533
|
+
path: `ui-dist/${pluginName}/index.html`,
|
|
1534
|
+
content: html,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
if (kind === "rn" || kind === "adaptive") {
|
|
1539
|
+
const tsx = `// ${pluginName} — React Native plugin. Generated by ateam_create_plugin.
|
|
1540
|
+
//
|
|
1541
|
+
// Fill in the Component body. Imports, hooks, default export shape are
|
|
1542
|
+
// template-provided — Phase 7 of the strip eliminates this boilerplate.
|
|
1543
|
+
|
|
1544
|
+
import React, { useState } from 'react';
|
|
1545
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
1546
|
+
import { useApi } from '../../plugin-sdk';
|
|
1547
|
+
import type { PluginProps } from '../../plugin-sdk/types';
|
|
1548
|
+
|
|
1549
|
+
// Plain object export — NO PluginSDK.register() (pollutes shared registry).
|
|
1550
|
+
export default {
|
|
1551
|
+
id: '${pluginName}',
|
|
1552
|
+
type: 'ui',
|
|
1553
|
+
version: '1.0.0',
|
|
1554
|
+
capabilities: { haptics: true },
|
|
1555
|
+
|
|
1556
|
+
Component({ bridge, native, theme }: PluginProps) {
|
|
1557
|
+
const api = useApi(bridge);
|
|
1558
|
+
const [output, setOutput] = useState<string>('');
|
|
1559
|
+
|
|
1560
|
+
const handlePress = async () => {
|
|
1561
|
+
try {
|
|
1562
|
+
native?.haptics?.selection?.();
|
|
1563
|
+
const result = await api.call('${connectorId}.echo', { message: 'hello' });
|
|
1564
|
+
setOutput(JSON.stringify(result, null, 2));
|
|
1565
|
+
} catch (err: any) {
|
|
1566
|
+
native?.haptics?.error?.();
|
|
1567
|
+
setOutput('Error: ' + err.message);
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
const styles = StyleSheet.create({
|
|
1572
|
+
container: { padding: 16, backgroundColor: theme.colors.bg, flex: 1 },
|
|
1573
|
+
card: { backgroundColor: theme.colors.surface, padding: 12, borderRadius: 8 },
|
|
1574
|
+
title: { fontSize: 18, fontWeight: '600', color: theme.colors.text, marginBottom: 8 },
|
|
1575
|
+
button: { backgroundColor: theme.colors.accent, padding: 12, borderRadius: 6, marginTop: 12 },
|
|
1576
|
+
buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
|
|
1577
|
+
output: { color: theme.colors.textMuted, marginTop: 12, fontFamily: 'Menlo' },
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
return (
|
|
1581
|
+
<View style={styles.container}>
|
|
1582
|
+
<View style={styles.card}>
|
|
1583
|
+
<Text style={styles.title}>${pluginName}</Text>
|
|
1584
|
+
<Text style={{ color: theme.colors.textMuted }}>
|
|
1585
|
+
Plugin body — replace with your real UI.
|
|
1586
|
+
</Text>
|
|
1587
|
+
<TouchableOpacity style={styles.button} onPress={handlePress}>
|
|
1588
|
+
<Text style={styles.buttonText}>Call sample tool</Text>
|
|
1589
|
+
</TouchableOpacity>
|
|
1590
|
+
{!!output && <Text style={styles.output}>{output}</Text>}
|
|
1591
|
+
</View>
|
|
1592
|
+
</View>
|
|
1593
|
+
);
|
|
1594
|
+
},
|
|
1595
|
+
};
|
|
1596
|
+
`;
|
|
1597
|
+
files.push({
|
|
1598
|
+
path: `plugins/${pluginName}/index.tsx`,
|
|
1599
|
+
content: tsx,
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
return files;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1270
1606
|
const handlers = {
|
|
1271
1607
|
ateam_bootstrap: async () => ({
|
|
1272
1608
|
platform_positioning: {
|
|
@@ -2323,6 +2659,9 @@ const handlers = {
|
|
|
2323
2659
|
return get(`/deploy/solutions/${solution_id}/metrics${qsStr}`, sid);
|
|
2324
2660
|
},
|
|
2325
2661
|
|
|
2662
|
+
ateam_verify_consistency: async ({ solution_id }, sid) =>
|
|
2663
|
+
get(`/deploy/solutions/${solution_id}/verify`, sid),
|
|
2664
|
+
|
|
2326
2665
|
ateam_diff: async ({ solution_id, skill_id }, sid) => {
|
|
2327
2666
|
const qs = skill_id ? `?skill_id=${encodeURIComponent(skill_id)}` : "";
|
|
2328
2667
|
return get(`/deploy/solutions/${solution_id}/diff${qs}`, sid);
|
|
@@ -2391,6 +2730,81 @@ const handlers = {
|
|
|
2391
2730
|
{ timeoutMs: 300_000, retries: 1 },
|
|
2392
2731
|
),
|
|
2393
2732
|
|
|
2733
|
+
// ── Phase 7 strip: scaffold helpers ─────────────────────────────────
|
|
2734
|
+
ateam_create_connector: async ({ solution_id, connector_id, name, ui_capable }, sid) => {
|
|
2735
|
+
if (!solution_id) throw new Error("solution_id required");
|
|
2736
|
+
if (!connector_id) throw new Error("connector_id required");
|
|
2737
|
+
if (!/^[a-z][a-z0-9-]*$/.test(connector_id)) {
|
|
2738
|
+
throw new Error("connector_id must be lowercase letters/digits/dashes only");
|
|
2739
|
+
}
|
|
2740
|
+
const files = _scaffoldConnectorFiles({
|
|
2741
|
+
connectorId: connector_id,
|
|
2742
|
+
displayName: name || connector_id,
|
|
2743
|
+
uiCapable: !!ui_capable,
|
|
2744
|
+
});
|
|
2745
|
+
const result = await post(
|
|
2746
|
+
`/deploy/solutions/${solution_id}/connectors/${connector_id}/upload`,
|
|
2747
|
+
{ files },
|
|
2748
|
+
sid,
|
|
2749
|
+
{ timeoutMs: 120_000, retries: 1 },
|
|
2750
|
+
);
|
|
2751
|
+
return {
|
|
2752
|
+
ok: true,
|
|
2753
|
+
connector_id,
|
|
2754
|
+
files_created: files.map(f => f.path),
|
|
2755
|
+
ui_capable: !!ui_capable,
|
|
2756
|
+
upload_result: result,
|
|
2757
|
+
next_steps: [
|
|
2758
|
+
`Edit server.js to add your real tools (replace the echo stub).`,
|
|
2759
|
+
ui_capable
|
|
2760
|
+
? `Use ateam_create_plugin to scaffold your first UI plugin.`
|
|
2761
|
+
: null,
|
|
2762
|
+
`Use ateam_test_connector or ateam_build_and_run to deploy + test.`,
|
|
2763
|
+
].filter(Boolean),
|
|
2764
|
+
};
|
|
2765
|
+
},
|
|
2766
|
+
|
|
2767
|
+
ateam_create_plugin: async ({ solution_id, connector_id, plugin_name, kind }, sid) => {
|
|
2768
|
+
if (!solution_id) throw new Error("solution_id required");
|
|
2769
|
+
if (!connector_id) throw new Error("connector_id required");
|
|
2770
|
+
if (!plugin_name) throw new Error("plugin_name required");
|
|
2771
|
+
if (!/^[a-z][a-z0-9-]*$/.test(plugin_name)) {
|
|
2772
|
+
throw new Error("plugin_name must be lowercase letters/digits/dashes only");
|
|
2773
|
+
}
|
|
2774
|
+
const k = kind || "adaptive";
|
|
2775
|
+
if (!["iframe", "rn", "adaptive"].includes(k)) {
|
|
2776
|
+
throw new Error(`kind must be one of: iframe, rn, adaptive (got ${k})`);
|
|
2777
|
+
}
|
|
2778
|
+
const files = _scaffoldPluginFiles({
|
|
2779
|
+
connectorId: connector_id,
|
|
2780
|
+
pluginName: plugin_name,
|
|
2781
|
+
kind: k,
|
|
2782
|
+
});
|
|
2783
|
+
const result = await post(
|
|
2784
|
+
`/deploy/solutions/${solution_id}/connectors/${connector_id}/upload`,
|
|
2785
|
+
{ files },
|
|
2786
|
+
sid,
|
|
2787
|
+
{ timeoutMs: 120_000, retries: 1 },
|
|
2788
|
+
);
|
|
2789
|
+
return {
|
|
2790
|
+
ok: true,
|
|
2791
|
+
plugin_id: `mcp:${connector_id}:${plugin_name}`,
|
|
2792
|
+
kind: k,
|
|
2793
|
+
files_created: files.map(f => f.path),
|
|
2794
|
+
upload_result: result,
|
|
2795
|
+
next_steps: [
|
|
2796
|
+
k === "rn" || k === "adaptive"
|
|
2797
|
+
? `Edit plugins/${plugin_name}/index.tsx — fill in the Component body.`
|
|
2798
|
+
: null,
|
|
2799
|
+
k === "iframe" || k === "adaptive"
|
|
2800
|
+
? `Edit ui-dist/${plugin_name}/index.html — replace the placeholder UI.`
|
|
2801
|
+
: null,
|
|
2802
|
+
`Phase 5 auto-discovery will register the plugin at next deploy.`,
|
|
2803
|
+
`Optional: drop a manifest.json next to the source for custom commands/capabilities.`,
|
|
2804
|
+
].filter(Boolean),
|
|
2805
|
+
};
|
|
2806
|
+
},
|
|
2807
|
+
|
|
2394
2808
|
ateam_redeploy: async ({ solution_id, skill_id }, sid) => {
|
|
2395
2809
|
const endpoint = skill_id
|
|
2396
2810
|
? `/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`
|