@amitdeshmukh/ax-crew 8.3.0 → 8.5.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [8.5.0] - 2026-03-13
4
+
5
+ ### Added
6
+ - **Lazy agent initialization** via `crew.addLazyAgent(agentName)`. Builds the tool schema immediately from the crew config (name, description, signature) but defers expensive initialization (MCP server startup, AI client creation) until the Manager actually delegates to the agent. Ideal for sub-agents with stdio MCP servers that spawn a process on init — avoids the cold-start penalty on every request when the agent isn't needed.
7
+
8
+ ## [8.4.0] - 2026-03-05
9
+
10
+ ### Added
11
+ - Optional `tools` allowlist on MCP server configs (stdio, HTTP SSE, streamable HTTP). When specified, only the listed tools are exposed to the agent, keeping system prompts small when connecting to MCP servers with many tools. Tested with GraphJin (40+ tools): filtering to 8 reduced token usage from 43K to 14K per request.
12
+ - `examples/run-crew-workflow.ts` — workflow-aware agent using GraphJin MCP with tool filtering.
13
+
3
14
  ## [8.3.0] - 2026-03-05
4
15
 
5
16
  ### Fixed
@@ -56,10 +56,15 @@ const initializeMCPServers = async (agentConfigData) => {
56
56
  initializedClients.push(mcpClient);
57
57
  // Normalize MCP tool schemas: some MCP servers omit `parameters` for
58
58
  // zero-arg tools, but providers like Gemini require a valid schema.
59
- const mcpFns = mcpClient.toFunction().map(fn => ({
59
+ let mcpFns = mcpClient.toFunction().map(fn => ({
60
60
  ...fn,
61
61
  parameters: fn.parameters ?? { type: 'object', properties: {} },
62
62
  }));
63
+ // Filter to allowlisted tools if specified
64
+ if (mcpServerConfig.tools && mcpServerConfig.tools.length > 0) {
65
+ const allowed = new Set(mcpServerConfig.tools);
66
+ mcpFns = mcpFns.filter(fn => allowed.has(fn.name));
67
+ }
63
68
  functions.push(...mcpFns);
64
69
  }
65
70
  return functions;
@@ -1,4 +1,4 @@
1
- import { AxAgent, AxAI } from "@ax-llm/ax";
1
+ import { AxAgent, AxAI, AxSignature as AxSignatureClass } from "@ax-llm/ax";
2
2
  import type { AxSignature, AxAgentic, AxFunction, AxProgramForwardOptions, AxProgramStreamingForwardOptions, AxGenStreamingOut } from "@ax-llm/ax";
3
3
  import type { StateInstance, FunctionRegistryType, UsageCost, AxCrewConfig, AxCrewOptions, MCPTransportConfig, ACEConfig, AgentExecutionMode, AxCrewAxAgentOptions } from "../types.js";
4
4
  declare class StatefulAxAgent extends AxAgent<any, any> {
@@ -113,6 +113,35 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
113
113
  */
114
114
  private composeInstructionWithPlaybook;
115
115
  }
116
+ /**
117
+ * Lightweight proxy that stands in for a real agent in the crew's agent map.
118
+ * It exposes the same `getFunction()` interface (built from the crew config)
119
+ * but defers the expensive `createAgent()` call — and therefore MCP server
120
+ * startup — until the Manager actually delegates to it.
121
+ *
122
+ * Usage: `crew.addLazyAgent("CreateChart")` instead of `crew.addAgent("CreateChart")`
123
+ */
124
+ declare class LazyStatefulAxAgent {
125
+ private realAgent;
126
+ private crewRef;
127
+ private agentName;
128
+ private description;
129
+ private signatureStr;
130
+ private func;
131
+ private _id;
132
+ constructor(crewRef: any, agentName: string, crewConfig: AxCrewConfig);
133
+ private resolve;
134
+ getFunction(): AxFunction;
135
+ getSignature(): AxSignatureClass<Record<string, any>, Record<string, any>>;
136
+ getId(): string;
137
+ setId(id: string): void;
138
+ getTraces(): any[];
139
+ setDemos(): void;
140
+ getUsage(): any[];
141
+ resetUsage(): void;
142
+ forward(...args: any[]): Promise<any>;
143
+ streamingForward(...args: any[]): any;
144
+ }
116
145
  /**
117
146
  * AxCrew orchestrates a set of Ax agents that share state,
118
147
  * tools (functions), optional MCP servers, streaming, and a built-in metrics
@@ -160,6 +189,18 @@ declare class AxCrew {
160
189
  * @param {string} agentName - The name of the agent to add.
161
190
  */
162
191
  addAgent(agentName: string): Promise<void>;
192
+ /**
193
+ * Adds a lazy agent to the crew by name.
194
+ * The agent's tool schema is built immediately from the crew config,
195
+ * but the expensive initialization (MCP servers, AI client, etc.) is
196
+ * deferred until the Manager actually delegates to this agent.
197
+ *
198
+ * Use this for sub-agents that may not be needed on every request
199
+ * (e.g., agents with stdio MCP servers that spawn a process).
200
+ *
201
+ * @param {string} agentName - The name of the agent to add lazily.
202
+ */
203
+ addLazyAgent(agentName: string): void;
163
204
  /**
164
205
  * Sets up agents in the crew by name.
165
206
  * For an array of Agent names provided, it adds
@@ -221,5 +262,5 @@ declare class AxCrew {
221
262
  */
222
263
  resetCrewMetrics(): void;
223
264
  }
224
- export { AxCrew };
265
+ export { AxCrew, LazyStatefulAxAgent };
225
266
  export type { StatefulAxAgent };
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
- import { AxAgent, AxGen } from "@ax-llm/ax";
2
+ import { AxAgent, AxGen, AxSignature as AxSignatureClass } from "@ax-llm/ax";
3
3
  import { createState } from "../state/index.js";
4
4
  import { parseCrewConfig, parseAgentConfig } from "./agentConfig.js";
5
5
  import { MetricsRegistry } from "../metrics/index.js";
@@ -515,6 +515,81 @@ class StatefulAxAgent extends AxAgent {
515
515
  }
516
516
  }
517
517
  }
518
+ /**
519
+ * Lightweight proxy that stands in for a real agent in the crew's agent map.
520
+ * It exposes the same `getFunction()` interface (built from the crew config)
521
+ * but defers the expensive `createAgent()` call — and therefore MCP server
522
+ * startup — until the Manager actually delegates to it.
523
+ *
524
+ * Usage: `crew.addLazyAgent("CreateChart")` instead of `crew.addAgent("CreateChart")`
525
+ */
526
+ class LazyStatefulAxAgent {
527
+ realAgent = null;
528
+ crewRef; // AxCrew — forward-declared to avoid circular ref
529
+ agentName;
530
+ description;
531
+ signatureStr;
532
+ func;
533
+ _id = "lazy";
534
+ constructor(crewRef, agentName, crewConfig) {
535
+ this.crewRef = crewRef;
536
+ this.agentName = agentName;
537
+ const agentDef = parseCrewConfig(crewConfig).crew.find((a) => a.name === agentName);
538
+ if (!agentDef) {
539
+ throw new Error(`Agent "${agentName}" not found in crew config`);
540
+ }
541
+ this.description = agentDef.description;
542
+ this.signatureStr = agentDef.signature;
543
+ // Build the tool schema from the signature's input fields
544
+ const sig = new AxSignatureClass(this.signatureStr);
545
+ const parameters = sig.toInputJSONSchema();
546
+ this.func = {
547
+ name: agentName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase(),
548
+ description: this.description,
549
+ parameters,
550
+ func: async (args) => {
551
+ const agent = await this.resolve();
552
+ return agent.forward(args);
553
+ },
554
+ };
555
+ }
556
+ async resolve() {
557
+ if (!this.realAgent) {
558
+ const agent = await this.crewRef.createAgent(this.agentName);
559
+ agent.setId(this._id);
560
+ this.realAgent = agent;
561
+ }
562
+ return this.realAgent;
563
+ }
564
+ // AxAgentic interface
565
+ getFunction() {
566
+ return this.func;
567
+ }
568
+ getSignature() {
569
+ return new AxSignatureClass(this.signatureStr);
570
+ }
571
+ // AxProgrammable / AxTunable stubs
572
+ getId() { return this._id; }
573
+ setId(id) { this._id = id; }
574
+ getTraces() { return this.realAgent?.getTraces() ?? []; }
575
+ setDemos() { }
576
+ getUsage() { return this.realAgent?.getUsage() ?? []; }
577
+ resetUsage() { this.realAgent?.resetUsage(); }
578
+ // Forward / streaming — resolve on demand
579
+ async forward(...args) {
580
+ const agent = await this.resolve();
581
+ return agent.forward(...args);
582
+ }
583
+ streamingForward(...args) {
584
+ // Must be sync to match the interface, so we wrap in an async generator
585
+ const self = this;
586
+ async function* lazyStream() {
587
+ const agent = await self.resolve();
588
+ yield* agent.streamingForward(...args);
589
+ }
590
+ return lazyStream();
591
+ }
592
+ }
518
593
  /**
519
594
  * AxCrew orchestrates a set of Ax agents that share state,
520
595
  * tools (functions), optional MCP servers, streaming, and a built-in metrics
@@ -689,6 +764,25 @@ class AxCrew {
689
764
  throw new Error(`Failed to add agent ${agentName}: ${error instanceof Error ? error.message : String(error)}`);
690
765
  }
691
766
  }
767
+ /**
768
+ * Adds a lazy agent to the crew by name.
769
+ * The agent's tool schema is built immediately from the crew config,
770
+ * but the expensive initialization (MCP servers, AI client, etc.) is
771
+ * deferred until the Manager actually delegates to this agent.
772
+ *
773
+ * Use this for sub-agents that may not be needed on every request
774
+ * (e.g., agents with stdio MCP servers that spawn a process).
775
+ *
776
+ * @param {string} agentName - The name of the agent to add lazily.
777
+ */
778
+ addLazyAgent(agentName) {
779
+ if (!this.agents) {
780
+ this.agents = new Map();
781
+ }
782
+ if (!this.agents.has(agentName)) {
783
+ this.agents.set(agentName, new LazyStatefulAxAgent(this, agentName, this.crewConfig));
784
+ }
785
+ }
692
786
  /**
693
787
  * Sets up agents in the crew by name.
694
788
  * For an array of Agent names provided, it adds
@@ -917,4 +1011,4 @@ class AxCrew {
917
1011
  MetricsRegistry.reset({ crewId: this.crewId });
918
1012
  }
919
1013
  }
920
- export { AxCrew };
1014
+ export { AxCrew, LazyStatefulAxAgent };
package/dist/types.d.ts CHANGED
@@ -109,24 +109,29 @@ interface MCPStdioTransportConfig {
109
109
  command: string;
110
110
  args?: string[];
111
111
  env?: NodeJS.ProcessEnv;
112
+ tools?: string[];
112
113
  }
113
114
  /**
114
115
  * Config for an HTTP SSE MCP server.
115
116
  *
116
117
  * @property {string} sseUrl - The SSE URL for the MCP server.
118
+ * @property {string[]} tools - Optional allowlist of tool names to expose to the agent.
117
119
  */
118
120
  interface MCPHTTPSSETransportConfig {
119
121
  sseUrl: string;
122
+ tools?: string[];
120
123
  }
121
124
  /**
122
125
  * Config for a streamable HTTP MCP server.
123
126
  *
124
127
  * @property {string} mcpEndpoint - The HTTP endpoint URL for the MCP server.
125
128
  * @property {AxMCPStreamableHTTPTransportOptions} options - Optional transport options.
129
+ * @property {string[]} tools - Optional allowlist of tool names to expose to the agent.
126
130
  */
127
131
  interface MCPStreamableHTTPTransportConfig {
128
132
  mcpEndpoint: string;
129
133
  options?: AxMCPStreamableHTTPTransportOptions;
134
+ tools?: string[];
130
135
  }
131
136
  /**
132
137
  * Config for an MCP server.
@@ -0,0 +1,76 @@
1
+ import 'dotenv/config';
2
+ import { AxCrew } from '../dist/index.js';
3
+ import type { AxCrewConfig } from '../dist/types.js';
4
+
5
+ const config: AxCrewConfig = {
6
+ crew: [
7
+ {
8
+ name: 'WorkflowAgent',
9
+ description: `You answer questions by querying databases and running calculations.
10
+
11
+ IMPORTANT — follow this strategy for database queries:
12
+ 1. FIRST call list_workflows to check if a reusable workflow already exists for this type of question.
13
+ 2. If a matching workflow exists, call execute_workflow with its name. Done!
14
+ 3. If NO matching workflow exists:
15
+ a. Call get_js_runtime_api to learn the available gj.tools.* functions
16
+ b. Call list_tables and describe_table to understand the schema
17
+ c. Author a JavaScript workflow that uses gj.tools.* to query the database and compute results server-side
18
+ d. Call save_workflow to persist it (use descriptive snake_case name and description for future discovery)
19
+ e. Call execute_workflow to run it
20
+ 4. Synthesize the answer from the workflow results.
21
+
22
+ This approach runs ALL database queries server-side in a single call, avoiding multiple round-trips.
23
+ The saved workflow will be reusable by future queries asking similar questions.`,
24
+ signature:
25
+ 'background:string "background information and context", question:string "the user question to answer" -> answer:string "the answer with explanation", references:json "list of references, each with fileName and url"',
26
+ provider: 'anthropic',
27
+ providerKeyName: 'ANTHROPIC_API_KEY',
28
+ ai: {
29
+ model: 'claude-haiku-4-5',
30
+ temperature: 0,
31
+ },
32
+ options: {
33
+ debug: true,
34
+ },
35
+ mcpServers: {
36
+ graphjin: {
37
+ mcpEndpoint: 'http://localhost:8080/api/v1/mcp',
38
+ tools: [
39
+ 'list_workflows', 'save_workflow', 'execute_workflow',
40
+ 'get_js_runtime_api', 'list_tables', 'describe_table',
41
+ 'get_query_syntax', 'execute_graphql',
42
+ ],
43
+ },
44
+ },
45
+ },
46
+ ],
47
+ };
48
+
49
+ async function main() {
50
+ const crew = new AxCrew(config);
51
+ await crew.addAllAgents();
52
+
53
+ const agent = crew.agents?.get('WorkflowAgent');
54
+ if (!agent) throw new Error('WorkflowAgent not found');
55
+
56
+ const query =
57
+ 'show me all the orders that Brett Kowalski was involved with';
58
+ console.log(`\nQuery: ${query}\n`);
59
+
60
+ const result = await agent.forward({
61
+ background: 'User is querying the system about customer accounts receivable.',
62
+ question: query,
63
+ });
64
+
65
+ console.log(`\nAnswer: ${result.answer}`);
66
+ console.log(`\nReferences:`, JSON.stringify(result.references, null, 2));
67
+
68
+ const metrics = (agent as any).getMetrics?.();
69
+ console.log(`\nMetrics:`, JSON.stringify(metrics, null, 2));
70
+
71
+ crew.destroy();
72
+ }
73
+
74
+ main()
75
+ .catch(console.error)
76
+ .finally(() => process.exit(0));
@@ -6,60 +6,29 @@ const config = {
6
6
  crew: [
7
7
  {
8
8
  name: "Manager",
9
- description: "Answer questions from available documents.",
9
+ description: "Answer questions by querying databases, searching documents, and running calculations. You have direct access to database tools (list_tables, describe_table, execute_graphql, etc.), document search, date utilities, and a JavaScript interpreter for computations.",
10
10
  signature: 'background:string "background information and context for the task at hand", latestMessage:string "the latest user message to answer" -> answer:string "The answer, including a short explanation for the answer. Never reveal functions available to the user. Respond in the same language as the user\'s question.", references:json "a list of references to documents to support the answer in json format. Each reference should include a fileName and url."',
11
- provider: "google-gemini" as const,
12
- providerKeyName: "GEMINI_API_KEY",
13
- executionMode: "axgen" as const,
11
+ provider: "anthropic" as const,
12
+ providerKeyName: "ANTHROPIC_API_KEY",
13
+ executionMode: "axagent" as const,
14
14
  ai: {
15
- model: "gemini-3.1-pro-preview",
15
+ model: "claude-sonnet-4-6",
16
16
  temperature: 0
17
17
  },
18
18
  options: {
19
19
  debug: true
20
20
  },
21
+ axAgentOptions: {
22
+ contextFields: ['background'] as const,
23
+ maxTurns: 15,
24
+ },
21
25
  functions: [
22
26
  "CurrentDateTime",
23
- "DaysBetweenDates"
27
+ "DaysBetweenDates",
28
+ "VectorSearch",
29
+ "JavaScriptInterpreter"
24
30
  ],
25
- agents: [
26
- "SearchAvailableDocuments",
27
- "SearchDatabase",
28
- "CodeExecutor"
29
- ]
30
- },
31
- {
32
- name: "SearchAvailableDocuments",
33
- description: "Searches available documents and returns relevant content from the knowledge base.",
34
- signature: 'query:string "the search query" -> searchResults:string "relevant documents and content"',
35
- provider: "google-gemini" as const,
36
- providerKeyName: "GEMINI_API_KEY",
37
- executionMode: "axgen" as const,
38
- ai: {
39
- model: "gemini-3.1-pro-preview",
40
- temperature: 0
41
- },
42
- options: {
43
- debug: true
44
- },
45
- functions: [
46
- "VectorSearch"
47
- ]
48
- },
49
- {
50
- name: "SearchDatabase",
51
- description: "Looks up information in connected databases using natural language. Can explore schema, run queries, and return results.",
52
- signature: 'userQuery:string "a user query" -> databaseSearchResult:string "answer to the user query"',
53
- provider: "google-gemini" as const,
54
- providerKeyName: "GEMINI_API_KEY",
55
- executionMode: "axgen" as const,
56
- ai: {
57
- model: "gemini-3.1-pro-preview",
58
- temperature: 0
59
- },
60
- options: {
61
- debug: true
62
- },
31
+ // Direct MCP access — no sub-agent double-hop
63
32
  mcpServers: {
64
33
  graphjin: {
65
34
  command: "/opt/homebrew/bin/graphjin",
@@ -70,24 +39,6 @@ const config = {
70
39
  ]
71
40
  }
72
41
  }
73
- },
74
- {
75
- name: "CodeExecutor",
76
- description: "Executes JavaScript code in a sandboxed environment. Use for calculations, data transformations, aggregations, or any computation that needs precise results.",
77
- signature: 'task:string "description of what to compute or calculate" -> executionResult:string "the result of the code execution"',
78
- provider: "google-gemini" as const,
79
- providerKeyName: "GEMINI_API_KEY",
80
- executionMode: "axgen" as const,
81
- ai: {
82
- model: "gemini-3.1-pro-preview",
83
- temperature: 0
84
- },
85
- options: {
86
- debug: true
87
- },
88
- functions: [
89
- "JavaScriptInterpreter"
90
- ]
91
42
  }
92
43
  ]
93
44
  };
@@ -99,8 +50,6 @@ const jsRuntime = new AxJSRuntime({
99
50
  });
100
51
  const JavaScriptInterpreter = jsRuntime.toFunction();
101
52
 
102
- // VectorSearch is referenced in config but not in AxCrewFunctions.
103
- // Provide a stub so the agent can be created. Replace with your real implementation.
104
53
  const VectorSearch = {
105
54
  name: 'VectorSearch',
106
55
  description: 'Search the vector knowledge base for relevant documents',
@@ -118,12 +67,8 @@ const VectorSearch = {
118
67
  };
119
68
 
120
69
  async function main() {
121
- // Merge built-in functions with custom ones
122
70
  const crew = new AxCrew(config, { ...AxCrewFunctions, VectorSearch, JavaScriptInterpreter });
123
71
 
124
- // Use addAllAgents() to automatically resolve dependency order.
125
- // This ensures sub-agents (SearchAvailableDocuments, SearchDatabase)
126
- // are initialized BEFORE Manager, which depends on them.
127
72
  await crew.addAllAgents();
128
73
 
129
74
  const manager = crew.agents?.get('Manager');
@@ -142,7 +87,6 @@ async function main() {
142
87
  console.log(`\nAnswer: ${result.answer}`);
143
88
  console.log(`\nReferences:`, JSON.stringify(result.references, null, 2));
144
89
 
145
- // Print metrics
146
90
  const crewMetrics = crew.getCrewMetrics();
147
91
  console.log(`\nCrew Metrics:`, JSON.stringify(crewMetrics, null, 2));
148
92
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@amitdeshmukh/ax-crew",
4
- "version": "8.3.0",
4
+ "version": "8.5.0",
5
5
  "description": "Build and launch a crew of AI agents with shared state. Built with axllm.dev",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -77,10 +77,17 @@ const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFun
77
77
  initializedClients.push(mcpClient);
78
78
  // Normalize MCP tool schemas: some MCP servers omit `parameters` for
79
79
  // zero-arg tools, but providers like Gemini require a valid schema.
80
- const mcpFns = mcpClient.toFunction().map(fn => ({
80
+ let mcpFns = mcpClient.toFunction().map(fn => ({
81
81
  ...fn,
82
82
  parameters: fn.parameters ?? { type: 'object' as const, properties: {} },
83
83
  }));
84
+
85
+ // Filter to allowlisted tools if specified
86
+ if (mcpServerConfig.tools && mcpServerConfig.tools.length > 0) {
87
+ const allowed = new Set(mcpServerConfig.tools);
88
+ mcpFns = mcpFns.filter(fn => allowed.has(fn.name));
89
+ }
90
+
84
91
  functions.push(...mcpFns);
85
92
  }
86
93
 
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
- import { AxAgent, AxAI, AxGen } from "@ax-llm/ax";
2
+ import { AxAgent, AxAI, AxGen, AxSignature as AxSignatureClass } from "@ax-llm/ax";
3
3
 
4
4
  import type {
5
5
  AxSignature,
@@ -697,6 +697,94 @@ class StatefulAxAgent extends AxAgent<any, any> {
697
697
  }
698
698
  }
699
699
 
700
+ /**
701
+ * Lightweight proxy that stands in for a real agent in the crew's agent map.
702
+ * It exposes the same `getFunction()` interface (built from the crew config)
703
+ * but defers the expensive `createAgent()` call — and therefore MCP server
704
+ * startup — until the Manager actually delegates to it.
705
+ *
706
+ * Usage: `crew.addLazyAgent("CreateChart")` instead of `crew.addAgent("CreateChart")`
707
+ */
708
+ class LazyStatefulAxAgent {
709
+ private realAgent: StatefulAxAgent | null = null;
710
+ private crewRef: any; // AxCrew — forward-declared to avoid circular ref
711
+ private agentName: string;
712
+ private description: string;
713
+ private signatureStr: string;
714
+ private func: AxFunction;
715
+ private _id: string = "lazy";
716
+
717
+ constructor(crewRef: any, agentName: string, crewConfig: AxCrewConfig) {
718
+ this.crewRef = crewRef;
719
+ this.agentName = agentName;
720
+
721
+ const agentDef = parseCrewConfig(crewConfig).crew.find(
722
+ (a) => a.name === agentName
723
+ );
724
+ if (!agentDef) {
725
+ throw new Error(`Agent "${agentName}" not found in crew config`);
726
+ }
727
+
728
+ this.description = agentDef.description;
729
+ this.signatureStr = agentDef.signature as string;
730
+
731
+ // Build the tool schema from the signature's input fields
732
+ const sig = new AxSignatureClass(this.signatureStr);
733
+ const parameters = sig.toInputJSONSchema();
734
+
735
+ this.func = {
736
+ name: agentName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase(),
737
+ description: this.description,
738
+ parameters,
739
+ func: async (args?: any) => {
740
+ const agent = await this.resolve();
741
+ return agent.forward(args);
742
+ },
743
+ };
744
+ }
745
+
746
+ private async resolve(): Promise<StatefulAxAgent> {
747
+ if (!this.realAgent) {
748
+ const agent = await this.crewRef.createAgent(this.agentName) as StatefulAxAgent;
749
+ agent.setId(this._id);
750
+ this.realAgent = agent;
751
+ }
752
+ return this.realAgent!;
753
+ }
754
+
755
+ // AxAgentic interface
756
+ getFunction(): AxFunction {
757
+ return this.func;
758
+ }
759
+
760
+ getSignature() {
761
+ return new AxSignatureClass(this.signatureStr);
762
+ }
763
+
764
+ // AxProgrammable / AxTunable stubs
765
+ getId(): string { return this._id; }
766
+ setId(id: string): void { this._id = id; }
767
+ getTraces(): any[] { return this.realAgent?.getTraces() ?? []; }
768
+ setDemos(): void { /* no-op until resolved */ }
769
+ getUsage(): any[] { return this.realAgent?.getUsage() ?? []; }
770
+ resetUsage(): void { this.realAgent?.resetUsage(); }
771
+
772
+ // Forward / streaming — resolve on demand
773
+ async forward(...args: any[]): Promise<any> {
774
+ const agent = await this.resolve();
775
+ return (agent as any).forward(...args);
776
+ }
777
+ streamingForward(...args: any[]): any {
778
+ // Must be sync to match the interface, so we wrap in an async generator
779
+ const self = this;
780
+ async function* lazyStream() {
781
+ const agent = await self.resolve();
782
+ yield* (agent as any).streamingForward(...args);
783
+ }
784
+ return lazyStream();
785
+ }
786
+ }
787
+
700
788
  /**
701
789
  * AxCrew orchestrates a set of Ax agents that share state,
702
790
  * tools (functions), optional MCP servers, streaming, and a built-in metrics
@@ -912,6 +1000,29 @@ class AxCrew {
912
1000
  }
913
1001
  }
914
1002
 
1003
+ /**
1004
+ * Adds a lazy agent to the crew by name.
1005
+ * The agent's tool schema is built immediately from the crew config,
1006
+ * but the expensive initialization (MCP servers, AI client, etc.) is
1007
+ * deferred until the Manager actually delegates to this agent.
1008
+ *
1009
+ * Use this for sub-agents that may not be needed on every request
1010
+ * (e.g., agents with stdio MCP servers that spawn a process).
1011
+ *
1012
+ * @param {string} agentName - The name of the agent to add lazily.
1013
+ */
1014
+ addLazyAgent(agentName: string): void {
1015
+ if (!this.agents) {
1016
+ this.agents = new Map<string, StatefulAxAgent>();
1017
+ }
1018
+ if (!this.agents.has(agentName)) {
1019
+ this.agents.set(
1020
+ agentName,
1021
+ new LazyStatefulAxAgent(this, agentName, this.crewConfig) as any
1022
+ );
1023
+ }
1024
+ }
1025
+
915
1026
  /**
916
1027
  * Sets up agents in the crew by name.
917
1028
  * For an array of Agent names provided, it adds
@@ -1166,5 +1277,5 @@ class AxCrew {
1166
1277
  }
1167
1278
  }
1168
1279
 
1169
- export { AxCrew };
1280
+ export { AxCrew, LazyStatefulAxAgent };
1170
1281
  export type { StatefulAxAgent };
package/src/types.ts CHANGED
@@ -127,26 +127,31 @@ interface MCPStdioTransportConfig {
127
127
  command: string
128
128
  args?: string[]
129
129
  env?: NodeJS.ProcessEnv
130
+ tools?: string[]
130
131
  }
131
132
 
132
133
  /**
133
134
  * Config for an HTTP SSE MCP server.
134
- *
135
+ *
135
136
  * @property {string} sseUrl - The SSE URL for the MCP server.
137
+ * @property {string[]} tools - Optional allowlist of tool names to expose to the agent.
136
138
  */
137
139
  interface MCPHTTPSSETransportConfig {
138
140
  sseUrl: string
141
+ tools?: string[]
139
142
  }
140
143
 
141
144
  /**
142
145
  * Config for a streamable HTTP MCP server.
143
- *
146
+ *
144
147
  * @property {string} mcpEndpoint - The HTTP endpoint URL for the MCP server.
145
148
  * @property {AxMCPStreamableHTTPTransportOptions} options - Optional transport options.
149
+ * @property {string[]} tools - Optional allowlist of tool names to expose to the agent.
146
150
  */
147
151
  interface MCPStreamableHTTPTransportConfig {
148
152
  mcpEndpoint: string
149
153
  options?: AxMCPStreamableHTTPTransportOptions
154
+ tools?: string[]
150
155
  }
151
156
 
152
157
  /**