@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "ADAS MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
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 TENANT = process.env.ADAS_TENANT || "main";
7
- const API_KEY = process.env.ADAS_API_KEY || "";
8
-
9
- function headers() {
10
- return {
11
- "Content-Type": "application/json",
12
- "X-ADAS-TENANT": TENANT,
13
- "X-API-KEY": API_KEY,
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
- const res = await fetch(`${BASE_URL}${path}`, { headers: headers() });
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
- const res = await fetch(`${BASE_URL}${path}`, {
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
- const res = await fetch(`${BASE_URL}${path}`, {
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
- const res = await fetch(`${BASE_URL}${path}`, {
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: () => randomUUID(),
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 && transports[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
- export function createServer() {
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.0" },
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
- * 13 tools covering the full ADAS External Agent API.
3
+ * 15 tools covering the full ADAS External Agent API + auth + bootstrap.
4
4
  */
5
5
 
6
- import { get, post, patch, del } from "./api.js";
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. Use this to understand how to build skills and solutions.",
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 definition — cross-skill contracts, grant economy, handoffs, and LLM quality scoring.",
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 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 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
- adas_get_spec: async ({ topic }) => get(SPEC_PATHS[topic]),
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
- adas_get_workflows: async () => get("/spec/workflows"),
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
- adas_get_examples: async ({ type }) => get(EXAMPLE_PATHS[type]),
399
+ adas_get_spec: async ({ topic }, sid) => get(SPEC_PATHS[topic], sid),
276
400
 
277
- adas_validate_skill: async ({ skill }) => post("/validate/skill", { skill }),
401
+ adas_get_workflows: async (_args, sid) => get("/spec/workflows", sid),
278
402
 
279
- adas_validate_solution: async ({ solution, skills }) =>
280
- post("/validate/solution", { solution, skills }),
403
+ adas_get_examples: async ({ type }, sid) => get(EXAMPLE_PATHS[type], sid),
281
404
 
282
- adas_deploy_solution: async ({ solution, skills, connectors, mcp_store }) =>
283
- post("/deploy/solution", { solution, skills, connectors, mcp_store }),
405
+ adas_validate_skill: async ({ skill }, sid) => post("/validate/skill", { skill }, sid),
284
406
 
285
- adas_deploy_skill: async ({ solution_id, skill }) =>
286
- post(`/deploy/solutions/${solution_id}/skills`, { skill }),
407
+ adas_validate_solution: async ({ solution, skills }, sid) =>
408
+ post("/validate/solution", { solution, skills }, sid),
287
409
 
288
- adas_deploy_connector: async ({ connector }) =>
289
- post("/deploy/connector", { connector }),
410
+ adas_deploy_solution: async ({ solution, skills, connectors, mcp_store }, sid) =>
411
+ post("/deploy/solution", { solution, skills, connectors, mcp_store }, sid),
290
412
 
291
- adas_list_solutions: async () => get("/deploy/solutions"),
413
+ adas_deploy_skill: async ({ solution_id, skill }, sid) =>
414
+ post(`/deploy/solutions/${solution_id}/skills`, { skill }, sid),
292
415
 
293
- adas_get_solution: async ({ solution_id, view, skill_id }) => {
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: JSON.stringify(result, null, 2) }],
547
+ content: [{ type: "text", text: formatResult(result, name) }],
340
548
  };
341
549
  } catch (err) {
342
550
  return {
343
- content: [{ type: "text", text: `Error: ${err.message}` }],
551
+ content: [{ type: "text", text: err.message }],
344
552
  isError: true,
345
553
  };
346
554
  }