@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +59 -9
  3. package/dist/cli/commands/config.d.ts +4 -4
  4. package/dist/cli/commands/mcp.d.ts +87 -0
  5. package/dist/cli/commands/mcp.js +1524 -0
  6. package/dist/cli/loop/optionsSchema.js +4 -0
  7. package/dist/core/modules/ToolsManager.js +29 -2
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.js +27 -1
  10. package/dist/lib/core/modules/ToolsManager.js +29 -2
  11. package/dist/lib/index.d.ts +2 -1
  12. package/dist/lib/index.js +27 -1
  13. package/dist/lib/mcp/agentExposure.d.ts +228 -0
  14. package/dist/lib/mcp/agentExposure.js +357 -0
  15. package/dist/lib/mcp/batching/index.d.ts +11 -0
  16. package/dist/lib/mcp/batching/index.js +11 -0
  17. package/dist/lib/mcp/batching/requestBatcher.d.ts +202 -0
  18. package/dist/lib/mcp/batching/requestBatcher.js +442 -0
  19. package/dist/lib/mcp/caching/index.d.ts +11 -0
  20. package/dist/lib/mcp/caching/index.js +11 -0
  21. package/dist/lib/mcp/caching/toolCache.d.ts +221 -0
  22. package/dist/lib/mcp/caching/toolCache.js +434 -0
  23. package/dist/lib/mcp/elicitation/elicitationManager.d.ts +169 -0
  24. package/dist/lib/mcp/elicitation/elicitationManager.js +377 -0
  25. package/dist/lib/mcp/elicitation/index.d.ts +11 -0
  26. package/dist/lib/mcp/elicitation/index.js +12 -0
  27. package/dist/lib/mcp/elicitation/types.d.ts +278 -0
  28. package/dist/lib/mcp/elicitation/types.js +11 -0
  29. package/dist/lib/mcp/elicitationProtocol.d.ts +228 -0
  30. package/dist/lib/mcp/elicitationProtocol.js +376 -0
  31. package/dist/lib/mcp/enhancedToolDiscovery.d.ts +205 -0
  32. package/dist/lib/mcp/enhancedToolDiscovery.js +482 -0
  33. package/dist/lib/mcp/index.d.ts +38 -1
  34. package/dist/lib/mcp/index.js +36 -3
  35. package/dist/lib/mcp/mcpRegistryClient.d.ts +332 -0
  36. package/dist/lib/mcp/mcpRegistryClient.js +489 -0
  37. package/dist/lib/mcp/mcpServerBase.d.ts +227 -0
  38. package/dist/lib/mcp/mcpServerBase.js +374 -0
  39. package/dist/lib/mcp/multiServerManager.d.ts +310 -0
  40. package/dist/lib/mcp/multiServerManager.js +580 -0
  41. package/dist/lib/mcp/routing/index.d.ts +11 -0
  42. package/dist/lib/mcp/routing/index.js +11 -0
  43. package/dist/lib/mcp/routing/toolRouter.d.ts +219 -0
  44. package/dist/lib/mcp/routing/toolRouter.js +417 -0
  45. package/dist/lib/mcp/serverCapabilities.d.ts +341 -0
  46. package/dist/lib/mcp/serverCapabilities.js +503 -0
  47. package/dist/lib/mcp/toolAnnotations.d.ts +154 -0
  48. package/dist/lib/mcp/toolAnnotations.js +240 -0
  49. package/dist/lib/mcp/toolConverter.d.ts +178 -0
  50. package/dist/lib/mcp/toolConverter.js +259 -0
  51. package/dist/lib/mcp/toolIntegration.d.ts +136 -0
  52. package/dist/lib/mcp/toolIntegration.js +335 -0
  53. package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
  54. package/dist/lib/memory/hippocampusInitializer.js +1 -1
  55. package/dist/lib/neurolink.d.ts +275 -2
  56. package/dist/lib/neurolink.js +596 -56
  57. package/dist/lib/providers/litellm.d.ts +10 -0
  58. package/dist/lib/providers/litellm.js +104 -2
  59. package/dist/lib/types/configTypes.d.ts +56 -0
  60. package/dist/lib/types/conversation.d.ts +2 -2
  61. package/dist/lib/types/generateTypes.d.ts +4 -0
  62. package/dist/lib/types/index.d.ts +2 -1
  63. package/dist/lib/types/modelTypes.d.ts +6 -6
  64. package/dist/lib/types/streamTypes.d.ts +2 -0
  65. package/dist/lib/types/tools.d.ts +2 -0
  66. package/dist/lib/utils/pricing.js +177 -17
  67. package/dist/lib/utils/schemaConversion.d.ts +6 -1
  68. package/dist/lib/utils/schemaConversion.js +50 -28
  69. package/dist/lib/workflow/config.d.ts +16 -16
  70. package/dist/mcp/agentExposure.d.ts +228 -0
  71. package/dist/mcp/agentExposure.js +356 -0
  72. package/dist/mcp/batching/index.d.ts +11 -0
  73. package/dist/mcp/batching/index.js +10 -0
  74. package/dist/mcp/batching/requestBatcher.d.ts +202 -0
  75. package/dist/mcp/batching/requestBatcher.js +441 -0
  76. package/dist/mcp/caching/index.d.ts +11 -0
  77. package/dist/mcp/caching/index.js +10 -0
  78. package/dist/mcp/caching/toolCache.d.ts +221 -0
  79. package/dist/mcp/caching/toolCache.js +433 -0
  80. package/dist/mcp/elicitation/elicitationManager.d.ts +169 -0
  81. package/dist/mcp/elicitation/elicitationManager.js +376 -0
  82. package/dist/mcp/elicitation/index.d.ts +11 -0
  83. package/dist/mcp/elicitation/index.js +11 -0
  84. package/dist/mcp/elicitation/types.d.ts +278 -0
  85. package/dist/mcp/elicitation/types.js +10 -0
  86. package/dist/mcp/elicitationProtocol.d.ts +228 -0
  87. package/dist/mcp/elicitationProtocol.js +375 -0
  88. package/dist/mcp/enhancedToolDiscovery.d.ts +205 -0
  89. package/dist/mcp/enhancedToolDiscovery.js +481 -0
  90. package/dist/mcp/index.d.ts +38 -1
  91. package/dist/mcp/index.js +36 -3
  92. package/dist/mcp/mcpRegistryClient.d.ts +332 -0
  93. package/dist/mcp/mcpRegistryClient.js +488 -0
  94. package/dist/mcp/mcpServerBase.d.ts +227 -0
  95. package/dist/mcp/mcpServerBase.js +373 -0
  96. package/dist/mcp/multiServerManager.d.ts +310 -0
  97. package/dist/mcp/multiServerManager.js +579 -0
  98. package/dist/mcp/routing/index.d.ts +11 -0
  99. package/dist/mcp/routing/index.js +10 -0
  100. package/dist/mcp/routing/toolRouter.d.ts +219 -0
  101. package/dist/mcp/routing/toolRouter.js +416 -0
  102. package/dist/mcp/serverCapabilities.d.ts +341 -0
  103. package/dist/mcp/serverCapabilities.js +502 -0
  104. package/dist/mcp/toolAnnotations.d.ts +154 -0
  105. package/dist/mcp/toolAnnotations.js +239 -0
  106. package/dist/mcp/toolConverter.d.ts +178 -0
  107. package/dist/mcp/toolConverter.js +258 -0
  108. package/dist/mcp/toolIntegration.d.ts +136 -0
  109. package/dist/mcp/toolIntegration.js +334 -0
  110. package/dist/memory/hippocampusInitializer.d.ts +2 -2
  111. package/dist/memory/hippocampusInitializer.js +1 -1
  112. package/dist/neurolink.d.ts +275 -2
  113. package/dist/neurolink.js +596 -56
  114. package/dist/providers/litellm.d.ts +10 -0
  115. package/dist/providers/litellm.js +104 -2
  116. package/dist/types/configTypes.d.ts +56 -0
  117. package/dist/types/conversation.d.ts +2 -2
  118. package/dist/types/generateTypes.d.ts +4 -0
  119. package/dist/types/index.d.ts +2 -1
  120. package/dist/types/streamTypes.d.ts +2 -0
  121. package/dist/types/tools.d.ts +2 -0
  122. package/dist/utils/pricing.js +177 -17
  123. package/dist/utils/schemaConversion.d.ts +6 -1
  124. package/dist/utils/schemaConversion.js +50 -28
  125. package/package.json +2 -2
@@ -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