code wiki / (root) / nx_mcp.nx

nx_mcp.nx source

↩ module page · 249 lines · 13017 B

1// nx_mcp.nx -- THE SOVEREIGN MCP CLIENT. Retires nishi-ops/nxmcp.py (Python) and nxfsedit.ps1 2// (PowerShell) from the operating path: composing a tools/call envelope is FORMATTING, and formatting 3// is an organ's job, not a script's (rule 29 -- NishiLang for everything, shell is break-glass only). 4// 5// nx_mcp <tool> <capfile> [argv...] 6// any argv element of the form @@FILE:<path> is replaced by that FILE'S CONTENT (positional, because 7// organs differ on where a payload goes -- nx_fs_write is `write <path> <content> [expect=]`, so a 8// "content is always last" helper silently passes the expect flag AS the content). 9// 10// WHY THIS EXISTS, MEASURED 2026-08-10: the Python client returned `{}` on a 36,616 B source push and 11// left the NAS file at 33,955 B -- a state matching NEITHER the new bytes NOR the previous ones, which 12// then COMPILED CLEAN and produced a plausible binary. A transport that can silently corrupt the thing 13// it is shipping is not a transport. ★★★★★★ A FOREIGN-RUNTIME DEPENDENCY IN THE SHIP PATH IS A DEFECT 14// YOU CANNOT DEBUG WITH YOUR OWN INSTRUMENTS -- I could not determine the cause because the failure was 15// inside somebody else's json encoder, and "undetermined" is the honest verdict a sovereign stack should 16// never have to write about its own ship lane. 17// 18// COMPOSITION, NOT REIMPLEMENTATION: the TLS-1.3 wire is nx_mgmt_client (proven, sovereign, already the 19// lane for /api/build|promote|deploy). This organ owns exactly ONE thing -- a correct RFC 8259 envelope. 20// license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_json_lib.nx" 23 24const MC_MAGIC_8388608: i64 = 8388608 25const MC_MAGIC_2048: i64 = 2048 26const MC_MAGIC_512: i64 = 512 27 28const MC_BODY_CAP: i64 = 8388608 // request envelope 29const MC_RESP_CAP: i64 = 8388608 // response 30const MC_FILE_CAP: i64 = 4194304 // one @@FILE payload 31const MC_URL: *u8 = "https://nishifamily.com" 32const MC_PATH: *u8 = "/mcp" 33const MC_CLIENT: *u8 = "./_offc/nx_mgmt_client.elf" 34 35func mc_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 36func mc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 37func mc_cat(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } return o + i } 38func mc_num(dst: *u8, o: i64, v: i64) -> i64 { 39 let t: *u8 = sys_mmap(32) 40 var m: i64 = v 41 var k: i64 = 0 42 if m == 0 { t[0] = 48 as u8; k = 1 } 43 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 44 var w: i64 = o 45 var i: i64 = 0 46 while i < k { dst[w] = t[k - 1 - i]; w = w + 1; i = i + 1 } 47 return w 48} 49func mc_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] == (0 as u8) { return 1 } return 0 } 50 51// --------------------------------------------------------------------------------------------- 52// RFC 8259 string escape, and NOTHING ELSE. Exactly: quote, backslash, and the C0 controls. 53// ★ UTF-8 BYTES PASS THROUGH UNTOUCHED. Escaping them to \uXXXX is what breaks this wire: the 54// sovereign JSON parser does not decode \uXXXX -- it DROPS THE BACKSLASH -- so an ascii-safe encoder 55// turns a star into the literal text u2605 and the write still answers OK. '>' and '/' need no escape. 56func mc_esc(dst: *u8, o0: i64, src: *u8, n: i64) -> i64 { 57 var o: i64 = o0 58 var i: i64 = 0 59 while i < n { 60 let c: i64 = src[i] as i64 61 if c == 34 { dst[o] = 92 as u8; o = o + 1; dst[o] = 34 as u8; o = o + 1 } else { 62 if c == 92 { dst[o] = 92 as u8; o = o + 1; dst[o] = 92 as u8; o = o + 1 } else { 63 if c == 8 { dst[o] = 92 as u8; o = o + 1; dst[o] = 98 as u8; o = o + 1 } else { 64 if c == 9 { dst[o] = 92 as u8; o = o + 1; dst[o] = 116 as u8; o = o + 1 } else { 65 if c == 10 { dst[o] = 92 as u8; o = o + 1; dst[o] = 110 as u8; o = o + 1 } else { 66 if c == 12 { dst[o] = 92 as u8; o = o + 1; dst[o] = 102 as u8; o = o + 1 } else { 67 if c == 13 { dst[o] = 92 as u8; o = o + 1; dst[o] = 114 as u8; o = o + 1 } else { 68 if c < 32 { 69 // the remaining C0 controls are rare and MUST be escaped; \u00XX is correct here because 70 // the value is pure ASCII hex, not a multi-byte character. 71 dst[o] = 92 as u8; o = o + 1 72 dst[o] = 117 as u8; o = o + 1 73 dst[o] = 48 as u8; o = o + 1 74 dst[o] = 48 as u8; o = o + 1 75 let hi: i64 = (c / 16) % 16 76 let lo: i64 = c % 16 77 if hi < 10 { dst[o] = (48 + hi) as u8 } else { dst[o] = (87 + hi) as u8 } 78 o = o + 1 79 if lo < 10 { dst[o] = (48 + lo) as u8 } else { dst[o] = (87 + lo) as u8 } 80 o = o + 1 81 } else { dst[o] = c as u8; o = o + 1 } } } } } } } } 82 i = i + 1 83 } 84 return o 85} 86 87// read a whole file into buf; returns bytes, or -1. Loops: one read() on a regular file may return short. 88func mc_read_file(path: *u8, buf: *u8, cap: i64) -> i64 { 89 let fd: i64 = sys_openat_rd(path) 90 if fd < 0 { return 0 - 1 } 91 var n: i64 = 0 92 var run: i64 = 1 93 while run == 1 { 94 let k: i64 = sys_read(fd, ((buf as i64) + n) as *u8, cap - n) 95 if k <= 0 { run = 0 } else { n = n + k; if n >= cap { run = 0 } } 96 } 97 sys_close(fd) 98 return n 99} 100 101// "@@FILE:<path>" -> that file's CONTENT into out; else a copy of the literal. Returns byte length, 102// or -1 when a named file cannot be read (REFUSE: shipping the literal string "@@FILE:/x" as if it 103// were the payload is the silent-corruption shape this organ exists to end). 104func mc_resolve(arg: *u8, out: *u8, cap: i64) -> i64 { 105 let pfx: *u8 = "@@FILE:" as *u8 106 var isf: i64 = 1 107 var i: i64 = 0 108 while i < 7 { if arg[i] != pfx[i] { isf = 0; i = 7 } else { i = i + 1 } } 109 if isf == 0 { 110 let n: i64 = mc_slen(arg) 111 var k: i64 = 0 112 while k < n { if k < cap { out[k] = arg[k] } k = k + 1 } 113 return n 114 } 115 return mc_read_file(((arg as i64) + 7) as *u8, out, cap) 116} 117 118func main(argc: i64, argv: *i64) -> i64 { 119 if argc < 3 { 120 mc_puts("usage: nx_mcp <tool> <capfile> [argv...] (an argv of @@FILE:<path> sends that file's CONTENT)\n" as *u8) 121 sys_exit(2); return 2 122 } 123 let tool: *u8 = argv[1] as *u8 124 let capf: *u8 = argv[2] as *u8 125 126 let cap: *u8 = sys_mmap(MC_MAGIC_2048) 127 let capn0: i64 = mc_read_file(capf, cap, MC_MAGIC_2048) 128 if capn0 <= 0 { mc_puts("nx_mcp: cannot read capfile\n" as *u8); sys_exit(3); return 3 } 129 // trim trailing whitespace/newline: a cap with a stray \n is rejected by the server as a bad token, 130 // and the error it returns talks about capabilities, not about your file's last byte. 131 var capn: i64 = capn0 132 while capn > 0 { let c: i64 = cap[capn - 1] as i64; if c == 10 { capn = capn - 1 } else { if c == 13 { capn = capn - 1 } else { if c == 32 { capn = capn - 1 } else { capn = 0 - capn } } } } 133 if capn < 0 { capn = 0 - capn } 134 135 let body: *u8 = sys_mmap(MC_BODY_CAP) 136 var o: i64 = mc_cat(body, 0, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8) 137 o = mc_esc(body, o, tool, mc_slen(tool)) 138 o = mc_cat(body, o, "\",\"arguments\":{\"argv\":[" as *u8) 139 let scratch: *u8 = sys_mmap(MC_FILE_CAP) 140 var i: i64 = 3 141 var emitted: i64 = 0 142 while i < argc { 143 let raw: *u8 = argv[i] as *u8 144 let rn: i64 = mc_resolve(raw, scratch, MC_FILE_CAP) 145 if rn < 0 { mc_puts("nx_mcp: @@FILE payload unreadable -- REFUSING to send the literal\n" as *u8); sys_exit(4); return 4 } 146 if emitted > 0 { body[o] = 44 as u8; o = o + 1 } 147 body[o] = 34 as u8; o = o + 1 148 o = mc_esc(body, o, scratch, rn) 149 body[o] = 34 as u8; o = o + 1 150 emitted = emitted + 1 151 i = i + 1 152 } 153 o = mc_cat(body, o, "],\"_cap\":\"" as *u8) 154 o = mc_esc(body, o, cap, capn) 155 o = mc_cat(body, o, "\"}}}" as *u8) 156 157 // Unique scratch so concurrent invocations cannot read each other's envelope. 158 // ⚠ NOT getpid: raw __syscall(39) is an x86_64 number with no wrapper here, and it returned a 159 // NEGATIVE value -- which mc_num renders as NOTHING, so all three temp paths silently collapsed to 160 // the same name (/tmp/nx_mcp_resp_.json). The wall clock is portable and already proven this session. 161 let pid: i64 = sys_now_realtime_us() 162 let bpath: *u8 = sys_mmap(MC_MAGIC_512) 163 var bp: i64 = mc_cat(bpath, 0, "/tmp/nx_mcp_body_" as *u8); bp = mc_num(bpath, bp, pid); bp = mc_cat(bpath, bp, ".json" as *u8); bpath[bp] = 0 as u8 164 let tpath: *u8 = sys_mmap(MC_MAGIC_512) 165 var tp: i64 = mc_cat(tpath, 0, "/tmp/nx_mcp_tok_" as *u8); tp = mc_num(tpath, tp, pid); tp = mc_cat(tpath, tp, ".txt" as *u8); tpath[tp] = 0 as u8 166 let rpath: *u8 = sys_mmap(MC_MAGIC_512) 167 var rp: i64 = mc_cat(rpath, 0, "/tmp/nx_mcp_resp_" as *u8); rp = mc_num(rpath, rp, pid); rp = mc_cat(rpath, rp, ".json" as *u8); rpath[rp] = 0 as u8 168 169 let bfd: i64 = sys_openat_wr(bpath, 0x1a4) 170 if bfd < 0 { mc_puts("nx_mcp: cannot write envelope\n" as *u8); sys_exit(5); return 5 } 171 sys_write(bfd, body, o) 172 sys_close(bfd) 173 // /mcp authenticates from _cap IN THE BODY, so the token file is unused -- but the client's argv 174 // grammar requires one, and an ABSENT file makes it fail for a reason that has nothing to do with auth. 175 let tfd: i64 = sys_openat_wr(tpath, 0x1a4) 176 if tfd >= 0 { sys_write(tfd, "x\n" as *u8, 2); sys_close(tfd) } 177 178 // fork the PROVEN sovereign client; capture its stdout so we can unwrap the envelope. 179 let cargv: *i64 = sys_mmap(80) as *i64 180 cargv[0] = MC_CLIENT as i64 181 cargv[1] = MC_URL as i64 182 cargv[2] = "call" as i64 183 cargv[3] = "POST" as i64 184 cargv[4] = MC_PATH as i64 185 cargv[5] = tpath as i64 186 cargv[6] = bpath as i64 187 cargv[7] = 0 188 let envp: *i64 = sys_mmap(8) as *i64 189 envp[0] = 0 190 let child: i64 = sys_fork() 191 if child == 0 { 192 let rfd: i64 = sys_openat_wr(rpath, 0x1a4) 193 if rfd < 0 { sys_exit(126) } 194 // ★ sys_dup3, NOT a raw dup2. RV64 HAS NO dup2 -- the syscall was removed from the arch, and 195 // this estate builds for RV64 as well as x86_64 (SYS_DUP3 = 292 / 24 respectively). A raw 196 // __syscall(33) compiles fine, silently fails, and the ONLY symptom is a zero-byte capture that 197 // looks like "the server said nothing". ★★★★★ A HARDCODED SYSCALL NUMBER IS AN ARCHITECTURE 198 // ASSUMPTION WEARING THE COSTUME OF A CONSTANT. 199 sys_dup3(rfd, 1, 0) 200 sys_execve(MC_CLIENT, cargv, envp) 201 sys_exit(127) 202 } 203 let st: *i64 = sys_mmap(16) as *i64 204 sys_wait4(child, st, 0) 205 let rc: i64 = (st[0] >> 8) & 0xff 206 207 let resp: *u8 = sys_mmap(MC_RESP_CAP) 208 let rn2: i64 = mc_read_file(rpath, resp, MC_RESP_CAP) 209 __syscall(263, 0 - 100, bpath as i64, 0, 0, 0, 0) 210 __syscall(263, 0 - 100, tpath as i64, 0, 0, 0, 0) 211 // ⚠ KEEP the response file when capture failed: deleting the only evidence of a failure is how a 212 // transport bug stays unexplained. It is unlinked ONLY on the success path below. 213 if rn2 > 0 { __syscall(263, 0 - 100, rpath as i64, 0, 0, 0, 0) } 214 if rn2 <= 0 { 215 mc_puts("nx_mcp: TRANSPORT-SILENT rn2=" as *u8) 216 let nb0: *u8 = sys_mmap(16); let ne0: i64 = mc_num(nb0, 0, rn2); sys_write(1, nb0, ne0) 217 mc_puts(" kept=" as *u8); mc_puts(rpath) 218 mc_puts(" (client rc=" as *u8) 219 let nb: *u8 = sys_mmap(16); let ne: i64 = mc_num(nb, 0, rc); sys_write(1, nb, ne) 220 // ★ never print an empty success: a silent transport is an UNKNOWN outcome, and the call may 221 // well have LANDED. Say so, so the caller checks the artifact instead of blindly retrying. 222 mc_puts(") -- OUTCOME UNKNOWN, the call may have landed: VERIFY THE ARTIFACT before retrying\n" as *u8) 223 sys_exit(6); return 6 224 } 225 226 // unwrap {"result":{"content":[{"type":"text","text":"..."}]}} -> the organ's own bytes. 227 // Anchored on the LAST "text":" so a tool whose OUTPUT mentions the field name cannot hijack it. 228 let out: *u8 = sys_mmap(MC_RESP_CAP) 229 var at: i64 = 0 - 1 230 var scan: i64 = 0 231 var run3: i64 = 1 232 while run3 == 1 { 233 let f: i64 = jx_find(resp, rn2, scan, "\"text\":\"" as *u8) 234 if f < 0 { run3 = 0 } else { at = f; scan = f + 8 } 235 } 236 if at < 0 { 237 // no text field: hand back the raw response rather than inventing a summary of it 238 sys_write(1, resp, rn2) 239 if rc != 0 { sys_exit(rc); return rc } 240 sys_exit(0); return 0 241 } 242 jx_copy_str(resp, rn2, at + 8, out, MC_RESP_CAP) 243 let on: i64 = mc_slen(out) 244 sys_write(1, out, on) 245 if on > 0 { if out[on - 1] != (10 as u8) { sys_write(1, "\n" as *u8, 1) } } 246 // isError is the organ's verdict, not the transport's: surface it in the exit code. 247 if jx_find(resp, rn2, 0, "\"isError\":true" as *u8) >= 0 { sys_exit(1); return 1 } 248 sys_exit(0); return 0 249}