From 55bfbb423ad4a27105236e7225f9425d56fc15d1 Mon Sep 17 00:00:00 2001 From: Calmy Jane Date: Fri, 30 Oct 2026 23:26:17 +0400 Subject: [PATCH] Rewrite the readme for users and move protocol details to docs The readme mixed a short MCP primer with wire-level detail: JSON-RPC, Server-Sent Events, header names, the order of the access checks. It did not say how to build a server, where the token comes from or how to use progress and cancel in a tool VI. The readme now explains, from the user's side, what the toolkit can do, a quick start with the Simple example and Claude Desktop, how a VI becomes a tool, the palette VIs, prompts and resources, progress and cancel, asking the user, connecting clients, and who can connect: how to generate an access token, where to wire it and how Claude Desktop and Claude Code send it. It shows the block diagrams and front panels of the three examples (docs/images, rendered from the examples with Print.VI To HTML) and adds a troubleshooting list. The example path now matches the installed package (\examples\MCP Server\examples). docs/technical-details.md carries the rest: an MCP introduction with a sequence diagram, the transport and methods, both protocol eras, the schema and name rules, results, the elicitation round trip, progress streaming and cancellation, the access checks and curl examples. docs is excluded from the package, so neither file is installed. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- docs/images/datalogger-main.png | Bin 0 -> 17314 bytes docs/images/datalogger-panel.png | Bin 0 -> 10588 bytes docs/images/full-featured-main.png | Bin 0 -> 32017 bytes docs/images/full-featured-panel.png | Bin 0 -> 17028 bytes docs/images/simple-server-main.png | Bin 0 -> 10315 bytes docs/images/simple-server-tool-diagram.png | Bin 0 -> 7334 bytes docs/images/simple-server-tool-panel.png | Bin 0 -> 4632 bytes docs/technical-details.md | 276 +++++++++++++ readme.md | 430 ++++++++++++++------- 10 files changed, 576 insertions(+), 132 deletions(-) create mode 100644 docs/images/datalogger-main.png create mode 100644 docs/images/datalogger-panel.png create mode 100644 docs/images/full-featured-main.png create mode 100644 docs/images/full-featured-panel.png create mode 100644 docs/images/simple-server-main.png create mode 100644 docs/images/simple-server-tool-diagram.png create mode 100644 docs/images/simple-server-tool-panel.png create mode 100644 docs/technical-details.md diff --git a/CLAUDE.md b/CLAUDE.md index 2ba2821..a69aa20 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ resources). Built on the IG HTTP Server Toolkit and JSONtext. | `tools/ci/` | Pull request review scripts and the workflow template | | `tools/setup/`, `tools/test/` | Environment setup and the protocol tester, where present | | `tools/icons/` | `New-VIIcon.ps1`, which draws VI icons in the toolkit's style | -| `docs/` | Guides for working on the repository, e.g. `pull-request-reports.md` | +| `docs/` | `technical-details.md` (protocol and internals, linked from the readme), `images/` (readme pictures), and guides for working on the repository, e.g. `pull-request-reports.md` | | `.github/workflows/` | CI. Maintainer-only, see below | ## Who decides what diff --git a/docs/images/datalogger-main.png b/docs/images/datalogger-main.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8d9f734beab0d6a6612a0d5ef39edc15b3ee4c GIT binary patch literal 17314 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 A5&!@I literal 0 HcmV?d00001 diff --git a/docs/images/datalogger-panel.png b/docs/images/datalogger-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..6a093345a96e704e3a561bf83be3de24208487c9 GIT binary patch literal 10588 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 literal 0 HcmV?d00001 diff --git a/docs/images/full-featured-main.png b/docs/images/full-featured-main.png new file mode 100644 index 0000000000000000000000000000000000000000..27d6e5e0438b39f846513bf7acd92200940aa2aa GIT binary patch literal 32017 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 LLg%0O>&pKD5d-rF literal 0 HcmV?d00001 diff --git a/docs/images/simple-server-main.png b/docs/images/simple-server-main.png new file mode 100644 index 0000000000000000000000000000000000000000..3474fbeccd2ac659d9dc9cc3f92b6f59ecb4efa8 GIT binary patch literal 10315 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 literal 0 HcmV?d00001 diff --git a/docs/images/simple-server-tool-diagram.png b/docs/images/simple-server-tool-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..ff61af9844de8696c05d85c41538e4d72249964e GIT binary patch literal 7334 zcmbt&cT^M6yX{bC?mfOj;59Han%1~AYuxkWh{1PTR5p`a)f1cd^eL<9nbfTIvl6as=m08WxP z0tJVo;7}ACf`S81=AsA`6pn&IQBVj93OHGcAW#rE3IatzASeg`M4|u$3V@>kC<=g} z0AK)OgU}!rxEP!boYXlPI06AhARq_?;G`@V;cx^Tihx59aKH%*Fp0tuP$&WlK|lc~ zq`PNCH#{vIfCG(jZH4D#!yY3}S=OAQo`q+({aY!GS=bfDTn*X*asmZ|3PIK&7)Tmi3DN?2fQ3P9 z5E?jX(aCHu4aVRAz>a}Ng8IO+V1xi4lz&gdiQIn;20MDv&wouiA@$GqllK0z@`U3* zcTT$fPw)xle;Q7j|Bu#*f`64jXb=nR5@;T%X#)Tz0iY28iUA-R00IHP6adr#KmY(( zK^p%b>5Ywz$;rva#>V2};^^q;z`#ILQ&V+ybpZhZR@M{j1BM8Y1owR{bybrfoAoqp zAD)J{&loi(7#5WT<3BoI;Mg_rdGT?9Q^u|&bCT6MP{8F>T=InBn3UHe*1Tk^KzNJI zi|kRx>D0)`+X9Ha>k0FeF}8M{X&~k>hSq!Q9eM(QZhLc9+yK zXs}|K8^1~*+8Z~7+G)SF7vQkKBJ+*%eLHF~UVme9_TCKxjh(2c56AsnRdw0rltudF zCF-Rd%dqw0Jqj(1eOXxY^OUmI=T&EZJ;Ib7dWFtiKt{(WS}#B8F$ z9EVYSADW!XAz(gfocdm#ap;)I5P#r*(0^F7*fCvr%F6hUA68j5I>!!a>@45S7KB*VvJ#uoTf>88xmcN9_e7$M^o!w8@s127&kfEC z#Wmx?JL`e>5Mu=q2v_&f#ZkGZ4-jm#W4Q~dhRm~Pr5%TE%di)}SV_o9?zhqPd$rkY zW4v*5T_kDmrf$sgnwDA3PvZhLSd)sCciEnra~Z{ZeVlduY~gkZ<3I=lq%&RO>hsec z?}JFc9>ibS9Dh*uIrZ*Aylp8@@Fu#DeBUFXf}9jsaV8}OifqrZOVxt>YAaZJQmp1? z;#ux3aJOWS$#y=uOeuF=N65uUr=lPtxJyLenb&@m?qDooUu}{m@ECzsYG8J`HN=@> zrs+T|y>LjAmLt<8?|i9ZGw#Y!)ZX#Ok}O@nRriYb&=rw8V$Ak_7aaT~Gn%Z_eO?)E z{K6}BOQKx&elLf9zdY>yb-Bg&XU!HVz(l^yC%E;LM;M8d!`>ro-%c5YL!+;8t+ppK zy&vp?#3Ydn%Dva-Laexlx_ZeNMUwe=|_qwJ6`^ z<2zX7D<{3}T%1?NijP+k@6C$?rVL?P)E!Qh5X%=EKBwsKS3;=HQh~1l_%_i0zg#3R zmnT~g5BY@ndQqF_Kk?FwcQ^OG{F(na^73Q|sbKGOhGyN2R?($6<;fSl7nlR}vbM-FnU+tV% z0uHP1c4wZeRfEyvf4JWqePm9TR9|qdGcLghU+_@I(wN;|L(iSZKrY~|g+y$5F}BXz zv}%z_O4aME2}3VxQR`SCnAGdKOMmn!P~P|N@qK<}mx7H96lvW9P}KO2mLqRo*pPlGGV1QBu^ z0###{9g=1JV^#-|lACEL3l!o~&fe}Hr#4!|G3h9qI;5)3!QV$<~n_>T`SE65cy#7z;IPlz*p> ze!Dj~w&v=jDZD~|sgmg_bFcD!K6DC(j40*s=@R8wL}@#wb*xBG>A^lFtCrix*)l#6 zlTQjasM<@4bc9Q)FRYYm#%w8$IlmS?g3M?g%gKD>C2&+0i>fnqs-tfhnQI2s5~XOW zk>kebyaYa$BCaYI_m1~s7$5Orn8eO1l>7S%Hdmca%u(kxw;KDb$e#8K4Jp;&>()tm zuef*j4iD_$I)ioe@Z4oH&39QOF<19Qq?^@C+tkm!ul$!yk{GvQ?>!x2%JvZ#6+Fgn z%BywL<8Ex)46ya-;}nH(ePvMGynfa0*>44Q>HDvJDW8yo3mf|w_v*2*jXkt z$&T>|c9pOFTK7~E{_vaLblJ7D(^6|@h#!{GOB*a>fzOgfw9us+ci^wQ^vQ1=zfJ9% z#18AnyL5{*r>WqOFveJKv<g?{n}V9LvXnzs(eNVrbURzLKxtup|$dzPeY@ZOgO8Q zYiugrS;=+-m1?Lg>_=q8WGL~d=+A5S*mC-mx4aT5Vr{`uY38mq(%lrp#Weq-TH%zi zM~}*p_O_Bqm*oGTql#>Ay&3L|rvqruvLo)9+cxGdR-RLC7bdbBtVuRFhs5?lj#ZrM z08#kJE&U3or)rO_tM$L$rPqD_{Hlao?3YNv)DhE(r-s+0xuUb&N)7`UU+j{j*3ZgG z$Vc1r&OUcjYz{;BdW7fKP`uqI;dfzQXGn$VlxIo#?!)(mP+@Rh)||<)(#!odhi|my zE`RG?92Dz?iNo+7IUGx0)C$#%dK=|6`fhri6Jxz++x5y$=Bk(|#}sGy)wLOvKUah% zZEdmd+_eGu8MmDiSW1yFK+&ShHl$>!?+KcvmTY1 z-Kf?Jl>{!T$JmDORY#7837dTTaHaAJ)r)c6FV185gT5S&!EmU^XpX~p(Ve1$({UxS zmb{XpVy)Xj_h_5SN&ZkSRZCNm)F_gB(hpkC=5Zxh?+0#mOIQZpluIm!DAR1!e5AkV zntB^zIT>^9E(EDTM~exH@>Kx7sB_83v$M$-w#!-iFD5Rgd}FJxpug2y&#ibj>mF?1 z^)#kwbjw-Vf~)F%qa>`;?0RwStq0fEw&%`1Oes^dJY&8}@gfR6T^%HfteL{*ubSy| zSC#j^qvY!bSit7DSXLM1o2$0<(53@CYRt}O&j8o6mJsp&^O*tP)V`fBD!WHKeAdA? zpYHuw*{2$LGqaL=EO2i-%WwLW^7pk8gOPTOe}Udo_T%YaneME)v~M5g(5cj~Mt|rZ z`ly`D(rm@!WZ<4*Kv)BQ9b<)RT#jA4e7Ao-*78^&KO4_@WD@l$DWByan_{GgrYK~- zE%v?HK?CT(_a_{9-FfVoY%8i(91`3if74TJZSt!Nbr>JUumdG=MPnxo-QYyL>* z22n%>Mt@%nGS=$F1)smDqYgV5RhqHqpr)h7JdrbCC>&U95dXnpU$3p0Z`X^yJ%A{9 ze2%>^c1HHICb4-=Uic&%2x zwe1{r86S=dx~x(V_xdb6D`N=G-m^H-Z`_%rV)rw)N4s6z}?sUW$ z6--ijuTUf9zzBbqx>CFWAgCkw9&4N5^mGh=luZ(z@DzL^j8%3d>vO&>l%s__2j zi}!zhNqddVw%&{#)Zp3o-7YtgA4In?9ZZCfw!W3_M2=jtsONny6DX$hHD8y!*{A^x7 zSQj2{`5ls8U1lcuW0h#ES7#mnB;j)&x20{6_m*u=bdyS<F{h8<-qrDK<5NZO>^w@V2BV{?hZ+3mVRns6%`Mf$NR?9P)E}kf`psYS!)1$3 zb8}8qgA*ZlUo@L)`VoG@aP*y8e}z_FDB((3v89?{z|w_RMlNA%Xf_^EXqo5o)4wOv zGK=D(yl(i`NrY#QJ$;^!{=OtJa3HuXCAz`%v;Y5tlDS5j;S`+_V1IsReq43cd}$AU!QJ%K_&iHE!oY5xwmqJtd*EaO#% znd_4nR}EkOWFFUlU=`(czB%}`QC7)e>=1EeHJ32aLYv`a8QK#$-{{!!X2E^)+xmEM z?Agh;v+TjU&d~yO7qhB(`wOsOp`Pym<|)It1*)W8l3Qs28ute~rhA4bX%;jl_q+e#uENxReDPv_@FgC&*|{*G7-;cle{K+Kk6XxS%|=b2Yoy()iO=ex^&dg;4%Ee+C@ zM6j}s>oQ)}zB2i&kT9skxDkYBU2L?AIxsDShp#Z=Rrxu;qG z&FS1bwZ{z#t>kZ+Av#caTb1VBUtQ@oKew5>I(|PvLO#feO{9$q@Fz?ka!?1*{AVLx zP>4^r2hL6>=P zrvLQ%l_F*^+w&Q=vne#>bJtUib6VUta-lp@!cQ)j3gLs3auw5<9&K@l zmdyG#i*Ts1cURTN4us}Nl)W@~#d+<;nP)A1GCjhxY~|izKO<&&&XX#@Pw3Clg1QDp zX?*kM3uEZ|U{dsBkptP?i@sCG(`ql~7>k4n zj7)y|tG^G<;&B<7DDA8yC*Z&ZN#cBi4w#G6V!Q5(R#P)fdASYq^`;NlXX|*BvdFUc zAbdj+crZe>KFGp)q(^pBM>hIBus~Y$HM=RlizA=I_P_P&b86!wjW#J~>P3A~YJGHw zeaPF5{*q#FeW!kY?owwHeP5naXD-$2VD`x$OE2BuW4)(PyTRD7j2zO`Qrx+ia|)EY zT&UBJy}v@#&hH3G1O2FN%X0s0_T^L2n}!N-RZ>O&U#Q2$jZWg) z0M81kPI>BWz>2o|ufr-omo(!Q zUDBBuqXRoV;uAj*fGg;u@lk(X?d4)d z?9xHFTaP?D8Spv2OE%fGn@; z6om@0JYe~E9AmYerUol$9aS2#-dNw=N{dAAD02AdkYq>SZsubz9rS#y6@=^s)2OtO zPJYk*uj5HVJG1Zc?wblC^7tl#MY-p&4A;;+dr_As)1E3-s!XZ*Vc5|F(~1WeBctd? zA?8&;&-toffu!Q>BT@0T&-6*1@AYlyZ(+_gpNp9+$|-p$&0`|Og?~?qKvovSJv#Iq z4)Ld7Pgoe@FVue7rOiTox~8^*tiL56Oz^WQMQR~sC`ew&9(14l43n6*Cx zyM(OO(O2ujCf;mjrZpC=^xKLSCr7-32s6zkZQg|>?{Jw9A2uY}Qjw|ZhrXDw%Le%Q zAMV)sMx88XLpK|%vkOjY#g9cL#lFKagbx?Z?y!Rs&Shb9l?Bc4U0tryTN&nf?LswF zp_;JHuf!Q!-gRiZ(hAfwCU1@c?o7WGzFlb|!kShrvUnw&_M6s=u~j|C(2yiqhyBL{ z=0W}lzN&;7OfhiQ<`41y5iExz!e)2ie&V8sawvDuhTe0~5_@{g?;l+JA?Bbj2j}^z z^`2h>oy=%U)zeYYKW)n?- zC}~T#Fmv<~?Q(qn^Rh&(OA`~Jwfks@Fflc{eqGsfZR(?^lIPTwH9kT$qpx_jXLH@3 zTMqnU9wYK@10kx_fgyVynv$Po?jZ8`nesF2=Llh=RBo${R|}pMA>6H*zv0su`<{8P@00 z;3o-A`m~J~&Yj#|c%e(<3vRa1>yK;>i%}kbipeBC4tukpx2ZHy#tb(_(;MwD3I`c5 z$k*3|MZE6_W06|O6$wok#-UX|(#6OSPbdqOv4V}BpS@5ds*W&#joK`%Et%o}t^?(; z9ob4<-d{$B4AM8`W%lnC|8gJk&y&M^GGUK6gX@j^3gIqq7n62Wy8Q`B_dAMdG@qMC zBI8o;N}&2UzeF+fw(tprfuSZCW%HgaiAqLF{=Mp367i0M#hF|9UAVuUTCIg0>x6`XgZJcvzUt~eXOWUyN6S6cIE3rw) zGEC-F&ucvlF#UfX37!b^Jp9Xq0d$k-GNIGpzmfkrDbOBos77IsS6?;ja=_kaVjJAnp~!}w>eZydzi?3^1N za=^byeBsC^LT1tZcvY}ZRI>+VBkR3y)D0oPRk-2H;g{n>FoX>og zjYBfIKdDdO;~zWnl1>P8?S*;id|ZK=X^sR{rA5oA3Xea@gsu(IX7vZ#`OcXD?^tM zP5K;GoO4GK)0Im*>*RL&MZIirw`&5YZG&_s^(aOoj|-xj{!Rz`#L4B}rZ&Lm-qhgz z<%>2&!5xh*HyVtBW=Q+>bpXc*@o;}ar6LImpp4fVs^$-M! z8R+YnfbSd#S_!RP1)j=bx)}tmf-V|e)XV3qL9F%(@{E%(upV?i_#&1 z6Q-jujKV||#-cDJm`kLiL>MI!Q6d&4LV~4OI*Nr+ED^7B~FbWY-2#Z3% z0N4N;V1dP8HYA867%-hqq|>o;087r5U3as z666QM0t*W^%JOayq`O>TP*FiWmy0AIwXD0KyvtGwI4-LrsP=Ng0?Nxd2#UW6QWAiHlp}&Eh(84!TG0CRot$)iUIU+Us{+J=zZzC4Y6V z$~Q);%4hOy--PhgG`_8tYooT!A{`Br-^fp-yX>co*?}q#v_zk3F~oHb%o5kI^bQ<+ z)U)%BZ*2o`H$)tO#0^_*`D~^;?=BpH%gno8E*;dcu#tl zU{TyW9>yI%i2DAV6EWD(VN@zxUhoNXsDz#+J1}6Q11SIH?^m*)s_p68tH#>DmxZyq zf*j?|>&_aBSG^8keek^&U*&E?*qx2J$>c~fNu?LW+woPm>H~M4XyxC|AjdCS*4uc; zZ~01ozraj!Wu2U`;r<$0huD1(Y3=x1E5PC`~J8oG4v2B#*zlXjUG6n~gU(djDR- z#_x!`;q#Obi{Jj;#bz&SyS(^#Xk*XbdfY;_g_?eHtODUn_%B`Fw=_$=f_@h}PpN^} zp}y{v;RIHpE(00in|S?dkyhh)u@b*p9M2<**#zVN%-d3B#ETdOmiW;;Gtc8$&wH-& z%*A9|zj|zMvyKujkZ2JOxWw^yDGhs+EIlsMHp~&XUojfU^D{i(y?UzWV+T^-?VWFy zS)-w@P^a!@zNkH7(^2?+X3}_!rBtQEaV$h+$Hc6a^;!7Y9ik05=o||Z^5qk$?g(pW zHnM-=YOSIgi+r0fnh==yg6#gY1!K(0-?J1MaZY=@FC0a~;Pu+$#sulez^RTeJLiJ; z9hnUx`)~Z{>fAG%9$(xk|Cdh!lY_?#?vnjaApyTBxrM6z(-qGPVOxvI3ZJrdS)|!m zYfT7wfa~*p+xKiqJ;3(RgmKc9 z2kNGwab$%ueB^G$sXrb@jpavHq2v*6f0Lx2-ECuJW(HICUZ{zz5+fokACgi4(g0V*snqtRqn zsmHG3_A?djh0j`~o!W_h_*!WOU+H}(({$e6QR#i1*2XbzqP_y5Y!E4(tX*NJ=dh5n zFjI#U{@dB2$dbF8SvNK2RENtvW@JVBNYjP)F7=A=m~qi>w?B8)Lo$Zh)t_qoVl&p` zc-pXIn@-Bd?N5Pv8BK?2pA5CCW4Y;;M-(<4cJfYfVMUSi#%v-F7R=?zrTIj^m0Sj( z3R@*-=)p9%$z37!pRJp5n+m?p-MLgFh**#%it6X9gsRU^)=r&m>Zx`5 zW%qMna=T1KUALyz2ij+gcG@vDZ@l14Eu{G@evR9Pe0{`C``(a*hL%jFwKP(pApMOjm8m2q?L^enr0_8NSk7V zyxBoFsT`e^f)*nk4we^o~eROpE-_*PvDJu;oqn zIfUg)?W+8Mw|V{ zGlNwbzJ%Sg^6B;^``6DL`Wxt8G#;zQYArOnI*YC8U6P*5SrophXl)caj%TW;>9KRG zP5Rw!Q|Y@LhMlQ-+~>#N+c?Lv?rzP$c4h}V?8Y8zJMrGNLkY*#ZWoTrF6Hr`I^9q1 zbyyv@*xX_^;p*#MHINn%A=SF-sP??t?UOsC&t3SaLC)(7Sl4r0h$ZTCl2vb)y6~+t zpv&HB0_+n1a*O;^J1QlsN!$w=zBH<^rJGe1E?Aa(0zdmyRq5OCq|1lW&PR_h{+H~H zKR2>Sx$x~AQX>l|`V1rGEUX)gSVqZjuyE%QFYyAZx+iL@S}@OCvs*Lqyu)|0i~2LQ zEWIVUE_XGy8h=MVzs%b|c0x9@h=+OBdWYwunx4jfJKdQhl6|wJ@Kl_0U=LFvz*vS+ zgl|_By*xc*gM7|c)#O)`OuK2L`gX@DuWJTHu6olNi&^3+^DtG|GrQCkgcd#d(!iA7cN8%%*cQQq}Ong>&V#I0Y)TCH=wRGkqjgV>1 z^JwG=N17>}NxhBbACc+PJhS2~Q+i>Ta3d;MVq)5G3JH?-#Zk^+#&6HxvsGYBiIqYy znZIdaPOS6`V`@p(BZxF5=xdnse-O(asmXbJ7lzTh%mw=rmJs;8>?Ii03N@GbcWWV= zg?mThexm`QFqbQ!~RETwH)gt%6xNtb?Qs+YC;G`yjme9 zIAqkqMG{@c~L>167ifTC=kb!E)nhRYw%9lde5Bl>OeW%?_T z0pZ$;*w*&cKTTUV;v*umNdB#F(lEZA{OaUW%$5^zTRs>ZE%(NbT;`A8iSpDsXnNzC zYLTmAx9MQ$*lg{I_?~zz6%CD~35Ke?a>1C>Hba_z-lsCyySgnid5U{6LE@8Kp2M+f zqsH`+lI%iC{Eatz%PTgpPUK$Sc1wejnPu8}mQvug<=a#VYfh#bN3kw{a<_}o#n>0O z|DW37C1SJ6!hwgvE0#g!t)|tD|1~_(Km|@IEPiOw|yc&Lm|%V2gKT$n~EK!2v@|^y`N2#<}WZdzF)#) z8%Ps;laot!HB&t|V>$Fn`4mm$3{n@-89!m1eu#3dCfvS;^|M7amnt}@Ht`1%R`*73 zF2FPw3%V{tp*UC@1fshsaC1YOr9jCyRim0ZzVkZ`x3u|eMZ;`aQY z&0eev@*A5@%N{vB88&`NQ}SK05cQ%XF6hTh_W7`VYFFLW3ldH>&RX7!QFhn9lFJN! z%M>y~9Q3OsO0+`TjlcZO)HW!{49Q>$i6RGMW0k+xmV@W zaGz@R6!!IGGa(Wli0>B|NRQY?)e|O}>&846tpT9N#Ume-TUeJ+0 z?VcBOWNBqbLY8~v9dKE3QEF+T$R+tA*z52P(u?4@GLF zy#$Byzglq(fq{b;$yUZGQ%{w7^L+kuepWS>TsrBgRb}Ibyc!8l@vZQiGJI9X$qzTx zj7XjkC0LnvHq=oM?dzDmtXUQB^CQcsOft%qzl+ed(0pMcEwO8#X^~WdOPuXWZgz5S zrq8{c31{0|m%a?Gbk+7Ptq@x7{@!i zjMav{S?b)6u2$Ed#7`fdxN$2>>(}r(4gaG9cq_@C(0Ynl^dob^KYH`Cqig*!kS|COu#0==Xwo)q=Nc-n{Eg6Mo|;f2l+92K$NUR0eIUvOo28@*htU1a3q9#16L7dimn#0A0{o>ZhQqM>`7k~{M>p@^De zETKafCAWzf-?sf8pN(EMyWSwZkKo4dti<&&a6*JM*|40A>@iJrPF2q}hwEhey;0hb z;9F58=}t&cOhQ`c&G)TAdCglyI*O)A0j-FUUa zBvPyWrkp$mhW>RtpQaQ*HfO`S^F17S-!r88_iehq%}zekV9v);@dW9T4@RAkw7G?d z|5M-Hf2f+hy1&M=`#d2XINS*OkDD?Ee7*LY^tKC88-sWcUKFo?ANcc6iD+SntoYT_ z%Xb0=R=wRQ%leP}S2^PH11GJoQ6zP~|^bQOUJ~j0r_T`oFg8|GGK+9~TP2 zYt6%a^5osYu1N0#DuXCv0i_0NsuP^~5xhmzP?RKeDm~}wows)}K1mO)24@~cqN^&t XK1NJp>S: initialize (or server/discover) + S-->>C: server name, version, capabilities + C->>S: tools/list + S-->>C: name, description and JSON Schema of every tool + Note over C: the model decides to use a tool + C->>S: tools/call get_system_stats {} + S->>V: set controls, run the VI + V-->>S: indicator values + S-->>C: result as text, structuredContent and images +``` + +### Messages and transport + +Every message is a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) +object. A request carries `method`, `params` and an `id`. The answer carries the +same `id` and either a `result` or an `error`. A notification has no `id` and +gets no answer. + +The transport is MCP's **Streamable HTTP**. The client sends every message as +an HTTP `POST` to the server's URL (default +`http://127.0.0.1:36987/mcp/server`): + +- A request is answered with `200` and a JSON body, or, when the client accepts + `text/event-stream` and the call streams, with a Server-Sent Events stream + (see [Progress and cancellation](#progress-and-cancellation)). +- A notification is answered with `202` and an empty body. +- `GET` is refused with `405` (`Allow: POST`): the server does not open a + stream of its own. + +Clients that only start servers as local programs (stdio), such as Claude +Desktop, reach the HTTP endpoint through the `mcp-remote` proxy. + +### Methods the server answers + +| Method | What it returns | +|---|---| +| `initialize` | protocol version, capabilities and server info (MCP 2025-11-25 and older) | +| `server/discover` | supported versions, capabilities and server info (MCP 2026-07-28) | +| `ping` | an empty result | +| `tools/list`, `tools/call` | the tools, and the result of a call | +| `prompts/list`, `prompts/get` | the prompts, and the text of one prompt with its arguments filled in | +| `resources/list`, `resources/read`, `resources/templates/list` | the files, and the content of one | +| `notifications/initialized`, `notifications/cancelled` | accepted with `202` | + +An unknown method is answered with error `-32601` (method not found), malformed +JSON with `-32700`, a request without a method with `-32600`. + +## Protocol versions + +The server speaks both eras of MCP on the same endpoint and decides per +request: + +- **MCP 2026-07-28** (stateless): a request whose `params._meta` carries + `io.modelcontextprotocol/protocolVersion`. + - Such a request must also carry `io.modelcontextprotocol/clientCapabilities` + and the headers `MCP-Protocol-Version`, `Mcp-Method` and, for `tools/call`, + `prompts/get` and `resources/read`, `Mcp-Name`. + - Results carry `resultType` and `_meta.io.modelcontextprotocol/serverInfo`. + Lists and `resources/read` also carry the caching hints `ttlMs` and + `cacheScope`. + - `server/discover` returns the supported versions, capabilities and server + info. +- **MCP 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05**: clients that start + with `initialize`. A client that asks for a version the server does not know + is answered with 2025-11-25, the newest of these (up to v3.0.0 it was + 2025-06-18). + +An unsupported `MCP-Protocol-Version` header is answered with HTTP `400` and +error `-32022`, listing every supported version. + +## How a VI becomes a tool + +`Add Tool.vi` reads the VI's connector pane and description once, when the tool +is added. `tools/list` then returns for each tool: + +- `name`: the VI's file name, made a valid MCP name (see below). +- `title` and `annotations`, when they are wired into `Add Tool.vi`. +- `description`: the VI's description. +- `inputSchema`: a JSON Schema object with one property per control on the + connector pane. `error in` is left out (the `Ignored` input of + `Add Tool.vi`, default `["error in"]`, matches by exact label). +- `outputSchema`: the same for the indicators. + +On `tools/call` the arguments are converted with JSONtext into the controls' +own types, the VI is run, and the indicator values are flattened to JSON. + +### Data types + +| LabVIEW type | JSON Schema | Value in `tools/call` | +|---|---|---| +| I8 … U64 | `integer` with `minimum`/`maximum` of the representation | number | +| SGL, DBL, EXT (also with unit) | `number` | number | +| Boolean | `boolean` | `true`/`false` | +| String | `string` | string | +| Path | `string` | path as text, e.g. `C:\\data\\log.csv` | +| Timestamp | `string` with `format: date-time` | ISO 8601, e.g. `2026-10-17T12:34:56Z` | +| Enum | `string` with `enum` of the item names | item name (a number is an error) | +| Cluster | `object` with `properties` named by the element labels | object; a member left out keeps the control's value | +| Array, any number of dimensions | nested `array` with `items` | nested arrays | +| Other types (variant, waveform, complex, fixed point, ring, map, set, refnum, class, picture) | `{}` (any value) | not supported as inputs yet | + +What else goes into the schema: + +- `description`: the control's description, and the description of every + cluster element at any depth (inside clusters and arrays). +- Numerics with a unit get `Unit: .` appended to their description. +- `minimum`/`maximum` from the control's Data Entry limits when they are set to + coerce, combined with the range of the integer representation. +- `default`: the control's default value for inputs that are not *Required* on + the connector pane (left out when it would be longer than 1024 characters). +- `required`: the inputs marked *Required* on the connector pane. + +### Names + +Tool and prompt names and the names of inputs, outputs and prompt arguments +are lowercased, and every character other than `a-z`, `0-9`, `_` and `-` +becomes `_` (`Wert (mm)` becomes `wert__mm_`). `Add Tool.vi` and +`Add Prompt.vi` return error 5010 and do not register the tool or prompt when a +converted name is empty, longer than 64 characters, or the same as another one +in that tool or prompt. The VI's controls keep their labels. + +### Results + +Outputs of any type are flattened to JSON (timestamps as ISO 8601, enums as +item names): + +- The result's `text` content block holds all outputs as one JSON object, for + older clients. +- `structuredContent` holds the same object without picture outputs, matching + `outputSchema`. +- Picture outputs are returned as separate `image` content (PNG, base64). + +### Title, annotations and errors + +`title` is a display name; without one, clients show the tool name. +`annotations` (`Tool Annotations.ctl`) carries `readOnlyHint`, +`destructiveHint`, `idempotentHint` and `openWorldHint`. Each hint is *not set*, +*true* or *false*, and only hints that are set are sent. MCP's defaults for +unset hints are: not read-only, destructive, not idempotent, open world. + +A call with missing required arguments, or with an argument that does not fit +its control's type, returns a normal result with `isError: true` and a message +the model can act on. The model sees `error out` of the tool VI like any other +output. Unknown tools and malformed requests are JSON-RPC errors. A tool VI +with a broken run arrow is answered with error `1003`. + +## Asking the user (elicitation) + +MCP 2026-07-28 is stateless: the server cannot hold a call open while the user +thinks. Instead, the server answers the call with the question, and the client +repeats the call with the answer. The tool VI therefore runs twice. + +1. The tool calls `Confirm With User.vi` or `Ask User.vi` with a `key`. No + answer for that key exists yet, so the VI returns *pending*. The tool must + end without side effects. +2. The server answers the `tools/call` with a result that asks for input + instead of a tool result. It holds the `message` and, for `Ask User.vi`, + the `requestedSchema` (a flat JSON Schema with string, number, Boolean or + enum fields). +3. The client shows the question, then repeats the `tools/call` with the + user's answer attached under the same key. +4. The tool runs again. This time the VI finds the answer and returns *accept* + with `content` (the entered values as JSON text), *decline* or *cancel*. + +This needs a client that speaks MCP 2026-07-28 and declares the `elicitation` +capability. For any other client the VI returns *not supported* without +sending anything. Keys must be unique within one tool, since a tool can ask +several questions, one per round trip. + +## Progress and cancellation + +A `tools/call` streams when the client sends `_meta.progressToken` and an +`Accept` header that includes `text/event-stream`. The answer is then a +Server-Sent Events stream: one `notifications/progress` event (with the +progress token, `progress`, `total` and `message`) for every +`Report Progress.vi` call, then the result as the final event. A call that +does not stream gets a plain JSON answer, and both `Report Progress.vi` and +`Cancel Requested.vi` do nothing and return FALSE. + +Cancelling works in two ways: + +- MCP 2026-07-28 clients cancel by closing the stream. A closed stream is + noticed when an event is not taken within about one second, so the next + `Report Progress.vi` call returns `cancelled?` TRUE. +- Older clients send `notifications/cancelled` with the request id. It only + reaches a streamed call: a non-streamed call blocks other requests until it + ends. + +Either way, the next `Report Progress.vi` or `Cancel Requested.vi` call +returns TRUE, and it is the tool's job to stop. + +## Access control + +The IG HTTP Server listens on **all network interfaces**, so every request is +checked, in this order: + +1. **Allowed clients** (`Start.vi`). + - Empty (default): only this computer. This is decided by the connection's + own local address: only connections to `127.x` (that is, over loopback) + are served. A remote client cannot produce such a connection. A program on + this computer that connects through the computer's LAN address is refused + too. + - `*`: any client. + - Other entries: client host names as the server sees them through reverse + DNS, or IP addresses. Whoever controls a client's address can choose its + reverse-DNS name, so host-name entries are a convenience, not protection. + + A client that is not allowed gets `403`. +2. **Access token** (`Start.vi`). When set, every request needs + `Authorization: Bearer `. Missing or wrong: `401` with + `WWW-Authenticate: Bearer`. +3. **Origin.** A request that carries an `Origin` header is only served when it + is a page on `localhost`, `127.0.0.1` or `[::1]`, as protection against DNS + rebinding. This holds even with `*`, so browser-based clients on other + computers are refused. + +The server speaks plain HTTP without TLS. The token travels unencrypted, so +beyond a trusted network put the server behind a TLS reverse proxy or an SSH +tunnel. + +The handler answers only its own path: `/mcp/server`, optionally with a +trailing slash, query or fragment. Any other path, such as `/mcp/serverABC`, +gets IG's `404`. + +## Talking to a server by hand + +Anything that can send HTTP can test a server. With curl, against a running +example: + +```bash +U=http://127.0.0.1:36987/mcp/server +H1='Content-Type: application/json'; H2='Accept: application/json, text/event-stream' + +curl -s -X POST $U -H "$H1" -H "$H2" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' +curl -s -X POST $U -H "$H1" -H "$H2" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' +curl -s -X POST $U -H "$H1" -H "$H2" -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_system_stats","arguments":{}}}' +``` + +Add `-H "Authorization: Bearer "` when the server has a token. For a +full check of a running server, `tools/test/Test-McpServer.ps1` in this +repository runs the protocol tests the CI uses. diff --git a/readme.md b/readme.md index 0a5c0af..2e12714 100644 --- a/readme.md +++ b/readme.md @@ -1,194 +1,362 @@ # 🧰 LabVIEW MCP Server Toolkit -With this toolkit you can make large language models like ChatGPT or Claude control your LabVIEW applications. - -It opens a http server on the current machine. An MCP client can connect to that server and retrieve information about the available tools, prompts and resources. Then during a chat-conversation the LLM can decide itself when to call a VI for a specific purpose. +Let an AI assistant such as Claude or ChatGPT use your LabVIEW application. + +You pick the VIs the assistant may call, and the toolkit turns them into an +[MCP](https://modelcontextprotocol.io) server. You can then ask in plain +language, for example "check which channels of my datalogger carry a signal and +log only those", and the assistant calls your VIs to do it. + +```mermaid +flowchart LR + U([You]) -- chat --> C[AI client
Claude Desktop, Claude Code, ...] + C -- MCP over HTTP --> S[MCP server
in your LabVIEW application] + S -- calls --> V[Your VIs] +``` --- -## ✨ Overview +## ✨ What you can do + +- **Expose any VI as a tool.** The VI's name, description and connector pane + tell the assistant what the tool does and what to pass in. You don't write a + separate schema. +- **Return rich results.** Numbers, strings, clusters, arrays and pictures. A + graph rendered as a picture lets the assistant look at your signal. +- **Offer prompt templates and documents.** The user can pick them from the + client's menu instead of typing them out. +- **Run long jobs.** A tool can show progress while it runs, and it stops + cleanly when the user cancels. +- **Ask the user before acting.** A tool can ask "Move the stage to 120 mm?" + in the chat and wait for the answer. +- **Control who can connect.** Only your own computer by default, with an + access token for anything else. + +## 🚀 Quick start + +1. **Install** the toolkit with VIPM (search for *LabVIEW MCP Server SDK*). + It needs LabVIEW 2020 or newer, 64-bit, and VIPM installs its dependencies + with it. +2. **Run an example.** Open + `\examples\MCP Server\examples\Simple MCP Server\main.vi` and run it. + The server is now listening on `http://127.0.0.1:36987/mcp/server`. +3. **Connect Claude Desktop.** Install [Node.js](https://nodejs.org), then add + the server to Claude Desktop's configuration file + (*Settings → Developer → Edit Config*): + + ```json + { + "mcpServers": { + "labview": { + "command": "npx", + "args": ["mcp-remote", "http://127.0.0.1:36987/mcp/server"] + } + } + } + ``` + + Quit Claude Desktop completely (including its icon in the taskbar) and start + it again. It reads the list of tools only at startup. +4. **Ask** something like "How much memory does my machine have?" Claude + calls `get_system_stats` and answers with the values your VI returned. + +## 🔧 How it works: a VI is a tool + +The Simple example registers a single tool. `Start.vi` opens the server, +`Add Tool.vi` adds a VI as a tool, and the loop keeps the server running +until you press Stop: + +![Block diagram of the Simple example: Start, Add Tool, a while loop and Stop](docs/images/simple-server-main.png) + +The tool itself is an ordinary VI: + +| Front panel | Block diagram | +|---|---| +| ![Front panel of Get System Stats.vi with the CPU Info and Memory info clusters](docs/images/simple-server-tool-panel.png) | ![Block diagram of Get System Stats.vi](docs/images/simple-server-tool-diagram.png) | + +The assistant learns everything about the tool from the VI: + +| What the assistant sees | Where it comes from | +|---|---| +| Tool name `get_system_stats` | The VI's file name | +| What the tool does | The VI's description (*File → VI Properties → Documentation*) | +| Inputs and what they mean | The controls on the connector pane and their descriptions | +| The result | The indicators on the connector pane, returned as JSON | + +Write the descriptions with the assistant in mind. Say what a value means, +give its unit and its allowed range, and say what the tool does *not* do. The +description is all the assistant has to go on. + +## 🏗️ Building your own server + +Your top-level VI follows the same pattern as the examples: + +1. Call `Start.vi` with a name and a description for the server. +2. Call `Add Tool.vi` once per tool VI, and `Add Prompt.vi` / + `Add Resource (File).vi` for prompts and documents. +3. Keep the VI running in a loop while the server should be reachable. +4. Call `Stop.vi` when you are done. It closes the port again. + +Everything is on the **MCP Server** palette: + +| VI | What it is for | +|---|---| +| `Start.vi` | Starts a server. Inputs: name, description, port (default 36987), URL path (default `/mcp/server`), access token, allowed clients. | +| `Add Tool.vi` | Makes a VI available as a tool. Optional: a display `title` and `annotations` (see below). | +| `Add Prompt.vi` | Adds a prompt template: a VI with string inputs and one string output. | +| `Add Resource (File).vi` | Adds a file (for example a text file of instructions) the user can attach to the chat. | +| `Stop.vi` | Stops the server and frees the port. | +| **Advanced** sub-palette | `Report Progress.vi`, `Cancel Requested.vi`, `Confirm With User.vi`, `Ask User.vi`: used *inside* tool VIs, see below. | + +### Inputs and outputs + +Tool inputs can be numbers, Booleans, strings, paths, timestamps, enums, and +clusters and arrays of these. The assistant is told the type, the allowed range +and the default of every input, so it knows what to send. Mark an input +*Required* on the connector pane if the tool cannot run without it. + +Outputs can be of any type. Picture indicators are sent to the assistant as +images. The full list of types is in [Technical details](docs/technical-details.md#data-types). + +Inputs are named after the control labels, lowercased and with spaces and +special characters turned into `_`. `Wert (mm)` becomes `wert__mm_`. +`Add Tool.vi` reports error 5010 when two inputs end up with the same name. + +### Telling the client which tools are safe + +Clients use this information to decide when to ask the user before running a +tool. Wire it into `Add Tool.vi`: + +- `title`: the name the client shows, such as "Move Stage". +- `annotations`: whether the tool only reads (*read-only*), whether it can + destroy something (*destructive*), and whether calling it twice does the same + as calling it once (*idempotent*). Leave a hint *not set* if you are unsure. + Mark tools that move hardware or delete data as destructive. + +### Errors + +If a tool VI returns an error, the assistant sees its code and message and can +react, for example by retrying with other values. The same happens when it +leaves out a required input or sends a value of the wrong type. -The Model Context Protocol (MCP) is a protocol to enable LLM chatbot applications to call external code. There is a huge community developing MCP servers typically tied to controlling some applications. -On mcp.so you can find MCP servers for apps like Blender, TouchDesigner, Photoshop, Git, and many more. -You can add multiple of these MCP servers to an MCP Client like Claude Desktop. The AI can then initiate multiple different actions in multiple different software products. +## 💬 Prompts and resources -Detailed info can be found here: +Most servers only need tools. Two more things can make a server easier to use: -https://modelcontextprotocol.io/docs/getting-started/intro +- **Prompts** are templates the user picks from the client's menu (the **+** + button in Claude Desktop). A prompt is a VI with string inputs and one string + output. The client asks the user for the inputs and puts the resulting text + into the message field. The Datalogger example ships a prompt that runs its + whole demo. +- **Resources** are files the user can attach to a conversation, such as + instructions or a data sheet. Add them with `Add Resource (File).vi`. -MCP consists of 3 types of objects: +## ⏳ Long-running tools: progress and cancel -### Tools -A tool is a function that can be called by the AI. It has in- and outputs, a name and a description. The AI can read this description to understand how to use the tool (e.g. which strings to pass to the input ...) +For a tool that takes a while, such as a sweep, a calibration or a long +measurement, call `Report Progress.vi` inside its loop: -### Prompts -A prompt is a function with string-inputs and a single string output. It's listed for the user to pick in the chat-client (little plus icon in claude desktop). When selected, a dialog appears with a description and all parameters (string inputs). When clicking okay the resulting string is inserted into the chat message field. -It's a much less used feature than tools and really just a "configurable prompt template". +- Wire the current step to `progress`, the number of steps to `total`, and a + short text such as "Measuring point 12 of 40" to `message`. +- Stop the loop when `cancelled?` is TRUE. That happens when the user cancels + the request in the client. Clean up and return. +- In a loop that does not report progress, call `Cancel Requested.vi` to check + for a cancel. -### Resources -Resources are very similar to prompts and added in the same way. They return a single string or byte stream. Resources are usually files that are fully returned as text. These are selected just as prompts, but they don't have parameters. -They are also much less common than tools and are just longer "prompt templates". +Clients that support progress show it while the tool runs. With other clients, +both VIs do nothing and `cancelled?` stays FALSE, so the same tool works +everywhere. -The core communication uses **JSON-RPC 2.0 messages** transmitted via HTTP POST requests. +## 🙋 Asking the user during a tool call -## Installation +A tool can ask the user to confirm something, or to fill in a small form, before +it acts: -Install the package with VIPM. It needs LabVIEW 2020 or newer, 64-bit. VIPM installs the dependencies (see [Dependencies](#dependencies)) with it. The VIs are then on the **MCP Server** palette; Ask User, Confirm With User, Report Progress and Cancel Requested are in its **Advanced** sub-palette. Example servers are installed under `\examples\MCP Server`. +- `Confirm With User.vi` asks a yes/no question, such as "Move the stage to + 120 mm?". +- `Ask User.vi` asks for a few values, such as a serial number and an operator + name. -## MCP Clients +Write the tool in two parts, because it runs twice: -This toolkit is tested using Claude Desktop as an MCP client. There are many more clients out there. Usually they have a ..config.json file that contains the MCP config which looks like this: +1. **First run:** the VI returns *pending* (`pending?` on + `Confirm With User.vi`). End the tool right away, **without doing + anything**. The client then shows the question to the user. +2. **Second run:** the client calls the tool again with the user's answer. + Now the VI returns it: `Confirm With User.vi` sets `confirmed?`, and + `Ask User.vi` returns *accept* with the values the user entered, or + *decline* or *cancel*. Do the real work only here. + +Give each question a key that is unique within the tool. If the client cannot +ask questions, the VI returns *not supported* (`not supported?` on +`Confirm With User.vi`); decide whether the tool then refuses or uses a safe +default. Never use this to ask for passwords. + +## 🔌 Connecting clients + +The server speaks MCP over HTTP at `http://127.0.0.1:36987/mcp/server`. You +can change the port and the path on `Start.vi`. + +**Claude Desktop** and other clients that start servers as a local program +connect through `mcp-remote`, which needs [Node.js](https://nodejs.org): ```json { "mcpServers": { - "vi-scripting-server": { + "labview": { "command": "npx", - "args": [ - "mcp-remote", - "http://127.0.0.1:36987/mcp/server" - ], - "env": { - } + "args": ["mcp-remote", "http://127.0.0.1:36987/mcp/server"] } } } ``` -It may of course contain multiple servers. `mcp-remote` needs [Node.js](https://nodejs.org). -The port can be changed when starting the server. - -When the server has an access token, pass it as a header: +**Claude Code** connects directly: -```json -"args": [ - "mcp-remote", - "http://127.0.0.1:36987/mcp/server", - "--header", - "Authorization: Bearer ${MCP_TOKEN}" -], -"env": { "MCP_TOKEN": "" } +``` +claude mcp add --transport http labview http://127.0.0.1:36987/mcp/server ``` -Clients that speak Streamable HTTP themselves need no proxy. In Claude Code, for example: +Other clients with HTTP support (often called *Streamable HTTP*) take the URL +directly. You can run several servers side by side on different ports and add +each of them to the client. -``` -claude mcp add --transport http labview http://127.0.0.1:36987/mcp/server --header "Authorization: Bearer " -``` -Claude Desktop usually retrieves the list of available tools and prompts on startup, so multiple restarts may be necessary (close taskbar-icon also). +## 🔒 Who can connect -Download Claude Desktop: -https://claude.com/download +Out of the box, **only programs on the same computer** can use the server. +Requests from other computers are refused. Nothing needs to be set up for that. -## 🚀 Datatypes -Every control of a tool VI's connector pane becomes a tool input, described to the model as JSON Schema in `tools/list`. Arguments in `tools/call` are converted with JSONtext into the control's own type. +### Using the server from another computer -| LabVIEW type | JSON Schema | Value in `tools/call` | -|---|---|---| -| I8 … U64 | `integer` with `minimum`/`maximum` of the representation | number | -| SGL, DBL, EXT (also with unit) | `number` | number | -| Boolean | `boolean` | `true`/`false` | -| String | `string` | string | -| Path | `string` | path as text, e.g. `C:\\data\\log.csv` | -| Timestamp | `string` with `format: date-time` | ISO 8601, e.g. `2026-10-17T12:34:56Z` | -| Enum | `string` with `enum` of the item names | item name (a number is an error) | -| Cluster | `object` with `properties` named by the element labels | object; a member left out keeps the control's value | -| Array, any number of dimensions | nested `array` with `items` | nested arrays | -| Other types (variant, waveform, complex, fixed point, ring, map, set, refnum, class, picture) | `{}` (any value) | not supported as inputs yet | +Do two things on `Start.vi`: -What else goes into the schema, so the model knows what to send: -- `description`: the control's description, and the description of every cluster element at any depth (inside clusters and arrays). -- Numerics with a unit get `Unit: .` appended to their description. -- `minimum`/`maximum` from the control's Data Entry limits when they are set to coerce, combined with the range of the integer representation. -- `default`: the control's default value for inputs that are not required on the connector pane (left out when it would be longer than 1024 characters). -- `required`: the inputs marked *Required* on the connector pane. +1. **Allow the clients.** Wire `Allowed clients` with `*` (any computer), or + with the IP addresses or host names of the computers that may connect. +2. **Set an access token.** This is a password that every client has to send. + Without a token, anyone who can reach the computer can call your tools. -Write the descriptions with the model in mind: what the value means, its unit and its allowed values. +**Get a token.** The token is a secret you make up yourself. It has no +account behind it. Use a long random string, for example generated in +PowerShell: -Names sent to the client are made valid MCP names: tool and prompt names and the names of inputs, outputs and prompt arguments are lowercased, and every character other than `a-z`, `0-9`, `_` and `-` becomes `_` (`Wert (mm)` becomes `wert__mm_`). `Add Tool.vi` and `Add Prompt.vi` return error 5010 and do not register the tool or prompt when a converted name is empty, longer than 64 characters, or the same as another one in that tool or prompt. The VI's controls keep their labels. +```powershell +$b = New-Object byte[] 32; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); [Convert]::ToBase64String($b) +``` -Outputs of any type are flattened to JSON (timestamps as ISO 8601, enums as item names): -- The result's text block holds all outputs as one JSON object, for older clients. -- `structuredContent` holds the same object without picture outputs. -- Picture outputs are returned as separate image content (PNG). -- `tools/list` describes the outputs as `outputSchema`, built like the input schema: types, descriptions, ranges, units. +Wire it to the `Access token` input of `Start.vi`. Don't hard-code it in a VI +you share; read it from a configuration file or an environment variable. -## Tool title, annotations and errors +**Give the token to the client.** The client sends it as an +`Authorization: Bearer ` header with every request. -`Add Tool.vi` takes two optional inputs that clients show to users or use to decide when to ask before running a tool: -- `title`: a display name. Without one, no `title` is sent and clients show the tool name. -- `annotations` (`Tool Annotations.ctl`): `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`. - - Each hint is *not set*, *true* or *false*. Only hints that are set are sent. - - MCP's defaults for unset hints are: not read-only, destructive, not idempotent, open world. - - Mark tools that only read values as read-only. Mark tools that move hardware or delete data as destructive, so clients ask the user first. +Claude Desktop (through `mcp-remote`): -A call with missing required arguments, or with an argument that does not fit its control's type, returns a normal result with `isError: true` and a message the model can act on. Unknown tools and malformed requests are JSON-RPC errors. +```json +{ + "mcpServers": { + "labview": { + "command": "npx", + "args": [ + "mcp-remote", + "http://192.168.1.20:36987/mcp/server", + "--allow-http", + "--header", + "Authorization:${AUTH_HEADER}" + ], + "env": { "AUTH_HEADER": "Bearer " } + } + } +} +``` + +Keep `Authorization:${AUTH_HEADER}` without a space, and put the space inside +the variable. Some clients on Windows split arguments at spaces. `--allow-http` +lets `mcp-remote` connect to a computer other than this one without HTTPS. + +Claude Code: -## Protocol versions +``` +claude mcp add --transport http labview http://192.168.1.20:36987/mcp/server --header "Authorization: Bearer " +``` -The server speaks both eras of MCP on the same endpoint, and decides per request: +A client without the token, or with a wrong one, gets *401 Unauthorized*. A +computer that is not allowed gets *403 Forbidden*. -- **MCP 2026-07-28** (stateless): a request whose `params._meta` carries `io.modelcontextprotocol/protocolVersion`. - - Such a request must also carry `io.modelcontextprotocol/clientCapabilities` and the headers `MCP-Protocol-Version`, `Mcp-Method` and, for `tools/call`, `prompts/get` and `resources/read`, `Mcp-Name`. - - Results carry `resultType` and `_meta.io.modelcontextprotocol/serverInfo`. Lists and `resources/read` also carry the caching hints `ttlMs` and `cacheScope`. - - `server/discover` returns the supported versions, capabilities and server info. -- **MCP 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05**: clients that start with `initialize`. A client that asks for a version the server does not know is answered with 2025-11-25, the newest of these (up to v3.0.0 it was 2025-06-18). +The connection is not encrypted, so the token can be read on the network. Use +the server on networks you trust, or put it behind an SSH tunnel or an HTTPS +reverse proxy. -An unsupported version is answered with HTTP 400 and error `-32022`, listing every supported version. +> **Changed after v3.0.0:** up to v3.0.0, any computer could connect. Now only +> this computer can, until you allow others. A program on this computer that +> connects through the computer's network address instead of `127.0.0.1` also +> counts as another computer. -## Asking the user (elicitation) +## 📦 Examples -A tool can ask the user a question while it runs, for example to confirm before it moves hardware. This works with clients that speak MCP 2026-07-28 and declare the `elicitation` capability. +The examples are installed under `\examples\MCP Server\examples`. -- `Confirm With User.vi` asks a yes/no question. `Ask User.vi` asks for a small form (a flat JSON Schema with string, number, Boolean or enum fields). -- Each question needs a key that is unique within the tool. -- The tool VI runs twice: - 1. On the first run, the VI returns `pending`. The tool must then end at once, **without side effects**. The server sends the question to the client. - 2. The client asks the user and calls the tool again with the answer. On this run the same VI returns the answer (*accept*, *decline* or *cancel*). Do the real work only here. -- If the client cannot ask (older protocol, or no `elicitation` capability), the VI returns `not supported`. The tool decides what to do then: refuse with an error, or use a safe default. -- Never use it to ask for passwords or keys. +### Simple MCP Server -## Progress and cancellation +One tool, `Get System Stats.vi`, that returns CPU and memory information. Start +here to see the minimum a server needs (pictures [above](#-how-it-works-a-vi-is-a-tool)). -Long-running tools can report progress and stop when the client cancels. +### Datalogger MCP Server -- `Report Progress.vi` (progress, optional total and message) sends a `notifications/progress` event to the client. It returns `cancelled?`. Stop the tool when it is TRUE. -- `Cancel Requested.vi` returns `cancelled?`, for loops that do not report progress. -- Progress is streamed only when the client asks for it: `tools/call` with `_meta.progressToken`, and an `Accept` header that includes `text/event-stream`. The answer is then a Server-Sent Events stream: the progress events, then the result. Without that, both VIs do nothing and return FALSE. -- Cancelling works in two ways: - - MCP 2026-07-28 clients cancel by closing the stream. - - Older clients send `notifications/cancelled`. - - Either way, the next `Report Progress.vi` / `Cancel Requested.vi` call returns TRUE. - - A closed stream is noticed when an event is not taken within about one second. - - `notifications/cancelled` only reaches a streamed call. A non-streamed call blocks other requests until it ends. +A simulated five-channel datalogger. Some channels carry a signal, the others +only noise. Use the *Connected?* switches on the front panel to change which. +The assistant fetches pictures of the waveforms, decides which channels are +connected, logs only those and checks the log file afterwards. -## Access control +![Front panel of the Datalogger example](docs/images/datalogger-panel.png) -The IG HTTP Server listens on **all network interfaces**, so an MCP server can be reached from other computers. `Start.vi` has two optional inputs: +![Block diagram of the Datalogger example: four tools, a prompt and a resource are added before the acquisition loop](docs/images/datalogger-main.png) -- `Access token`: when set, every request needs the header `Authorization: Bearer `. Without it, or with a wrong token, the answer is HTTP 401 with `WWW-Authenticate: Bearer`. **Set a token whenever the server can be reached beyond this computer.** -- `Allowed clients`: which clients may connect. - - Empty, the default, allows only this computer. That is decided by the connection's own address: only connections to `127.0.0.1` (or `localhost`) are served. A remote client cannot fake this. A program on this computer that connects through the computer's network address instead of `127.0.0.1` is refused too. - - `*` allows any client. - - Other entries are client host names as the server sees them through reverse DNS, or IP addresses. The owner of a client's address can choose its reverse-DNS name, so these entries are a convenience, not protection. Always set a token when you list remote clients or use `*`. - - Other clients get HTTP 403. +Try: -Requests are checked in this order: client not allowed → 403, missing or wrong token → 401, then the Origin header. A request that carries an `Origin` header is only served when it is a page on `localhost`, `127.0.0.1` or `[::1]` (protection against DNS rebinding); this holds even with `*`, so browser-based clients on other computers are refused. +> Check my datalogger through MCP. Which channels are connected? Identify the +> channels that have not just noise on them, then log the connected channels +> for a moment, then stop and read the log file and check if the logging was +> successful. -The server speaks plain HTTP, without TLS: the token travels unencrypted. Beyond a trusted network, put the server behind a TLS reverse proxy or an SSH tunnel. +The same text is registered as a prompt, so in Claude Desktop you can also pick +it from the **+** menu. -This is a change from v3.0.0 and earlier, which served any client: from the release after v3.0.0 on, clients that do not connect through `127.0.0.1` are refused unless you list them or use `*`. +### Full Featured MCP Server -## Dependencies -This toolkit uses IG HTTP Server Toolkit and JKI JSONtext. +A simulated stock broker that uses every kind of building block: tools to look +up prices, show a history chart and buy stocks; prompts such as "find the best +trending stock"; and a resource that makes the assistant act as a stock market +expert. The MCP log on the front panel shows every request the client sends. -https://www.vipm.io/package/illuminatedg_lib_ig_http_server/ +![Front panel of the Full Featured example: stock price chart and MCP log](docs/images/full-featured-panel.png) -https://www.vipm.io/package/jdp_science_jsontext/ +![Block diagram of the Full Featured example](docs/images/full-featured-main.png) -## G-AI -Main purpose to develop this toolkit is to use it for my G-AI project which can generate LabVIEW code using LLMs. Have a look at it here: -https://github.com/JanGoebel/G-AI +## 🩺 Troubleshooting + +- **The client does not list my tools.** Restart the client completely; most + read the tool list only at startup. Check that the server VI is running. +- **A tool reports "not executable".** The tool VI has a broken run arrow. + Open it and fix it. +- **Error 5001 from `Start.vi`.** The port is already in use, often by another + server you started earlier. Stop that one, or pick another port. +- **Error 5010 from `Add Tool.vi`.** Two inputs of that tool end up with the + same name, or a name is empty or longer than 64 characters. +- **403 from another computer.** Add it to `Allowed clients` (see + [Who can connect](#-who-can-connect)). +- **401.** The token is missing or wrong on the client side. + +## 📚 More + +- [Technical details](docs/technical-details.md): how MCP works, protocol + versions, how VIs are translated into tool descriptions, and how progress, + cancel and questions to the user work on the wire. +- Dependencies: [IG HTTP Server Toolkit](https://www.vipm.io/package/illuminatedg_lib_ig_http_server/) + and [JKI JSONtext](https://www.vipm.io/package/jdp_science_jsontext/), + installed by VIPM. +- This toolkit was built for [G-AI](https://github.com/JanGoebel/G-AI), which + generates LabVIEW code with LLMs. ## License -This toolkit is published under the MIT license. Feel free to use and modify it. \ No newline at end of file + +MIT. Feel free to use and modify it.