Skip to content

Commit c36b933

Browse files
committed
feat: Update validate command to support optional manifest argument and improve error handling
1 parent a4fdc90 commit c36b933

4 files changed

Lines changed: 77 additions & 13 deletions

File tree

CLI.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Options:
1919
2020
Commands:
2121
init [directory] Create a new MCPB extension manifest
22-
validate <manifest> Validate a MCPB manifest file
22+
validate [manifest] Validate a MCPB manifest file
2323
pack <directory> [output] Pack a directory into a MCPB extension
2424
sign [options] <mcpb-file> Sign a MCPB extension file
2525
verify <mcpb-file> Verify the signature of a MCPB extension file
@@ -58,7 +58,7 @@ The command will prompt you for:
5858

5959
After creating the manifest, it provides helpful next steps based on your server type.
6060

61-
### `mcpb validate <path>`
61+
### `mcpb validate [path]`
6262

6363
Validates a MCPB manifest file against the schema. You can provide either a direct path to a manifest.json file or a directory containing one.
6464

@@ -69,6 +69,9 @@ mcpb validate manifest.json
6969
# Validate manifest in directory
7070
mcpb validate ./my-extension
7171
mcpb validate .
72+
73+
# Validate using --dirname without specifying manifest.json explicitly
74+
mcpb validate --dirname ./my-extension
7275
```
7376

7477
#### Additional validation with `--dirname`
@@ -79,7 +82,7 @@ Passing `--dirname <directory>` performs deeper checks that require access to th
7982
- Launches the server (honoring `${__dirname}` tokens) and discovers tools & prompts using the same logic as `mcpb pack`.
8083
- Compares discovered capability names against the manifest and fails if they differ.
8184

82-
Use `--update` alongside `--dirname` to rewrite the manifest in-place with the discovered tool/prompt lists (including `tools_generated` / `prompts_generated` flags). When rewriting, the CLI also copies over tool descriptions and prompt metadata (descriptions, declared arguments, and prompt text) returned by the server. Without `--update`, any mismatch causes the command to fail.
85+
When `--dirname` is supplied without an explicit manifest argument, the CLI automatically resolves `<directory>/manifest.json`. Use `--update` alongside `--dirname` to rewrite the manifest in-place with the discovered tool/prompt lists (including `tools_generated` / `prompts_generated` flags). When rewriting, the CLI also copies over tool descriptions and prompt metadata (descriptions, declared arguments, and prompt text) returned by the server. Without `--update`, any mismatch causes the command to fail.
8386

8487
The discovery step respects the same environment overrides as `mcpb pack`:
8588

dotnet/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dotnet tool install --global Mcpb.Cli --add-source ./bin/Release
2323
| Command | Description |
2424
| --------------------------------------------------------------------------------------- | -------------------------------- |
2525
| `mcpb init [directory] [--server-type node\|python\|binary\|auto] [--entry-point path]` | Create manifest.json |
26-
| `mcpb validate <manifest\|directory>` | Validate manifest |
26+
| `mcpb validate [manifest\|directory]` | Validate manifest |
2727
| `mcpb pack [directory] [output]` | Create .mcpb archive |
2828
| `mcpb unpack <file> [outputDir]` | Extract archive |
2929
| `mcpb sign <file> [--cert cert.pem --key key.pem --self-signed]` | Sign bundle |

dotnet/mcpb.Tests/CliValidateTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.IO;
34
using System.Text.Json;
@@ -48,6 +49,51 @@ public void Validate_ValidManifest_Succeeds()
4849
Assert.True(string.IsNullOrWhiteSpace(stderr));
4950
}
5051

52+
[Fact]
53+
public void Validate_WithDirnameOnly_UsesDefaultManifest()
54+
{
55+
var dir = CreateTempDir();
56+
var manifest = new Mcpb.Core.McpbManifest
57+
{
58+
Name = "ok",
59+
Description = "desc",
60+
Author = new Mcpb.Core.McpbManifestAuthor { Name = "A" },
61+
Server = new Mcpb.Core.McpbManifestServer
62+
{
63+
Type = "binary",
64+
EntryPoint = "server/ok",
65+
McpConfig = new Mcpb.Core.McpServerConfigWithOverrides { Command = "${__dirname}/server/ok" }
66+
},
67+
Tools = new List<Mcpb.Core.McpbManifestTool>
68+
{
69+
new() { Name = "dummy", Description = "fake" }
70+
},
71+
Prompts = new List<Mcpb.Core.McpbManifestPrompt>
72+
{
73+
new() { Name = "prompt1", Description = "desc", Text = "body" }
74+
}
75+
};
76+
Directory.CreateDirectory(Path.Combine(dir, "server"));
77+
File.WriteAllText(Path.Combine(dir, "server", "ok"), "binary");
78+
File.WriteAllText(Path.Combine(dir, "manifest.json"), JsonSerializer.Serialize(manifest, McpbJsonContext.WriteOptions));
79+
Environment.SetEnvironmentVariable("MCPB_TOOL_DISCOVERY_JSON", "[{\"name\":\"dummy\",\"description\":\"fake\"}]");
80+
Environment.SetEnvironmentVariable("MCPB_PROMPT_DISCOVERY_JSON", "[{\"name\":\"prompt1\",\"description\":\"desc\",\"text\":\"body\"}]");
81+
try
82+
{
83+
var (code, stdout, stderr) = InvokeCli(dir, "validate", "--dirname", dir);
84+
_output.WriteLine("STDOUT: " + stdout);
85+
_output.WriteLine("STDERR: " + stderr);
86+
Assert.Equal(0, code);
87+
Assert.Contains("Manifest is valid!", stdout);
88+
Assert.True(string.IsNullOrWhiteSpace(stderr));
89+
}
90+
finally
91+
{
92+
Environment.SetEnvironmentVariable("MCPB_TOOL_DISCOVERY_JSON", null);
93+
Environment.SetEnvironmentVariable("MCPB_PROMPT_DISCOVERY_JSON", null);
94+
}
95+
}
96+
5197
[Fact]
5298
public void Validate_MissingDescription_Fails()
5399
{

dotnet/mcpb/Commands/ValidateCommand.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,50 @@ public static class ValidateCommand
1212
{
1313
public static Command Create()
1414
{
15-
var manifestArg = new Argument<string>("manifest", description: "Path to manifest.json or its directory");
15+
var manifestArg = new Argument<string?>("manifest", description: "Path to manifest.json or its directory");
16+
manifestArg.Arity = ArgumentArity.ZeroOrOne;
1617
var dirnameOpt = new Option<string?>("--dirname", description: "Directory containing referenced files and server entry point");
1718
var updateOpt = new Option<bool>("--update", description: "Update manifest tools/prompts to match discovery results");
1819
var cmd = new Command("validate", "Validate an MCPB manifest file") { manifestArg, dirnameOpt, updateOpt };
19-
cmd.SetHandler(async (string path, string? dirname, bool update) =>
20+
cmd.SetHandler(async (string? path, string? dirname, bool update) =>
2021
{
2122
if (update && string.IsNullOrWhiteSpace(dirname))
2223
{
2324
Console.Error.WriteLine("ERROR: --update requires --dirname to locate manifest assets.");
2425
Environment.ExitCode = 1;
2526
return;
2627
}
27-
if (Directory.Exists(path))
28+
if (string.IsNullOrWhiteSpace(path))
2829
{
29-
path = Path.Combine(path, "manifest.json");
30+
if (!string.IsNullOrWhiteSpace(dirname))
31+
{
32+
path = Path.Combine(dirname, "manifest.json");
33+
}
34+
else
35+
{
36+
Console.Error.WriteLine("ERROR: Manifest path or --dirname must be specified.");
37+
Environment.ExitCode = 1;
38+
return;
39+
}
40+
}
41+
var manifestPath = path!;
42+
if (Directory.Exists(manifestPath))
43+
{
44+
manifestPath = Path.Combine(manifestPath, "manifest.json");
3045
}
31-
if (!File.Exists(path))
46+
if (!File.Exists(manifestPath))
3247
{
33-
Console.Error.WriteLine($"ERROR: File not found: {path}");
48+
Console.Error.WriteLine($"ERROR: File not found: {manifestPath}");
3449
Environment.ExitCode = 1;
3550
return;
3651
}
3752
string json;
3853
try
3954
{
40-
json = File.ReadAllText(path);
55+
json = File.ReadAllText(manifestPath);
4156
if (Environment.GetEnvironmentVariable("MCPB_DEBUG_VALIDATE") == "1")
4257
{
43-
Console.WriteLine($"DEBUG: Read manifest {path} length={json.Length}");
58+
Console.WriteLine($"DEBUG: Read manifest {manifestPath} length={json.Length}");
4459
}
4560

4661
static void PrintWarnings(IEnumerable<ValidationIssue> warnings, bool toError)
@@ -186,7 +201,7 @@ static void PrintWarnings(IEnumerable<ValidationIssue> warnings, bool toError)
186201
var updatedWarnings = updatedIssues.Where(i => i.Severity == ValidationSeverity.Warning).ToList();
187202
var updatedManifest = JsonSerializer.Deserialize<McpbManifest>(updatedJson, McpbJsonContext.Default.McpbManifest)!;
188203

189-
File.WriteAllText(path, updatedJson);
204+
File.WriteAllText(manifestPath, updatedJson);
190205

191206
if (updatedErrors.Count > 0)
192207
{

0 commit comments

Comments
 (0)