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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
* net_mcp.h — C SDK header for libnet_mcp_ffi (the MCP bridge pure
* helpers + the graduated consent / pin surface, C ABI).
*
* One header, one shared library. Mirrors the layout of `net.h` /
* `net_meshdb.h` next to it. Symbols live in the
* `libnet_mcp_ffi.{so,dylib}` / `net_mcp_ffi.dll` cdylib built from
* `bindings/go/mcp-ffi`. This is the C face of exactly what the Python
* (`bindings/python/src/{consent,mcp_helpers}.rs`) and Node
* (`bindings/node/src/{consent,mcp_helpers}.rs`) bindings expose — one
* Rust implementation, three faces. The Go binding's `go/mcp.go` cgo
* block is the de-facto contract; this file is the canonical drop-in for
* C / C++ / Zig / Swift / Java JNI / etc.
*
* # Build
*
* cargo build --release -p net-mcp-ffi
*
* Linux: target/release/libnet_mcp_ffi.so
* macOS: target/release/libnet_mcp_ffi.dylib
* Windows: target/release/net_mcp_ffi.dll
*
* # Link
*
* gcc -o app app.c -L target/release -lnet_mcp_ffi -lpthread -ldl -lm
*
* # Scope
*
* Pure helpers (no mesh, no process, no secret ever crosses — the
* bridge's forwarding / keychain internals are NOT bound):
*
* net_mcp_classify — credential-risk score a wrapped server, for
* DISPLAY before publishing. Only env KEYS drive
* detection; values never appear in the result.
* net_mcp_lower_tool — lower an MCP tools/list entry to the Net
* ToolDescriptor + bridge metadata (JSON DTO).
*
* Consent gate:
*
* net_mcp_credential_requires_consent — the wire-"none"-is-never-trusted
* boundary (a discovered capability can only ever
* over-gate, never bypass consent).
* net_mcp_cap_id_canonicalize — canonical provider/capability id.
* ConsentPolicy (opaque) — the allowlist / pin gate.
*
* Pin store (path-scoped, cross-process-locked): every mutation runs the
* core's full locked load->apply->save transaction, so the same file the
* `net mcp pin` CLI and a running `net mcp serve` shim use is honored
* bidirectionally. The store file is never opened here directly.
*
* # Memory model
*
* A `char*` returned by any function is heap-owned by the caller and MUST
* be released with net_mcp_free_string exactly once (idempotent on NULL).
* The one opaque handle, ConsentPolicy, is freed with
* net_mcp_consent_policy_free (idempotent on NULL).
*
* # Error model
*
* Functions returning `char*` yield NULL on error. net_mcp_pin_state also
* returns an empty string "" for "no record" (states are never empty, so
* it is unambiguous). Functions returning `int` use -1 for error; a
* non-negative value is the result (0/1 for a bool) — except
* net_mcp_credential_requires_consent, which has no error return and gates
* (returns 1) if a runtime panic is trapped, so a failure never under-gates.
*
* Detail for the most recent failure is available per-thread via
* net_mcp_last_error_message (human-readable) and net_mcp_last_error_kind
* (a stable tag: "invalid_arg", "classify_error", "pins_error",
* "encode_error", "runtime_panic"). Both return NULL when no error has
* been recorded on the calling thread; the returned pointers are valid
* until the next FFI call on the same thread touches the thread-local and
* must NOT be freed. Every entry point clears the last-error at the top,
* so a NULL / -1 with no last-error set means "not an error" (e.g. an
* absent pin record). Use net_mcp_clear_last_error to reset.
*
* # Threading
*
* Every function is safe to call from any thread. The pin-store functions
* are serialized across processes by the store's own advisory file lock;
* a ConsentPolicy handle is NOT internally synchronized — do not share one
* handle across threads without external synchronization.
*/
extern "C" __cplusplus
} /* extern "C" */
/* NET_MCP_H */