Skip to content

Commit c541ee2

Browse files
committed
Refactor: MCP Client Lifecycle and CLI Runner Renaming #7945
1 parent 5b4e52b commit c541ee2

File tree

10 files changed

+254
-67
lines changed

10 files changed

+254
-67
lines changed

.github/.sync-metadata.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"lastSync": "2025-11-29T23:57:42.777Z",
3-
"releasesLastFetched": "2025-11-29T23:57:42.783Z",
2+
"lastSync": "2025-11-30T00:50:55.968Z",
3+
"releasesLastFetched": "2025-11-30T00:50:55.973Z",
44
"pushFailures": [],
55
"issues": {
66
"3789": {
@@ -11757,11 +11757,25 @@
1175711757
"contentHash": "713ad618fcce33b746af5715869833031ca1cc51d2cd8dddc4ec0540c9d97858"
1175811758
},
1175911759
"7942": {
11760-
"state": "OPEN",
11760+
"state": "CLOSED",
1176111761
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-7942.md",
11762+
"closedAt": "2025-11-30T00:18:18Z",
11763+
"updatedAt": "2025-11-30T00:18:18Z",
11764+
"contentHash": "6925404d3d129e3a6ae140f0a5a68462721210b8bf04afcbed1e2a8289300759"
11765+
},
11766+
"7944": {
11767+
"state": "OPEN",
11768+
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-7944.md",
11769+
"closedAt": null,
11770+
"updatedAt": "2025-11-30T00:46:37Z",
11771+
"contentHash": "ea4ded9ea1cde5b4222b3ae65a1fda7cabb8790b08e80bb3a6111ad860dd3f95"
11772+
},
11773+
"7945": {
11774+
"state": "OPEN",
11775+
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-7945.md",
1176211776
"closedAt": null,
11763-
"updatedAt": "2025-11-29T23:56:44Z",
11764-
"contentHash": "fa0f000d9240ccea4a6571bcbea260f6a7304c99e762d98cb17f075acde4740a"
11777+
"updatedAt": "2025-11-30T00:50:25Z",
11778+
"contentHash": "7f03331f353e0a15bd39a1b628f99b007791535a03c7c056e5e360ab91cb5ccc"
1176511779
}
1176611780
},
1176711781
"releases": {

.github/ISSUE/issue-7942.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
---
22
id: 7942
33
title: 'Feat: Enable External Configuration and Generic Runner for MCP Client CLI'
4-
state: OPEN
4+
state: CLOSED
55
labels:
66
- enhancement
77
- ai
88
- refactoring
99
assignees:
1010
- tobiu
1111
createdAt: '2025-11-29T23:56:44Z'
12-
updatedAt: '2025-11-29T23:56:44Z'
12+
updatedAt: '2025-11-30T00:18:18Z'
1313
githubUrl: 'https://github.com/neomjs/neo/issues/7942'
1414
author: tobiu
15-
commentsCount: 0
15+
commentsCount: 1
1616
parentIssue: 7931
1717
subIssues: []
1818
subIssuesCompleted: 0
1919
subIssuesTotal: 0
2020
blockedBy: []
2121
blocking: []
22+
closedAt: '2025-11-30T00:18:18Z'
2223
---
2324
# Feat: Enable External Configuration and Generic Runner for MCP Client CLI
2425

@@ -33,10 +34,33 @@ To align the MCP Client CLI with the server-side runner architecture, this task
3334
* Make `--server <name>` option optional (default action TBD, perhaps list available servers or require it for specific commands).
3435
2. **Update Documentation:** Ensure JSDoc and comments reflect the new usage.
3536

37+
## Comments
38+
39+
### @tobiu - 2025-11-30 00:17
40+
41+
**Input from Gemini 2.5:**
42+
43+
> ✦ I have enabled external configuration and refactored the MCP Client CLI.
44+
>
45+
> ### Deliverables Completed
46+
> 1. **Refactor `ai/mcp/client/mcp-stdio.mjs`:**
47+
> * Removed `#!/usr/bin/env node` and `dotenv` usage.
48+
> * Added `-c, --config` option for external configuration files.
49+
> * Updated initialization logic to pass the config file path to the `Client` instance.
50+
> * Imported `ClientConfig` (as `aiConfig`) to handle debug flag consistently.
51+
> 2. **Refactor `ai/mcp/client/Client.mjs`:**
52+
> * Added `configFile` config option.
53+
> * Updated `initAsync` to load the external configuration if provided, using `ClientConfig.load()` and handling errors via `console.error`.
54+
> * Removed complex path resolution logic.
55+
>
56+
> The CLI now matches the server-side runner pattern, offering better DX and consistency.
57+
3658
## Activity Log
3759

3860
- 2025-11-29 @tobiu assigned to @tobiu
3961
- 2025-11-29 @tobiu added the `enhancement` label
4062
- 2025-11-29 @tobiu added the `ai` label
4163
- 2025-11-29 @tobiu added the `refactoring` label
64+
- 2025-11-30 @tobiu referenced in commit `5b4e52b` - "Feat: Enable External Configuration and Generic Runner for MCP Client CLI #7942"
65+
- 2025-11-30 @tobiu closed this issue
4266

.github/ISSUE/issue-7943.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
id: 7943
3+
title: 'Refactor: Extract MCP Client CLI Logic into Dedicated Controller Class'
4+
state: OPEN
5+
labels:
6+
- ai
7+
- refactoring
8+
assignees:
9+
- tobiu
10+
createdAt: '2025-11-30T00:20:49Z'
11+
updatedAt: '2025-11-30T00:20:49Z'
12+
githubUrl: 'https://github.com/neomjs/neo/issues/7943'
13+
author: tobiu
14+
commentsCount: 0
15+
parentIssue: 7931
16+
subIssues: []
17+
subIssuesCompleted: 0
18+
subIssuesTotal: 0
19+
blockedBy: []
20+
blocking: []
21+
---
22+
# Refactor: Extract MCP Client CLI Logic into Dedicated Controller Class
23+
24+
The current `ai/mcp/client/mcp-stdio.mjs` script is overloaded with logic (CLI parsing, tool execution, output formatting) that violates the "thin runner" pattern established by the server implementation.
25+
26+
To fix this and maintain architectural consistency:
27+
1. **Create `Neo.ai.mcp.client.Cli`:** A new class in `ai/mcp/client/Cli.mjs` responsible for handling the CLI interaction flow. It will:
28+
* Accept parsed options (server, listTools, callTool, args).
29+
* Instantiate and manage the `Neo.ai.mcp.client.Client`.
30+
* Execute the requested operation.
31+
* Handle output formatting and logging.
32+
2. **Refactor `ai/mcp/client/mcp-stdio.mjs`:** Reduce this file to a minimal runner that simply parses command-line arguments using `commander` and instantiates `Neo.ai.mcp.client.Cli` to do the work.
33+
34+
This separation ensures `Client.mjs` remains a clean transport wrapper for use by Agents, while the CLI logic is encapsulated in its own class, and the runner remains purely a bootstrapper.
35+
36+
### Deliverables
37+
* `ai/mcp/client/Cli.mjs`
38+
* Refactored `ai/mcp/client/mcp-stdio.mjs`
39+
40+
## Activity Log
41+
42+
- 2025-11-30 @tobiu assigned to @tobiu
43+
- 2025-11-30 @tobiu added the `ai` label
44+
- 2025-11-30 @tobiu added the `refactoring` label
45+

.github/ISSUE/issue-7944.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
id: 7944
3+
title: 'Refactor: MCP Client Lifecycle and CLI Runner Renaming'
4+
state: OPEN
5+
labels:
6+
- ai
7+
- refactoring
8+
assignees:
9+
- tobiu
10+
createdAt: '2025-11-30T00:46:37Z'
11+
updatedAt: '2025-11-30T00:46:37Z'
12+
githubUrl: 'https://github.com/neomjs/neo/issues/7944'
13+
author: tobiu
14+
commentsCount: 0
15+
parentIssue: 7931
16+
subIssues: []
17+
subIssuesCompleted: 0
18+
subIssuesTotal: 0
19+
blockedBy: []
20+
blocking: []
21+
---
22+
# Refactor: MCP Client Lifecycle and CLI Runner Renaming
23+
24+
Refactor the MCP Client architecture to ensure clear separation of concerns, consistent lifecycle management, and appropriate naming.
25+
26+
### Deliverables
27+
28+
1. **Refactor `Neo.ai.mcp.client.Client` (`ai/mcp/client/Client.mjs`):**
29+
* **Lifecycle:** Move connection logic from `connect()` to `initAsync()`. The client should self-connect during initialization. `connect()` should be removed or made private.
30+
* **Configuration:** Add `configFile` config. Inside `initAsync`, call `ClientConfig.load(this.configFile)` if provided, before resolving server details.
31+
* **Proxies:** Ensure dynamic tool proxies (`this.tools`) are created during `initAsync`.
32+
33+
2. **Rename and Refactor CLI Runner:**
34+
* **Rename:** Move `ai/mcp/client/mcp-stdio.mjs` to `ai/mcp/client/mcp-cli.mjs`.
35+
* **Logic:** Update the script to use the new Client lifecycle (`await Neo.create(Client, ...).ready()`).
36+
* **Implementation:** Keep the CLI logic (argument parsing, executing `client.listTools()` or `client.tools.x()`, logging results) inside this runner script.
37+
* **Cleanup:** Ensure `client.close()` is called before exit.
38+
39+
3. **Update `package.json`:**
40+
* Update `ai:mcp-client` script to point to `ai/mcp/client/mcp-cli.mjs`.
41+
42+
4. **Update `Neo.ai.Agent` (`ai/Agent.mjs`):**
43+
* Update `connect()` to use `await Neo.create(Client).ready()` instead of `client.connect()`.
44+
45+
5. **Update Demo Agent (`ai/agents/mcp-demo-agent.mjs`):**
46+
* Verify compatibility with the new `Agent` and `Client` structure (likely no changes needed if `Agent` abstraction holds, but good to check).
47+
48+
## Activity Log
49+
50+
- 2025-11-30 @tobiu assigned to @tobiu
51+
- 2025-11-30 @tobiu added the `ai` label
52+
- 2025-11-30 @tobiu added the `refactoring` label
53+

.github/ISSUE/issue-7945.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
id: 7945
3+
title: 'Refactor: MCP Client Lifecycle and CLI Runner Renaming'
4+
state: OPEN
5+
labels:
6+
- ai
7+
- refactoring
8+
assignees:
9+
- tobiu
10+
createdAt: '2025-11-30T00:50:25Z'
11+
updatedAt: '2025-11-30T00:50:25Z'
12+
githubUrl: 'https://github.com/neomjs/neo/issues/7945'
13+
author: tobiu
14+
commentsCount: 0
15+
parentIssue: 7931
16+
subIssues: []
17+
subIssuesCompleted: 0
18+
subIssuesTotal: 0
19+
blockedBy: []
20+
blocking: []
21+
---
22+
# Refactor: MCP Client Lifecycle and CLI Runner Renaming
23+
24+
Refactor the MCP Client architecture to ensure clear separation of concerns, consistent lifecycle management, and appropriate naming.
25+
26+
### Deliverables
27+
28+
1. **Refactor `Neo.ai.mcp.client.Client` (`ai/mcp/client/Client.mjs`):**
29+
* **Lifecycle:** Move connection logic from `connect()` to `initAsync()`. The client should self-connect during initialization. `connect()` should be removed or made private.
30+
* **Configuration:** Add `configFile` config. Inside `initAsync`, call `ClientConfig.load(this.configFile)` if provided, before resolving server details.
31+
* **Proxies:** Ensure dynamic tool proxies (`this.tools`) are created during `initAsync`.
32+
33+
2. **Rename and Refactor CLI Runner:**
34+
* **Rename:** Move `ai/mcp/client/mcp-stdio.mjs` to `ai/mcp/client/mcp-cli.mjs`.
35+
* **Logic:** Update the script to use the new Client lifecycle (`await Neo.create(Client, ...).ready()`).
36+
* **Implementation:** Keep the CLI logic (argument parsing, executing `client.listTools()` or `client.tools.x()`, logging results) inside this runner script.
37+
* **Cleanup:** Ensure `client.close()` is called before exit.
38+
39+
3. **Update `package.json`:**
40+
* Update `ai:mcp-client` script to point to `ai/mcp/client/mcp-cli.mjs`.
41+
42+
4. **Refactor `Neo.ai.Agent` (`ai/Agent.mjs`):**
43+
* **Remove the `connect()` method.**
44+
* **Move client creation, `client.ready()` calls, and tool aggregation logic into `Agent.initAsync()`**.
45+
* The `Agent` itself will then be ready when its `initAsync` completes.
46+
47+
5. **Update `ai/agents/mcp-demo-agent.mjs`:**
48+
* Refactor to use `await agent.ready()` instead of `await agent.connect()`.
49+
* Verify compatibility.
50+
51+
## Activity Log
52+
53+
- 2025-11-30 @tobiu assigned to @tobiu
54+
- 2025-11-30 @tobiu added the `ai` label
55+
- 2025-11-30 @tobiu added the `refactoring` label
56+

ai/Agent.mjs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,37 @@ class Agent extends Base {
1919
* A list of server names (keys in ClientConfig) to connect to.
2020
* @member {String[]} servers=[]
2121
*/
22-
servers: []
22+
servers: [],
23+
/**
24+
* Map of connected Client instances, keyed by server name.
25+
* @member {Object} clients={}
26+
*/
27+
clients: {}
2328
}
2429

2530
/**
26-
* Map of connected Client instances, keyed by server name.
27-
* @member {Object} clients={}
31+
* Async initialization sequence.
32+
* Creates and connects all configured clients.
33+
* @returns {Promise<void>}
2834
*/
29-
clients = {}
35+
async initAsync() {
36+
await super.initAsync();
3037

31-
/**
32-
* Aggregated namespace of tools.
33-
* Structure: `this.tools.<serverName>.<toolName>()`
34-
* @member {Object} tools={}
35-
*/
36-
tools = {}
38+
// Ensure clients object is ready
39+
if (!this.clients) this.clients = {};
3740

38-
/**
39-
* Connects to all configured servers and aggregates their tools.
40-
*/
41-
async connect() {
4241
for (const serverName of this.servers) {
4342
const client = Neo.create(Client, {
4443
serverName,
4544
env: process.env // Pass generic env for now
4645
});
46+
await client.ready();
4747

48-
await client.connect();
49-
this.clients[serverName] = client;
50-
51-
// Namespace tools
52-
this.tools[this.snakeToCamel(serverName)] = client.tools;
48+
const key = this.snakeToCamel(serverName);
49+
console.log(`[Agent] Registering client '${serverName}' as '${key}'`);
50+
this.clients[key] = client;
5351
}
52+
console.log('[Agent] Final clients map:', Object.keys(this.clients));
5453
}
5554

5655
/**
@@ -61,7 +60,6 @@ class Agent extends Base {
6160
await client.close();
6261
}
6362
this.clients = {};
64-
this.tools = {};
6563
}
6664

6765
/**

ai/agents/mcp-demo-agent.mjs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,18 @@ async function run() {
3131
});
3232

3333
try {
34-
// Connect the agent, which in turn connects all its clients
35-
console.log('🔌 Agent connecting to servers...');
36-
await agent.connect();
37-
console.log('✅ Agent connected to servers.');
34+
// Ensure the agent is ready (connected to all its clients)
35+
console.log('🔌 Agent preparing (connecting to servers)...');
36+
await agent.ready();
37+
console.log('✅ Agent ready (connected to servers).');
3838

3939
// Example: List tools from GitHub Workflow Server
40-
console.log('\n📋 Fetching recent issues via agent.tools.githubWorkflow.listIssues()...');
41-
const ghResult = await agent.tools.githubWorkflow.listIssues({
40+
console.log('Agent Instance:', agent); // Log entire agent
41+
console.log('Agent Clients Direct Access:', agent.clients); // Log property access
42+
console.log('GitHub Client Exists:', !!agent.clients?.githubWorkflow); // Safe check
43+
44+
console.log('\n📋 Fetching recent issues via agent.clients.githubWorkflow.tools.listIssues()...');
45+
const ghResult = await agent.clients.githubWorkflow.tools.listIssues({
4246
limit: 5,
4347
state: 'open'
4448
});
@@ -54,8 +58,8 @@ async function run() {
5458
}
5559

5660
// Example: Perform a healthcheck on Knowledge Base
57-
console.log('\n🩺 Checking Knowledge Base health via agent.tools.knowledgeBase.healthcheck()...');
58-
const kbHealthResult = await agent.tools.knowledgeBase.healthcheck({});
61+
console.log('\n🩺 Checking Knowledge Base health via agent.clients.knowledgeBase.tools.healthcheck()...');
62+
const kbHealthResult = await agent.clients.knowledgeBase.tools.healthcheck({});
5963
if (kbHealthResult.isError) {
6064
console.error('❌ Knowledge Base Healthcheck Failed:', kbHealthResult.content[0].text);
6165
} else {
@@ -64,8 +68,8 @@ async function run() {
6468
}
6569

6670
// Example: Get all session summaries from Memory Core
67-
console.log('\n🧠 Getting Memory Core session summaries via agent.tools.memoryCore.getAllSummaries()...');
68-
const memSummariesResult = await agent.tools.memoryCore.getAllSummaries({ limit: 2 });
71+
console.log('\n🧠 Getting Memory Core session summaries via agent.clients.memoryCore.tools.getAllSummaries()...');
72+
const memSummariesResult = await agent.clients.memoryCore.tools.getAllSummaries({ limit: 2 });
6973
if (memSummariesResult.isError) {
7074
console.error('❌ Memory Core Summaries Failed:', memSummariesResult.content[0].text);
7175
} else {

0 commit comments

Comments
 (0)