1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! What a forjar verb IS, independent of the transport carrying it.
//!
//! A [`VerbSpec`] is the single declaration of a capability: its name, what it
//! does, whether it mutates anything, and the schemas its params and result
//! must satisfy. Every transport derives its own view from this — the CLI its
//! flags, MCP its `tools/list` entry and annotations, the manifest its rows.
//!
//! The rule that makes it worth having: **nothing here may be stated twice.**
//! `src/mcp/registry.rs` used to declare the same 9 tools in four separate
//! places (`export_schema`, `build_registry`, `build_forge_config`, and again
//! inside `serve`). The literal `forjar_validate` appeared four times in one
//! file, so adding a tenth tool meant editing four lists and the compiler
//! would not notice if you edited three. Worse, `build_registry` was reachable
//! only from tests — production registered its handlers inside `serve`, so the
//! test asserting "the registry has all tools" was asserting it about a
//! registry no user ever touched.
use Serialize;
/// Whether invoking a verb can change anything.
///
/// This exists once, here, because it is published to MCP as `readOnlyHint`.
/// Stating it a second time next to the transport would let the two drift, and
/// a wrong `readOnlyHint` is worse than a missing one: an agent trusts it
/// before deciding whether it may call the tool unattended.
///
/// It is also the reason a verb's input schema is a security surface. A field
/// that makes the verb run a caller-named script falsifies `readOnlyHint`
/// however carefully its description is worded, because the hint is machine-read
/// and the description is not. `lint` carried exactly such a field
/// (`policy_dir`, compliance packs, `sh -c`) until #356; the fix was to delete
/// it from `mcp::types::LintInput` and leave it a CLI flag. See
/// `core::quality_gate::GateThresholds::policy_dir`, which states the same
/// boundary from the other side.
/// One capability, declared once, rendered by every transport.