@juspay/neurolink 9.26.2 → 9.28.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 +12 -0
- package/README.md +59 -9
- package/dist/cli/commands/config.d.ts +4 -4
- package/dist/cli/commands/mcp.d.ts +87 -0
- package/dist/cli/commands/mcp.js +1524 -0
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/modules/ToolsManager.js +29 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +27 -1
- package/dist/lib/core/modules/ToolsManager.js +29 -2
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.js +27 -1
- package/dist/lib/mcp/agentExposure.d.ts +228 -0
- package/dist/lib/mcp/agentExposure.js +357 -0
- package/dist/lib/mcp/batching/index.d.ts +11 -0
- package/dist/lib/mcp/batching/index.js +11 -0
- package/dist/lib/mcp/batching/requestBatcher.d.ts +202 -0
- package/dist/lib/mcp/batching/requestBatcher.js +442 -0
- package/dist/lib/mcp/caching/index.d.ts +11 -0
- package/dist/lib/mcp/caching/index.js +11 -0
- package/dist/lib/mcp/caching/toolCache.d.ts +221 -0
- package/dist/lib/mcp/caching/toolCache.js +434 -0
- package/dist/lib/mcp/elicitation/elicitationManager.d.ts +169 -0
- package/dist/lib/mcp/elicitation/elicitationManager.js +377 -0
- package/dist/lib/mcp/elicitation/index.d.ts +11 -0
- package/dist/lib/mcp/elicitation/index.js +12 -0
- package/dist/lib/mcp/elicitation/types.d.ts +278 -0
- package/dist/lib/mcp/elicitation/types.js +11 -0
- package/dist/lib/mcp/elicitationProtocol.d.ts +228 -0
- package/dist/lib/mcp/elicitationProtocol.js +376 -0
- package/dist/lib/mcp/enhancedToolDiscovery.d.ts +205 -0
- package/dist/lib/mcp/enhancedToolDiscovery.js +482 -0
- package/dist/lib/mcp/index.d.ts +38 -1
- package/dist/lib/mcp/index.js +36 -3
- package/dist/lib/mcp/mcpRegistryClient.d.ts +332 -0
- package/dist/lib/mcp/mcpRegistryClient.js +489 -0
- package/dist/lib/mcp/mcpServerBase.d.ts +227 -0
- package/dist/lib/mcp/mcpServerBase.js +374 -0
- package/dist/lib/mcp/multiServerManager.d.ts +310 -0
- package/dist/lib/mcp/multiServerManager.js +580 -0
- package/dist/lib/mcp/routing/index.d.ts +11 -0
- package/dist/lib/mcp/routing/index.js +11 -0
- package/dist/lib/mcp/routing/toolRouter.d.ts +219 -0
- package/dist/lib/mcp/routing/toolRouter.js +417 -0
- package/dist/lib/mcp/serverCapabilities.d.ts +341 -0
- package/dist/lib/mcp/serverCapabilities.js +503 -0
- package/dist/lib/mcp/toolAnnotations.d.ts +154 -0
- package/dist/lib/mcp/toolAnnotations.js +240 -0
- package/dist/lib/mcp/toolConverter.d.ts +178 -0
- package/dist/lib/mcp/toolConverter.js +259 -0
- package/dist/lib/mcp/toolIntegration.d.ts +136 -0
- package/dist/lib/mcp/toolIntegration.js +335 -0
- package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/lib/memory/hippocampusInitializer.js +1 -1
- package/dist/lib/neurolink.d.ts +275 -2
- package/dist/lib/neurolink.js +596 -56
- package/dist/lib/providers/litellm.d.ts +10 -0
- package/dist/lib/providers/litellm.js +104 -2
- package/dist/lib/types/configTypes.d.ts +56 -0
- package/dist/lib/types/conversation.d.ts +2 -2
- package/dist/lib/types/generateTypes.d.ts +4 -0
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/modelTypes.d.ts +6 -6
- package/dist/lib/types/streamTypes.d.ts +2 -0
- package/dist/lib/types/tools.d.ts +2 -0
- package/dist/lib/utils/pricing.js +177 -17
- package/dist/lib/utils/schemaConversion.d.ts +6 -1
- package/dist/lib/utils/schemaConversion.js +50 -28
- package/dist/lib/workflow/config.d.ts +16 -16
- package/dist/mcp/agentExposure.d.ts +228 -0
- package/dist/mcp/agentExposure.js +356 -0
- package/dist/mcp/batching/index.d.ts +11 -0
- package/dist/mcp/batching/index.js +10 -0
- package/dist/mcp/batching/requestBatcher.d.ts +202 -0
- package/dist/mcp/batching/requestBatcher.js +441 -0
- package/dist/mcp/caching/index.d.ts +11 -0
- package/dist/mcp/caching/index.js +10 -0
- package/dist/mcp/caching/toolCache.d.ts +221 -0
- package/dist/mcp/caching/toolCache.js +433 -0
- package/dist/mcp/elicitation/elicitationManager.d.ts +169 -0
- package/dist/mcp/elicitation/elicitationManager.js +376 -0
- package/dist/mcp/elicitation/index.d.ts +11 -0
- package/dist/mcp/elicitation/index.js +11 -0
- package/dist/mcp/elicitation/types.d.ts +278 -0
- package/dist/mcp/elicitation/types.js +10 -0
- package/dist/mcp/elicitationProtocol.d.ts +228 -0
- package/dist/mcp/elicitationProtocol.js +375 -0
- package/dist/mcp/enhancedToolDiscovery.d.ts +205 -0
- package/dist/mcp/enhancedToolDiscovery.js +481 -0
- package/dist/mcp/index.d.ts +38 -1
- package/dist/mcp/index.js +36 -3
- package/dist/mcp/mcpRegistryClient.d.ts +332 -0
- package/dist/mcp/mcpRegistryClient.js +488 -0
- package/dist/mcp/mcpServerBase.d.ts +227 -0
- package/dist/mcp/mcpServerBase.js +373 -0
- package/dist/mcp/multiServerManager.d.ts +310 -0
- package/dist/mcp/multiServerManager.js +579 -0
- package/dist/mcp/routing/index.d.ts +11 -0
- package/dist/mcp/routing/index.js +10 -0
- package/dist/mcp/routing/toolRouter.d.ts +219 -0
- package/dist/mcp/routing/toolRouter.js +416 -0
- package/dist/mcp/serverCapabilities.d.ts +341 -0
- package/dist/mcp/serverCapabilities.js +502 -0
- package/dist/mcp/toolAnnotations.d.ts +154 -0
- package/dist/mcp/toolAnnotations.js +239 -0
- package/dist/mcp/toolConverter.d.ts +178 -0
- package/dist/mcp/toolConverter.js +258 -0
- package/dist/mcp/toolIntegration.d.ts +136 -0
- package/dist/mcp/toolIntegration.js +334 -0
- package/dist/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/memory/hippocampusInitializer.js +1 -1
- package/dist/neurolink.d.ts +275 -2
- package/dist/neurolink.js +596 -56
- package/dist/providers/litellm.d.ts +10 -0
- package/dist/providers/litellm.js +104 -2
- package/dist/types/configTypes.d.ts +56 -0
- package/dist/types/conversation.d.ts +2 -2
- package/dist/types/generateTypes.d.ts +4 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/streamTypes.d.ts +2 -0
- package/dist/types/tools.d.ts +2 -0
- package/dist/utils/pricing.js +177 -17
- package/dist/utils/schemaConversion.d.ts +6 -1
- package/dist/utils/schemaConversion.js +50 -28
- package/package.json +2 -2
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* MCP CLI Commands for NeuroLink
|
|
3
3
|
* Implements comprehensive MCP server management commands
|
|
4
4
|
* Part of Phase 4.2 - MCP CLI Commands
|
|
5
|
+
*
|
|
6
|
+
* Enhanced in Phase 8.39.0 with:
|
|
7
|
+
* - neurolink mcp servers - List all MCP servers
|
|
8
|
+
* - neurolink mcp tools - List all available tools
|
|
9
|
+
* - neurolink mcp discover - Enhanced tool discovery
|
|
10
|
+
* - neurolink mcp create-server - Scaffold a new MCP server
|
|
11
|
+
* - neurolink mcp annotate - Add/update tool annotations
|
|
5
12
|
*/
|
|
6
13
|
import { createExternalServerInfo } from "../../lib/utils/mcpDefaults.js";
|
|
7
14
|
import { NeuroLink } from "../../lib/neurolink.js";
|
|
@@ -10,6 +17,8 @@ import chalk from "chalk";
|
|
|
10
17
|
import ora from "ora";
|
|
11
18
|
import fs from "fs";
|
|
12
19
|
import path from "path";
|
|
20
|
+
import { getAnnotationSummary, validateAnnotations, mergeAnnotations, inferAnnotations, } from "../../lib/mcp/toolAnnotations.js";
|
|
21
|
+
import { withTimeout, ErrorFactory } from "../../lib/utils/errorHandling.js";
|
|
13
22
|
// Using MCPCommandArgs from types/cli.ts
|
|
14
23
|
/**
|
|
15
24
|
* Response interface for MCP status information returned from the NeuroLink SDK.
|
|
@@ -120,11 +129,17 @@ export class MCPCommandFactory {
|
|
|
120
129
|
builder: (yargs) => {
|
|
121
130
|
return yargs
|
|
122
131
|
.command("list", "List configured MCP servers with status", (yargs) => this.buildListOptions(yargs), (argv) => this.executeList(argv))
|
|
132
|
+
.command("servers", "List all MCP servers with detailed status", (yargs) => this.buildServersOptions(yargs), (argv) => this.executeServers(argv))
|
|
133
|
+
.command("tools", "List all available tools across MCP servers", (yargs) => this.buildToolsOptions(yargs), (argv) => this.executeTools(argv))
|
|
134
|
+
.command("discover", "Discover tools from MCP servers with annotations", (yargs) => this.buildDiscoverToolsOptions(yargs), (argv) => this.executeDiscoverTools(argv))
|
|
135
|
+
.command("create-server <name>", "Create a new MCP server scaffold", (yargs) => this.buildCreateServerOptions(yargs), (argv) => this.executeCreateServer(argv))
|
|
136
|
+
.command("annotate", "Add or update annotations for MCP tools", (yargs) => this.buildAnnotateOptions(yargs), (argv) => this.executeAnnotate(argv))
|
|
123
137
|
.command("install <server>", "Install popular MCP servers", (yargs) => this.buildInstallOptions(yargs), (argv) => this.executeInstall(argv))
|
|
124
138
|
.command("add <name> <command>", "Add custom MCP server configuration", (yargs) => this.buildAddOptions(yargs), (argv) => this.executeAdd(argv))
|
|
125
139
|
.command("test [server]", "Test connectivity to MCP servers", (yargs) => this.buildTestOptions(yargs), (argv) => this.executeTest(argv))
|
|
126
140
|
.command("exec <server> <tool>", "Execute tools from MCP servers", (yargs) => this.buildExecOptions(yargs), (argv) => this.executeExec(argv))
|
|
127
141
|
.command("remove <server>", "Remove MCP server configuration", (yargs) => this.buildRemoveOptions(yargs), (argv) => this.executeRemove(argv))
|
|
142
|
+
.command(this.createRegistryCommand())
|
|
128
143
|
.option("format", {
|
|
129
144
|
choices: ["table", "json", "compact"],
|
|
130
145
|
default: "table",
|
|
@@ -927,5 +942,1514 @@ export class MCPCommandFactory {
|
|
|
927
942
|
}
|
|
928
943
|
return servers;
|
|
929
944
|
}
|
|
945
|
+
// ========================================
|
|
946
|
+
// NEW MCP ENHANCEMENT COMMANDS (Phase 8.39.0)
|
|
947
|
+
// ========================================
|
|
948
|
+
/**
|
|
949
|
+
* Build options for servers command
|
|
950
|
+
*/
|
|
951
|
+
static buildServersOptions(yargs) {
|
|
952
|
+
return yargs
|
|
953
|
+
.option("status", {
|
|
954
|
+
choices: ["all", "connected", "disconnected", "failed"],
|
|
955
|
+
default: "all",
|
|
956
|
+
description: "Filter servers by status",
|
|
957
|
+
})
|
|
958
|
+
.option("category", {
|
|
959
|
+
type: "string",
|
|
960
|
+
description: "Filter servers by category",
|
|
961
|
+
})
|
|
962
|
+
.option("detailed", {
|
|
963
|
+
type: "boolean",
|
|
964
|
+
default: false,
|
|
965
|
+
description: "Show detailed server information including metrics",
|
|
966
|
+
})
|
|
967
|
+
.example("neurolink mcp servers", "List all MCP servers")
|
|
968
|
+
.example("neurolink mcp servers --status connected", "List only connected servers")
|
|
969
|
+
.example("neurolink mcp servers --detailed", "Show detailed metrics for each server");
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Build options for tools command
|
|
973
|
+
*/
|
|
974
|
+
static buildToolsOptions(yargs) {
|
|
975
|
+
return yargs
|
|
976
|
+
.option("server", {
|
|
977
|
+
type: "string",
|
|
978
|
+
description: "Filter tools by server ID",
|
|
979
|
+
})
|
|
980
|
+
.option("category", {
|
|
981
|
+
type: "string",
|
|
982
|
+
description: "Filter tools by category",
|
|
983
|
+
})
|
|
984
|
+
.option("tag", {
|
|
985
|
+
type: "array",
|
|
986
|
+
description: "Filter tools by tags",
|
|
987
|
+
})
|
|
988
|
+
.option("safety", {
|
|
989
|
+
choices: ["safe", "moderate", "dangerous"],
|
|
990
|
+
description: "Filter tools by safety level",
|
|
991
|
+
})
|
|
992
|
+
.option("annotations", {
|
|
993
|
+
type: "boolean",
|
|
994
|
+
default: false,
|
|
995
|
+
description: "Show tool annotations",
|
|
996
|
+
})
|
|
997
|
+
.option("search", {
|
|
998
|
+
type: "string",
|
|
999
|
+
description: "Search tools by name or description",
|
|
1000
|
+
})
|
|
1001
|
+
.example("neurolink mcp tools", "List all available tools")
|
|
1002
|
+
.example("neurolink mcp tools --server filesystem", "List tools from specific server")
|
|
1003
|
+
.example("neurolink mcp tools --safety safe", "List only read-only safe tools")
|
|
1004
|
+
.example("neurolink mcp tools --annotations", "Show tools with their annotations");
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Build options for discover tools command
|
|
1008
|
+
*/
|
|
1009
|
+
static buildDiscoverToolsOptions(yargs) {
|
|
1010
|
+
return yargs
|
|
1011
|
+
.option("server", {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
description: "Specific server to discover tools from",
|
|
1014
|
+
})
|
|
1015
|
+
.option("infer-annotations", {
|
|
1016
|
+
type: "boolean",
|
|
1017
|
+
default: true,
|
|
1018
|
+
description: "Automatically infer tool annotations",
|
|
1019
|
+
})
|
|
1020
|
+
.option("verbose", {
|
|
1021
|
+
type: "boolean",
|
|
1022
|
+
alias: "v",
|
|
1023
|
+
default: false,
|
|
1024
|
+
description: "Show verbose discovery output",
|
|
1025
|
+
})
|
|
1026
|
+
.example("neurolink mcp discover", "Discover tools from all servers")
|
|
1027
|
+
.example("neurolink mcp discover --server github", "Discover tools from specific server");
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Build options for create-server command
|
|
1031
|
+
*/
|
|
1032
|
+
static buildCreateServerOptions(yargs) {
|
|
1033
|
+
return yargs
|
|
1034
|
+
.positional("name", {
|
|
1035
|
+
type: "string",
|
|
1036
|
+
description: "Name for the new MCP server",
|
|
1037
|
+
demandOption: true,
|
|
1038
|
+
})
|
|
1039
|
+
.option("template", {
|
|
1040
|
+
choices: ["basic", "typescript", "python"],
|
|
1041
|
+
default: "typescript",
|
|
1042
|
+
description: "Server template to use",
|
|
1043
|
+
})
|
|
1044
|
+
.option("output", {
|
|
1045
|
+
type: "string",
|
|
1046
|
+
alias: "o",
|
|
1047
|
+
description: "Output directory (defaults to current directory)",
|
|
1048
|
+
})
|
|
1049
|
+
.option("tools", {
|
|
1050
|
+
type: "array",
|
|
1051
|
+
description: "Initial tools to include in the server",
|
|
1052
|
+
})
|
|
1053
|
+
.option("description", {
|
|
1054
|
+
type: "string",
|
|
1055
|
+
alias: "d",
|
|
1056
|
+
description: "Server description",
|
|
1057
|
+
})
|
|
1058
|
+
.example("neurolink mcp create-server my-server", "Create a new TypeScript MCP server")
|
|
1059
|
+
.example("neurolink mcp create-server my-server --template python", "Create a Python MCP server")
|
|
1060
|
+
.example("neurolink mcp create-server my-server --tools readFile writeFile", "Create server with initial tools");
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Execute servers command
|
|
1064
|
+
*/
|
|
1065
|
+
static async executeServers(argv) {
|
|
1066
|
+
try {
|
|
1067
|
+
const spinner = argv.quiet ? null : ora("Loading MCP servers...").start();
|
|
1068
|
+
const sdk = new NeuroLink();
|
|
1069
|
+
let servers = await sdk.listMCPServers();
|
|
1070
|
+
// Filter by status
|
|
1071
|
+
if (argv.status && argv.status !== "all") {
|
|
1072
|
+
servers = servers.filter((s) => s.status === argv.status);
|
|
1073
|
+
}
|
|
1074
|
+
// Filter by category
|
|
1075
|
+
if (argv.category) {
|
|
1076
|
+
servers = servers.filter((s) => s.metadata?.category === argv.category);
|
|
1077
|
+
}
|
|
1078
|
+
if (spinner) {
|
|
1079
|
+
spinner.succeed(`Found ${servers.length} MCP servers`);
|
|
1080
|
+
}
|
|
1081
|
+
if (servers.length === 0) {
|
|
1082
|
+
logger.always(chalk.yellow("No MCP servers match the criteria."));
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
// Output formatting
|
|
1086
|
+
if (argv.format === "json") {
|
|
1087
|
+
logger.always(JSON.stringify(servers, null, 2));
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
logger.always(chalk.bold("\n📡 MCP Servers:\n"));
|
|
1091
|
+
for (const server of servers) {
|
|
1092
|
+
const statusIcon = server.status === "connected"
|
|
1093
|
+
? chalk.green("●")
|
|
1094
|
+
: server.status === "failed"
|
|
1095
|
+
? chalk.red("●")
|
|
1096
|
+
: chalk.yellow("●");
|
|
1097
|
+
const statusText = server.status === "connected"
|
|
1098
|
+
? chalk.green(server.status.toUpperCase())
|
|
1099
|
+
: server.status === "failed"
|
|
1100
|
+
? chalk.red(server.status.toUpperCase())
|
|
1101
|
+
: chalk.yellow(server.status.toUpperCase());
|
|
1102
|
+
logger.always(`${statusIcon} ${chalk.cyan.bold(server.name)} [${statusText}]`);
|
|
1103
|
+
logger.always(` ID: ${server.id}`);
|
|
1104
|
+
logger.always(` Transport: ${server.transport}`);
|
|
1105
|
+
logger.always(` Description: ${server.description || "No description"}`);
|
|
1106
|
+
logger.always(` Tools: ${server.tools?.length || 0} available`);
|
|
1107
|
+
if (argv.detailed) {
|
|
1108
|
+
if (server.command) {
|
|
1109
|
+
logger.always(` Command: ${server.command}`);
|
|
1110
|
+
}
|
|
1111
|
+
if (server.url) {
|
|
1112
|
+
logger.always(` URL: ${server.url}`);
|
|
1113
|
+
}
|
|
1114
|
+
if (server.metadata) {
|
|
1115
|
+
logger.always(` Category: ${server.metadata.category || "uncategorized"}`);
|
|
1116
|
+
if (server.metadata.version) {
|
|
1117
|
+
logger.always(` Version: ${server.metadata.version}`);
|
|
1118
|
+
}
|
|
1119
|
+
if (server.metadata.uptime) {
|
|
1120
|
+
logger.always(` Uptime: ${Math.round(server.metadata.uptime / 1000)}s`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (server.error) {
|
|
1124
|
+
logger.always(` ${chalk.red("Error:")} ${server.error}`);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
logger.always();
|
|
1128
|
+
}
|
|
1129
|
+
// Summary
|
|
1130
|
+
const connected = servers.filter((s) => s.status === "connected").length;
|
|
1131
|
+
const totalTools = servers.reduce((sum, s) => sum + (s.tools?.length || 0), 0);
|
|
1132
|
+
logger.always(chalk.gray("---"));
|
|
1133
|
+
logger.always(`${chalk.bold("Summary:")} ${connected}/${servers.length} servers connected, ${totalTools} total tools`);
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
logger.error(chalk.red(`Servers command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Execute tools command
|
|
1142
|
+
*/
|
|
1143
|
+
static async executeTools(argv) {
|
|
1144
|
+
try {
|
|
1145
|
+
const spinner = argv.quiet ? null : ora("Loading MCP tools...").start();
|
|
1146
|
+
const sdk = new NeuroLink();
|
|
1147
|
+
const servers = await sdk.listMCPServers();
|
|
1148
|
+
let tools = [];
|
|
1149
|
+
const { inferAnnotations } = await import("../../lib/mcp/toolAnnotations.js");
|
|
1150
|
+
for (const server of servers) {
|
|
1151
|
+
if (server.status !== "connected") {
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
for (const tool of server.tools || []) {
|
|
1155
|
+
const annotations = tool.annotations ??
|
|
1156
|
+
inferAnnotations({
|
|
1157
|
+
name: tool.name,
|
|
1158
|
+
description: tool.description,
|
|
1159
|
+
});
|
|
1160
|
+
tools.push({
|
|
1161
|
+
name: tool.name,
|
|
1162
|
+
description: tool.description,
|
|
1163
|
+
serverId: server.id,
|
|
1164
|
+
serverName: server.name,
|
|
1165
|
+
inputSchema: tool.inputSchema,
|
|
1166
|
+
category: this.inferToolCategory(tool.name, tool.description),
|
|
1167
|
+
annotations,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
// Apply filters
|
|
1172
|
+
if (argv.server) {
|
|
1173
|
+
tools = tools.filter((t) => t.serverId === argv.server || t.serverName === argv.server);
|
|
1174
|
+
}
|
|
1175
|
+
if (argv.category) {
|
|
1176
|
+
tools = tools.filter((t) => t.category === argv.category);
|
|
1177
|
+
}
|
|
1178
|
+
if (argv.search) {
|
|
1179
|
+
const searchTerm = String(argv.search).toLowerCase();
|
|
1180
|
+
tools = tools.filter((t) => t.name.toLowerCase().includes(searchTerm) ||
|
|
1181
|
+
t.description.toLowerCase().includes(searchTerm));
|
|
1182
|
+
}
|
|
1183
|
+
if (argv.safety) {
|
|
1184
|
+
tools = tools.filter((t) => {
|
|
1185
|
+
switch (argv.safety) {
|
|
1186
|
+
case "safe":
|
|
1187
|
+
return t.annotations?.readOnlyHint === true;
|
|
1188
|
+
case "dangerous":
|
|
1189
|
+
return t.annotations?.destructiveHint === true;
|
|
1190
|
+
case "moderate":
|
|
1191
|
+
return (!t.annotations?.readOnlyHint && !t.annotations?.destructiveHint);
|
|
1192
|
+
default:
|
|
1193
|
+
return true;
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
if (spinner) {
|
|
1198
|
+
spinner.succeed(`Found ${tools.length} tools`);
|
|
1199
|
+
}
|
|
1200
|
+
if (tools.length === 0) {
|
|
1201
|
+
logger.always(chalk.yellow("No tools match the criteria."));
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
// Output formatting
|
|
1205
|
+
if (argv.format === "json") {
|
|
1206
|
+
logger.always(JSON.stringify(tools, null, 2));
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
logger.always(chalk.bold("\n🔧 MCP Tools:\n"));
|
|
1210
|
+
// Group tools by server for better readability
|
|
1211
|
+
const toolsByServer = new Map();
|
|
1212
|
+
for (const tool of tools) {
|
|
1213
|
+
const key = tool.serverName;
|
|
1214
|
+
if (!toolsByServer.has(key)) {
|
|
1215
|
+
toolsByServer.set(key, []);
|
|
1216
|
+
}
|
|
1217
|
+
const list = toolsByServer.get(key);
|
|
1218
|
+
if (list) {
|
|
1219
|
+
list.push(tool);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
for (const [serverName, serverTools] of toolsByServer) {
|
|
1223
|
+
logger.always(chalk.cyan.bold(`${serverName}`) +
|
|
1224
|
+
chalk.gray(` (${serverTools.length} tools)`));
|
|
1225
|
+
for (const tool of serverTools) {
|
|
1226
|
+
// Safety indicator
|
|
1227
|
+
let safetyIcon = "○";
|
|
1228
|
+
if (tool.annotations?.destructiveHint) {
|
|
1229
|
+
safetyIcon = chalk.red("⚠");
|
|
1230
|
+
}
|
|
1231
|
+
else if (tool.annotations?.readOnlyHint) {
|
|
1232
|
+
safetyIcon = chalk.green("●");
|
|
1233
|
+
}
|
|
1234
|
+
else if (tool.annotations?.idempotentHint) {
|
|
1235
|
+
safetyIcon = chalk.blue("◐");
|
|
1236
|
+
}
|
|
1237
|
+
logger.always(` ${safetyIcon} ${chalk.white.bold(tool.name)}`);
|
|
1238
|
+
logger.always(` ${chalk.gray(tool.description)}`);
|
|
1239
|
+
if (argv.annotations && tool.annotations) {
|
|
1240
|
+
const annotationStr = getAnnotationSummary(tool.annotations);
|
|
1241
|
+
if (annotationStr !== "[no annotations]") {
|
|
1242
|
+
logger.always(` ${chalk.dim(annotationStr)}`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
logger.always();
|
|
1247
|
+
}
|
|
1248
|
+
// Summary
|
|
1249
|
+
const safeCount = tools.filter((t) => t.annotations?.readOnlyHint).length;
|
|
1250
|
+
const dangerousCount = tools.filter((t) => t.annotations?.destructiveHint).length;
|
|
1251
|
+
logger.always(chalk.gray("---"));
|
|
1252
|
+
logger.always(`${chalk.bold("Summary:")} ${tools.length} tools (${chalk.green(`${safeCount} safe`)}, ${chalk.red(`${dangerousCount} destructive`)})`);
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
logger.error(chalk.red(`Tools command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Execute discover tools command
|
|
1261
|
+
*/
|
|
1262
|
+
static async executeDiscoverTools(argv) {
|
|
1263
|
+
try {
|
|
1264
|
+
const spinner = argv.quiet
|
|
1265
|
+
? null
|
|
1266
|
+
: ora("Discovering MCP tools...").start();
|
|
1267
|
+
const sdk = new NeuroLink();
|
|
1268
|
+
const servers = await sdk.listMCPServers();
|
|
1269
|
+
// Filter to specific server if requested
|
|
1270
|
+
let targetServers = servers.filter((s) => s.status === "connected");
|
|
1271
|
+
if (argv.server) {
|
|
1272
|
+
targetServers = targetServers.filter((s) => s.id === argv.server || s.name === argv.server);
|
|
1273
|
+
if (targetServers.length === 0) {
|
|
1274
|
+
if (spinner) {
|
|
1275
|
+
spinner.fail();
|
|
1276
|
+
}
|
|
1277
|
+
logger.error(chalk.red(`Server not found or not connected: ${argv.server}`));
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (spinner) {
|
|
1282
|
+
spinner.text = `Discovering tools from ${targetServers.length} servers...`;
|
|
1283
|
+
}
|
|
1284
|
+
const results = [];
|
|
1285
|
+
for (const server of targetServers) {
|
|
1286
|
+
const { inferAnnotations } = await import("../../lib/mcp/toolAnnotations.js");
|
|
1287
|
+
const toolsWithAnnotations = (server.tools || []).map((tool) => {
|
|
1288
|
+
const existing = tool
|
|
1289
|
+
.annotations;
|
|
1290
|
+
const annotations = existing ??
|
|
1291
|
+
(argv.inferAnnotations !== false
|
|
1292
|
+
? inferAnnotations({
|
|
1293
|
+
name: tool.name,
|
|
1294
|
+
description: tool.description,
|
|
1295
|
+
})
|
|
1296
|
+
: {});
|
|
1297
|
+
return {
|
|
1298
|
+
name: tool.name,
|
|
1299
|
+
description: tool.description,
|
|
1300
|
+
annotations,
|
|
1301
|
+
};
|
|
1302
|
+
});
|
|
1303
|
+
results.push({
|
|
1304
|
+
serverId: server.id,
|
|
1305
|
+
serverName: server.name,
|
|
1306
|
+
toolCount: toolsWithAnnotations.length,
|
|
1307
|
+
tools: toolsWithAnnotations,
|
|
1308
|
+
});
|
|
1309
|
+
if (argv.verbose) {
|
|
1310
|
+
logger.always(chalk.dim(`Discovered ${toolsWithAnnotations.length} tools from ${server.name}`));
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (spinner) {
|
|
1314
|
+
const totalTools = results.reduce((sum, r) => sum + r.toolCount, 0);
|
|
1315
|
+
spinner.succeed(`Discovered ${totalTools} tools from ${results.length} servers`);
|
|
1316
|
+
}
|
|
1317
|
+
// Output formatting
|
|
1318
|
+
if (argv.format === "json") {
|
|
1319
|
+
logger.always(JSON.stringify(results, null, 2));
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
logger.always(chalk.bold("\n🔍 Tool Discovery Results:\n"));
|
|
1323
|
+
for (const result of results) {
|
|
1324
|
+
logger.always(chalk.cyan.bold(result.serverName) +
|
|
1325
|
+
chalk.gray(` [${result.serverId}]`));
|
|
1326
|
+
logger.always(` Tools discovered: ${result.toolCount}`);
|
|
1327
|
+
if (argv.verbose && result.tools.length > 0) {
|
|
1328
|
+
for (const tool of result.tools) {
|
|
1329
|
+
const annotationStr = getAnnotationSummary(tool.annotations);
|
|
1330
|
+
logger.always(` • ${tool.name}${annotationStr !== "[no annotations]"
|
|
1331
|
+
? ` ${chalk.dim(annotationStr)}`
|
|
1332
|
+
: ""}`);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
logger.always();
|
|
1336
|
+
}
|
|
1337
|
+
// Summary by annotations
|
|
1338
|
+
const allTools = results.flatMap((r) => r.tools);
|
|
1339
|
+
const safeCount = allTools.filter((t) => t.annotations.readOnlyHint).length;
|
|
1340
|
+
const destructiveCount = allTools.filter((t) => t.annotations.destructiveHint).length;
|
|
1341
|
+
const idempotentCount = allTools.filter((t) => t.annotations.idempotentHint).length;
|
|
1342
|
+
logger.always(chalk.gray("---"));
|
|
1343
|
+
logger.always(chalk.bold("Annotation Summary:"));
|
|
1344
|
+
logger.always(` ${chalk.green("Read-only (safe):")} ${safeCount}`);
|
|
1345
|
+
logger.always(` ${chalk.red("Destructive:")} ${destructiveCount}`);
|
|
1346
|
+
logger.always(` ${chalk.blue("Idempotent:")} ${idempotentCount}`);
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
logger.error(chalk.red(`Discover command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Execute create-server command
|
|
1355
|
+
*/
|
|
1356
|
+
static async executeCreateServer(argv) {
|
|
1357
|
+
try {
|
|
1358
|
+
const serverName = argv.name;
|
|
1359
|
+
if (!serverName) {
|
|
1360
|
+
logger.error(chalk.red("Server name is required"));
|
|
1361
|
+
process.exit(1);
|
|
1362
|
+
}
|
|
1363
|
+
const template = argv.template || "typescript";
|
|
1364
|
+
const outputDir = argv.output || process.cwd();
|
|
1365
|
+
const description = argv.description || `Custom MCP server: ${serverName}`;
|
|
1366
|
+
const initialTools = argv.tools || [];
|
|
1367
|
+
const spinner = argv.quiet
|
|
1368
|
+
? null
|
|
1369
|
+
: ora(`Creating MCP server: ${serverName}...`).start();
|
|
1370
|
+
// Create output directory
|
|
1371
|
+
const serverDir = path.join(outputDir, serverName);
|
|
1372
|
+
if (fs.existsSync(serverDir)) {
|
|
1373
|
+
if (spinner) {
|
|
1374
|
+
spinner.fail();
|
|
1375
|
+
}
|
|
1376
|
+
logger.error(chalk.red(`Directory already exists: ${serverDir}`));
|
|
1377
|
+
process.exit(1);
|
|
1378
|
+
}
|
|
1379
|
+
fs.mkdirSync(serverDir, { recursive: true });
|
|
1380
|
+
// Generate server files based on template
|
|
1381
|
+
if (template === "typescript") {
|
|
1382
|
+
await this.generateTypeScriptServer(serverDir, serverName, description, initialTools);
|
|
1383
|
+
}
|
|
1384
|
+
else if (template === "python") {
|
|
1385
|
+
await this.generatePythonServer(serverDir, serverName, description, initialTools);
|
|
1386
|
+
}
|
|
1387
|
+
else {
|
|
1388
|
+
await this.generateBasicServer(serverDir, serverName, description, initialTools);
|
|
1389
|
+
}
|
|
1390
|
+
if (spinner) {
|
|
1391
|
+
spinner.succeed(chalk.green(`Successfully created MCP server: ${serverName}`));
|
|
1392
|
+
}
|
|
1393
|
+
logger.always(chalk.bold("\n📁 Server created at:") + ` ${serverDir}`);
|
|
1394
|
+
logger.always();
|
|
1395
|
+
logger.always(chalk.bold("Next steps:"));
|
|
1396
|
+
logger.always(` 1. cd ${serverDir}`);
|
|
1397
|
+
if (template === "typescript") {
|
|
1398
|
+
logger.always(" 2. npm install");
|
|
1399
|
+
logger.always(" 3. npm run build");
|
|
1400
|
+
logger.always(" 4. npm start");
|
|
1401
|
+
}
|
|
1402
|
+
else if (template === "python") {
|
|
1403
|
+
logger.always(" 2. pip install -r requirements.txt");
|
|
1404
|
+
logger.always(" 3. python server.py");
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
logger.always(" 2. node server.js");
|
|
1408
|
+
}
|
|
1409
|
+
logger.always();
|
|
1410
|
+
logger.always(chalk.blue("💡 Add to NeuroLink: neurolink mcp add " +
|
|
1411
|
+
serverName +
|
|
1412
|
+
" node " +
|
|
1413
|
+
path.join(serverDir, "dist", "index.js")));
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
logger.error(chalk.red(`Create server command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Generate TypeScript MCP server
|
|
1422
|
+
*/
|
|
1423
|
+
static async generateTypeScriptServer(serverDir, serverName, description, tools) {
|
|
1424
|
+
// package.json
|
|
1425
|
+
const packageJson = {
|
|
1426
|
+
name: serverName,
|
|
1427
|
+
version: "1.0.0",
|
|
1428
|
+
description,
|
|
1429
|
+
type: "module",
|
|
1430
|
+
main: "dist/index.js",
|
|
1431
|
+
scripts: {
|
|
1432
|
+
build: "tsc",
|
|
1433
|
+
start: "node dist/index.js",
|
|
1434
|
+
dev: "tsc --watch",
|
|
1435
|
+
},
|
|
1436
|
+
dependencies: {
|
|
1437
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
1438
|
+
},
|
|
1439
|
+
devDependencies: {
|
|
1440
|
+
"@types/node": "^20.0.0",
|
|
1441
|
+
typescript: "^5.0.0",
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1444
|
+
fs.writeFileSync(path.join(serverDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
1445
|
+
// tsconfig.json
|
|
1446
|
+
const tsConfig = {
|
|
1447
|
+
compilerOptions: {
|
|
1448
|
+
target: "ES2022",
|
|
1449
|
+
module: "NodeNext",
|
|
1450
|
+
moduleResolution: "NodeNext",
|
|
1451
|
+
outDir: "./dist",
|
|
1452
|
+
rootDir: "./src",
|
|
1453
|
+
strict: true,
|
|
1454
|
+
esModuleInterop: true,
|
|
1455
|
+
skipLibCheck: true,
|
|
1456
|
+
declaration: true,
|
|
1457
|
+
},
|
|
1458
|
+
include: ["src/**/*"],
|
|
1459
|
+
};
|
|
1460
|
+
fs.writeFileSync(path.join(serverDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
|
|
1461
|
+
// Create src directory
|
|
1462
|
+
fs.mkdirSync(path.join(serverDir, "src"), { recursive: true });
|
|
1463
|
+
// Generate tool definitions
|
|
1464
|
+
const toolDefs = tools.length > 0
|
|
1465
|
+
? `
|
|
1466
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1467
|
+
tools: [
|
|
1468
|
+
${tools
|
|
1469
|
+
.map((toolName) => ` { name: "${toolName}", description: "TODO: Add description for ${toolName}", inputSchema: { type: "object", properties: {}, required: [] } },`)
|
|
1470
|
+
.join("\n")}
|
|
1471
|
+
],
|
|
1472
|
+
}));
|
|
1473
|
+
|
|
1474
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1475
|
+
switch (request.params.name) {
|
|
1476
|
+
${tools
|
|
1477
|
+
.map((toolName) => ` case "${toolName}":
|
|
1478
|
+
// TODO: Implement ${toolName} tool
|
|
1479
|
+
return {
|
|
1480
|
+
content: [{ type: "text", text: "${toolName} executed successfully" }],
|
|
1481
|
+
};`)
|
|
1482
|
+
.join("\n")}
|
|
1483
|
+
default:
|
|
1484
|
+
throw new Error(\`Unknown tool: \${request.params.name}\`);
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
`
|
|
1488
|
+
: `
|
|
1489
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1490
|
+
tools: [
|
|
1491
|
+
{
|
|
1492
|
+
name: "hello",
|
|
1493
|
+
description: "A simple hello tool",
|
|
1494
|
+
inputSchema: {
|
|
1495
|
+
type: "object",
|
|
1496
|
+
properties: {
|
|
1497
|
+
name: { type: "string", description: "Name to greet" },
|
|
1498
|
+
},
|
|
1499
|
+
required: ["name"],
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
],
|
|
1503
|
+
}));
|
|
1504
|
+
|
|
1505
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1506
|
+
if (request.params.name === "hello") {
|
|
1507
|
+
const args = request.params.arguments as { name?: string };
|
|
1508
|
+
return {
|
|
1509
|
+
content: [{
|
|
1510
|
+
type: "text",
|
|
1511
|
+
text: \`Hello, \${args.name || "World"}!\`,
|
|
1512
|
+
}],
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
throw new Error(\`Unknown tool: \${request.params.name}\`);
|
|
1516
|
+
});
|
|
1517
|
+
`;
|
|
1518
|
+
// Main server file
|
|
1519
|
+
const mainFile = `/**
|
|
1520
|
+
* ${serverName} - MCP Server
|
|
1521
|
+
* ${description}
|
|
1522
|
+
*
|
|
1523
|
+
* Generated by NeuroLink CLI
|
|
1524
|
+
*/
|
|
1525
|
+
|
|
1526
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1527
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1528
|
+
import {
|
|
1529
|
+
CallToolRequestSchema,
|
|
1530
|
+
ListToolsRequestSchema,
|
|
1531
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1532
|
+
|
|
1533
|
+
const server = new Server(
|
|
1534
|
+
{
|
|
1535
|
+
name: "${serverName}",
|
|
1536
|
+
version: "1.0.0",
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
capabilities: {
|
|
1540
|
+
tools: {},
|
|
1541
|
+
},
|
|
1542
|
+
},
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
${toolDefs}
|
|
1546
|
+
|
|
1547
|
+
async function main() {
|
|
1548
|
+
const transport = new StdioServerTransport();
|
|
1549
|
+
await server.connect(transport);
|
|
1550
|
+
console.error("${serverName} MCP server running on stdio");
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
main().catch(console.error);
|
|
1554
|
+
`;
|
|
1555
|
+
fs.writeFileSync(path.join(serverDir, "src", "index.ts"), mainFile);
|
|
1556
|
+
// README.md
|
|
1557
|
+
const readme = `# ${serverName}
|
|
1558
|
+
|
|
1559
|
+
${description}
|
|
1560
|
+
|
|
1561
|
+
## Installation
|
|
1562
|
+
|
|
1563
|
+
\`\`\`bash
|
|
1564
|
+
npm install
|
|
1565
|
+
npm run build
|
|
1566
|
+
\`\`\`
|
|
1567
|
+
|
|
1568
|
+
## Usage
|
|
1569
|
+
|
|
1570
|
+
Add to your NeuroLink configuration:
|
|
1571
|
+
|
|
1572
|
+
\`\`\`bash
|
|
1573
|
+
neurolink mcp add ${serverName} node ${path.join(serverDir, "dist", "index.js")}
|
|
1574
|
+
\`\`\`
|
|
1575
|
+
|
|
1576
|
+
## Development
|
|
1577
|
+
|
|
1578
|
+
\`\`\`bash
|
|
1579
|
+
npm run dev # Watch mode
|
|
1580
|
+
npm run build # Build for production
|
|
1581
|
+
\`\`\`
|
|
1582
|
+
|
|
1583
|
+
## Tools
|
|
1584
|
+
|
|
1585
|
+
${tools.length > 0 ? tools.map((t) => `- **${t}**: TODO: Add description`).join("\n") : "- **hello**: A simple hello tool"}
|
|
1586
|
+
`;
|
|
1587
|
+
fs.writeFileSync(path.join(serverDir, "README.md"), readme);
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Generate Python MCP server
|
|
1591
|
+
*/
|
|
1592
|
+
static async generatePythonServer(serverDir, serverName, description, tools) {
|
|
1593
|
+
// requirements.txt
|
|
1594
|
+
const requirements = `mcp>=1.0.0
|
|
1595
|
+
`;
|
|
1596
|
+
fs.writeFileSync(path.join(serverDir, "requirements.txt"), requirements);
|
|
1597
|
+
// Generate tool definitions - Python MCP SDK expects a single @server.call_tool() handler
|
|
1598
|
+
// that dispatches on the tool name
|
|
1599
|
+
const toolDefs = tools.length > 0
|
|
1600
|
+
? (() => {
|
|
1601
|
+
const branches = tools
|
|
1602
|
+
.map((toolName, index) => {
|
|
1603
|
+
const _safeName = toolName.replace(/-/g, "_");
|
|
1604
|
+
const keyword = index === 0 ? "if" : "elif";
|
|
1605
|
+
return ` ${keyword} name == "${toolName}":
|
|
1606
|
+
# TODO: Implement ${toolName} tool
|
|
1607
|
+
return [TextContent(type="text", text="${toolName} executed successfully")]`;
|
|
1608
|
+
})
|
|
1609
|
+
.join("\n");
|
|
1610
|
+
return `
|
|
1611
|
+
@server.call_tool()
|
|
1612
|
+
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
1613
|
+
"""Handle tool calls."""
|
|
1614
|
+
${branches}
|
|
1615
|
+
else:
|
|
1616
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
1617
|
+
`;
|
|
1618
|
+
})()
|
|
1619
|
+
: `
|
|
1620
|
+
@server.call_tool()
|
|
1621
|
+
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
1622
|
+
"""Handle tool calls."""
|
|
1623
|
+
if name == "hello":
|
|
1624
|
+
name_arg = arguments.get("name", "World")
|
|
1625
|
+
return [TextContent(type="text", text=f"Hello, {name_arg}!")]
|
|
1626
|
+
else:
|
|
1627
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
1628
|
+
`;
|
|
1629
|
+
const toolListDefs = tools.length > 0
|
|
1630
|
+
? tools
|
|
1631
|
+
.map((toolName) => `
|
|
1632
|
+
Tool(
|
|
1633
|
+
name="${toolName}",
|
|
1634
|
+
description="TODO: Add description for ${toolName}",
|
|
1635
|
+
inputSchema={
|
|
1636
|
+
"type": "object",
|
|
1637
|
+
"properties": {},
|
|
1638
|
+
"required": [],
|
|
1639
|
+
},
|
|
1640
|
+
),`)
|
|
1641
|
+
.join("\n")
|
|
1642
|
+
: `
|
|
1643
|
+
Tool(
|
|
1644
|
+
name="hello",
|
|
1645
|
+
description="A simple hello tool",
|
|
1646
|
+
inputSchema={
|
|
1647
|
+
"type": "object",
|
|
1648
|
+
"properties": {
|
|
1649
|
+
"name": {"type": "string", "description": "Name to greet"},
|
|
1650
|
+
},
|
|
1651
|
+
"required": ["name"],
|
|
1652
|
+
},
|
|
1653
|
+
),`;
|
|
1654
|
+
// Main server file
|
|
1655
|
+
const mainFile = `"""
|
|
1656
|
+
${serverName} - MCP Server
|
|
1657
|
+
${description}
|
|
1658
|
+
|
|
1659
|
+
Generated by NeuroLink CLI
|
|
1660
|
+
"""
|
|
1661
|
+
|
|
1662
|
+
import asyncio
|
|
1663
|
+
from mcp.server.models import InitializationOptions
|
|
1664
|
+
from mcp.server import NotificationOptions, Server
|
|
1665
|
+
from mcp.types import (
|
|
1666
|
+
Resource,
|
|
1667
|
+
Tool,
|
|
1668
|
+
TextContent,
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
server = Server("${serverName}")
|
|
1672
|
+
|
|
1673
|
+
@server.list_tools()
|
|
1674
|
+
async def list_tools() -> list[Tool]:
|
|
1675
|
+
"""List available tools."""
|
|
1676
|
+
return [${toolListDefs}
|
|
1677
|
+
]
|
|
1678
|
+
|
|
1679
|
+
${toolDefs}
|
|
1680
|
+
|
|
1681
|
+
async def main():
|
|
1682
|
+
from mcp.server.stdio import stdio_server
|
|
1683
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
1684
|
+
await server.run(
|
|
1685
|
+
read_stream,
|
|
1686
|
+
write_stream,
|
|
1687
|
+
InitializationOptions(
|
|
1688
|
+
server_name="${serverName}",
|
|
1689
|
+
server_version="1.0.0",
|
|
1690
|
+
capabilities=server.get_capabilities(
|
|
1691
|
+
notification_options=NotificationOptions(),
|
|
1692
|
+
experimental_capabilities={},
|
|
1693
|
+
),
|
|
1694
|
+
),
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
if __name__ == "__main__":
|
|
1698
|
+
asyncio.run(main())
|
|
1699
|
+
`;
|
|
1700
|
+
fs.writeFileSync(path.join(serverDir, "server.py"), mainFile);
|
|
1701
|
+
// README.md
|
|
1702
|
+
const readme = `# ${serverName}
|
|
1703
|
+
|
|
1704
|
+
${description}
|
|
1705
|
+
|
|
1706
|
+
## Installation
|
|
1707
|
+
|
|
1708
|
+
\`\`\`bash
|
|
1709
|
+
pip install -r requirements.txt
|
|
1710
|
+
\`\`\`
|
|
1711
|
+
|
|
1712
|
+
## Usage
|
|
1713
|
+
|
|
1714
|
+
Add to your NeuroLink configuration:
|
|
1715
|
+
|
|
1716
|
+
\`\`\`bash
|
|
1717
|
+
neurolink mcp add ${serverName} python ${path.join(serverDir, "server.py")}
|
|
1718
|
+
\`\`\`
|
|
1719
|
+
|
|
1720
|
+
## Tools
|
|
1721
|
+
|
|
1722
|
+
${tools.length > 0 ? tools.map((t) => `- **${t}**: TODO: Add description`).join("\n") : "- **hello**: A simple hello tool"}
|
|
1723
|
+
`;
|
|
1724
|
+
fs.writeFileSync(path.join(serverDir, "README.md"), readme);
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Generate basic JavaScript MCP server
|
|
1728
|
+
*/
|
|
1729
|
+
static async generateBasicServer(serverDir, serverName, description, tools) {
|
|
1730
|
+
// package.json
|
|
1731
|
+
const packageJson = {
|
|
1732
|
+
name: serverName,
|
|
1733
|
+
version: "1.0.0",
|
|
1734
|
+
description,
|
|
1735
|
+
type: "module",
|
|
1736
|
+
main: "server.js",
|
|
1737
|
+
scripts: {
|
|
1738
|
+
start: "node server.js",
|
|
1739
|
+
},
|
|
1740
|
+
dependencies: {
|
|
1741
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
1742
|
+
},
|
|
1743
|
+
};
|
|
1744
|
+
fs.writeFileSync(path.join(serverDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
1745
|
+
// Generate tool handler
|
|
1746
|
+
const toolHandler = tools.length > 0
|
|
1747
|
+
? `const handlers = {
|
|
1748
|
+
${tools.map((t) => `"${t}": async (args) => ({ type: "text", text: "${t} executed" })`).join(",\n ")}
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
if (handlers[request.params.name]) {
|
|
1752
|
+
return { content: [await handlers[request.params.name](request.params.arguments)] };
|
|
1753
|
+
}`
|
|
1754
|
+
: `if (request.params.name === "hello") {
|
|
1755
|
+
const args = request.params.arguments || {};
|
|
1756
|
+
return {
|
|
1757
|
+
content: [{
|
|
1758
|
+
type: "text",
|
|
1759
|
+
text: \`Hello, \${args.name || "World"}!\`,
|
|
1760
|
+
}],
|
|
1761
|
+
};
|
|
1762
|
+
}`;
|
|
1763
|
+
const toolList = tools.length > 0
|
|
1764
|
+
? tools.map((t) => `{ name: "${t}", description: "TODO: Add description", inputSchema: { type: "object", properties: {} } }`)
|
|
1765
|
+
: [
|
|
1766
|
+
`{ name: "hello", description: "A simple hello tool", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } }`,
|
|
1767
|
+
];
|
|
1768
|
+
// Main server file
|
|
1769
|
+
const mainFile = `/**
|
|
1770
|
+
* ${serverName} - MCP Server
|
|
1771
|
+
* ${description}
|
|
1772
|
+
*
|
|
1773
|
+
* Generated by NeuroLink CLI
|
|
1774
|
+
*/
|
|
1775
|
+
|
|
1776
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1777
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1778
|
+
|
|
1779
|
+
const server = new Server(
|
|
1780
|
+
{ name: "${serverName}", version: "1.0.0" },
|
|
1781
|
+
{ capabilities: { tools: {} } }
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
server.setRequestHandler({ method: "tools/list" }, async () => ({
|
|
1785
|
+
tools: [
|
|
1786
|
+
${toolList.join(",\n ")}
|
|
1787
|
+
],
|
|
1788
|
+
}));
|
|
1789
|
+
|
|
1790
|
+
server.setRequestHandler({ method: "tools/call" }, async (request) => {
|
|
1791
|
+
${toolHandler}
|
|
1792
|
+
throw new Error(\`Unknown tool: \${request.params.name}\`);
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
const transport = new StdioServerTransport();
|
|
1796
|
+
server.connect(transport).then(() => {
|
|
1797
|
+
console.error("${serverName} MCP server running on stdio");
|
|
1798
|
+
}).catch(console.error);
|
|
1799
|
+
`;
|
|
1800
|
+
fs.writeFileSync(path.join(serverDir, "server.js"), mainFile);
|
|
1801
|
+
// README.md
|
|
1802
|
+
const readme = `# ${serverName}
|
|
1803
|
+
|
|
1804
|
+
${description}
|
|
1805
|
+
|
|
1806
|
+
## Installation
|
|
1807
|
+
|
|
1808
|
+
\`\`\`bash
|
|
1809
|
+
npm install
|
|
1810
|
+
\`\`\`
|
|
1811
|
+
|
|
1812
|
+
## Usage
|
|
1813
|
+
|
|
1814
|
+
\`\`\`bash
|
|
1815
|
+
neurolink mcp add ${serverName} node ${path.join(serverDir, "server.js")}
|
|
1816
|
+
\`\`\`
|
|
1817
|
+
|
|
1818
|
+
## Tools
|
|
1819
|
+
|
|
1820
|
+
${tools.length > 0 ? tools.map((t) => `- **${t}**: TODO: Add description`).join("\n") : "- **hello**: A simple hello tool"}
|
|
1821
|
+
`;
|
|
1822
|
+
fs.writeFileSync(path.join(serverDir, "README.md"), readme);
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Infer tool category from name and description
|
|
1826
|
+
*/
|
|
1827
|
+
static inferToolCategory(name, description) {
|
|
1828
|
+
const combined = (name + " " + description).toLowerCase();
|
|
1829
|
+
if (combined.includes("git") || combined.includes("commit")) {
|
|
1830
|
+
return "version-control";
|
|
1831
|
+
}
|
|
1832
|
+
if (combined.includes("file") ||
|
|
1833
|
+
combined.includes("read") ||
|
|
1834
|
+
combined.includes("write") ||
|
|
1835
|
+
combined.includes("directory")) {
|
|
1836
|
+
return "file-system";
|
|
1837
|
+
}
|
|
1838
|
+
if (combined.includes("api") ||
|
|
1839
|
+
combined.includes("http") ||
|
|
1840
|
+
combined.includes("request") ||
|
|
1841
|
+
combined.includes("fetch")) {
|
|
1842
|
+
return "api";
|
|
1843
|
+
}
|
|
1844
|
+
if (combined.includes("database") ||
|
|
1845
|
+
combined.includes("query") ||
|
|
1846
|
+
combined.includes("sql")) {
|
|
1847
|
+
return "database";
|
|
1848
|
+
}
|
|
1849
|
+
if (combined.includes("search") ||
|
|
1850
|
+
combined.includes("find") ||
|
|
1851
|
+
combined.includes("grep")) {
|
|
1852
|
+
return "search";
|
|
1853
|
+
}
|
|
1854
|
+
if (combined.includes("auth") ||
|
|
1855
|
+
combined.includes("login") ||
|
|
1856
|
+
combined.includes("token")) {
|
|
1857
|
+
return "authentication";
|
|
1858
|
+
}
|
|
1859
|
+
return "general";
|
|
1860
|
+
}
|
|
1861
|
+
// ========================================
|
|
1862
|
+
// REGISTRY COMMANDS (Phase 8.39.0)
|
|
1863
|
+
// ========================================
|
|
1864
|
+
/**
|
|
1865
|
+
* Create the registry subcommand
|
|
1866
|
+
*/
|
|
1867
|
+
static createRegistryCommand() {
|
|
1868
|
+
return {
|
|
1869
|
+
command: "registry <action>",
|
|
1870
|
+
describe: "Browse and search the MCP server registry",
|
|
1871
|
+
builder: (yargs) => {
|
|
1872
|
+
return yargs
|
|
1873
|
+
.command("search [query]", "Search for MCP servers in the registry", (yargs) => yargs
|
|
1874
|
+
.positional("query", {
|
|
1875
|
+
type: "string",
|
|
1876
|
+
description: "Search query",
|
|
1877
|
+
})
|
|
1878
|
+
.option("category", {
|
|
1879
|
+
type: "string",
|
|
1880
|
+
alias: "c",
|
|
1881
|
+
description: "Filter by category",
|
|
1882
|
+
})
|
|
1883
|
+
.option("tag", {
|
|
1884
|
+
type: "array",
|
|
1885
|
+
alias: "t",
|
|
1886
|
+
description: "Filter by tags",
|
|
1887
|
+
})
|
|
1888
|
+
.option("verified", {
|
|
1889
|
+
type: "boolean",
|
|
1890
|
+
default: false,
|
|
1891
|
+
description: "Show only verified servers",
|
|
1892
|
+
})
|
|
1893
|
+
.option("limit", {
|
|
1894
|
+
type: "number",
|
|
1895
|
+
default: 10,
|
|
1896
|
+
description: "Maximum results to show",
|
|
1897
|
+
}), (argv) => this.executeRegistrySearch(argv))
|
|
1898
|
+
.command("list", "List all available servers in the registry", (yargs) => yargs
|
|
1899
|
+
.option("category", {
|
|
1900
|
+
type: "string",
|
|
1901
|
+
description: "Filter by category",
|
|
1902
|
+
})
|
|
1903
|
+
.option("limit", {
|
|
1904
|
+
type: "number",
|
|
1905
|
+
default: 25,
|
|
1906
|
+
description: "Maximum results",
|
|
1907
|
+
}), (argv) => this.executeRegistryList(argv))
|
|
1908
|
+
.command("info <server>", "Get detailed information about a server", (yargs) => yargs.positional("server", {
|
|
1909
|
+
type: "string",
|
|
1910
|
+
description: "Server ID to get info for",
|
|
1911
|
+
demandOption: true,
|
|
1912
|
+
}), (argv) => this.executeRegistryInfo(argv))
|
|
1913
|
+
.command("categories", "List all server categories", () => { }, () => this.executeRegistryCategories())
|
|
1914
|
+
.command("popular", "Show popular MCP servers", (yargs) => yargs.option("limit", {
|
|
1915
|
+
type: "number",
|
|
1916
|
+
default: 10,
|
|
1917
|
+
description: "Number of servers to show",
|
|
1918
|
+
}), (argv) => this.executeRegistryPopular(argv))
|
|
1919
|
+
.demandCommand(1, "Please specify a registry action")
|
|
1920
|
+
.help();
|
|
1921
|
+
},
|
|
1922
|
+
handler: () => { },
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Execute registry search
|
|
1927
|
+
*/
|
|
1928
|
+
static async executeRegistrySearch(argv) {
|
|
1929
|
+
try {
|
|
1930
|
+
const { globalMCPRegistryClient } = await import("../../lib/mcp/mcpRegistryClient.js");
|
|
1931
|
+
const spinner = argv.quiet ? null : ora("Searching registry...").start();
|
|
1932
|
+
const results = await withTimeout(globalMCPRegistryClient.search({
|
|
1933
|
+
query: argv.query,
|
|
1934
|
+
categories: argv.category ? [argv.category] : undefined,
|
|
1935
|
+
tags: argv.tag,
|
|
1936
|
+
verifiedOnly: argv.verified,
|
|
1937
|
+
limit: argv.limit ?? 10,
|
|
1938
|
+
}), 30_000, ErrorFactory.toolTimeout("registrySearch", 30_000));
|
|
1939
|
+
if (spinner) {
|
|
1940
|
+
spinner.succeed(`Found ${results.totalCount} servers`);
|
|
1941
|
+
}
|
|
1942
|
+
if (results.entries.length === 0) {
|
|
1943
|
+
logger.always(chalk.yellow("No servers found matching your criteria."));
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
if (argv.format === "json") {
|
|
1947
|
+
logger.always(JSON.stringify(results.entries, null, 2));
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
logger.always(chalk.bold("\n📦 MCP Server Registry Results:\n"));
|
|
1951
|
+
for (const entry of results.entries) {
|
|
1952
|
+
const verified = entry.verified ? chalk.green(" ✓") : "";
|
|
1953
|
+
logger.always(`${chalk.cyan.bold(entry.name)}${verified} (${entry.id})`);
|
|
1954
|
+
logger.always(` ${chalk.gray(entry.description)}`);
|
|
1955
|
+
logger.always(` Version: ${entry.version} | Categories: ${entry.categories?.join(", ") ?? "none"}`);
|
|
1956
|
+
if (entry.tags?.length) {
|
|
1957
|
+
logger.always(` Tags: ${entry.tags.join(", ")}`);
|
|
1958
|
+
}
|
|
1959
|
+
logger.always();
|
|
1960
|
+
}
|
|
1961
|
+
if (results.hasMore) {
|
|
1962
|
+
logger.always(chalk.dim(`Showing ${results.entries.length} of ${results.totalCount} results. Use --limit to see more.`));
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
catch (error) {
|
|
1966
|
+
logger.error(chalk.red(`Registry search failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1967
|
+
process.exit(1);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Execute registry list
|
|
1972
|
+
*/
|
|
1973
|
+
static async executeRegistryList(argv) {
|
|
1974
|
+
try {
|
|
1975
|
+
const { globalMCPRegistryClient } = await import("../../lib/mcp/mcpRegistryClient.js");
|
|
1976
|
+
const spinner = argv.quiet ? null : ora("Loading registry...").start();
|
|
1977
|
+
const results = await withTimeout(globalMCPRegistryClient.search({
|
|
1978
|
+
categories: argv.category ? [argv.category] : undefined,
|
|
1979
|
+
limit: argv.limit ?? 25,
|
|
1980
|
+
sortBy: "name",
|
|
1981
|
+
sortDirection: "asc",
|
|
1982
|
+
}), 30_000, ErrorFactory.toolTimeout("registrySearch", 30_000));
|
|
1983
|
+
if (spinner) {
|
|
1984
|
+
spinner.succeed(`Found ${results.totalCount} servers`);
|
|
1985
|
+
}
|
|
1986
|
+
if (argv.format === "json") {
|
|
1987
|
+
logger.always(JSON.stringify(results.entries, null, 2));
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
logger.always(chalk.bold("\n📦 Available MCP Servers:\n"));
|
|
1991
|
+
// Group by category
|
|
1992
|
+
const byCategory = new Map();
|
|
1993
|
+
for (const entry of results.entries) {
|
|
1994
|
+
const category = entry.categories?.[0] ?? "uncategorized";
|
|
1995
|
+
if (!byCategory.has(category)) {
|
|
1996
|
+
byCategory.set(category, []);
|
|
1997
|
+
}
|
|
1998
|
+
const catList = byCategory.get(category);
|
|
1999
|
+
if (catList) {
|
|
2000
|
+
catList.push(entry);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
for (const [category, entries] of byCategory) {
|
|
2004
|
+
logger.always(chalk.yellow.bold(`\n${category.toUpperCase()}`));
|
|
2005
|
+
for (const entry of entries) {
|
|
2006
|
+
const verified = entry.verified ? chalk.green("✓") : " ";
|
|
2007
|
+
logger.always(` ${verified} ${chalk.cyan(entry.id.padEnd(20))} ${entry.description.slice(0, 50)}${entry.description.length > 50 ? "..." : ""}`);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
catch (error) {
|
|
2012
|
+
logger.error(chalk.red(`Registry list failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2013
|
+
process.exit(1);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Execute registry info
|
|
2018
|
+
*/
|
|
2019
|
+
static async executeRegistryInfo(argv) {
|
|
2020
|
+
try {
|
|
2021
|
+
const { globalMCPRegistryClient } = await import("../../lib/mcp/mcpRegistryClient.js");
|
|
2022
|
+
const serverId = argv.server;
|
|
2023
|
+
const entry = await withTimeout(globalMCPRegistryClient.getEntry(serverId), 30_000, ErrorFactory.toolTimeout("registryGetEntry", 30_000));
|
|
2024
|
+
if (!entry) {
|
|
2025
|
+
logger.error(chalk.red(`Server not found: ${serverId}`));
|
|
2026
|
+
process.exit(1);
|
|
2027
|
+
}
|
|
2028
|
+
if (argv.format === "json") {
|
|
2029
|
+
logger.always(JSON.stringify(entry, null, 2));
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
logger.always(chalk.bold(`\n📦 ${entry.name}`));
|
|
2033
|
+
logger.always(chalk.gray(`ID: ${entry.id}`));
|
|
2034
|
+
if (entry.verified) {
|
|
2035
|
+
logger.always(chalk.green("✓ Verified"));
|
|
2036
|
+
}
|
|
2037
|
+
logger.always();
|
|
2038
|
+
logger.always(entry.description);
|
|
2039
|
+
logger.always();
|
|
2040
|
+
logger.always(chalk.bold("Details:"));
|
|
2041
|
+
logger.always(` Version: ${entry.version}`);
|
|
2042
|
+
if (entry.author) {
|
|
2043
|
+
logger.always(` Author: ${entry.author}`);
|
|
2044
|
+
}
|
|
2045
|
+
if (entry.license) {
|
|
2046
|
+
logger.always(` License: ${entry.license}`);
|
|
2047
|
+
}
|
|
2048
|
+
if (entry.homepage) {
|
|
2049
|
+
logger.always(` Homepage: ${entry.homepage}`);
|
|
2050
|
+
}
|
|
2051
|
+
if (entry.repository) {
|
|
2052
|
+
logger.always(` Repository: ${entry.repository}`);
|
|
2053
|
+
}
|
|
2054
|
+
if (entry.categories?.length) {
|
|
2055
|
+
logger.always(`\n${chalk.bold("Categories:")} ${entry.categories.join(", ")}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (entry.tags?.length) {
|
|
2058
|
+
logger.always(`${chalk.bold("Tags:")} ${entry.tags.join(", ")}`);
|
|
2059
|
+
}
|
|
2060
|
+
if (entry.transports?.length) {
|
|
2061
|
+
logger.always(`${chalk.bold("Transports:")} ${entry.transports.join(", ")}`);
|
|
2062
|
+
}
|
|
2063
|
+
if (entry.tools?.length) {
|
|
2064
|
+
logger.always(`\n${chalk.bold("Tools:")} (${entry.tools.length})`);
|
|
2065
|
+
for (const tool of entry.tools.slice(0, 10)) {
|
|
2066
|
+
logger.always(` • ${tool}`);
|
|
2067
|
+
}
|
|
2068
|
+
if (entry.tools.length > 10) {
|
|
2069
|
+
logger.always(` ... and ${entry.tools.length - 10} more`);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
if (entry.requiredEnvVars?.length) {
|
|
2073
|
+
logger.always(`\n${chalk.bold("Required Environment Variables:")}`);
|
|
2074
|
+
for (const envVar of entry.requiredEnvVars) {
|
|
2075
|
+
const isSet = process.env[envVar] ? chalk.green("✓") : chalk.red("✗");
|
|
2076
|
+
logger.always(` ${isSet} ${envVar}`);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
// Installation command
|
|
2080
|
+
logger.always(`\n${chalk.bold("Installation:")}`);
|
|
2081
|
+
if (entry.npmPackage) {
|
|
2082
|
+
logger.always(chalk.cyan(` neurolink mcp install ${entry.id}`));
|
|
2083
|
+
logger.always(chalk.dim(` or: npx -y ${entry.npmPackage}`));
|
|
2084
|
+
}
|
|
2085
|
+
else if (entry.command) {
|
|
2086
|
+
logger.always(chalk.cyan(` ${entry.command} ${entry.args?.join(" ") ?? ""}`));
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
catch (error) {
|
|
2090
|
+
logger.error(chalk.red(`Registry info failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2091
|
+
process.exit(1);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* Execute registry categories
|
|
2096
|
+
*/
|
|
2097
|
+
static async executeRegistryCategories() {
|
|
2098
|
+
try {
|
|
2099
|
+
const { globalMCPRegistryClient } = await import("../../lib/mcp/mcpRegistryClient.js");
|
|
2100
|
+
const categories = await withTimeout(globalMCPRegistryClient.getCategories(), 30_000, ErrorFactory.toolTimeout("registryGetCategories", 30_000));
|
|
2101
|
+
logger.always(chalk.bold("\n📁 Available Categories:\n"));
|
|
2102
|
+
for (const category of categories) {
|
|
2103
|
+
const entries = await withTimeout(globalMCPRegistryClient.getByCategory(category), 30_000, ErrorFactory.toolTimeout("registryGetByCategory", 30_000));
|
|
2104
|
+
logger.always(` ${chalk.cyan(category.padEnd(20))} (${entries.length} servers)`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
catch (error) {
|
|
2108
|
+
logger.error(chalk.red(`Registry categories failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2109
|
+
process.exit(1);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Execute registry popular
|
|
2114
|
+
*/
|
|
2115
|
+
static async executeRegistryPopular(argv) {
|
|
2116
|
+
try {
|
|
2117
|
+
const { globalMCPRegistryClient } = await import("../../lib/mcp/mcpRegistryClient.js");
|
|
2118
|
+
const spinner = argv.quiet
|
|
2119
|
+
? null
|
|
2120
|
+
: ora("Loading popular servers...").start();
|
|
2121
|
+
const entries = await withTimeout(globalMCPRegistryClient.getPopularServers(argv.limit ?? 10), 30_000, ErrorFactory.toolTimeout("registryGetPopular", 30_000));
|
|
2122
|
+
if (spinner) {
|
|
2123
|
+
spinner.succeed(`Top ${entries.length} popular servers`);
|
|
2124
|
+
}
|
|
2125
|
+
if (argv.format === "json") {
|
|
2126
|
+
logger.always(JSON.stringify(entries, null, 2));
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
logger.always(chalk.bold("\n🌟 Popular MCP Servers:\n"));
|
|
2130
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2131
|
+
const entry = entries[i];
|
|
2132
|
+
const rank = chalk.yellow(`#${i + 1}`);
|
|
2133
|
+
const verified = entry.verified ? chalk.green(" ✓") : "";
|
|
2134
|
+
logger.always(`${rank} ${chalk.cyan.bold(entry.name)}${verified}`);
|
|
2135
|
+
logger.always(` ${chalk.gray(entry.description)}`);
|
|
2136
|
+
logger.always(` ${chalk.dim(`Install: neurolink mcp install ${entry.id}`)}`);
|
|
2137
|
+
logger.always();
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
catch (error) {
|
|
2141
|
+
logger.error(chalk.red(`Registry popular failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2142
|
+
process.exit(1);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Build options for annotate command
|
|
2147
|
+
*/
|
|
2148
|
+
static buildAnnotateOptions(yargs) {
|
|
2149
|
+
return yargs
|
|
2150
|
+
.option("tool", {
|
|
2151
|
+
type: "string",
|
|
2152
|
+
alias: "t",
|
|
2153
|
+
description: "Tool name to annotate",
|
|
2154
|
+
})
|
|
2155
|
+
.option("server", {
|
|
2156
|
+
type: "string",
|
|
2157
|
+
alias: "s",
|
|
2158
|
+
description: "Server ID containing the tool",
|
|
2159
|
+
})
|
|
2160
|
+
.option("annotations", {
|
|
2161
|
+
type: "string",
|
|
2162
|
+
alias: "a",
|
|
2163
|
+
description: "Annotations as JSON string",
|
|
2164
|
+
})
|
|
2165
|
+
.option("read-only", {
|
|
2166
|
+
type: "boolean",
|
|
2167
|
+
description: "Set readOnlyHint annotation",
|
|
2168
|
+
})
|
|
2169
|
+
.option("destructive", {
|
|
2170
|
+
type: "boolean",
|
|
2171
|
+
description: "Set destructiveHint annotation",
|
|
2172
|
+
})
|
|
2173
|
+
.option("idempotent", {
|
|
2174
|
+
type: "boolean",
|
|
2175
|
+
description: "Set idempotentHint annotation",
|
|
2176
|
+
})
|
|
2177
|
+
.option("requires-confirmation", {
|
|
2178
|
+
type: "boolean",
|
|
2179
|
+
description: "Set requiresConfirmation annotation",
|
|
2180
|
+
})
|
|
2181
|
+
.option("tags", {
|
|
2182
|
+
type: "array",
|
|
2183
|
+
description: "Tags for the tool",
|
|
2184
|
+
})
|
|
2185
|
+
.option("estimated-duration", {
|
|
2186
|
+
type: "number",
|
|
2187
|
+
description: "Estimated execution duration in ms",
|
|
2188
|
+
})
|
|
2189
|
+
.option("rate-limit", {
|
|
2190
|
+
type: "number",
|
|
2191
|
+
description: "Rate limit hint (calls per minute)",
|
|
2192
|
+
})
|
|
2193
|
+
.option("cost", {
|
|
2194
|
+
type: "number",
|
|
2195
|
+
description: "Cost hint (arbitrary units)",
|
|
2196
|
+
})
|
|
2197
|
+
.option("complexity", {
|
|
2198
|
+
choices: ["simple", "medium", "complex"],
|
|
2199
|
+
description: "Complexity level",
|
|
2200
|
+
})
|
|
2201
|
+
.option("security-level", {
|
|
2202
|
+
choices: ["public", "internal", "restricted"],
|
|
2203
|
+
description: "Security classification",
|
|
2204
|
+
})
|
|
2205
|
+
.option("audit", {
|
|
2206
|
+
type: "boolean",
|
|
2207
|
+
description: "Whether tool execution should be audited",
|
|
2208
|
+
})
|
|
2209
|
+
.option("infer", {
|
|
2210
|
+
type: "boolean",
|
|
2211
|
+
description: "Infer annotations from tool name/description",
|
|
2212
|
+
})
|
|
2213
|
+
.option("list", {
|
|
2214
|
+
type: "boolean",
|
|
2215
|
+
description: "List all tools with their current annotations",
|
|
2216
|
+
})
|
|
2217
|
+
.option("validate", {
|
|
2218
|
+
type: "boolean",
|
|
2219
|
+
description: "Validate annotations without applying",
|
|
2220
|
+
})
|
|
2221
|
+
.example("neurolink mcp annotate --tool readFile --read-only", "Mark readFile as read-only")
|
|
2222
|
+
.example("neurolink mcp annotate --tool deleteFile --destructive --requires-confirmation", "Mark deleteFile as destructive requiring confirmation")
|
|
2223
|
+
.example('neurolink mcp annotate --tool myTool --annotations \'{"readOnlyHint": true, "tags": ["data"]}\'', "Set custom annotations via JSON")
|
|
2224
|
+
.example("neurolink mcp annotate --list", "List all tools with annotations")
|
|
2225
|
+
.example("neurolink mcp annotate --tool myTool --infer", "Infer annotations from tool name/description");
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Execute annotate command
|
|
2229
|
+
*/
|
|
2230
|
+
static async executeAnnotate(argv) {
|
|
2231
|
+
try {
|
|
2232
|
+
const sdk = new NeuroLink();
|
|
2233
|
+
const servers = await sdk.listMCPServers();
|
|
2234
|
+
// List mode - show all tools with annotations
|
|
2235
|
+
if (argv.list) {
|
|
2236
|
+
const spinner = argv.quiet
|
|
2237
|
+
? null
|
|
2238
|
+
: ora("Loading tool annotations...").start();
|
|
2239
|
+
const allTools = [];
|
|
2240
|
+
for (const server of servers) {
|
|
2241
|
+
if (server.status !== "connected") {
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
for (const tool of server.tools || []) {
|
|
2245
|
+
const existing = tool
|
|
2246
|
+
.annotations;
|
|
2247
|
+
const annotations = existing ??
|
|
2248
|
+
inferAnnotations({
|
|
2249
|
+
name: tool.name,
|
|
2250
|
+
description: tool.description,
|
|
2251
|
+
});
|
|
2252
|
+
allTools.push({
|
|
2253
|
+
serverName: server.name,
|
|
2254
|
+
serverId: server.id,
|
|
2255
|
+
toolName: tool.name,
|
|
2256
|
+
description: tool.description,
|
|
2257
|
+
annotations,
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
if (spinner) {
|
|
2262
|
+
spinner.succeed(`Found ${allTools.length} tools`);
|
|
2263
|
+
}
|
|
2264
|
+
if (argv.format === "json") {
|
|
2265
|
+
logger.always(JSON.stringify(allTools, null, 2));
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
logger.always(chalk.bold("\n Tool Annotations:\n"));
|
|
2269
|
+
// Group by server
|
|
2270
|
+
const byServer = allTools.reduce((acc, tool) => {
|
|
2271
|
+
if (!acc[tool.serverId]) {
|
|
2272
|
+
acc[tool.serverId] = {
|
|
2273
|
+
serverName: tool.serverName,
|
|
2274
|
+
tools: [],
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
acc[tool.serverId].tools.push(tool);
|
|
2278
|
+
return acc;
|
|
2279
|
+
}, {});
|
|
2280
|
+
for (const [serverId, { serverName, tools }] of Object.entries(byServer)) {
|
|
2281
|
+
logger.always(chalk.cyan.bold(`\n${serverName} (${serverId}):`));
|
|
2282
|
+
for (const tool of tools) {
|
|
2283
|
+
const annotationStr = getAnnotationSummary(tool.annotations);
|
|
2284
|
+
logger.always(` ${chalk.yellow(tool.toolName)} ${chalk.gray(annotationStr)}`);
|
|
2285
|
+
if (argv.detailed) {
|
|
2286
|
+
logger.always(` ${chalk.gray(tool.description)}`);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
logger.always();
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
// Annotate specific tool
|
|
2294
|
+
const toolName = argv.tool;
|
|
2295
|
+
if (!toolName) {
|
|
2296
|
+
logger.error(chalk.red("Tool name is required. Use --tool <name> or --list to see all tools."));
|
|
2297
|
+
process.exit(1);
|
|
2298
|
+
}
|
|
2299
|
+
// Find the tool
|
|
2300
|
+
let foundTool = null;
|
|
2301
|
+
const serverId = argv.server;
|
|
2302
|
+
for (const server of servers) {
|
|
2303
|
+
if (serverId && server.id !== serverId) {
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
for (const tool of server.tools || []) {
|
|
2307
|
+
if (tool.name === toolName) {
|
|
2308
|
+
foundTool = {
|
|
2309
|
+
name: tool.name,
|
|
2310
|
+
description: tool.description,
|
|
2311
|
+
serverId: server.id,
|
|
2312
|
+
serverName: server.name,
|
|
2313
|
+
};
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
if (foundTool) {
|
|
2318
|
+
break;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
if (!foundTool) {
|
|
2322
|
+
logger.error(chalk.red(`Tool '${toolName}' not found.${serverId ? ` Server: ${serverId}` : ""}`));
|
|
2323
|
+
logger.always(chalk.yellow("Use 'neurolink mcp annotate --list' to see available tools."));
|
|
2324
|
+
process.exit(1);
|
|
2325
|
+
}
|
|
2326
|
+
// Build annotations from options
|
|
2327
|
+
let annotations = {};
|
|
2328
|
+
// Parse JSON annotations if provided
|
|
2329
|
+
if (argv.annotations) {
|
|
2330
|
+
try {
|
|
2331
|
+
const parsed = JSON.parse(argv.annotations);
|
|
2332
|
+
annotations = { ...annotations, ...parsed };
|
|
2333
|
+
}
|
|
2334
|
+
catch {
|
|
2335
|
+
logger.error(chalk.red("Invalid JSON in --annotations"));
|
|
2336
|
+
process.exit(1);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
// Apply individual annotation flags
|
|
2340
|
+
if (argv["read-only"] !== undefined) {
|
|
2341
|
+
annotations.readOnlyHint = argv["read-only"];
|
|
2342
|
+
}
|
|
2343
|
+
if (argv.destructive !== undefined) {
|
|
2344
|
+
annotations.destructiveHint = argv.destructive;
|
|
2345
|
+
}
|
|
2346
|
+
if (argv.idempotent !== undefined) {
|
|
2347
|
+
annotations.idempotentHint = argv.idempotent;
|
|
2348
|
+
}
|
|
2349
|
+
if (argv["requires-confirmation"] !== undefined) {
|
|
2350
|
+
annotations.requiresConfirmation = argv["requires-confirmation"];
|
|
2351
|
+
}
|
|
2352
|
+
if (argv.tags) {
|
|
2353
|
+
annotations.tags = argv.tags;
|
|
2354
|
+
}
|
|
2355
|
+
if (argv["estimated-duration"] !== undefined) {
|
|
2356
|
+
annotations.estimatedDuration = argv["estimated-duration"];
|
|
2357
|
+
}
|
|
2358
|
+
if (argv["rate-limit"] !== undefined) {
|
|
2359
|
+
annotations.rateLimitHint = argv["rate-limit"];
|
|
2360
|
+
}
|
|
2361
|
+
if (argv.cost !== undefined) {
|
|
2362
|
+
annotations.costHint = argv.cost;
|
|
2363
|
+
}
|
|
2364
|
+
if (argv.complexity) {
|
|
2365
|
+
annotations.complexity = argv.complexity;
|
|
2366
|
+
}
|
|
2367
|
+
if (argv["security-level"]) {
|
|
2368
|
+
annotations.securityLevel = argv["security-level"];
|
|
2369
|
+
}
|
|
2370
|
+
if (argv.audit !== undefined) {
|
|
2371
|
+
annotations.auditRequired = argv.audit;
|
|
2372
|
+
}
|
|
2373
|
+
// Infer annotations if requested
|
|
2374
|
+
if (argv.infer) {
|
|
2375
|
+
const inferred = inferAnnotations({
|
|
2376
|
+
name: foundTool.name,
|
|
2377
|
+
description: foundTool.description,
|
|
2378
|
+
});
|
|
2379
|
+
annotations = mergeAnnotations(inferred, annotations);
|
|
2380
|
+
}
|
|
2381
|
+
// Validate annotations
|
|
2382
|
+
const errors = validateAnnotations(annotations);
|
|
2383
|
+
if (errors.length > 0) {
|
|
2384
|
+
logger.error(chalk.red("Annotation validation errors:"));
|
|
2385
|
+
for (const error of errors) {
|
|
2386
|
+
logger.error(chalk.red(` - ${error}`));
|
|
2387
|
+
}
|
|
2388
|
+
process.exit(1);
|
|
2389
|
+
}
|
|
2390
|
+
// Validate only mode
|
|
2391
|
+
if (argv.validate) {
|
|
2392
|
+
logger.always(chalk.green("Annotations are valid:"));
|
|
2393
|
+
logger.always(JSON.stringify(annotations, null, 2));
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
// Display the annotations
|
|
2397
|
+
logger.always(chalk.bold("\n Tool Annotation Update:\n"));
|
|
2398
|
+
logger.always(` Server: ${chalk.cyan(foundTool.serverName)} (${foundTool.serverId})`);
|
|
2399
|
+
logger.always(` Tool: ${chalk.yellow(foundTool.name)}`);
|
|
2400
|
+
logger.always(` Description: ${chalk.gray(foundTool.description)}`);
|
|
2401
|
+
logger.always();
|
|
2402
|
+
logger.always(chalk.bold(" Annotations:"));
|
|
2403
|
+
if (annotations.readOnlyHint !== undefined) {
|
|
2404
|
+
logger.always(` readOnlyHint: ${annotations.readOnlyHint ? chalk.green("true") : chalk.red("false")}`);
|
|
2405
|
+
}
|
|
2406
|
+
if (annotations.destructiveHint !== undefined) {
|
|
2407
|
+
logger.always(` destructiveHint: ${annotations.destructiveHint ? chalk.red("true") : chalk.green("false")}`);
|
|
2408
|
+
}
|
|
2409
|
+
if (annotations.idempotentHint !== undefined) {
|
|
2410
|
+
logger.always(` idempotentHint: ${annotations.idempotentHint ? chalk.green("true") : chalk.gray("false")}`);
|
|
2411
|
+
}
|
|
2412
|
+
if (annotations.requiresConfirmation !== undefined) {
|
|
2413
|
+
logger.always(` requiresConfirmation: ${annotations.requiresConfirmation ? chalk.yellow("true") : chalk.gray("false")}`);
|
|
2414
|
+
}
|
|
2415
|
+
if (annotations.tags?.length) {
|
|
2416
|
+
logger.always(` tags: ${chalk.blue(annotations.tags.join(", "))}`);
|
|
2417
|
+
}
|
|
2418
|
+
if (annotations.estimatedDuration !== undefined) {
|
|
2419
|
+
logger.always(` estimatedDuration: ${annotations.estimatedDuration}ms`);
|
|
2420
|
+
}
|
|
2421
|
+
if (annotations.rateLimitHint !== undefined) {
|
|
2422
|
+
logger.always(` rateLimitHint: ${annotations.rateLimitHint} calls/min`);
|
|
2423
|
+
}
|
|
2424
|
+
if (annotations.costHint !== undefined) {
|
|
2425
|
+
logger.always(` costHint: ${annotations.costHint}`);
|
|
2426
|
+
}
|
|
2427
|
+
if (annotations.complexity) {
|
|
2428
|
+
logger.always(` complexity: ${chalk.cyan(annotations.complexity)}`);
|
|
2429
|
+
}
|
|
2430
|
+
if (annotations.securityLevel) {
|
|
2431
|
+
const secColor = annotations.securityLevel === "restricted"
|
|
2432
|
+
? chalk.red
|
|
2433
|
+
: annotations.securityLevel === "internal"
|
|
2434
|
+
? chalk.yellow
|
|
2435
|
+
: chalk.green;
|
|
2436
|
+
logger.always(` securityLevel: ${secColor(annotations.securityLevel)}`);
|
|
2437
|
+
}
|
|
2438
|
+
if (annotations.auditRequired !== undefined) {
|
|
2439
|
+
logger.always(` auditRequired: ${annotations.auditRequired ? chalk.yellow("true") : chalk.gray("false")}`);
|
|
2440
|
+
}
|
|
2441
|
+
logger.always();
|
|
2442
|
+
logger.always(chalk.gray(" Summary: ") + getAnnotationSummary(annotations));
|
|
2443
|
+
logger.always();
|
|
2444
|
+
// Note: In a full implementation, this would persist the annotations
|
|
2445
|
+
// to a configuration file or database. For now, we just display them.
|
|
2446
|
+
logger.always(chalk.blue("Note: Annotations displayed above. To persist annotations, add them to your MCP server configuration."));
|
|
2447
|
+
logger.always(chalk.gray(`Example: Add to your server's tool definition or use environment-specific annotation overrides.`));
|
|
2448
|
+
}
|
|
2449
|
+
catch (error) {
|
|
2450
|
+
logger.error(chalk.red(`Annotate command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2451
|
+
process.exit(1);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
930
2454
|
}
|
|
931
2455
|
//# sourceMappingURL=mcp.js.map
|