Skip to content

Commit 5b4e52b

Browse files
committed
Feat: Enable External Configuration and Generic Runner for MCP Client CLI #7942
1 parent 82d9904 commit 5b4e52b

File tree

5 files changed

+201
-35
lines changed

5 files changed

+201
-35
lines changed

.github/.sync-metadata.json

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"lastSync": "2025-11-29T23:30:07.482Z",
3-
"releasesLastFetched": "2025-11-29T23:30:07.487Z",
2+
"lastSync": "2025-11-29T23:57:42.777Z",
3+
"releasesLastFetched": "2025-11-29T23:57:42.783Z",
44
"pushFailures": [],
55
"issues": {
66
"3789": {
@@ -11750,11 +11750,18 @@
1175011750
"contentHash": "3f286abb15cd6cd8bc99275b790e837feb0bca6cb198bf066a15bd55e8cb9bc3"
1175111751
},
1175211752
"7941": {
11753-
"state": "OPEN",
11753+
"state": "CLOSED",
1175411754
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-7941.md",
11755+
"closedAt": "2025-11-29T23:45:11Z",
11756+
"updatedAt": "2025-11-29T23:45:11Z",
11757+
"contentHash": "713ad618fcce33b746af5715869833031ca1cc51d2cd8dddc4ec0540c9d97858"
11758+
},
11759+
"7942": {
11760+
"state": "OPEN",
11761+
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-7942.md",
1175511762
"closedAt": null,
11756-
"updatedAt": "2025-11-29T23:29:54Z",
11757-
"contentHash": "33317b08d72584bc98fc7217afc2dc4258fec5ea359421ac53495090e38be51c"
11763+
"updatedAt": "2025-11-29T23:56:44Z",
11764+
"contentHash": "fa0f000d9240ccea4a6571bcbea260f6a7304c99e762d98cb17f075acde4740a"
1175811765
}
1175911766
},
1176011767
"releases": {

.github/ISSUE/issue-7941.md

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
---
22
id: 7941
33
title: Investigate Multi-Server Tool Namespacing for MCP Client
4-
state: OPEN
4+
state: CLOSED
55
labels:
66
- enhancement
77
- ai
88
- refactoring
99
assignees:
1010
- tobiu
1111
createdAt: '2025-11-29T23:29:54Z'
12-
updatedAt: '2025-11-29T23:29:54Z'
12+
updatedAt: '2025-11-29T23:45:11Z'
1313
githubUrl: 'https://github.com/neomjs/neo/issues/7941'
1414
author: tobiu
15-
commentsCount: 0
15+
commentsCount: 2
1616
parentIssue: 7931
1717
subIssues: []
1818
subIssuesCompleted: 0
1919
subIssuesTotal: 0
2020
blockedBy: []
2121
blocking: []
22+
closedAt: '2025-11-29T23:45:11Z'
2223
---
2324
# Investigate Multi-Server Tool Namespacing for MCP Client
2425

@@ -32,10 +33,96 @@ The goal is to determine how the `Neo.ai.mcp.client.Client` should expose tools
3233
3. **Implement Namespacing (Proposal):** Based on findings, refactor `Neo.ai.mcp.client.Client` or create a higher-level `Neo.ai.Agent` class that aggregates tools into `this.tools.<serverName>.<toolName>`.
3334
4. **Update Client Config:** Ensure `ai/mcp/client/config.mjs` supports the multi-server setup.
3435

36+
## Comments
37+
38+
### @tobiu - 2025-11-29 23:31
39+
40+
**Input from Gemini 2.5:**
41+
42+
> ✦ I have conducted the **Multi-Server Experiment**.
43+
>
44+
> ### Findings
45+
> * **Collision Confirmed:** All three servers (`github-workflow`, `knowledge-base`, `memory-core`) expose a `healthcheck` tool.
46+
> * **Partial Collision:** `knowledge-base` and `memory-core` both expose `start_database` and `stop_database` tools.
47+
> * **Current Behavior:** The `Neo.ai.mcp.client.Client` creates dynamic proxies on `this.tools`. If we were to merge these into a single object (e.g., in an Agent class), these collisions would be problematic.
48+
>
49+
> ### Proposed Solution: Server Namespacing
50+
> As suggested in the ticket, we should namespace tools by server.
51+
>
52+
> **Implementation Plan:**
53+
> 1. Modify `Neo.ai.mcp.client.Client` to **not** flatten tools onto `this.tools` directly, OR...
54+
> 2. Actually, the `Client` represents a *single* connection. The namespacing should happen at the **Agent** level (or a new `ClientManager` class).
55+
> 3. Since `Neo.ai.Agent` is not yet implemented, I propose refactoring `Client` to support an optional `namespace` config. If provided, tools are mounted at `client.tools[namespace]`. If not, they remain at `client.tools` (backward compatibility).
56+
>
57+
> **Correction:**
58+
> Wait, `Neo.ai.mcp.client.Client` connects to **one** server.
59+
> So `client.tools` *is* effectively namespaced by the client instance itself.
60+
>
61+
> In `test-multi-server.mjs`:
62+
> ```javascript
63+
> const ghClient = ...;
64+
> const kbClient = ...;
65+
>
66+
> ghClient.tools.healthcheck(); // GitHub health
67+
> kbClient.tools.healthcheck(); // KB health
68+
> ```
69+
>
70+
> The collision only happens if we try to merge them.
71+
>
72+
> **Refined Proposal:**
73+
> We don't need to change `Client.mjs` to namespace tools *internally*.
74+
> Instead, the future `Neo.ai.Agent` should likely hold a map of clients:
75+
> ```javascript
76+
> class Agent {
77+
> clients = {
78+
> github: new Client({ serverName: 'github-workflow' }),
79+
> memory: new Client({ serverName: 'memory-core' })
80+
> }
81+
>
82+
> async work() {
83+
> await this.clients.github.tools.listIssues();
84+
> await this.clients.memory.tools.addMemory();
85+
> }
86+
> }
87+
> ```
88+
>
89+
> This seems cleaner than `this.tools.githubWorkflow.healthcheck`.
90+
>
91+
> **However**, for the "Agent OS" vision, we often want a unified toolbox.
92+
> If we *do* want a unified toolbox, we can implement a `ToolAggregator` that handles collisions by prefixing.
93+
>
94+
> **Decision:**
95+
> I will verify if `config.mjs` needs updates. It currently supports the map.
96+
> I will conclude the investigation with the recommendation: **Keep Client simple. Handle aggregation in Agent/Manager.**
97+
>
98+
> For this ticket, I will clean up the test script and close it, as the "investigation" is complete and the "namespacing" is best handled by the consumer (Agent) rather than forcing it into the low-level Client.
99+
>
100+
> Wait, the ticket says: *"Implement Namespacing (Proposal): ... refactor Client or create Agent ... aggregates tools"*.
101+
>
102+
> I will write a simple `Neo.ai.Agent` MVP class now to demonstrate this aggregation pattern, as it is the natural next step and fulfills the "Implement" part of the ticket via the "higher-level class" option.
103+
104+
### @tobiu - 2025-11-29 23:44
105+
106+
**Input from Gemini 2.5:**
107+
108+
> ✦ I have completed the investigation and implementation for multi-server tool namespacing.
109+
>
110+
> ### Deliverables Completed
111+
> 1. **Multi-Server Experiment & Analysis:** Confirmed tool name collisions and determined that namespacing is necessary.
112+
> 2. **Implemented Namespacing:** Created the `Neo.ai.Agent` base class (`ai/Agent.mjs`) which:
113+
> * Manages multiple `Neo.ai.mcp.client.Client` instances.
114+
> * Aggregates tools into a structured `this.tools.<serverName>.<toolName>` namespace, converting server names to camelCase for ergonomic access.
115+
> 3. **Updated Demo Agent:** Refactored `ai/agents/mcp-demo-agent.mjs` to instantiate and use `Neo.ai.Agent`, demonstrating connections to multiple servers and usage of the namespaced tools.
116+
> 4. **Clean-up:** Deleted the temporary `ai/mcp/client/test-multi-server.mjs` script.
117+
>
118+
> This establishes a clear and scalable architecture for agents to interact with multiple MCP servers. (Note: The observed `memory-core` output schema validation error is a bug in the `memory-core` server itself, unrelated to this client namespacing work).
119+
35120
## Activity Log
36121
37122
- 2025-11-29 @tobiu assigned to @tobiu
38123
- 2025-11-29 @tobiu added the `enhancement` label
39124
- 2025-11-29 @tobiu added the `ai` label
40125
- 2025-11-29 @tobiu added the `refactoring` label
126+
- 2025-11-29 @tobiu referenced in commit `82d9904` - "Feat: Implement Multi-Server Tool Namespacing for MCP Client #7941"
127+
- 2025-11-29 @tobiu closed this issue
41128

.github/ISSUE/issue-7942.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
id: 7942
3+
title: 'Feat: Enable External Configuration and Generic Runner for MCP Client CLI'
4+
state: OPEN
5+
labels:
6+
- enhancement
7+
- ai
8+
- refactoring
9+
assignees:
10+
- tobiu
11+
createdAt: '2025-11-29T23:56:44Z'
12+
updatedAt: '2025-11-29T23:56:44Z'
13+
githubUrl: 'https://github.com/neomjs/neo/issues/7942'
14+
author: tobiu
15+
commentsCount: 0
16+
parentIssue: 7931
17+
subIssues: []
18+
subIssuesCompleted: 0
19+
subIssuesTotal: 0
20+
blockedBy: []
21+
blocking: []
22+
---
23+
# Feat: Enable External Configuration and Generic Runner for MCP Client CLI
24+
25+
To align the MCP Client CLI with the server-side runner architecture, this task will enhance `ai/mcp/client/mcp-stdio.mjs` to support loading external configuration files via a `-c` option and remove unnecessary environment setup specific to direct Node.js execution.
26+
27+
### Deliverables
28+
1. **Refactor `ai/mcp/client/mcp-stdio.mjs`:**
29+
* Remove `#!/usr/bin/env node` shebang.
30+
* Remove `dotenv` import and usage.
31+
* Add a Commander option `-c, --config <path>` for specifying an external client configuration file.
32+
* Integrate `ClientConfig.load(options.config)` to load and merge external configurations.
33+
* Make `--server <name>` option optional (default action TBD, perhaps list available servers or require it for specific commands).
34+
2. **Update Documentation:** Ensure JSDoc and comments reflect the new usage.
35+
36+
## Activity Log
37+
38+
- 2025-11-29 @tobiu assigned to @tobiu
39+
- 2025-11-29 @tobiu added the `enhancement` label
40+
- 2025-11-29 @tobiu added the `ai` label
41+
- 2025-11-29 @tobiu added the `refactoring` label
42+

ai/mcp/client/Client.mjs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class Client extends Base {
1919
* @protected
2020
*/
2121
className: 'Neo.ai.mcp.client.Client',
22+
/**
23+
* Path to a custom client configuration file.
24+
* @member {String|null} configFile=null
25+
*/
26+
configFile: null,
2227
/**
2328
* The logical name of the MCP server to connect to (e.g., 'github-workflow').
2429
* This name will be used to look up connection details from ClientConfig.
@@ -80,7 +85,18 @@ class Client extends Base {
8085

8186
async initAsync() {
8287
await super.initAsync();
83-
// Load initial server config based on the default or provided serverName
88+
89+
// 1. Load custom configuration if provided
90+
if (this.configFile) {
91+
try {
92+
await ClientConfig.load(this.configFile);
93+
} catch (error) {
94+
console.error('Failed to load configuration:', error);
95+
throw error;
96+
}
97+
}
98+
99+
// 2. Load initial server config based on the default or provided serverName
84100
this.loadServerConfig(this.serverName);
85101
}
86102

ai/mcp/client/mcp-stdio.mjs

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,61 @@
1-
#!/usr/bin/env node
2-
31
/**
42
* Neo.mjs MCP Client CLI
53
*
64
* This script provides a command-line interface to interact with MCP servers
75
* using the Neo.ai.mcp.client.Client.
86
*
97
* Usage:
10-
* node ai/mcp/client/mcp-cli.mjs --server <serverName> --list-tools
11-
* node ai/mcp/client/mcp-cli.mjs --server <serverName> --call-tool <toolName> --args '{"key": "value"}'
8+
* npm run ai:mcp-client -- --server <serverName> --list-tools
9+
* npm run ai:mcp-client -- --server <serverName> --call-tool <toolName> --args '{"key": "value"}'
10+
* npm run ai:mcp-client -- -c ./my-client-config.mjs --server <serverName> --list-tools
1211
*/
1312

1413
import { Command } from 'commander';
1514
import Neo from '../../../src/Neo.mjs';
1615
import * as core from '../../../src/core/_export.mjs'; // For Neo.core.Base setup
1716
import InstanceManager from '../../../src/manager/Instance.mjs'; // For Neo.core.Base setup
1817
import Client from './Client.mjs';
19-
import ClientConfig from './config.mjs';
20-
import path from 'path';
21-
import { fileURLToPath } from 'url';
22-
import dotenv from 'dotenv';
23-
24-
// Load environment variables from project root
25-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
26-
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
18+
import aiConfig from './config.mjs';
2719

2820
const program = new Command();
2921

3022
program
31-
.name('mcp-cli')
23+
.name('mcp-client')
3224
.description('CLI for interacting with MCP servers')
33-
.requiredOption('-s, --server <name>', 'Logical name of the MCP server to connect to (e.g., github-workflow)')
34-
.option('-l, --list-tools', 'List available tools on the server')
35-
.option('-c, --call-tool <toolName>', 'Name of the tool to call')
25+
.option('-s, --server <name>', 'Logical name of the MCP server to connect to (e.g., github-workflow)')
26+
.option('-l, --list-tools', 'List available tools on the specified server')
27+
.option('-c, --config <path>', 'Path to an external client configuration file')
28+
.option('-t, --call-tool <toolName>', 'Name of the tool to call')
3629
.option('-a, --args <json>', 'JSON string of arguments for --call-tool', '{}')
3730
.option('-d, --debug', 'Enable debug logging');
3831

3932
program.parse(process.argv);
4033

4134
const options = program.opts();
4235

36+
// Apply debug flag immediately
37+
if (options.debug) {
38+
aiConfig.data.debug = true;
39+
}
40+
4341
async function run() {
44-
console.log(`🤖 MCP Client CLI starting for server: ${options.server}`);
45-
console.log('GH_TOKEN present:', !!process.env.GH_TOKEN); // Debug info
42+
console.log(`🤖 MCP Client CLI starting.`);
4643

47-
const mcpClient = Neo.create(Client, {
48-
clientName: 'Neo.ai.MCP.CLI',
49-
serverName: options.server,
50-
env: process.env // Pass environment variables
51-
});
44+
let mcpClient = null;
5245

5346
try {
47+
if (!options.server) {
48+
console.error('❌ Error: A target server must be specified using -s, --server <name>.');
49+
process.exit(1);
50+
}
51+
52+
mcpClient = Neo.create(Client, {
53+
clientName: 'Neo.ai.MCP.CLI',
54+
configFile: options.config,
55+
serverName: options.server,
56+
env: process.env // Pass environment variables
57+
});
58+
5459
await mcpClient.connect();
5560
console.log(`✅ Connected to ${options.server} MCP server.`);
5661

@@ -64,7 +69,14 @@ async function run() {
6469
} else if (options.callTool) {
6570
console.log(`\nCalling tool: ${options.callTool} with args: ${options.args}`);
6671
const args = JSON.parse(options.args);
67-
const result = await mcpClient.callTool(options.callTool, args);
72+
const toolMethod = mcpClient.tools[Neo.snakeToCamel(options.callTool)]; // Use dynamic proxy
73+
74+
if (!toolMethod) {
75+
console.error(`❌ Error: Tool '${options.callTool}' not found on server '${options.server}'.`);
76+
process.exit(1);
77+
}
78+
79+
const result = await toolMethod(args);
6880

6981
if (result.isError) {
7082
console.error(`\n❌ Tool call failed:`);
@@ -78,10 +90,12 @@ async function run() {
7890
}
7991

8092
} catch (error) {
81-
console.error('\n💥 MCP CLI Error:', error.message);
93+
console.error('Fatal error during client initialization:', error);
8294
process.exit(1);
8395
} finally {
84-
await mcpClient.close();
96+
if (mcpClient) {
97+
await mcpClient.close();
98+
}
8599
console.log('\n🔌 Connection closed.');
86100
}
87101
}

0 commit comments

Comments
 (0)