Skip to content

Commit 24153a4

Browse files
sharpninjaclaude
andcommitted
Add director add-workspace command with trust verification
New CLI command: `director add-workspace [-w <path>] [-n <name>] [-s <url>]` Registers CWD as a new MCP Server workspace via the full trust lifecycle: 1. Fetches the default API key from unauthenticated GET /api-key 2. Registers the workspace via POST /mcpserver/workspace 3. Watches for AGENTS-README-FIRST.yaml (FileSystemWatcher + polling) 4. Verifies HMAC-SHA256 marker signature against the canonical payload 5. Performs health nonce echo verification (GET /health?nonce=<random>) Also fixes a stale BuildConsolePromptStatus reference in AgentHostCommand.cs left over from a prior banner-polish session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 98e83ab commit 24153a4

2 files changed

Lines changed: 445 additions & 30 deletions

File tree

src/McpServer.Director/Commands/AgentHostCommand.cs

Lines changed: 143 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ private static string ExtractResponseText(AgentResponse response)
756756
: messageText;
757757
}
758758

759-
private string BuildConsolePrompt() => $"{BuildConsolePromptLabel()} {_powerShellCurrentLocation}> ";
759+
private string BuildConsolePrompt() => $"{BuildConsolePromptLabel()} {FormatPromptLocation(_powerShellCurrentLocation)}> ";
760760

761761
private string BuildConsolePromptLabel() => $"{BuildConsolePromptLeadSegment()} [{_verbosity}]";
762762

@@ -771,6 +771,44 @@ private string BuildConsolePromptLeadSegment()
771771
return "PS";
772772
}
773773

774+
/// <summary>
775+
/// Condenses a filesystem location for display in the interactive prompt.
776+
/// Keeps the prompt short even when the user has cd'd deep inside the workspace
777+
/// or when the absolute path is long, so there is always room to type.
778+
/// </summary>
779+
private string FormatPromptLocation(string location)
780+
{
781+
if (string.IsNullOrWhiteSpace(location))
782+
return string.Empty;
783+
784+
var trimmed = location.TrimEnd('\\', '/');
785+
786+
// If the current location is inside the workspace, show it as ~/<relative>.
787+
var workspace = _settings.WorkspacePath?.TrimEnd('\\', '/');
788+
if (!string.IsNullOrWhiteSpace(workspace)
789+
&& trimmed.StartsWith(workspace, StringComparison.OrdinalIgnoreCase))
790+
{
791+
if (trimmed.Length == workspace.Length)
792+
return "~";
793+
794+
var relative = trimmed[workspace.Length..].TrimStart('\\', '/');
795+
return $"~/{relative.Replace('\\', '/')}";
796+
}
797+
798+
// Otherwise, middle-ellipsis very long paths so the prompt stays usable.
799+
const int maxLength = 40;
800+
if (trimmed.Length <= maxLength)
801+
return trimmed;
802+
803+
var tailLength = maxLength - 4; // reserve "...<sep>" prefix
804+
var tail = trimmed[^tailLength..];
805+
var separatorIndex = tail.IndexOfAny(['\\', '/']);
806+
if (separatorIndex > 0)
807+
tail = tail[separatorIndex..];
808+
809+
return $"...{tail}";
810+
}
811+
774812
private void ClosePowerShellSession()
775813
{
776814
if (string.IsNullOrWhiteSpace(_powerShellSessionId))
@@ -1093,44 +1131,119 @@ private static bool WriteConsoleBlock(string? value, TextWriter writer)
10931131

10941132
private void WriteBanner()
10951133
{
1096-
Console.WriteLine("MCP Agent host (Director)");
1097-
Console.WriteLine("-----------------------------------");
1098-
Console.WriteLine($"MCP server : {_settings.BaseUrl}");
1099-
Console.WriteLine($"Workspace : {_settings.WorkspacePath}");
1100-
Console.WriteLine($"Model : {_settings.ModelId}");
1134+
var rows = new List<(string Label, string Value)>
1135+
{
1136+
("MCP server", _settings.BaseUrl.ToString()),
1137+
("Workspace", _settings.WorkspacePath),
1138+
("Model", _settings.ModelId),
1139+
};
1140+
11011141
if (_settings.ModelEndpoint is not null)
1102-
Console.WriteLine($"Model URL : {_settings.ModelEndpoint}");
1103-
Console.WriteLine($"SourceType : {_settings.SourceType}");
1104-
Console.WriteLine($"Tools : {string.Join(", ", _hostedAgent.Registration.Tools.Select(static tool => tool.Name))}");
1142+
rows.Add(("Model URL", _settings.ModelEndpoint.ToString()));
1143+
1144+
rows.Add(("SourceType", _settings.SourceType));
1145+
rows.Add(("Auth", DescribeAuthMode()));
1146+
1147+
var labelWidth = rows.Max(static r => r.Label.Length);
1148+
1149+
const string title = "MCP Agent host (Director)";
1150+
Console.WriteLine(title);
1151+
Console.WriteLine(new string('-', title.Length));
1152+
1153+
foreach (var (label, value) in rows)
1154+
Console.WriteLine($"{label.PadRight(labelWidth)} : {value}");
1155+
1156+
var toolNames = _hostedAgent.Registration.Tools
1157+
.Select(static tool => tool.Name)
1158+
.ToList();
1159+
var toolHeader = $"Tools ({toolNames.Count})".PadRight(labelWidth);
1160+
WriteWrappedList(toolHeader, labelWidth, toolNames);
1161+
11051162
Console.WriteLine();
11061163
}
11071164

1165+
private string DescribeAuthMode()
1166+
{
1167+
if (!string.IsNullOrWhiteSpace(_settings.BearerToken))
1168+
return "bearer token";
1169+
if (!string.IsNullOrWhiteSpace(_settings.ApiKey))
1170+
return "api-key (workspace marker)";
1171+
return "(none \u2014 requests will fail)";
1172+
}
1173+
1174+
private static void WriteWrappedList(string header, int labelWidth, IReadOnlyList<string> items)
1175+
{
1176+
if (items.Count == 0)
1177+
{
1178+
Console.WriteLine($"{header} : (none)");
1179+
return;
1180+
}
1181+
1182+
var targetWidth = 80;
1183+
try
1184+
{
1185+
if (Console.WindowWidth > 20)
1186+
targetWidth = Console.WindowWidth - 1;
1187+
}
1188+
catch (IOException)
1189+
{
1190+
// Console.WindowWidth can throw on some hosted terminals; fall back to 80.
1191+
}
1192+
1193+
var indent = new string(' ', labelWidth + 3);
1194+
var maxLineLength = Math.Max(20, targetWidth - (labelWidth + 3));
1195+
1196+
var buffer = new StringBuilder();
1197+
var first = true;
1198+
foreach (var item in items)
1199+
{
1200+
var separator = buffer.Length == 0 ? string.Empty : ", ";
1201+
if (buffer.Length + separator.Length + item.Length > maxLineLength && buffer.Length > 0)
1202+
{
1203+
Console.WriteLine($"{(first ? header : new string(' ', labelWidth))} : {buffer}");
1204+
first = false;
1205+
buffer.Clear();
1206+
}
1207+
else if (separator.Length > 0)
1208+
{
1209+
buffer.Append(separator);
1210+
}
1211+
1212+
buffer.Append(item);
1213+
}
1214+
1215+
if (buffer.Length > 0)
1216+
Console.WriteLine($"{(first ? header : new string(' ', labelWidth))} : {buffer}");
1217+
}
1218+
11081219
private void WriteHelp()
11091220
{
1110-
Console.WriteLine("Commands:");
1111-
Console.WriteLine(" /help Show this help text.");
1112-
Console.WriteLine(" /tools List the MCP-backed tools attached to the hosted agent.");
1113-
Console.WriteLine(" /session Show the current MCP session-log identifier.");
1114-
Console.WriteLine(" /v N Set verbosity level (1=concise, 2=balanced, 3=detailed).");
1115-
Console.WriteLine(" /new Start a fresh conversation and session log.");
1116-
Console.WriteLine(" /exit Exit the Director agent host.");
1221+
Console.WriteLine("Session commands:");
1222+
Console.WriteLine(" /help Show this help text.");
1223+
Console.WriteLine(" /tools List the MCP-backed tools and REPL workflow tools attached to the agent.");
1224+
Console.WriteLine(" /session Show the current session id, agent name, and source type.");
1225+
Console.WriteLine($" /v <1|2|3> Set verbosity: 1=concise, 2=balanced, 3=detailed (currently: {_verbosity}).");
1226+
Console.WriteLine(" /new (alias: /reset) Start a fresh conversation and session log.");
1227+
Console.WriteLine(" /exit (alias: /quit) Exit the Director agent host.");
1228+
Console.WriteLine();
1229+
Console.WriteLine("Workspace commands:");
1230+
Console.WriteLine(" /todo List all TODO items.");
1231+
Console.WriteLine(" /todo <keyword> Search TODOs by keyword.");
1232+
Console.WriteLine(" /todo select <id> Select a TODO as the active context.");
1233+
Console.WriteLine(" /todo get <id> Show TODO details.");
1234+
Console.WriteLine(" /requirements (/reqs) List functional requirements summary.");
1235+
Console.WriteLine(" /client <c>.<m> [json] Invoke McpServerClient sub-client method (e.g. /client context.SearchAsync).");
11171236
Console.WriteLine();
1118-
Console.WriteLine("REPL workflow commands:");
1119-
Console.WriteLine(" /todo List all TODO items.");
1120-
Console.WriteLine(" /todo <keyword> Search TODOs by keyword.");
1121-
Console.WriteLine(" /todo select <id> Select a TODO as the active context.");
1122-
Console.WriteLine(" /todo get <id> Show TODO details.");
1123-
Console.WriteLine(" /requirements List functional requirements summary.");
1124-
Console.WriteLine(" /reqs Alias for /requirements.");
1125-
Console.WriteLine(" /client <c>.<m> Invoke McpServerClient sub-client method (e.g. /client context.SearchAsync).");
1237+
Console.WriteLine("Input:");
1238+
Console.WriteLine(" <text> Sent to the hosted agent as a chat prompt.");
1239+
Console.WriteLine(" ! <command> Runs the rest of the line in the local PowerShell session.");
1240+
Console.WriteLine(" \u2191 / \u2193 Recall previous inputs from history.");
1241+
Console.WriteLine(" Ctrl+C Cancel the active PowerShell command; press again to exit.");
11261242
Console.WriteLine();
1127-
Console.WriteLine("Prompt behavior:");
1128-
Console.WriteLine(" - The prompt shows model [verbosity] <location>> (model id from MCP_AGENT_MODEL_NAME or default).");
1129-
Console.WriteLine(" - ↑ / ↓ recall previous inputs; history and verbosity persist in .mcpServer/director-agent-console-state.json.");
1130-
Console.WriteLine(" - Prefix a line with ! to run it directly in the local PowerShell session.");
1131-
Console.WriteLine(" - Any line without ! is sent to the hosted agent as a normal chat prompt.");
1243+
Console.WriteLine("Persistence:");
1244+
Console.WriteLine(" History and verbosity persist at .mcpServer/director-agent-console-state.json in the workspace.");
11321245
Console.WriteLine();
1133-
Console.WriteLine("You can also execute a single prompt non-interactively:");
1246+
Console.WriteLine("Non-interactive mode — pass a single prompt as an argument:");
11341247
Console.WriteLine(@" director agent ""List open TODO items""");
11351248
Console.WriteLine(@" director agent ""! Get-Location""");
11361249
Console.WriteLine();

0 commit comments

Comments
 (0)