Step 505: add MCP tool documentation resource and tests

This commit is contained in:
Bill
2026-02-16 22:46:22 -07:00
parent a85fd59403
commit 1e75f08b55
4 changed files with 402 additions and 0 deletions

View File

@@ -3424,4 +3424,13 @@ target_link_libraries(step504_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step505_test tests/step505_test.cpp)
target_include_directories(step505_test PRIVATE src)
target_link_libraries(step505_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,192 @@
#pragma once
// Step 505: MCP Tool Documentation
// Structured MCP tool docs with category index and exportable resource formats.
#include <filesystem>
#include <fstream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
struct MCPToolErrorCode {
std::string code;
std::string meaning;
};
struct MCPToolDoc {
std::string name;
std::string category; // AST, Diagnostics, Workflow, Routing, Security, etc.
std::string description;
std::string inputSchema;
std::string inputExample;
std::string outputSchema;
std::string outputFields;
std::vector<MCPToolErrorCode> errorCodes;
bool isComplete() const {
return !name.empty() && !category.empty() && !description.empty() &&
!inputSchema.empty() && !inputExample.empty() &&
!outputSchema.empty() && !outputFields.empty() &&
!errorCodes.empty();
}
};
struct MCPDocumentationResource {
std::vector<MCPToolDoc> tools;
std::map<std::string, std::vector<std::string>> categories;
std::vector<std::string> notes;
};
class MCPToolDocumentation {
public:
static MCPDocumentationResource buildDefault() {
MCPDocumentationResource out;
out.tools = {
{"whetstone_parse_ast", "AST",
"Parse source code into a normalized AST with semantic node identities.",
R"({"type":"object","properties":{"source":{"type":"string"},"language":{"type":"string"}},"required":["source","language"]})",
R"({"source":"fn add(a,b){return a+b;}","language":"javascript"})",
R"({"type":"object","properties":{"ast":{"type":"object"},"nodeCount":{"type":"integer"}}})",
"ast: parsed tree, nodeCount: number of nodes",
{{"-32602", "Invalid params"}, {"-32001", "Parse failure"}}},
{"whetstone_run_diagnostics", "Diagnostics",
"Run static diagnostics and return normalized findings with severity.",
R"({"type":"object","properties":{"source":{"type":"string"},"language":{"type":"string"}},"required":["source","language"]})",
R"({"source":"int x = gets(buf);","language":"c"})",
R"({"type":"object","properties":{"findings":{"type":"array"},"summary":{"type":"object"}}})",
"findings: diagnostic list, summary: by-severity counts",
{{"-32602", "Invalid params"}, {"-32003", "Diagnostics engine failure"}}},
{"whetstone_create_workflow", "Workflow",
"Create a workflow from skeleton annotations with dependency ordering.",
R"({"type":"object","properties":{"projectName":{"type":"string"}},"required":["projectName"]})",
R"({"projectName":"demo"})",
R"({"type":"object","properties":{"workflow":{"type":"object"},"itemCount":{"type":"integer"}}})",
"workflow: serialized workflow state, itemCount: total items",
{{"-32602", "Invalid params"}, {"-32000", "No active project"}}},
{"whetstone_route_work_items", "Routing",
"Route ready workflow items to deterministic/template/slm/llm/human workers.",
R"({"type":"object","properties":{"maxItems":{"type":"integer"}}})",
R"({"maxItems":25})",
R"({"type":"object","properties":{"routed":{"type":"array"},"counts":{"type":"object"}}})",
"routed: per-item routing decisions, counts: worker-type distribution",
{{"-32602", "Invalid params"}, {"-32010", "Routing state unavailable"}}},
{"whetstone_security_audit", "Security",
"Run security audit with CWE mappings and risk ratings.",
R"({"type":"object","properties":{"source":{"type":"string"},"language":{"type":"string"}},"required":["source","language"]})",
R"({"source":"char buf[8]; gets(buf);","language":"c"})",
R"({"type":"object","properties":{"findings":{"type":"array"},"overallRisk":{"type":"integer"}}})",
"findings: vulnerability details, overallRisk: max severity level",
{{"-32602", "Invalid params"}, {"-32020", "Security analysis failed"}}}
};
indexCategories(out);
out.notes.push_back("Default MCP tool documentation generated");
out.notes.push_back("Includes AST, Diagnostics, Workflow, Routing, Security categories");
return out;
}
static bool validateCompleteness(const MCPDocumentationResource& docs,
std::string* error = nullptr) {
if (docs.tools.empty()) {
if (error) *error = "No tools documented";
return false;
}
for (const auto& t : docs.tools) {
if (!t.isComplete()) {
if (error) *error = "Incomplete tool documentation: " + t.name;
return false;
}
}
return true;
}
static std::string toMarkdown(const MCPDocumentationResource& docs) {
std::ostringstream o;
o << "# MCP Tool Documentation\n\n";
for (const auto& t : docs.tools) {
o << "## " << t.name << "\n";
o << "- Category: " << t.category << "\n";
o << "- Description: " << t.description << "\n";
o << "- Input schema: `" << t.inputSchema << "`\n";
o << "- Input example: `" << t.inputExample << "`\n";
o << "- Output schema: `" << t.outputSchema << "`\n";
o << "- Output fields: " << t.outputFields << "\n";
o << "- Errors:\n";
for (const auto& e : t.errorCodes) {
o << " - " << e.code << ": " << e.meaning << "\n";
}
o << "\n";
}
return o.str();
}
static std::string toJson(const MCPDocumentationResource& docs) {
std::ostringstream o;
o << "{\n \"tools\": [\n";
for (size_t i = 0; i < docs.tools.size(); ++i) {
const auto& t = docs.tools[i];
o << " {\n"
<< " \"name\": \"" << escape(t.name) << "\",\n"
<< " \"category\": \"" << escape(t.category) << "\",\n"
<< " \"description\": \"" << escape(t.description) << "\"\n"
<< " }";
if (i + 1 < docs.tools.size()) o << ",";
o << "\n";
}
o << " ]\n}\n";
return o.str();
}
static bool writeResource(const MCPDocumentationResource& docs,
const std::string& directory,
std::string* markdownPath = nullptr,
std::string* jsonPath = nullptr) {
namespace fs = std::filesystem;
fs::path root = directory.empty() ? fs::path(".") : fs::path(directory);
std::error_code ec;
fs::create_directories(root, ec);
if (ec) return false;
fs::path md = root / "mcp_tool_docs.md";
fs::path js = root / "mcp_tool_docs.json";
{
std::ofstream f(md);
if (!f) return false;
f << toMarkdown(docs);
}
{
std::ofstream f(js);
if (!f) return false;
f << toJson(docs);
}
if (markdownPath) *markdownPath = md.string();
if (jsonPath) *jsonPath = js.string();
return true;
}
private:
static void indexCategories(MCPDocumentationResource& docs) {
docs.categories.clear();
for (const auto& t : docs.tools) docs.categories[t.category].push_back(t.name);
}
static std::string escape(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '\"') out += "\\\"";
else if (c == '\\') out += "\\\\";
else out.push_back(c);
}
return out;
}
};

View File

@@ -0,0 +1,167 @@
// Step 505: MCP Tool Documentation Tests (12 tests)
#include "MCPToolDocumentation.h"
#include <filesystem>
#include <fstream>
#include <iostream>
namespace fs = std::filesystem;
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
static MCPDocumentationResource buildDocs() {
return MCPToolDocumentation::buildDefault();
}
void test_default_docs_include_multiple_tools() {
TEST(default_docs_include_multiple_tools);
auto docs = buildDocs();
CHECK(docs.tools.size() >= 5, "expected at least 5 documented tools");
PASS();
}
void test_each_tool_has_required_documentation_sections() {
TEST(each_tool_has_required_documentation_sections);
auto docs = buildDocs();
for (const auto& t : docs.tools) {
CHECK(t.isComplete(), "tool doc is incomplete");
}
PASS();
}
void test_completeness_validator_passes_for_default_docs() {
TEST(completeness_validator_passes_for_default_docs);
auto docs = buildDocs();
std::string err;
CHECK(MCPToolDocumentation::validateCompleteness(docs, &err), err.c_str());
PASS();
}
void test_required_categories_are_present() {
TEST(required_categories_are_present);
auto docs = buildDocs();
CHECK(docs.categories.count("AST") == 1, "missing AST category");
CHECK(docs.categories.count("Diagnostics") == 1, "missing Diagnostics category");
CHECK(docs.categories.count("Workflow") == 1, "missing Workflow category");
CHECK(docs.categories.count("Routing") == 1, "missing Routing category");
CHECK(docs.categories.count("Security") == 1, "missing Security category");
PASS();
}
void test_error_codes_are_documented_for_all_tools() {
TEST(error_codes_are_documented_for_all_tools);
auto docs = buildDocs();
for (const auto& t : docs.tools) {
CHECK(!t.errorCodes.empty(), "missing error codes");
CHECK(!t.errorCodes[0].code.empty(), "error code missing code");
CHECK(!t.errorCodes[0].meaning.empty(), "error code missing meaning");
}
PASS();
}
void test_markdown_export_contains_tool_names_and_sections() {
TEST(markdown_export_contains_tool_names_and_sections);
auto docs = buildDocs();
auto md = MCPToolDocumentation::toMarkdown(docs);
CHECK(md.find("# MCP Tool Documentation") != std::string::npos, "missing markdown header");
CHECK(md.find("whetstone_parse_ast") != std::string::npos, "missing AST tool section");
CHECK(md.find("Errors:") != std::string::npos, "missing error section");
PASS();
}
void test_json_export_contains_tool_entries() {
TEST(json_export_contains_tool_entries);
auto docs = buildDocs();
auto js = MCPToolDocumentation::toJson(docs);
CHECK(js.find("\"tools\"") != std::string::npos, "missing tools array");
CHECK(js.find("\"name\": \"whetstone_security_audit\"") != std::string::npos,
"missing security tool entry");
PASS();
}
void test_resource_write_materializes_markdown_and_json_files() {
TEST(resource_write_materializes_markdown_and_json_files);
auto docs = buildDocs();
fs::path root = fs::temp_directory_path() / "whetstone_step505_docs";
fs::remove_all(root);
std::string mdPath, jsPath;
CHECK(MCPToolDocumentation::writeResource(docs, root.string(), &mdPath, &jsPath),
"resource write failed");
CHECK(fs::exists(mdPath), "markdown resource file missing");
CHECK(fs::exists(jsPath), "json resource file missing");
fs::remove_all(root);
PASS();
}
void test_written_markdown_resource_is_readable_by_client() {
TEST(written_markdown_resource_is_readable_by_client);
auto docs = buildDocs();
fs::path root = fs::temp_directory_path() / "whetstone_step505_read";
fs::remove_all(root);
std::string mdPath;
CHECK(MCPToolDocumentation::writeResource(docs, root.string(), &mdPath, nullptr),
"resource write failed");
std::ifstream in(mdPath);
CHECK(in.good(), "markdown resource not readable");
std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
CHECK(content.find("whetstone_create_workflow") != std::string::npos,
"workflow tool missing from markdown resource");
fs::remove_all(root);
PASS();
}
void test_invalid_docs_are_rejected_by_completeness_validator() {
TEST(invalid_docs_are_rejected_by_completeness_validator);
MCPDocumentationResource docs;
docs.tools.push_back({"broken_tool", "", "", "", "", "", "", {}});
std::string err;
CHECK(!MCPToolDocumentation::validateCompleteness(docs, &err), "invalid docs should fail");
CHECK(!err.empty(), "expected error message for invalid docs");
PASS();
}
void test_category_index_points_to_documented_tools() {
TEST(category_index_points_to_documented_tools);
auto docs = buildDocs();
CHECK(!docs.categories["AST"].empty(), "AST category index empty");
CHECK(!docs.categories["Workflow"].empty(), "Workflow category index empty");
PASS();
}
void test_notes_include_generation_summary() {
TEST(notes_include_generation_summary);
auto docs = buildDocs();
CHECK(!docs.notes.empty(), "missing documentation notes");
CHECK(docs.notes[0].find("generated") != std::string::npos, "missing generation summary note");
PASS();
}
int main() {
std::cout << "Step 505: MCP Tool Documentation Tests\n";
test_default_docs_include_multiple_tools(); // 1
test_each_tool_has_required_documentation_sections(); // 2
test_completeness_validator_passes_for_default_docs(); // 3
test_required_categories_are_present(); // 4
test_error_codes_are_documented_for_all_tools(); // 5
test_markdown_export_contains_tool_names_and_sections(); // 6
test_json_export_contains_tool_entries(); // 7
test_resource_write_materializes_markdown_and_json_files(); // 8
test_written_markdown_resource_is_readable_by_client(); // 9
test_invalid_docs_are_rejected_by_completeness_validator(); // 10
test_category_index_points_to_documented_tools(); // 11
test_notes_include_generation_summary(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -7822,3 +7822,37 @@ representation generation, with sustained-run stability checks.
- `editor/src/PerformanceOptimizationSuite.h` within header-size limit (`222` <= `600`)
- `editor/tests/step504_test.cpp` within test-file size guidance (`136` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 505: MCP Tool Documentation
**Status:** PASS (12/12 tests)
Implements structured MCP tool documentation generation with category indexing,
schema/example coverage, error-code metadata, and materialized markdown/json
resources for MCP client consumption.
**Files added:**
- `editor/src/MCPToolDocumentation.h` - documentation resource builder:
- structured tool documentation model (description, input/output schema, examples, errors)
- category index generation (`AST`, `Diagnostics`, `Workflow`, `Routing`, `Security`)
- completeness validation checks
- markdown + JSON renderers
- resource materialization writer (`mcp_tool_docs.md`, `mcp_tool_docs.json`)
- `editor/tests/step505_test.cpp` - 12 tests covering:
- per-tool documentation completeness
- required category presence
- schema/error metadata coverage
- markdown/json export validity
- writable/readable documentation resource output
- invalid-doc rejection behavior
- `editor/CMakeLists.txt` - `step505_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step505_test step504_test` - PASS
- `./editor/build-native/step505_test` - PASS (12/12)
- `./editor/build-native/step504_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/MCPToolDocumentation.h` within header-size limit (`192` <= `600`)
- `editor/tests/step505_test.cpp` within test-file size guidance (`167` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`