@ateam-ai/mcp 0.1.3 → 0.1.6
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 +246 -38
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.6" },
|
|
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 when A-Team MCP is connected. Returns platform positioning, product vision, example solutions, and assistant behavior instructions for onboarding.",
|
|
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: {
|
|
@@ -54,7 +85,7 @@ export const tools = [
|
|
|
54
85
|
{
|
|
55
86
|
name: "adas_validate_skill",
|
|
56
87
|
description:
|
|
57
|
-
"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. Part of building a governed AI Team solution. Returns errors and suggestions to fix. Always validate before deploying.",
|
|
58
89
|
inputSchema: {
|
|
59
90
|
type: "object",
|
|
60
91
|
properties: {
|
|
@@ -69,7 +100,7 @@ export const tools = [
|
|
|
69
100
|
{
|
|
70
101
|
name: "adas_validate_solution",
|
|
71
102
|
description:
|
|
72
|
-
"Validate a solution
|
|
103
|
+
"Validate a governed AI Team solution — cross-skill contracts, grant economy, handoffs, and LLM quality scoring. Part of building a governed AI Team solution. Always validate before deploying.",
|
|
73
104
|
inputSchema: {
|
|
74
105
|
type: "object",
|
|
75
106
|
properties: {
|
|
@@ -89,7 +120,7 @@ export const tools = [
|
|
|
89
120
|
{
|
|
90
121
|
name: "adas_deploy_solution",
|
|
91
122
|
description:
|
|
92
|
-
"Deploy a
|
|
123
|
+
"Deploy a governed AI Team solution to ADAS Core — identity, connectors, skills. The Skill Builder auto-generates MCP servers from tool definitions. Used after defining system architecture. Always validate first using adas_validate_solution. Requires authentication (call adas_auth first if not using env vars).",
|
|
93
124
|
inputSchema: {
|
|
94
125
|
type: "object",
|
|
95
126
|
properties: {
|
|
@@ -118,7 +149,7 @@ export const tools = [
|
|
|
118
149
|
},
|
|
119
150
|
{
|
|
120
151
|
name: "adas_deploy_skill",
|
|
121
|
-
description: "Deploy a single skill into an existing solution.",
|
|
152
|
+
description: "Deploy a single skill into an existing solution. Requires authentication.",
|
|
122
153
|
inputSchema: {
|
|
123
154
|
type: "object",
|
|
124
155
|
properties: {
|
|
@@ -136,7 +167,7 @@ export const tools = [
|
|
|
136
167
|
},
|
|
137
168
|
{
|
|
138
169
|
name: "adas_deploy_connector",
|
|
139
|
-
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.",
|
|
140
171
|
inputSchema: {
|
|
141
172
|
type: "object",
|
|
142
173
|
properties: {
|
|
@@ -184,7 +215,7 @@ export const tools = [
|
|
|
184
215
|
{
|
|
185
216
|
name: "adas_update",
|
|
186
217
|
description:
|
|
187
|
-
"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.",
|
|
188
219
|
inputSchema: {
|
|
189
220
|
type: "object",
|
|
190
221
|
properties: {
|
|
@@ -213,7 +244,7 @@ export const tools = [
|
|
|
213
244
|
{
|
|
214
245
|
name: "adas_redeploy",
|
|
215
246
|
description:
|
|
216
|
-
"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.",
|
|
217
248
|
inputSchema: {
|
|
218
249
|
type: "object",
|
|
219
250
|
properties: {
|
|
@@ -267,32 +298,129 @@ const EXAMPLE_PATHS = {
|
|
|
267
298
|
solution: "/spec/examples/solution",
|
|
268
299
|
};
|
|
269
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
|
+
|
|
270
311
|
const handlers = {
|
|
271
|
-
|
|
312
|
+
adas_bootstrap: async () => ({
|
|
313
|
+
platform_positioning: {
|
|
314
|
+
name: "A-Team",
|
|
315
|
+
category: "AI Team Solution Platform",
|
|
316
|
+
summary: "A-Team is a platform for building governed AI Teams as complete operational solutions.",
|
|
317
|
+
},
|
|
318
|
+
what_is_a_team: {
|
|
319
|
+
definition: "A Team is a structured multi-role AI system composed of Skills, Connectors, Governance contracts, and Managed Runtime deployment.",
|
|
320
|
+
core_components: {
|
|
321
|
+
skill: "Operational AI role — intents, tools, policies, workflows",
|
|
322
|
+
solution: "Complete AI Team system — multiple skills + routing + grants + handoffs",
|
|
323
|
+
connector: "External system integration via MCP tools",
|
|
324
|
+
governance: "Permissions, grants, handoffs, auditability",
|
|
325
|
+
deploy: "Activation into controlled runtime on ADAS Core",
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
example_solutions: [
|
|
329
|
+
{ name: "Fleet Command Center", description: "Live vehicle tracking, route optimization, safety monitoring, governed execution" },
|
|
330
|
+
{ name: "Customer Support Operations Team", description: "Multi-role support system with escalation, refund controls, CRM integration" },
|
|
331
|
+
{ name: "Enterprise Compliance Platform", description: "Approval flows, audit logs, policy enforcement" },
|
|
332
|
+
],
|
|
333
|
+
recommended_flow: [
|
|
334
|
+
{ step: 1, title: "Clarify the goal", description: "Understand what the user wants their Team to do", suggested_tools: [] },
|
|
335
|
+
{ step: 2, title: "Generate Team map", description: "Design skills, solution architecture, and connectors", suggested_tools: ["adas_get_spec", "adas_get_examples", "adas_get_workflows"] },
|
|
336
|
+
{ step: 3, title: "Validate", description: "Run validation before deploying", suggested_tools: ["adas_validate_skill", "adas_validate_solution"] },
|
|
337
|
+
{ step: 4, title: "Deploy", description: "Push the Team to ADAS Core", suggested_tools: ["adas_auth", "adas_deploy_solution"] },
|
|
338
|
+
{ step: 5, title: "Iterate", description: "Inspect, update, and redeploy as needed", suggested_tools: ["adas_get_solution", "adas_update", "adas_redeploy", "adas_solution_chat"] },
|
|
339
|
+
],
|
|
340
|
+
first_questions: [
|
|
341
|
+
{ id: "goal", question: "What do you want your Team to accomplish?", type: "text" },
|
|
342
|
+
{ id: "domain", question: "Which domain fits best?", type: "enum", options: ["ecommerce", "logistics", "enterprise_ops", "other"] },
|
|
343
|
+
{ id: "systems", question: "Which systems should the Team connect to?", type: "multi_select", options: ["slack", "email", "zendesk", "shopify", "jira", "postgres", "custom_api", "none"] },
|
|
344
|
+
{ id: "security", question: "What environment constraints?", type: "enum", options: ["sandbox", "controlled", "regulated"] },
|
|
345
|
+
],
|
|
346
|
+
static_pages: {
|
|
347
|
+
features: "https://ateam-ai.com/#features",
|
|
348
|
+
use_cases: "https://ateam-ai.com/#usecases",
|
|
349
|
+
security: "https://ateam-ai.com/#security",
|
|
350
|
+
engine: "https://ateam-ai.com/#engine",
|
|
351
|
+
},
|
|
352
|
+
assistant_behavior_contract: {
|
|
353
|
+
first_run_requirements: [
|
|
354
|
+
"Explain platform before endpoints",
|
|
355
|
+
"Frame as AI Team solution platform",
|
|
356
|
+
"Give at least one example solution",
|
|
357
|
+
"Define Skill vs Solution vs Connector",
|
|
358
|
+
"Ask user what solution they want to build",
|
|
359
|
+
],
|
|
360
|
+
thinking_order: ["Platform", "Solution", "Skills", "Connectors", "Governance", "Validation", "Deployment"],
|
|
361
|
+
tone: "Architectural, enterprise-grade, serious",
|
|
362
|
+
always: [
|
|
363
|
+
"Explain Skill vs Solution vs Connector before deploying anything",
|
|
364
|
+
"Validate before deploy",
|
|
365
|
+
"Ask discovery questions if goal unclear",
|
|
366
|
+
],
|
|
367
|
+
never: [
|
|
368
|
+
"Deploy before validation",
|
|
369
|
+
"Dump raw spec unless requested",
|
|
370
|
+
],
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
272
373
|
|
|
273
|
-
|
|
374
|
+
adas_auth: async ({ api_key, tenant }, sessionId) => {
|
|
375
|
+
// Auto-extract tenant from key if not provided
|
|
376
|
+
let resolvedTenant = tenant;
|
|
377
|
+
if (!resolvedTenant) {
|
|
378
|
+
const parsed = parseApiKey(api_key);
|
|
379
|
+
resolvedTenant = parsed.tenant || "main";
|
|
380
|
+
}
|
|
381
|
+
setSessionCredentials(sessionId, { tenant: resolvedTenant, apiKey: api_key });
|
|
382
|
+
// Verify the key works by listing solutions
|
|
383
|
+
try {
|
|
384
|
+
const result = await get("/deploy/solutions", sessionId);
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
tenant: resolvedTenant,
|
|
388
|
+
message: `Authenticated to tenant "${resolvedTenant}". ${result.solutions?.length || 0} solution(s) found.`,
|
|
389
|
+
};
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
393
|
+
tenant: resolvedTenant,
|
|
394
|
+
message: `Authentication failed: ${err.message}. The user can get a valid API key at https://mcp.ateam-ai.com/get-api-key`,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
},
|
|
274
398
|
|
|
275
|
-
|
|
399
|
+
adas_get_spec: async ({ topic }, sid) => get(SPEC_PATHS[topic], sid),
|
|
276
400
|
|
|
277
|
-
|
|
401
|
+
adas_get_workflows: async (_args, sid) => get("/spec/workflows", sid),
|
|
278
402
|
|
|
279
|
-
|
|
280
|
-
post("/validate/solution", { solution, skills }),
|
|
403
|
+
adas_get_examples: async ({ type }, sid) => get(EXAMPLE_PATHS[type], sid),
|
|
281
404
|
|
|
282
|
-
|
|
283
|
-
post("/deploy/solution", { solution, skills, connectors, mcp_store }),
|
|
405
|
+
adas_validate_skill: async ({ skill }, sid) => post("/validate/skill", { skill }, sid),
|
|
284
406
|
|
|
285
|
-
|
|
286
|
-
post(
|
|
407
|
+
adas_validate_solution: async ({ solution, skills }, sid) =>
|
|
408
|
+
post("/validate/solution", { solution, skills }, sid),
|
|
287
409
|
|
|
288
|
-
|
|
289
|
-
post("/deploy/
|
|
410
|
+
adas_deploy_solution: async ({ solution, skills, connectors, mcp_store }, sid) =>
|
|
411
|
+
post("/deploy/solution", { solution, skills, connectors, mcp_store }, sid),
|
|
290
412
|
|
|
291
|
-
|
|
413
|
+
adas_deploy_skill: async ({ solution_id, skill }, sid) =>
|
|
414
|
+
post(`/deploy/solutions/${solution_id}/skills`, { skill }, sid),
|
|
292
415
|
|
|
293
|
-
|
|
416
|
+
adas_deploy_connector: async ({ connector }, sid) =>
|
|
417
|
+
post("/deploy/connector", { connector }, sid),
|
|
418
|
+
|
|
419
|
+
adas_list_solutions: async (_args, sid) => get("/deploy/solutions", sid),
|
|
420
|
+
|
|
421
|
+
adas_get_solution: async ({ solution_id, view, skill_id }, sid) => {
|
|
294
422
|
const base = `/deploy/solutions/${solution_id}`;
|
|
295
|
-
if (skill_id) return get(`${base}/skills/${skill_id}
|
|
423
|
+
if (skill_id) return get(`${base}/skills/${skill_id}`, sid);
|
|
296
424
|
const paths = {
|
|
297
425
|
definition: `${base}/definition`,
|
|
298
426
|
skills: `${base}/skills`,
|
|
@@ -302,30 +430,88 @@ const handlers = {
|
|
|
302
430
|
validate: `${base}/validate`,
|
|
303
431
|
connectors_health: `${base}/connectors/health`,
|
|
304
432
|
};
|
|
305
|
-
return get(paths[view]);
|
|
433
|
+
return get(paths[view], sid);
|
|
306
434
|
},
|
|
307
435
|
|
|
308
|
-
adas_update: async ({ solution_id, target, skill_id, updates }) => {
|
|
436
|
+
adas_update: async ({ solution_id, target, skill_id, updates }, sid) => {
|
|
309
437
|
if (target === "skill") {
|
|
310
|
-
return patch(`/deploy/solutions/${solution_id}/skills/${skill_id}`, { updates });
|
|
438
|
+
return patch(`/deploy/solutions/${solution_id}/skills/${skill_id}`, { updates }, sid);
|
|
311
439
|
}
|
|
312
|
-
return patch(`/deploy/solutions/${solution_id}`, { state_update: updates });
|
|
440
|
+
return patch(`/deploy/solutions/${solution_id}`, { state_update: updates }, sid);
|
|
313
441
|
},
|
|
314
442
|
|
|
315
|
-
adas_redeploy: async ({ solution_id, skill_id }) => {
|
|
443
|
+
adas_redeploy: async ({ solution_id, skill_id }, sid) => {
|
|
316
444
|
if (skill_id) {
|
|
317
|
-
return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`, {});
|
|
445
|
+
return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`, {}, sid);
|
|
318
446
|
}
|
|
319
|
-
return post(`/deploy/solutions/${solution_id}/redeploy`, {});
|
|
447
|
+
return post(`/deploy/solutions/${solution_id}/redeploy`, {}, sid);
|
|
320
448
|
},
|
|
321
449
|
|
|
322
|
-
adas_solution_chat: async ({ solution_id, message }) =>
|
|
323
|
-
post(`/deploy/solutions/${solution_id}/chat`, { message }),
|
|
450
|
+
adas_solution_chat: async ({ solution_id, message }, sid) =>
|
|
451
|
+
post(`/deploy/solutions/${solution_id}/chat`, { message }, sid),
|
|
324
452
|
};
|
|
325
453
|
|
|
454
|
+
// ─── Response formatting ────────────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
// Max characters to send back in a single tool response.
|
|
457
|
+
// Larger payloads get summarized to avoid overwhelming LLM context.
|
|
458
|
+
const MAX_RESPONSE_CHARS = 50_000;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Format tool results — summarize oversized payloads.
|
|
462
|
+
*/
|
|
463
|
+
function formatResult(result, toolName) {
|
|
464
|
+
const json = JSON.stringify(result, null, 2);
|
|
465
|
+
|
|
466
|
+
if (json.length <= MAX_RESPONSE_CHARS) {
|
|
467
|
+
return json;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// For large responses, provide a summary + truncated data
|
|
471
|
+
const summary = summarizeLargeResult(result, toolName);
|
|
472
|
+
return summary + `\n\n(Response truncated from ${json.length.toLocaleString()} chars. Use more specific queries to get smaller results.)`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Create a useful summary for large results.
|
|
477
|
+
*/
|
|
478
|
+
function summarizeLargeResult(result, toolName) {
|
|
479
|
+
// Spec responses — keep content but cap size
|
|
480
|
+
if (toolName === "adas_get_spec" && result && typeof result === "object") {
|
|
481
|
+
const keys = Object.keys(result);
|
|
482
|
+
return JSON.stringify({
|
|
483
|
+
_note: `ADAS spec with ${keys.length} sections. Content truncated — ask about specific sections for detail.`,
|
|
484
|
+
sections: keys,
|
|
485
|
+
...result,
|
|
486
|
+
}, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Validation results — keep errors/warnings, trim echoed input
|
|
490
|
+
if ((toolName === "adas_validate_skill" || toolName === "adas_validate_solution") && result) {
|
|
491
|
+
const slim = { ...result };
|
|
492
|
+
if (slim.skill) delete slim.skill;
|
|
493
|
+
if (slim.solution) delete slim.solution;
|
|
494
|
+
const slimJson = JSON.stringify(slim, null, 2);
|
|
495
|
+
if (slimJson.length <= MAX_RESPONSE_CHARS) return slimJson;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Export results — summarize structure
|
|
499
|
+
if (toolName === "adas_get_solution" && result?.skills) {
|
|
500
|
+
return JSON.stringify({
|
|
501
|
+
_note: `Solution with ${result.skills.length} skill(s). Use adas_get_solution with skill_id to inspect individual skills.`,
|
|
502
|
+
solution_id: result.solution?.id || result.id,
|
|
503
|
+
skill_ids: result.skills.map(s => s.id || s.name),
|
|
504
|
+
...result,
|
|
505
|
+
}, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Generic fallback — truncate
|
|
509
|
+
return JSON.stringify(result, null, 2).slice(0, MAX_RESPONSE_CHARS);
|
|
510
|
+
}
|
|
511
|
+
|
|
326
512
|
// ─── Dispatcher ─────────────────────────────────────────────────────
|
|
327
513
|
|
|
328
|
-
export async function handleToolCall(name, args) {
|
|
514
|
+
export async function handleToolCall(name, args, sessionId) {
|
|
329
515
|
const handler = handlers[name];
|
|
330
516
|
if (!handler) {
|
|
331
517
|
return {
|
|
@@ -333,14 +519,36 @@ export async function handleToolCall(name, args) {
|
|
|
333
519
|
isError: true,
|
|
334
520
|
};
|
|
335
521
|
}
|
|
522
|
+
|
|
523
|
+
// Check auth for write operations
|
|
524
|
+
if (WRITE_TOOLS.has(name) && !isAuthenticated(sessionId)) {
|
|
525
|
+
return {
|
|
526
|
+
content: [{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: [
|
|
529
|
+
"🔐 Authentication required.",
|
|
530
|
+
"",
|
|
531
|
+
"This tool needs an API key. Please ask the user to:",
|
|
532
|
+
"",
|
|
533
|
+
"1. Get their API key at: https://mcp.ateam-ai.com/get-api-key",
|
|
534
|
+
"2. Then call: adas_auth(api_key: \"<their key>\")",
|
|
535
|
+
"",
|
|
536
|
+
"The key looks like: adas_<tenant>_<32hex>",
|
|
537
|
+
"The tenant is auto-extracted — no separate tenant parameter needed.",
|
|
538
|
+
].join("\n"),
|
|
539
|
+
}],
|
|
540
|
+
isError: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
336
544
|
try {
|
|
337
|
-
const result = await handler(args);
|
|
545
|
+
const result = await handler(args, sessionId);
|
|
338
546
|
return {
|
|
339
|
-
content: [{ type: "text", text:
|
|
547
|
+
content: [{ type: "text", text: formatResult(result, name) }],
|
|
340
548
|
};
|
|
341
549
|
} catch (err) {
|
|
342
550
|
return {
|
|
343
|
-
content: [{ type: "text", text:
|
|
551
|
+
content: [{ type: "text", text: err.message }],
|
|
344
552
|
isError: true,
|
|
345
553
|
};
|
|
346
554
|
}
|