<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vigneshwaran M</title>
    <description>The latest articles on DEV Community by Vigneshwaran M (@vigneshwaran_m).</description>
    <link>https://dev.to/vigneshwaran_m</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3954774%2F72c803fd-36ab-41d3-a306-9944b4a6a2b6.png</url>
      <title>DEV Community: Vigneshwaran M</title>
      <link>https://dev.to/vigneshwaran_m</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vigneshwaran_m"/>
    <language>en</language>
    <item>
      <title>On-device tool calling in React Native: 228 ms, no server, no API key</title>
      <dc:creator>Vigneshwaran M</dc:creator>
      <pubDate>Tue, 25 Aug 2026 08:33:09 +0000</pubDate>
      <link>https://dev.to/vigneshwaran_m/i-put-a-45m-parameter-llm-on-a-phone-it-was-100-confident-about-a-number-it-invented-aej</link>
      <guid>https://dev.to/vigneshwaran_m/i-put-a-45m-parameter-llm-on-a-phone-it-was-100-confident-about-a-number-it-invented-aej</guid>
      <description>&lt;p&gt;Say &lt;strong&gt;"Turn the kitchen lights down to 30 percent"&lt;/strong&gt; and get back &lt;code&gt;{ room: "kitchen", level: 30 }&lt;/code&gt; in &lt;strong&gt;228 ms&lt;/strong&gt; — on the phone, offline, with no server, no API key and no per-token cost.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://www.npmjs.com/package/react-native-needle" rel="noopener noreferrer"&gt;&lt;code&gt;react-native-needle&lt;/code&gt;&lt;/a&gt;, a React Native binding I built for &lt;a href="https://huggingface.co/Cactus-Compute/needle2" rel="noopener noreferrer"&gt;Cactus Compute's Needle 2&lt;/a&gt; — a 45M-parameter model that runs entirely on-device. As far as I can tell it's the first RN binding for this model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-needle
npx react-native-needle fetch-model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below: what it does well, the numbers I measured rather than the ones in the docs, and exactly where it breaks — including one failure you want to know about &lt;em&gt;before&lt;/em&gt; you build on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a 45M model at all
&lt;/h2&gt;

&lt;p&gt;Needle 2 isn't a chatbot. Give it a tool schema and it emits a schema-conforming call or declines. That's a narrower job than "generate text," and a much better fit for phones: no server, no API key, no per-token cost, no network, and nothing leaves the device.&lt;/p&gt;

&lt;p&gt;It's also small enough that binding it is not a project. The C API is four functions — that's the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="nf"&gt;needle_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="nf"&gt;needle_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tools_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tool_index_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="nf"&gt;needle_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;out_capacity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;needle_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd budgeted days for the native work. Four functions behind a JNI bridge and a CMake target is an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;needleSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadBundledModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-needle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needleSupported&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadBundledModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You control smart home devices. Call a tool for the user request.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_brightness&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set the brightness of the lights in a room&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Which room&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Brightness percentage, 0-100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;room&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Turn the kitchen lights down to 30 percent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// → { room: 'kitchen', level: 30 }&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool schema &lt;em&gt;is&lt;/em&gt; the extraction schema. &lt;code&gt;extract()&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; rather than throwing when the model declines — a caller pre-filling a form wants "no answer", not an exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's genuinely good at
&lt;/h2&gt;

&lt;p&gt;I ran eight measured cases on an arm64 emulator. Clean, single-intent commands are excellent:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Turn the kitchen lights down to 30 percent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{room: "kitchen", level: 30}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;228 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Set a timer for 12 minutes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{minutes: 12}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;136 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Your parcel weighing 2.4 kg has left the depot, tracking AB4471.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{weight_kg: 2.4, tracking: "AB4471"}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;619 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;What is the capital of France?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;declined — no matching tool&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;174 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row matters as much as the others. Asked something outside its tools, it returns an empty call list and a reason rather than inventing an answer.&lt;/p&gt;

&lt;p&gt;For device control and voice-command routing, this is genuinely usable: sub-second round trips, offline, free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one build gotcha
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;arm64-v8a&lt;/code&gt; linked cleanly. &lt;code&gt;armeabi-v7a&lt;/code&gt; did not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;undefined&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;__ndk1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;__hash_memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cactus's 32-bit archive is built against a newer libc++ than NDK 27 exports. No flag fixes that — it's an ABI mismatch in a prebuilt binary. So the package is arm64-only, with a &lt;code&gt;needleSupported&lt;/code&gt; flag so 32-bit devices degrade instead of crashing with an &lt;code&gt;UnsatisfiedLinkError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Worth knowing before you plan around "it's only 14 MB": in the APK it's &lt;strong&gt;28 MB&lt;/strong&gt; — 14.5 MB of engine plus 13.7 MB of weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limits
&lt;/h2&gt;

&lt;p&gt;This is the part I'd want to read before adopting something, so here it is in full.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A leading unrelated clause kills the whole request.&lt;/strong&gt; Given:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Preheat to 200 C and bake 25 minutes, serves 4. Add 250 grams of flour to the list.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;with an &lt;code&gt;add_item&lt;/code&gt; tool available, it declined outright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"function_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No tool available for preheating or baking."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second sentence on its own works fine. It latches onto the first instruction and gives up instead of scanning for the part it &lt;em&gt;can&lt;/em&gt; serve. Segment multi-intent input yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;String fields over-capture.&lt;/strong&gt; In a denser message, &lt;code&gt;tracking&lt;/code&gt; came back as &lt;code&gt;"AB4471, ref 522119876543"&lt;/code&gt; — the code plus the next field glued on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers in crowded strings get fabricated.&lt;/strong&gt; Given a message with several numbers in it, the model returned &lt;code&gt;1230.0&lt;/code&gt; for an input value of &lt;code&gt;560.00&lt;/code&gt; — at confidence &lt;strong&gt;1.0000&lt;/strong&gt;, with a written rationale that quoted the correct number and then produced a different one. Reproducible. Tightening the schema didn't help; adding "copy verbatim, never calculate" to the system prompt produced byte-identical output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory.&lt;/strong&gt; Documented at ~28 MB per session. Measured peak RSS: &lt;strong&gt;500–530 MB&lt;/strong&gt;. That's the real deployment constraint, not the model file size.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signal to gate on
&lt;/h2&gt;

&lt;p&gt;Here's the part worth stealing even if you never touch this package. Needle tells you when it's making things up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ungrounded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"record_item.amount"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"negation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It flagged the exact field it had fabricated — &lt;strong&gt;in the same response where it reported confidence 1.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the confidence score was useless and the self-report was correct. The package exposes this directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ungroundedFields&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-needle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ungroundedFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The model is telling you it made these up. Discard them.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build on any model that exposes something like this, gate on the grounding flag and ignore the confidence number. I'd have shipped a wrong value if I'd trusted the metric that &lt;em&gt;looked&lt;/em&gt; like the trustworthy one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two numbers I got wrong myself
&lt;/h2&gt;

&lt;p&gt;My initial notes said "9–15 s per completion." Re-measuring across eight runs gave &lt;strong&gt;140 ms – 1.6 s&lt;/strong&gt;. The original figure came from one cold run that included initialisation, and I'd written it down as steady-state. Measure more than once before you publish a benchmark — including in your own README.&lt;/p&gt;

&lt;p&gt;I also shipped a real bug by guessing. I wrote the response parser from what I &lt;em&gt;assumed&lt;/em&gt; the envelope looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real output nests one level deeper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"function_calls"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"set_brightness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;extract()&lt;/code&gt; handed callers the whole envelope instead of their fields, and a decline came back as a &lt;em&gt;truthy object&lt;/em&gt; — indistinguishable from a real answer. Fixed in 0.1.1. The tests now run against &lt;strong&gt;real captured device output&lt;/strong&gt;, not my idea of the shape; every one of my original hand-written tests had passed against a format the model never emits.&lt;/p&gt;

&lt;h2&gt;
  
  
  One npm packaging trap
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;files&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; is an allowlist, and it &lt;strong&gt;beats &lt;code&gt;.npmignore&lt;/code&gt;&lt;/strong&gt;. My &lt;code&gt;.npmignore&lt;/code&gt; excluded the 20 MB engine and 14 MB weights. npm ignored it and cut a &lt;strong&gt;97.6 MB tarball&lt;/strong&gt;. The exclusions have to be &lt;code&gt;!&lt;/code&gt; negations inside &lt;code&gt;files&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"!android/libs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"!android/src/main/assets/*.cact"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, 18.6 kB. There's now a &lt;code&gt;prepack&lt;/code&gt; check that fails the pack if those negations ever go missing, because the failure is invisible until you actually inspect the tarball.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;, if you want offline tool calling or voice-command routing: declare your tools, get a conforming call back in 140–600 ms, and ship with no backend at all. The engine is fetched and checksum-pinned on install; the weights are one explicit command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be careful&lt;/strong&gt; if a wrong number is expensive in your app. Gate on &lt;code&gt;ungroundedFields()&lt;/code&gt;, segment multi-intent input yourself, and treat &lt;code&gt;confidence&lt;/code&gt; as decoration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-needle
npx react-native-needle fetch-model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/react-native-needle" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/react-native-needle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/VigneshDev16/react-native-needle" rel="noopener noreferrer"&gt;https://github.com/VigneshDev16/react-native-needle&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Android/arm64, Expo SDK 51+. MIT; the engine is Apache-2.0 from Cactus Compute.&lt;/p&gt;

&lt;p&gt;The natural next step is a LoRA fine-tune on domain data, which &lt;code&gt;cactus-needle&lt;/code&gt; supports — a 14 MB model that's &lt;em&gt;actually good&lt;/em&gt; at one narrow job is a far more useful thing than a general one. If you try that, I'd like to hear how it goes.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>android</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built an MCP Server for INDmoney — Ask Claude About Your Portfolio in Plain English</title>
      <dc:creator>Vigneshwaran M</dc:creator>
      <pubDate>Wed, 27 May 2026 20:06:02 +0000</pubDate>
      <link>https://dev.to/vigneshwaran_m/i-built-an-mcp-server-for-indmoney-ask-claude-about-your-portfolio-in-plain-english-431i</link>
      <guid>https://dev.to/vigneshwaran_m/i-built-an-mcp-server-for-indmoney-ask-claude-about-your-portfolio-in-plain-english-431i</guid>
      <description>&lt;p&gt;INDmoney is great. The problem is I can't open it "just to check something." Every time I do, I'm there for two hours — tapping through charts, reading stock articles, checking what changed since yesterday.&lt;/p&gt;

&lt;p&gt;I also just started investing seriously. And staring at green/red numbers doesn't actually tell me if I'm doing things right.&lt;/p&gt;

&lt;p&gt;So I built something that lets me check my portfolio by asking Claude — without ever opening the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"How are my mutual funds performing?"
"Am I over-indexed on mid-caps?"
"What's dragging my Experian credit score down?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live answers, from your actual INDmoney account, right inside Claude.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is MCP?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; is an open standard from Anthropic that lets Claude connect to external tools and data sources. Think of it like USB-C for AI — a standard port that any app can plug into.&lt;/p&gt;

&lt;p&gt;Once an MCP server is running locally, Claude can call its tools the same way it uses any built-in capability. No copy-pasting, no screenshots, no switching apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Rebuilt It (The TypeScript Version Broke Constantly)
&lt;/h2&gt;

&lt;p&gt;I had a first version in TypeScript. It kept breaking mid-request — INDmoney's session would expire while fetching data, US stocks would time out, and half the time I'd get an error, close the laptop, and just open the app anyway. Which is exactly what I was trying to avoid.&lt;/p&gt;

&lt;p&gt;The core problem: no persistent sessions. Every server restart meant a new OTP login.&lt;/p&gt;

&lt;p&gt;So I rebuilt it in Python with Playwright. Now sessions are encrypted with AES-256-GCM and saved to disk. Even after a server restart, you're still connected — no re-login, no OTP every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python" rel="noopener noreferrer"&gt;indmoney-mcp-python&lt;/a&gt;&lt;/strong&gt; — 14 tools covering every INDmoney asset class:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_holdings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Indian stocks — symbol, qty, avg price, P&amp;amp;L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_mutual_funds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MF holdings — invested, current value, XIRR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_us_stocks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;US equities in INR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_gold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Digital gold, SGBs, ETFs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_liquid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bank balances, FDs, EPF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_credit_cards&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outstanding balances, due dates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_loans&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loan accounts, outstanding amounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_debt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bonds, NCDs, debt MFs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_credit_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Experian score + risk factors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_portfolio_summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full aggregated P&amp;amp;L + asset allocation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus session management tools: &lt;code&gt;broker_connect&lt;/code&gt;, &lt;code&gt;broker_status&lt;/code&gt;, &lt;code&gt;broker_disconnect&lt;/code&gt;, &lt;code&gt;discover_endpoints&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude ──STDIO JSON-RPC──► MCP Server (Python)
                                │
                    ┌───────────┴───────────┐
                    │                       │
              Session Manager         INDmoney Scraper
              (AES-256-GCM disk)      (Playwright)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Login (once):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Playwright opens a Chromium window&lt;/li&gt;
&lt;li&gt;You enter your phone number + OTP directly in the browser — the server never sees it&lt;/li&gt;
&lt;li&gt;The server grabs the JWT from the browser's cookie store&lt;/li&gt;
&lt;li&gt;Encrypts it and saves to disk&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Data fetching:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most endpoints: direct &lt;code&gt;fetch()&lt;/code&gt; calls from inside the browser context using the JWT — bypasses CORS completely&lt;/li&gt;
&lt;li&gt;Some endpoints (gold, fallbacks): captures the network response while navigating to the relevant page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Two-level cache:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-memory (5 min) — instant for repeated queries in the same session&lt;/li&gt;
&lt;li&gt;Disk (60 min) — survives restarts, used as fallback if the API is slow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Setup (3 Steps)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;indmoney-mcp
python &lt;span class="nt"&gt;-m&lt;/span&gt; playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generate an encryption key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print(secrets.token_hex(32))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this. If you lose it, your session gets wiped and you'll need to log in again — no big deal, just slightly annoying.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add to Claude Desktop config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indmoney"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indmoney-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SESSION_ENCRYPTION_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-32-byte-hex-key-here"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Desktop, then say: &lt;strong&gt;"Connect me to INDmoney"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A browser window opens, you enter your OTP, done. From that point you're connected for 12 hours — even across server restarts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things I Actually Ask It
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which of my mutual funds is underperforming its benchmark?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Give me a full portfolio breakdown — what percentage is in stocks vs MFs vs US?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Do I have any credit card dues this week?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"My gold ETF — is it up or down from what I invested?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As someone still learning, being able to ask "am I going in the right direction?" and get a structured answer from my actual numbers is genuinely useful. Claude can reason over the data, not just display it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Won't Do
&lt;/h2&gt;

&lt;p&gt;It won't place orders or move money. Read-only, always. Honestly that's intentional — I don't trust myself with one-click investing while chatting with an AI at midnight.&lt;/p&gt;

&lt;p&gt;Your data also never leaves your machine. No third-party servers, no cloud sync.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/indmoney-mcp/" rel="noopener noreferrer"&gt;pypi.org/project/indmoney-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/VIckys-AI-Stuffs/indmoney-mcp-python" rel="noopener noreferrer"&gt;VIckys-AI-Stuffs/indmoney-mcp-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📋 MCP Registry: &lt;a href="https://mcp.so/server/indmoney-mcp" rel="noopener noreferrer"&gt;mcp.so/server/indmoney-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're an INDmoney user — especially if you're also figuring out investing as you go — give it a try. If something breaks, open an issue. I'm using it myself so I'll see it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>indmoney</category>
    </item>
  </channel>
</rss>
