@ateam-ai/mcp 0.3.40 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.js +397 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.40",
3
+ "version": "0.3.41",
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",
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,
@@ -1281,6 +1345,264 @@ const TENANT_TOOLS = new Set([
1281
1345
  /** Small delay helper */
1282
1346
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
1283
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
+
1284
1606
  const handlers = {
1285
1607
  ateam_bootstrap: async () => ({
1286
1608
  platform_positioning: {
@@ -2408,6 +2730,81 @@ const handlers = {
2408
2730
  { timeoutMs: 300_000, retries: 1 },
2409
2731
  ),
2410
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
+
2411
2808
  ateam_redeploy: async ({ solution_id, skill_id }, sid) => {
2412
2809
  const endpoint = skill_id
2413
2810
  ? `/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`