@ateam-ai/mcp 0.1.2 → 0.1.5
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/api.js +164 -35
- package/src/http.js +14 -4
- package/src/server.js +10 -3
- package/src/tools.js +230 -37
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1,50 +1,179 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ADAS API client — thin HTTP wrapper for the External Agent API.
|
|
3
|
+
*
|
|
4
|
+
* Credentials resolve in this order:
|
|
5
|
+
* 1. Per-session override (set via adas_auth tool — used by HTTP transport)
|
|
6
|
+
* 2. Environment variables (ADAS_API_KEY, ADAS_TENANT — used by stdio transport)
|
|
7
|
+
* 3. Defaults (no key, tenant "main")
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
const BASE_URL = process.env.ADAS_API_URL || "https://api.ateam-ai.com";
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const ENV_TENANT = process.env.ADAS_TENANT || "";
|
|
12
|
+
const ENV_API_KEY = process.env.ADAS_API_KEY || "";
|
|
13
|
+
|
|
14
|
+
// Request timeout (30 seconds)
|
|
15
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
16
|
+
|
|
17
|
+
// Per-session credential store (sessionId → { tenant, apiKey })
|
|
18
|
+
const sessions = new Map();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a tenant-embedded API key.
|
|
22
|
+
* Format: adas_<tenant>_<32hex>
|
|
23
|
+
* Legacy: adas_<32hex> (no tenant embedded)
|
|
24
|
+
* @returns {{ tenant: string|null, isValid: boolean }}
|
|
25
|
+
*/
|
|
26
|
+
export function parseApiKey(key) {
|
|
27
|
+
if (!key || typeof key !== 'string') return { tenant: null, isValid: false };
|
|
28
|
+
const match = key.match(/^adas_([a-z0-9][a-z0-9-]{0,28}[a-z0-9])_([0-9a-f]{32})$/);
|
|
29
|
+
if (match) return { tenant: match[1], isValid: true };
|
|
30
|
+
const legacy = key.match(/^adas_([0-9a-f]{32})$/);
|
|
31
|
+
if (legacy) return { tenant: null, isValid: true };
|
|
32
|
+
return { tenant: null, isValid: false };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set credentials for a session (called by adas_auth tool).
|
|
37
|
+
* If tenant is not provided, it's auto-extracted from the key.
|
|
38
|
+
*/
|
|
39
|
+
export function setSessionCredentials(sessionId, { tenant, apiKey }) {
|
|
40
|
+
let resolvedTenant = tenant;
|
|
41
|
+
if (!resolvedTenant && apiKey) {
|
|
42
|
+
const parsed = parseApiKey(apiKey);
|
|
43
|
+
if (parsed.tenant) resolvedTenant = parsed.tenant;
|
|
44
|
+
}
|
|
45
|
+
sessions.set(sessionId, { tenant: resolvedTenant || "main", apiKey });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get credentials for a session, falling back to env vars.
|
|
50
|
+
* If using env var key with embedded tenant and no explicit ADAS_TENANT, auto-extract.
|
|
51
|
+
*/
|
|
52
|
+
export function getCredentials(sessionId) {
|
|
53
|
+
const session = sessionId ? sessions.get(sessionId) : null;
|
|
54
|
+
if (session) {
|
|
55
|
+
return { tenant: session.tenant, apiKey: session.apiKey };
|
|
56
|
+
}
|
|
57
|
+
const apiKey = ENV_API_KEY || "";
|
|
58
|
+
let tenant = ENV_TENANT;
|
|
59
|
+
if (!tenant && apiKey) {
|
|
60
|
+
const parsed = parseApiKey(apiKey);
|
|
61
|
+
if (parsed.tenant) tenant = parsed.tenant;
|
|
62
|
+
}
|
|
63
|
+
return { tenant: tenant || "main", apiKey };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a session is authenticated (has an API key from any source).
|
|
68
|
+
*/
|
|
69
|
+
export function isAuthenticated(sessionId) {
|
|
70
|
+
const { apiKey } = getCredentials(sessionId);
|
|
71
|
+
return apiKey.length > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove session credentials (on disconnect).
|
|
76
|
+
*/
|
|
77
|
+
export function clearSession(sessionId) {
|
|
78
|
+
sessions.delete(sessionId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function headers(sessionId) {
|
|
82
|
+
const { tenant, apiKey } = getCredentials(sessionId);
|
|
83
|
+
const h = { "Content-Type": "application/json" };
|
|
84
|
+
if (tenant) h["X-ADAS-TENANT"] = tenant;
|
|
85
|
+
if (apiKey) h["X-API-KEY"] = apiKey;
|
|
86
|
+
return h;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format an API error into a user-friendly message with actionable hints.
|
|
91
|
+
*/
|
|
92
|
+
function formatError(method, path, status, body) {
|
|
93
|
+
const hints = {
|
|
94
|
+
401: "Your API key may be invalid or expired. Try calling adas_auth again with a valid key.",
|
|
95
|
+
403: "You don't have permission for this operation. Check your tenant and API key.",
|
|
96
|
+
404: "Resource not found. Check the solution_id or skill_id you're using. Use adas_list_solutions to see available solutions.",
|
|
97
|
+
409: "Conflict — the resource may already exist or is in a conflicting state.",
|
|
98
|
+
422: "Validation failed. Check the request payload against the spec (use adas_get_spec).",
|
|
99
|
+
429: "Rate limited. Wait a moment and try again.",
|
|
100
|
+
500: "ADAS server error. The platform may be temporarily unavailable. Try again in a minute.",
|
|
101
|
+
502: "ADAS API is unreachable. The service may be restarting. Try again in a minute.",
|
|
102
|
+
503: "ADAS API is temporarily unavailable. Try again in a minute.",
|
|
14
103
|
};
|
|
104
|
+
|
|
105
|
+
const hint = hints[status] || "";
|
|
106
|
+
const detail = typeof body === "string" && body.length > 0 && body.length < 500 ? body : "";
|
|
107
|
+
|
|
108
|
+
let msg = `ADAS API error: ${method} ${path} returned ${status}`;
|
|
109
|
+
if (detail) msg += ` — ${detail}`;
|
|
110
|
+
if (hint) msg += `\nHint: ${hint}`;
|
|
111
|
+
|
|
112
|
+
return msg;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Core fetch wrapper with timeout and error formatting.
|
|
117
|
+
*/
|
|
118
|
+
async function request(method, path, body, sessionId) {
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const opts = {
|
|
124
|
+
method,
|
|
125
|
+
headers: headers(sessionId),
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
};
|
|
128
|
+
if (body !== undefined) {
|
|
129
|
+
opts.body = JSON.stringify(body);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const res = await fetch(`${BASE_URL}${path}`, opts);
|
|
133
|
+
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const text = await res.text().catch(() => "");
|
|
136
|
+
throw new Error(formatError(method, path, res.status, text));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return res.json();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err.name === "AbortError") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`ADAS API timeout: ${method} ${path} did not respond within ${REQUEST_TIMEOUT_MS / 1000}s.\n` +
|
|
144
|
+
`Hint: The ADAS API at ${BASE_URL} may be down. Check https://api.ateam-ai.com/health`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (err.cause?.code === "ECONNREFUSED") {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Cannot connect to ADAS API at ${BASE_URL}.\n` +
|
|
150
|
+
`Hint: The service may be down. Check https://api.ateam-ai.com/health`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (err.cause?.code === "ENOTFOUND") {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Cannot resolve ADAS API host: ${BASE_URL}.\n` +
|
|
156
|
+
`Hint: Check your internet connection and ADAS_API_URL setting.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
throw err;
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
}
|
|
15
163
|
}
|
|
16
164
|
|
|
17
|
-
export async function get(path) {
|
|
18
|
-
|
|
19
|
-
if (!res.ok) throw new Error(`GET ${path} → ${res.status}: ${await res.text()}`);
|
|
20
|
-
return res.json();
|
|
165
|
+
export async function get(path, sessionId) {
|
|
166
|
+
return request("GET", path, undefined, sessionId);
|
|
21
167
|
}
|
|
22
168
|
|
|
23
|
-
export async function post(path, body) {
|
|
24
|
-
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: headers(),
|
|
27
|
-
body: JSON.stringify(body),
|
|
28
|
-
});
|
|
29
|
-
if (!res.ok) throw new Error(`POST ${path} → ${res.status}: ${await res.text()}`);
|
|
30
|
-
return res.json();
|
|
169
|
+
export async function post(path, body, sessionId) {
|
|
170
|
+
return request("POST", path, body, sessionId);
|
|
31
171
|
}
|
|
32
172
|
|
|
33
|
-
export async function patch(path, body) {
|
|
34
|
-
|
|
35
|
-
method: "PATCH",
|
|
36
|
-
headers: headers(),
|
|
37
|
-
body: JSON.stringify(body),
|
|
38
|
-
});
|
|
39
|
-
if (!res.ok) throw new Error(`PATCH ${path} → ${res.status}: ${await res.text()}`);
|
|
40
|
-
return res.json();
|
|
173
|
+
export async function patch(path, body, sessionId) {
|
|
174
|
+
return request("PATCH", path, body, sessionId);
|
|
41
175
|
}
|
|
42
176
|
|
|
43
|
-
export async function del(path) {
|
|
44
|
-
|
|
45
|
-
method: "DELETE",
|
|
46
|
-
headers: headers(),
|
|
47
|
-
});
|
|
48
|
-
if (!res.ok) throw new Error(`DELETE ${path} → ${res.status}: ${await res.text()}`);
|
|
49
|
-
return res.json();
|
|
177
|
+
export async function del(path, sessionId) {
|
|
178
|
+
return request("DELETE", path, undefined, sessionId);
|
|
50
179
|
}
|
package/src/http.js
CHANGED
|
@@ -8,6 +8,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
8
8
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import express from "express";
|
|
10
10
|
import { createServer } from "./server.js";
|
|
11
|
+
import { clearSession } from "./api.js";
|
|
11
12
|
|
|
12
13
|
// Active sessions
|
|
13
14
|
const transports = {};
|
|
@@ -21,6 +22,11 @@ export function startHttpServer(port = 3100) {
|
|
|
21
22
|
res.json({ ok: true, service: "ateam-mcp", transport: "http" });
|
|
22
23
|
});
|
|
23
24
|
|
|
25
|
+
// ─── Get API Key — redirect to Skill Builder with auto-open ──
|
|
26
|
+
app.get("/get-api-key", (_req, res) => {
|
|
27
|
+
res.redirect("https://app.ateam-ai.com/builder/?show=api-key");
|
|
28
|
+
});
|
|
29
|
+
|
|
24
30
|
// ─── MCP POST — handle tool calls + initialize ───────────────
|
|
25
31
|
app.post("/mcp", async (req, res) => {
|
|
26
32
|
const sessionId = req.headers["mcp-session-id"];
|
|
@@ -32,9 +38,11 @@ export function startHttpServer(port = 3100) {
|
|
|
32
38
|
// Reuse existing session
|
|
33
39
|
transport = transports[sessionId];
|
|
34
40
|
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
35
|
-
// New session
|
|
41
|
+
// New session — generate ID upfront so we can bind it to the server
|
|
42
|
+
const newSessionId = randomUUID();
|
|
43
|
+
|
|
36
44
|
transport = new StreamableHTTPServerTransport({
|
|
37
|
-
sessionIdGenerator: () =>
|
|
45
|
+
sessionIdGenerator: () => newSessionId,
|
|
38
46
|
onsessioninitialized: (sid) => {
|
|
39
47
|
transports[sid] = transport;
|
|
40
48
|
},
|
|
@@ -42,12 +50,13 @@ export function startHttpServer(port = 3100) {
|
|
|
42
50
|
|
|
43
51
|
transport.onclose = () => {
|
|
44
52
|
const sid = transport.sessionId;
|
|
45
|
-
if (sid
|
|
53
|
+
if (sid) {
|
|
46
54
|
delete transports[sid];
|
|
55
|
+
clearSession(sid); // drop per-session credentials
|
|
47
56
|
}
|
|
48
57
|
};
|
|
49
58
|
|
|
50
|
-
const server = createServer();
|
|
59
|
+
const server = createServer(newSessionId);
|
|
51
60
|
await server.connect(transport);
|
|
52
61
|
await transport.handleRequest(req, res, req.body);
|
|
53
62
|
return;
|
|
@@ -107,6 +116,7 @@ export function startHttpServer(port = 3100) {
|
|
|
107
116
|
await transports[sid].close();
|
|
108
117
|
} catch {}
|
|
109
118
|
delete transports[sid];
|
|
119
|
+
clearSession(sid);
|
|
110
120
|
}
|
|
111
121
|
process.exit(0);
|
|
112
122
|
});
|
package/src/server.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared MCP server factory — used by both stdio and HTTP transports.
|
|
3
|
+
*
|
|
4
|
+
* Each server instance is bound to a sessionId so that tool handlers
|
|
5
|
+
* can resolve per-session credentials (set via the adas_auth tool).
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -9,9 +12,13 @@ import {
|
|
|
9
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
13
|
import { tools, handleToolCall } from "./tools.js";
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} sessionId — identifier for credential isolation.
|
|
17
|
+
* HTTP transport passes the MCP session UUID; stdio uses "stdio".
|
|
18
|
+
*/
|
|
19
|
+
export function createServer(sessionId = "stdio") {
|
|
13
20
|
const server = new Server(
|
|
14
|
-
{ name: "ateam-mcp", version: "0.1.
|
|
21
|
+
{ name: "ateam-mcp", version: "0.1.5" },
|
|
15
22
|
{ capabilities: { tools: {} } }
|
|
16
23
|
);
|
|
17
24
|
|
|
@@ -19,7 +26,7 @@ export function createServer() {
|
|
|
19
26
|
|
|
20
27
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
21
28
|
const { name, arguments: args } = request.params;
|
|
22
|
-
return handleToolCall(name, args);
|
|
29
|
+
return handleToolCall(name, args, sessionId);
|
|
23
30
|
});
|
|
24
31
|
|
|
25
32
|
return server;
|
package/src/tools.js
CHANGED
|
@@ -1,17 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ADAS MCP tool definitions and handlers.
|
|
3
|
-
*
|
|
3
|
+
* 15 tools covering the full ADAS External Agent API + auth + bootstrap.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
get, post, patch, del,
|
|
8
|
+
setSessionCredentials, isAuthenticated, getCredentials, parseApiKey,
|
|
9
|
+
} from "./api.js";
|
|
7
10
|
|
|
8
11
|
// ─── Tool definitions ───────────────────────────────────────────────
|
|
9
12
|
|
|
10
13
|
export const tools = [
|
|
14
|
+
{
|
|
15
|
+
name: "adas_bootstrap",
|
|
16
|
+
description:
|
|
17
|
+
"Call this FIRST after installing A-Team MCP. Returns a structured overview of what A-Team is, what the user can build, and the recommended step-by-step flow. Includes the first questions to ask the user and suggested next tool calls.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "adas_auth",
|
|
25
|
+
description:
|
|
26
|
+
"Authenticate with ADAS. Required before deploying or modifying solutions. The user can get their API key at https://mcp.ateam-ai.com/get-api-key. Read-only operations (spec, examples, validate) work without auth.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
api_key: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Your ADAS API key (e.g., adas_xxxxx)",
|
|
33
|
+
},
|
|
34
|
+
tenant: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Tenant name (e.g., dev, main). Optional if your key has the format adas_<tenant>_<hex> — the tenant is auto-extracted.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["api_key"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
11
42
|
{
|
|
12
43
|
name: "adas_get_spec",
|
|
13
44
|
description:
|
|
14
|
-
"Get the ADAS specification — schemas, validation rules, system tools, agent guides, and templates.
|
|
45
|
+
"Get the ADAS specification — schemas, validation rules, system tools, agent guides, and templates. Start here after bootstrap to understand how to build skills and solutions.",
|
|
15
46
|
inputSchema: {
|
|
16
47
|
type: "object",
|
|
17
48
|
properties: {
|
|
@@ -25,6 +56,15 @@ export const tools = [
|
|
|
25
56
|
required: ["topic"],
|
|
26
57
|
},
|
|
27
58
|
},
|
|
59
|
+
{
|
|
60
|
+
name: "adas_get_workflows",
|
|
61
|
+
description:
|
|
62
|
+
"Get the builder workflows — step-by-step state machines for building skills and solutions. Use this to guide users through the entire build process conversationally. Returns phases, what to ask, what to build, exit criteria, and tips for each stage.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
28
68
|
{
|
|
29
69
|
name: "adas_get_examples",
|
|
30
70
|
description:
|
|
@@ -45,7 +85,7 @@ export const tools = [
|
|
|
45
85
|
{
|
|
46
86
|
name: "adas_validate_skill",
|
|
47
87
|
description:
|
|
48
|
-
"Validate a skill definition through the 5-stage ADAS validation pipeline. Returns errors and suggestions to fix.",
|
|
88
|
+
"Validate a skill definition through the 5-stage ADAS validation pipeline. Returns errors and suggestions to fix. Always validate before deploying.",
|
|
49
89
|
inputSchema: {
|
|
50
90
|
type: "object",
|
|
51
91
|
properties: {
|
|
@@ -60,7 +100,7 @@ export const tools = [
|
|
|
60
100
|
{
|
|
61
101
|
name: "adas_validate_solution",
|
|
62
102
|
description:
|
|
63
|
-
"Validate a solution definition — cross-skill contracts, grant economy, handoffs, and LLM quality scoring.",
|
|
103
|
+
"Validate a solution definition — cross-skill contracts, grant economy, handoffs, and LLM quality scoring. Always validate before deploying.",
|
|
64
104
|
inputSchema: {
|
|
65
105
|
type: "object",
|
|
66
106
|
properties: {
|
|
@@ -80,7 +120,7 @@ export const tools = [
|
|
|
80
120
|
{
|
|
81
121
|
name: "adas_deploy_solution",
|
|
82
122
|
description:
|
|
83
|
-
"Deploy a complete solution to ADAS Core — identity, connectors, skills. The Skill Builder auto-generates MCP servers from tool definitions. This is the main deployment action.",
|
|
123
|
+
"Deploy a complete solution to ADAS Core — identity, connectors, skills. The Skill Builder auto-generates MCP servers from tool definitions. This is the main deployment action. Always validate first using adas_validate_solution. Requires authentication (call adas_auth first if not using env vars).",
|
|
84
124
|
inputSchema: {
|
|
85
125
|
type: "object",
|
|
86
126
|
properties: {
|
|
@@ -109,7 +149,7 @@ export const tools = [
|
|
|
109
149
|
},
|
|
110
150
|
{
|
|
111
151
|
name: "adas_deploy_skill",
|
|
112
|
-
description: "Deploy a single skill into an existing solution.",
|
|
152
|
+
description: "Deploy a single skill into an existing solution. Requires authentication.",
|
|
113
153
|
inputSchema: {
|
|
114
154
|
type: "object",
|
|
115
155
|
properties: {
|
|
@@ -127,7 +167,7 @@ export const tools = [
|
|
|
127
167
|
},
|
|
128
168
|
{
|
|
129
169
|
name: "adas_deploy_connector",
|
|
130
|
-
description: "Deploy a connector — registers in the Skill Builder catalog and connects in ADAS Core.",
|
|
170
|
+
description: "Deploy a connector — registers in the Skill Builder catalog and connects in ADAS Core. Requires authentication.",
|
|
131
171
|
inputSchema: {
|
|
132
172
|
type: "object",
|
|
133
173
|
properties: {
|
|
@@ -175,7 +215,7 @@ export const tools = [
|
|
|
175
215
|
{
|
|
176
216
|
name: "adas_update",
|
|
177
217
|
description:
|
|
178
|
-
"Update a deployed solution or skill incrementally using PATCH. Supports dot notation for scalar fields and _push/_delete/_update for arrays.",
|
|
218
|
+
"Update a deployed solution or skill incrementally using PATCH. Supports dot notation for scalar fields and _push/_delete/_update for arrays. Requires authentication.",
|
|
179
219
|
inputSchema: {
|
|
180
220
|
type: "object",
|
|
181
221
|
properties: {
|
|
@@ -204,7 +244,7 @@ export const tools = [
|
|
|
204
244
|
{
|
|
205
245
|
name: "adas_redeploy",
|
|
206
246
|
description:
|
|
207
|
-
"Re-deploy after making updates. Regenerates MCP servers and pushes to ADAS Core.",
|
|
247
|
+
"Re-deploy after making updates. Regenerates MCP servers and pushes to ADAS Core. Requires authentication.",
|
|
208
248
|
inputSchema: {
|
|
209
249
|
type: "object",
|
|
210
250
|
properties: {
|
|
@@ -258,30 +298,103 @@ const EXAMPLE_PATHS = {
|
|
|
258
298
|
solution: "/spec/examples/solution",
|
|
259
299
|
};
|
|
260
300
|
|
|
301
|
+
// Tools that require authentication (write operations)
|
|
302
|
+
const WRITE_TOOLS = new Set([
|
|
303
|
+
"adas_deploy_solution",
|
|
304
|
+
"adas_deploy_skill",
|
|
305
|
+
"adas_deploy_connector",
|
|
306
|
+
"adas_update",
|
|
307
|
+
"adas_redeploy",
|
|
308
|
+
"adas_solution_chat",
|
|
309
|
+
]);
|
|
310
|
+
|
|
261
311
|
const handlers = {
|
|
262
|
-
|
|
312
|
+
adas_bootstrap: async () => ({
|
|
313
|
+
product_name: "A-Team (ADAS)",
|
|
314
|
+
what_this_mcp_is:
|
|
315
|
+
"A-Team MCP is a collection of tools that lets an AI assistant generate, validate, deploy, and iterate multi-agent Teams on the A-Team/ADAS platform.",
|
|
316
|
+
core_concepts: [
|
|
317
|
+
{ term: "Skill", meaning: "One agent: role, intents, tools, policies, workflows" },
|
|
318
|
+
{ term: "Solution", meaning: "A Team: multiple skills + routing + grants + handoffs" },
|
|
319
|
+
{ term: "Connector", meaning: "External system integration via MCP tools" },
|
|
320
|
+
{ term: "Deploy", meaning: "Make the Team runnable on ADAS Core" },
|
|
321
|
+
],
|
|
322
|
+
recommended_flow: [
|
|
323
|
+
{ step: 1, title: "Clarify the goal", description: "Understand what the user wants their Team to do", suggested_tools: [] },
|
|
324
|
+
{ step: 2, title: "Generate Team map", description: "Design skills, solution architecture, and connectors", suggested_tools: ["adas_get_spec", "adas_get_examples", "adas_get_workflows"] },
|
|
325
|
+
{ step: 3, title: "Validate", description: "Run validation before deploying", suggested_tools: ["adas_validate_skill", "adas_validate_solution"] },
|
|
326
|
+
{ step: 4, title: "Deploy", description: "Push the Team to ADAS Core", suggested_tools: ["adas_auth", "adas_deploy_solution"] },
|
|
327
|
+
{ step: 5, title: "Iterate", description: "Inspect, update, and redeploy as needed", suggested_tools: ["adas_get_solution", "adas_update", "adas_redeploy", "adas_solution_chat"] },
|
|
328
|
+
],
|
|
329
|
+
first_questions: [
|
|
330
|
+
{ id: "goal", question: "What do you want your Team to accomplish?", type: "text" },
|
|
331
|
+
{ id: "domain", question: "Which domain fits best?", type: "enum", options: ["ecommerce", "logistics", "enterprise_ops", "other"] },
|
|
332
|
+
{ id: "systems", question: "Which systems should the Team connect to?", type: "multi_select", options: ["slack", "email", "zendesk", "shopify", "jira", "postgres", "custom_api", "none"] },
|
|
333
|
+
{ id: "security", question: "What environment constraints?", type: "enum", options: ["sandbox", "controlled", "regulated"] },
|
|
334
|
+
],
|
|
335
|
+
assistant_instructions: {
|
|
336
|
+
always: [
|
|
337
|
+
"Explain Skill vs Solution vs Connector before deploying anything",
|
|
338
|
+
"Validate before deploy",
|
|
339
|
+
"Ask discovery questions if goal unclear",
|
|
340
|
+
],
|
|
341
|
+
never: [
|
|
342
|
+
"Deploy before validation",
|
|
343
|
+
"Dump raw spec unless requested",
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
263
347
|
|
|
264
|
-
|
|
348
|
+
adas_auth: async ({ api_key, tenant }, sessionId) => {
|
|
349
|
+
// Auto-extract tenant from key if not provided
|
|
350
|
+
let resolvedTenant = tenant;
|
|
351
|
+
if (!resolvedTenant) {
|
|
352
|
+
const parsed = parseApiKey(api_key);
|
|
353
|
+
resolvedTenant = parsed.tenant || "main";
|
|
354
|
+
}
|
|
355
|
+
setSessionCredentials(sessionId, { tenant: resolvedTenant, apiKey: api_key });
|
|
356
|
+
// Verify the key works by listing solutions
|
|
357
|
+
try {
|
|
358
|
+
const result = await get("/deploy/solutions", sessionId);
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
tenant: resolvedTenant,
|
|
362
|
+
message: `Authenticated to tenant "${resolvedTenant}". ${result.solutions?.length || 0} solution(s) found.`,
|
|
363
|
+
};
|
|
364
|
+
} catch (err) {
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
tenant: resolvedTenant,
|
|
368
|
+
message: `Authentication failed: ${err.message}. The user can get a valid API key at https://mcp.ateam-ai.com/get-api-key`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
adas_get_spec: async ({ topic }, sid) => get(SPEC_PATHS[topic], sid),
|
|
374
|
+
|
|
375
|
+
adas_get_workflows: async (_args, sid) => get("/spec/workflows", sid),
|
|
376
|
+
|
|
377
|
+
adas_get_examples: async ({ type }, sid) => get(EXAMPLE_PATHS[type], sid),
|
|
265
378
|
|
|
266
|
-
adas_validate_skill: async ({ skill }) => post("/validate/skill", { skill }),
|
|
379
|
+
adas_validate_skill: async ({ skill }, sid) => post("/validate/skill", { skill }, sid),
|
|
267
380
|
|
|
268
|
-
adas_validate_solution: async ({ solution, skills }) =>
|
|
269
|
-
post("/validate/solution", { solution, skills }),
|
|
381
|
+
adas_validate_solution: async ({ solution, skills }, sid) =>
|
|
382
|
+
post("/validate/solution", { solution, skills }, sid),
|
|
270
383
|
|
|
271
|
-
adas_deploy_solution: async ({ solution, skills, connectors, mcp_store }) =>
|
|
272
|
-
post("/deploy/solution", { solution, skills, connectors, mcp_store }),
|
|
384
|
+
adas_deploy_solution: async ({ solution, skills, connectors, mcp_store }, sid) =>
|
|
385
|
+
post("/deploy/solution", { solution, skills, connectors, mcp_store }, sid),
|
|
273
386
|
|
|
274
|
-
adas_deploy_skill: async ({ solution_id, skill }) =>
|
|
275
|
-
post(`/deploy/solutions/${solution_id}/skills`, { skill }),
|
|
387
|
+
adas_deploy_skill: async ({ solution_id, skill }, sid) =>
|
|
388
|
+
post(`/deploy/solutions/${solution_id}/skills`, { skill }, sid),
|
|
276
389
|
|
|
277
|
-
adas_deploy_connector: async ({ connector }) =>
|
|
278
|
-
post("/deploy/connector", { connector }),
|
|
390
|
+
adas_deploy_connector: async ({ connector }, sid) =>
|
|
391
|
+
post("/deploy/connector", { connector }, sid),
|
|
279
392
|
|
|
280
|
-
adas_list_solutions: async () => get("/deploy/solutions"),
|
|
393
|
+
adas_list_solutions: async (_args, sid) => get("/deploy/solutions", sid),
|
|
281
394
|
|
|
282
|
-
adas_get_solution: async ({ solution_id, view, skill_id }) => {
|
|
395
|
+
adas_get_solution: async ({ solution_id, view, skill_id }, sid) => {
|
|
283
396
|
const base = `/deploy/solutions/${solution_id}`;
|
|
284
|
-
if (skill_id) return get(`${base}/skills/${skill_id}
|
|
397
|
+
if (skill_id) return get(`${base}/skills/${skill_id}`, sid);
|
|
285
398
|
const paths = {
|
|
286
399
|
definition: `${base}/definition`,
|
|
287
400
|
skills: `${base}/skills`,
|
|
@@ -291,30 +404,88 @@ const handlers = {
|
|
|
291
404
|
validate: `${base}/validate`,
|
|
292
405
|
connectors_health: `${base}/connectors/health`,
|
|
293
406
|
};
|
|
294
|
-
return get(paths[view]);
|
|
407
|
+
return get(paths[view], sid);
|
|
295
408
|
},
|
|
296
409
|
|
|
297
|
-
adas_update: async ({ solution_id, target, skill_id, updates }) => {
|
|
410
|
+
adas_update: async ({ solution_id, target, skill_id, updates }, sid) => {
|
|
298
411
|
if (target === "skill") {
|
|
299
|
-
return patch(`/deploy/solutions/${solution_id}/skills/${skill_id}`, { updates });
|
|
412
|
+
return patch(`/deploy/solutions/${solution_id}/skills/${skill_id}`, { updates }, sid);
|
|
300
413
|
}
|
|
301
|
-
return patch(`/deploy/solutions/${solution_id}`, { state_update: updates });
|
|
414
|
+
return patch(`/deploy/solutions/${solution_id}`, { state_update: updates }, sid);
|
|
302
415
|
},
|
|
303
416
|
|
|
304
|
-
adas_redeploy: async ({ solution_id, skill_id }) => {
|
|
417
|
+
adas_redeploy: async ({ solution_id, skill_id }, sid) => {
|
|
305
418
|
if (skill_id) {
|
|
306
|
-
return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`, {});
|
|
419
|
+
return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`, {}, sid);
|
|
307
420
|
}
|
|
308
|
-
return post(`/deploy/solutions/${solution_id}/redeploy`, {});
|
|
421
|
+
return post(`/deploy/solutions/${solution_id}/redeploy`, {}, sid);
|
|
309
422
|
},
|
|
310
423
|
|
|
311
|
-
adas_solution_chat: async ({ solution_id, message }) =>
|
|
312
|
-
post(`/deploy/solutions/${solution_id}/chat`, { message }),
|
|
424
|
+
adas_solution_chat: async ({ solution_id, message }, sid) =>
|
|
425
|
+
post(`/deploy/solutions/${solution_id}/chat`, { message }, sid),
|
|
313
426
|
};
|
|
314
427
|
|
|
428
|
+
// ─── Response formatting ────────────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
// Max characters to send back in a single tool response.
|
|
431
|
+
// Larger payloads get summarized to avoid overwhelming LLM context.
|
|
432
|
+
const MAX_RESPONSE_CHARS = 50_000;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Format tool results — summarize oversized payloads.
|
|
436
|
+
*/
|
|
437
|
+
function formatResult(result, toolName) {
|
|
438
|
+
const json = JSON.stringify(result, null, 2);
|
|
439
|
+
|
|
440
|
+
if (json.length <= MAX_RESPONSE_CHARS) {
|
|
441
|
+
return json;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// For large responses, provide a summary + truncated data
|
|
445
|
+
const summary = summarizeLargeResult(result, toolName);
|
|
446
|
+
return summary + `\n\n(Response truncated from ${json.length.toLocaleString()} chars. Use more specific queries to get smaller results.)`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Create a useful summary for large results.
|
|
451
|
+
*/
|
|
452
|
+
function summarizeLargeResult(result, toolName) {
|
|
453
|
+
// Spec responses — keep content but cap size
|
|
454
|
+
if (toolName === "adas_get_spec" && result && typeof result === "object") {
|
|
455
|
+
const keys = Object.keys(result);
|
|
456
|
+
return JSON.stringify({
|
|
457
|
+
_note: `ADAS spec with ${keys.length} sections. Content truncated — ask about specific sections for detail.`,
|
|
458
|
+
sections: keys,
|
|
459
|
+
...result,
|
|
460
|
+
}, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Validation results — keep errors/warnings, trim echoed input
|
|
464
|
+
if ((toolName === "adas_validate_skill" || toolName === "adas_validate_solution") && result) {
|
|
465
|
+
const slim = { ...result };
|
|
466
|
+
if (slim.skill) delete slim.skill;
|
|
467
|
+
if (slim.solution) delete slim.solution;
|
|
468
|
+
const slimJson = JSON.stringify(slim, null, 2);
|
|
469
|
+
if (slimJson.length <= MAX_RESPONSE_CHARS) return slimJson;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Export results — summarize structure
|
|
473
|
+
if (toolName === "adas_get_solution" && result?.skills) {
|
|
474
|
+
return JSON.stringify({
|
|
475
|
+
_note: `Solution with ${result.skills.length} skill(s). Use adas_get_solution with skill_id to inspect individual skills.`,
|
|
476
|
+
solution_id: result.solution?.id || result.id,
|
|
477
|
+
skill_ids: result.skills.map(s => s.id || s.name),
|
|
478
|
+
...result,
|
|
479
|
+
}, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Generic fallback — truncate
|
|
483
|
+
return JSON.stringify(result, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
484
|
+
}
|
|
485
|
+
|
|
315
486
|
// ─── Dispatcher ─────────────────────────────────────────────────────
|
|
316
487
|
|
|
317
|
-
export async function handleToolCall(name, args) {
|
|
488
|
+
export async function handleToolCall(name, args, sessionId) {
|
|
318
489
|
const handler = handlers[name];
|
|
319
490
|
if (!handler) {
|
|
320
491
|
return {
|
|
@@ -322,14 +493,36 @@ export async function handleToolCall(name, args) {
|
|
|
322
493
|
isError: true,
|
|
323
494
|
};
|
|
324
495
|
}
|
|
496
|
+
|
|
497
|
+
// Check auth for write operations
|
|
498
|
+
if (WRITE_TOOLS.has(name) && !isAuthenticated(sessionId)) {
|
|
499
|
+
return {
|
|
500
|
+
content: [{
|
|
501
|
+
type: "text",
|
|
502
|
+
text: [
|
|
503
|
+
"🔐 Authentication required.",
|
|
504
|
+
"",
|
|
505
|
+
"This tool needs an API key. Please ask the user to:",
|
|
506
|
+
"",
|
|
507
|
+
"1. Get their API key at: https://mcp.ateam-ai.com/get-api-key",
|
|
508
|
+
"2. Then call: adas_auth(api_key: \"<their key>\")",
|
|
509
|
+
"",
|
|
510
|
+
"The key looks like: adas_<tenant>_<32hex>",
|
|
511
|
+
"The tenant is auto-extracted — no separate tenant parameter needed.",
|
|
512
|
+
].join("\n"),
|
|
513
|
+
}],
|
|
514
|
+
isError: true,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
325
518
|
try {
|
|
326
|
-
const result = await handler(args);
|
|
519
|
+
const result = await handler(args, sessionId);
|
|
327
520
|
return {
|
|
328
|
-
content: [{ type: "text", text:
|
|
521
|
+
content: [{ type: "text", text: formatResult(result, name) }],
|
|
329
522
|
};
|
|
330
523
|
} catch (err) {
|
|
331
524
|
return {
|
|
332
|
-
content: [{ type: "text", text:
|
|
525
|
+
content: [{ type: "text", text: err.message }],
|
|
333
526
|
isError: true,
|
|
334
527
|
};
|
|
335
528
|
}
|