<?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: Pooya Golchian</title>
    <description>The latest articles on DEV Community by Pooya Golchian (@pooyagolchian).</description>
    <link>https://dev.to/pooyagolchian</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%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg</url>
      <title>DEV Community: Pooya Golchian</title>
      <link>https://dev.to/pooyagolchian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pooyagolchian"/>
    <language>en</language>
    <item>
      <title>The 200-result ceiling that forced a tiled crawler</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Sat, 12 Sep 2026 19:59:31 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/the-200-result-ceiling-that-forced-a-tiled-crawler-1o92</link>
      <guid>https://dev.to/pooyagolchian/the-200-result-ceiling-that-forced-a-tiled-crawler-1o92</guid>
      <description>&lt;p&gt;One Google Maps query stops at roughly 200 results. Every other decision in this pipeline falls out of that single number.&lt;/p&gt;

&lt;p&gt;I live in Dubai. Google Maps knows every plumber, nursery and blood-test lab in the city. No tool lets me browse that, filter it, or ask a structural question of it.&lt;/p&gt;

&lt;p&gt;So I built one. &lt;a href="https://github.com/pooyagolchian/business-directory-toolkit" rel="noopener noreferrer"&gt;Business Directory Toolkit&lt;/a&gt; is MIT-licensed and turns a city into a browsable business directory. &lt;a href="https://directory.pooyagolchian.com" rel="noopener noreferrer"&gt;directory.pooyagolchian.com&lt;/a&gt; is a live Dubai deployment standing on 1,400 requests to &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=pooyagolchian.com" rel="noopener noreferrer"&gt;SearchApi&lt;/a&gt;'s Google Maps engine. I am writing it up as part of their developer ambassador programme. That programme backs developers who ship something real on their APIs and leaves the work in your name.&lt;/p&gt;

&lt;p&gt;1,400 SearchApi requests returned 15,246 unique businesses. That is 10.9 new businesses per request.&lt;/p&gt;

&lt;p&gt;Most articles about scraping Google Maps stop at one API call and a &lt;code&gt;console.log&lt;/code&gt;. That part already works. Everything expensive happens after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ceiling that shapes everything
&lt;/h2&gt;

&lt;p&gt;A single Google Maps query cannot enumerate a city. Page 11 does not come back as an empty list. It comes back with no &lt;code&gt;local_results&lt;/code&gt; key at all, so the results are absent rather than empty. I committed that probe to the repository as a test fixture so nobody has to spend a credit rediscovering it.&lt;/p&gt;

&lt;p&gt;If you were expecting 60, that is the ceiling on the Places API Text Search, which returns 20 results across three pages and stops. The Maps engine goes considerably deeper before it gives out. It still gives out, and that ceiling decides how you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiling forces a budget model
&lt;/h2&gt;

&lt;p&gt;Tiling is mandatory, not an optimisation. Dubai is 44 geographic squares crossed with 40 categories, which makes 1,760 tile-and-category pairs. Pagination depth is the only dial that moves the bill.&lt;/p&gt;

&lt;p&gt;SearchApi fits that shape for structural reasons rather than promotional ones. It takes Google's own location format straight through, as &lt;code&gt;ll=@lat,lng,zoom&lt;/code&gt;. Tiling a city means expressing a coordinate and a zoom level, so no geocoding service sits in the middle.&lt;/p&gt;

&lt;p&gt;One SearchApi request costs one credit. Because the unit of billing matches the unit of work, the whole cost model collapses into a 3x3 lookup table. Budget guards only work because the arithmetic stays that simple.&lt;/p&gt;

&lt;p&gt;The response also carries fields that replace other services. Roughly 99.8% of results arrive with &lt;code&gt;country_code&lt;/code&gt; and &lt;code&gt;city&lt;/code&gt;, which drives the in-city filter with no geocoder. Every single result carries &lt;code&gt;gps_coordinates&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The pipeline reassigns each business to the tile it actually sits in rather than the one whose query found it. About half of them sat somewhere other than where I looked, because Google answers from a radius.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;place_id&lt;/code&gt; arrives on 100% of results and stays stable. It is the dedup key, the database partition key, and the only value the takedown suppression list may hold.&lt;/p&gt;

&lt;p&gt;Failure is predictable enough that the retry policy fits in one predicate.&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="cm"&gt;/**
 * Rate limits and server errors are worth retrying; a 400 or a 401 will fail
 * identically forever and retrying only burns time and credits.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 400 throws immediately, which is correct, because a malformed query does not improve on the fourth attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Price it before you buy
&lt;/h2&gt;

&lt;p&gt;Five commands are the required spine, and only one of them spends money on the corpus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan the crawl.&lt;/strong&gt; It costs nothing, issues nothing and writes nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm plan &lt;span class="nt"&gt;--city&lt;/span&gt; dubai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints 1,250 first-page jobs against a 3,170 worst case and a 2,000 default budget. It also warns that full depth overruns by 1,170. The planner drops 510 of the 1,760 pairs before committing a single credit. Crawling law firms in the desert spends money to find nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only irreversible spend
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Crawl.&lt;/strong&gt; This is the only irreversible spend in the project, so it refuses to run without &lt;code&gt;--yes&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm crawl &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
pnpm crawl &lt;span class="nt"&gt;--city&lt;/span&gt; dubai &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--budget&lt;/span&gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four guards stack here. The mandatory flag, the free dry run, a hard budget stop, and the plan-time drops. A fifth brake sits inside the crawl loop, and it is four lines of arithmetic.&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shouldFetchNextPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageOutcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;minNewUniqueRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_MIN_NEW_RATIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 0.3&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxPages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resultCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resultCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;FULL_PAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// FULL_PAGE = 20&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newUnique&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resultCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;minNewUniqueRatio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crawler buys a page only when the previous page came back full and at least 30% of it was new. Every constant in there came from a measurement rather than a guess.&lt;/p&gt;

&lt;p&gt;The crawler also writes every raw response to disk before anything parses it. That habit is the single best decision in the project, and I will come back to why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classify the vocabulary, not businesses
&lt;/h2&gt;

&lt;p&gt;The taxonomy pass turns a scrape into a directory, and it costs almost nothing. This is the step that makes the unit economics work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seed the taxonomy.&lt;/strong&gt; No model call, no network, no credits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm seed-taxonomy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;132 ordered keyword rules classify the head of the category vocabulary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classify the tail.&lt;/strong&gt; This spends Anthropic tokens and never SearchApi credits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm classify &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea worth stealing comes next. The obvious approach sends every business to a model, which scales with your dataset forever. Category vocabulary saturates while business count does not.&lt;/p&gt;

&lt;p&gt;My 15,246 businesses contained only 1,787 distinct category strings, which is 8.5 times fewer items. The keyword rules handle the head, so the model only ever sees a tail of 537 strings. It sees them once, across every crawl this project will ever run. A re-crawl that introduces no new strings prints a marginal cost of zero and exits.&lt;/p&gt;

&lt;p&gt;Handle the head deterministically and the model never sees 99.5% of your corpus. The committed map wins over both the rules and the model, so the taxonomy is permanent. The next crawl re-applies it for free, and so does the next city.&lt;/p&gt;

&lt;p&gt;Thin pages stay out of it. A category needs at least three businesses in a neighbourhood before that page exists at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four gates measure, one does not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Load.&lt;/strong&gt; Dedupe, drop suppressed listings, assign each business to its nearest tile by coordinates, then normalise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm load &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
pnpm load &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five acceptance gates print on every run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;at least 10,000 unique businesses&lt;/li&gt;
&lt;li&gt;90% phone coverage&lt;/li&gt;
&lt;li&gt;99% taxonomy coverage&lt;/li&gt;
&lt;li&gt;unique slugs&lt;/li&gt;
&lt;li&gt;zero rows outside the country&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My crawl passes at 92.2% phone coverage and 99.5% taxonomy coverage. Four of those five gates measure something. The fifth prints a pass because normalise already rejects out-of-country rows, so it restates an invariant rather than testing one.&lt;/p&gt;

&lt;p&gt;Two of those thresholds started higher and came down on evidence. I wanted 95% phone coverage. A probe of the detail endpoint across 15 businesses recovered exactly one extra number. The parser rejected it correctly as an Indian number. So 92.2% is a ceiling, not a shortfall, and the gate now says so.&lt;/p&gt;

&lt;p&gt;One crawl produced 14,981 business pages, 782 neighbourhood-by-category landing pages, 81 category pages and 40 neighbourhood hubs. That is 15,887 URLs in the sitemap, all from a single dataset.&lt;/p&gt;

&lt;p&gt;Optional passes sit after the spine. &lt;code&gt;pnpm demand&lt;/code&gt; asks &lt;code&gt;google_autocomplete&lt;/code&gt; what people actually search, at one credit per category, ordered by real query popularity. That turns "which pages should I build" from a guess into a measurement. &lt;code&gt;pnpm leads&lt;/code&gt; scores prospects. &lt;code&gt;pnpm export&lt;/code&gt; writes CSV, JSON or NDJSON for a CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  20,226 review snippets nearly shipped
&lt;/h2&gt;

&lt;p&gt;Because the crawler stores every response untouched before parsing, everything downstream is free to re-run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm load &lt;span class="nt"&gt;--from-archive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No network, no credits, full rebuild. I changed the taxonomy, the ranking constants and the normalisation repeatedly over weeks, all against one paid crawl. The test suite runs against committed fixtures and spends nothing.&lt;/p&gt;

&lt;p&gt;That archive also produced the ugliest moment in the project. A single unresolvable path in the data loader made Next's file tracer give up and wildcard the directory. That put 1,400 raw crawl files inside the deployment bundle, carrying 20,226 verbatim Google review snippets. Some of them named individual employees. Nothing read them and all of them would have shipped.&lt;/p&gt;

&lt;p&gt;The related bug is worse and more interesting. Review theme extraction rewards terms frequent for one business and rare everywhere else, which is precisely the shape of a staff member's name. The first live run produced a Sofitel listing tagged with three employees' first names. A blocklist would have been endless. The fix is a property instead, since a real theme recurs across many businesses while a person's name belongs to one.&lt;/p&gt;

&lt;p&gt;Theme extraction reruns against the archive, so the fix never needed a fresh crawl.&lt;/p&gt;

&lt;p&gt;My tests sat green throughout. Every headline defect in this repository turned up the same way, by reading real output rather than by failing an assertion. That is the argument for building against a city you already know. Business Directory Toolkit had a real target from day one. When the crawler came back with a couple of hundred nurseries, I could tell whether that smelled right.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the live demo does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://directory.pooyagolchian.com" rel="noopener noreferrer"&gt;directory.pooyagolchian.com&lt;/a&gt; runs on CloudFront as prerendered pages.&lt;/p&gt;

&lt;p&gt;A query for &lt;code&gt;dentist&lt;/code&gt; returns 512 matches. Typing a &lt;code&gt;+971&lt;/code&gt; number returns the business that owns it. Typeahead answers from a JSON endpoint in one round trip. Business pages carry &lt;code&gt;LocalBusiness&lt;/code&gt; structured data, a canonical, and both phone formats. An honest label says the rating came from Google rather than from me.&lt;/p&gt;

&lt;p&gt;Some things genuinely do not exist yet. The site has no pagination, so large categories cap at 120 rendered rows and disclose it in plain text. Sorting filters what is already on the page rather than querying the server. I wrote the DynamoDB path and nothing reads it. I would rather say that than let you find out by clicking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lead score ranked ATMs
&lt;/h2&gt;

&lt;p&gt;One crawl produces two products. The 1,400 requests that built a public website also built four prospect lists, offline, at zero marginal cost. That is one acquisition cost against two outputs, and it is the only version of the growth-engine claim I will defend.&lt;/p&gt;

&lt;p&gt;The second product nearly shipped broken, and the failure was mine. The scoring took two attempts.&lt;/p&gt;

&lt;p&gt;The directory carries a chart I did not expect to build. Median review count climbs with rating exactly as you would hope. The bands run 12, 18, 48, 93 and 139. Then it collapses to 11 at exactly 5.0. 2,283 Dubai businesses hold a perfect score, and the typical one has eleven reviews behind it. A perfect score is the bottom of the evidence, not the top of the scale. Listings therefore rank on a credibility-weighted mean instead of a raw average.&lt;/p&gt;

&lt;p&gt;My first lead score reused that same credibility-weighted rating, which sounds sensible and inverts itself in practice. Across 641 real leads the score correlated with review count at negative 0.28. The more trade a business had, the further down my call sheet it went. Bank ATMs topped the list. A 3.6-rated hospital with 5,562 reviews sat at number 522 of 641.&lt;/p&gt;

&lt;p&gt;The fix generalises into one rule worth stealing. The health term must never be a function of the quantity the signal measures. Score on establishment instead, and the correlation flips to positive 0.08.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pnpm leads&lt;/code&gt; scores against four signals, which are no-website, no-hours, weak-reputation and low-visibility. Each signal maps to a different sales conversation, so the tool enforces one signal per run. On my Dubai corpus, 3,820 reachable prospects have no website and 892 have never published hours. Compose the filters and you get 321 restaurants with 20 or more reviews and no website, which is a web agency's afternoon.&lt;/p&gt;

&lt;p&gt;Two caveats I will not bury. Reachable means the business has a phone number, so those counts run smaller than the raw count of businesses with the gap. And a lead list is research, not permission to contact anyone. The CLI prints that on every run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting the crawl to Lisbon
&lt;/h2&gt;

&lt;p&gt;The crawl is genuinely city-agnostic. Tiles, categories, bounding boxes, country code and phone region all live in one JSON file. Adding a city requires no code change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm cities generate &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Lisbon"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reads OpenStreetMap and writes the config at zero SearchApi cost. It refuses to fabricate, so a city with too few candidate centres throws rather than falling back to an even grid. Every generated config carries an unverified stamp until somebody actually crawls it. Verification stores evidence rather than a boolean, because a bare &lt;code&gt;verified: true&lt;/code&gt; invites a flip, and producing a false one costs a real crawl.&lt;/p&gt;

&lt;p&gt;The web layer still says Dubai in a few places, and so do a couple of CLI report labels. Porting the crawl is a data file. Porting the site is still a small patch, and I would rather write that down than imply otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;p&gt;I have proven the toolkit on exactly one city, and that run stopped after 18 of the 44 configured tiles. Even Dubai is not finished. I have taken no latency, Core Web Vitals, ranking or traffic numbers, so I am quoting none.&lt;/p&gt;

&lt;p&gt;The repository ships the pipeline and never the corpus. Git ignores both the raw archive and the built dataset, and CI fails the build if either one lands in a commit. Running this requires your own key and your own crawl.&lt;/p&gt;

&lt;p&gt;That last constraint is deliberate, and it is the part I find most interesting commercially. A toolkit that does nothing without a SearchApi key means every serious user opens an account. Shipping a hosted API or a paid dataset would have inverted that, because then I would hold the only key that mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to copy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Probe the ceiling first, then commit the probe. Page 11 of a Google Maps search returns no &lt;code&gt;local_results&lt;/code&gt; key, and that fixture now costs nobody a credit.&lt;/li&gt;
&lt;li&gt;Make the unit of billing the unit of work. One request equals one credit, so a 3x3 lookup table prints the invoice before you spend it.&lt;/li&gt;
&lt;li&gt;Classify the vocabulary, not the rows. My 15,246 businesses held 1,787 distinct category strings, so the model saw a tail of 537 once.&lt;/li&gt;
&lt;li&gt;Never let a health term depend on the quantity its signal measures. That one rule moved the lead score from negative 0.28 to positive 0.08.&lt;/li&gt;
&lt;li&gt;Write every raw response to disk before you parse it. You pay for the call once, and every rebuild after that costs nothing. Then check what your bundler copies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is on &lt;a href="https://github.com/pooyagolchian/business-directory-toolkit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Take the ranking, the phone parsing or the taxonomy pass on its own, since &lt;code&gt;packages/core&lt;/code&gt; is pure and has no idea what a city is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I originally published this at &lt;a href="https://pooyagolchian.com/blog/google-maps-all-businesses-in-a-city/" rel="noopener noreferrer"&gt;pooyagolchian.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>aws</category>
      <category>webscraping</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>dirham v1.3.0 — A Universal Web Component for the UAE Dirham Symbol</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Wed, 25 Mar 2026 19:01:43 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/dirham-v130-a-universal-web-component-for-the-uae-dirham-symbol-5gfh</link>
      <guid>https://dev.to/pooyagolchian/dirham-v130-a-universal-web-component-for-the-uae-dirham-symbol-5gfh</guid>
      <description>&lt;p&gt;The UAE Dirham now has an official Unicode codepoint — &lt;strong&gt;U+20C3&lt;/strong&gt; — accepted for Unicode 18.0. But operating systems won't ship native font support until September 2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/dirham" rel="noopener noreferrer"&gt;&lt;code&gt;dirham&lt;/code&gt;&lt;/a&gt; bridges that gap. It maps the official glyph to U+20C3 via a custom web font, so your content is semantically correct today and future-proof for when native support arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's new in v1.3.0
&lt;/h3&gt;

&lt;p&gt;The headline feature is &lt;strong&gt;&lt;code&gt;&amp;lt;dirham-price&amp;gt;&lt;/code&gt;&lt;/strong&gt; — a Web Component that formats and displays Dirham amounts in any framework, no wrappers needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/dirham/dist/web-component/index.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"1250"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dirham-price&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"5000000"&lt;/span&gt; &lt;span class="na"&gt;notation=&lt;/span&gt;&lt;span class="s"&gt;"compact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dirham-price&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; &lt;span class="na"&gt;use-code&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dirham-price&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It supports 8 attributes: &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;locale&lt;/code&gt;, &lt;code&gt;decimals&lt;/code&gt;, &lt;code&gt;notation&lt;/code&gt;, &lt;code&gt;use-code&lt;/code&gt;, &lt;code&gt;symbol-size&lt;/code&gt;, &lt;code&gt;weight&lt;/code&gt;, and &lt;code&gt;currency&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Works everywhere
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vue:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dirham/web-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"1250"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"5000000"&lt;/span&gt; &lt;span class="na"&gt;notation=&lt;/span&gt;&lt;span class="s"&gt;"compact"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Angular:&lt;/strong&gt;&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;CUSTOM_ELEMENTS_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Component&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="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dirham/web-component&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="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CUSTOM_ELEMENTS_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;dirham-price amount="1250"&amp;gt;&amp;lt;/dirham-price&amp;gt;`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Svelte:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dirham/web-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dirham-price&lt;/span&gt; &lt;span class="na"&gt;amount=&lt;/span&gt;&lt;span class="s"&gt;"1250"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dirham-price&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;React&lt;/strong&gt; still has dedicated components with full TypeScript support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;DirhamPrice&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="s2"&gt;dirham/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DirhamPrice&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1250&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DirhamPrice&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-emerald-400 text-2xl"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full feature set
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React SVG component&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DirhamSymbol size={24} weight="bold" /&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React price component&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DirhamPrice amount={1250} notation="compact" /&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Component (any framework)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;dirham-price amount="1250"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS web font&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;i class="dirham-symbol"&amp;gt;&amp;lt;/i&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JS formatting&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;formatDirham(1234.5)&lt;/code&gt; → &lt;code&gt;℃ 1,234.50&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parsing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parseDirham("℃ 1,234.50")&lt;/code&gt; → &lt;code&gt;1234.5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clipboard&lt;/td&gt;
&lt;td&gt;&lt;code&gt;await copyDirhamSymbol()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx dirham copy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;jsDelivr, zero install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 weight variants&lt;/td&gt;
&lt;td&gt;thin → black&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 font variants&lt;/td&gt;
&lt;td&gt;sans, serif, mono, arabic, default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTL support&lt;/td&gt;
&lt;td&gt;Arabic locale with correct symbol placement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why U+20C3?
&lt;/h3&gt;

&lt;p&gt;Other approaches use Private Use Area hacks or plain text substitution. This package uses the &lt;strong&gt;officially assigned codepoint&lt;/strong&gt;, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your content is &lt;strong&gt;semantically correct&lt;/strong&gt; — search engines, screen readers, and parsers understand it&lt;/li&gt;
&lt;li&gt;When Unicode 18.0 ships natively (Sep 2026), the web font &lt;strong&gt;gracefully becomes unnecessary&lt;/strong&gt; — zero migration&lt;/li&gt;
&lt;li&gt;The symbol behaves like &lt;code&gt;$&lt;/code&gt;, &lt;code&gt;€&lt;/code&gt;, and &lt;code&gt;£&lt;/code&gt; — it's a real currency character&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/dirham" rel="noopener noreferrer"&gt;npmjs.com/package/dirham&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://pooya.blog/dirham/" rel="noopener noreferrer"&gt;pooya.blog/dirham&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/pooyagolchian/dirham" rel="noopener noreferrer"&gt;github.com/pooyagolchian/dirham&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;dirham
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Tue, 17 Mar 2026 17:57:42 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-5g32</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-5g32</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__hidden-navigation-link"&gt;RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pooyagolchian" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" alt="pooyagolchian profile" class="crayons-avatar__image" width="800" height="1071"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pooyagolchian" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pooya Golchian
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pooya Golchian
                
              
              &lt;div id="story-author-preview-content-3358774" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pooyagolchian" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" class="crayons-avatar__image" alt="" width="800" height="1071"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pooya Golchian&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" id="article-link-3358774"&gt;
          RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;15&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>notionchallenge</category>
    </item>
    <item>
      <title>RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP 💥</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Mon, 16 Mar 2026 15:09:54 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-7ko</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-7ko</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__hidden-navigation-link"&gt;RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pooyagolchian" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" alt="pooyagolchian profile" class="crayons-avatar__image" width="800" height="1071"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pooyagolchian" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pooya Golchian
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pooya Golchian
                
              
              &lt;div id="story-author-preview-content-3358774" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pooyagolchian" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" class="crayons-avatar__image" alt="" width="800" height="1071"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pooya Golchian&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" id="article-link-3358774"&gt;
          RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;15&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>notionchallenge</category>
    </item>
    <item>
      <title>RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Mon, 16 Mar 2026 15:09:38 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-3ch8</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-3ch8</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__hidden-navigation-link"&gt;RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pooyagolchian" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" alt="pooyagolchian profile" class="crayons-avatar__image" width="800" height="1071"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pooyagolchian" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pooya Golchian
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pooya Golchian
                
              
              &lt;div id="story-author-preview-content-3358774" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pooyagolchian" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" class="crayons-avatar__image" alt="" width="800" height="1071"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pooya Golchian&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" id="article-link-3358774"&gt;
          RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;15&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>notionchallenge</category>
    </item>
    <item>
      <title>RevOps AI. I Turned Notion into an AI-Powered CRM with Gemini and MCP</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Mon, 16 Mar 2026 12:49:07 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-i-turned-notion-into-an-ai-powered-crm-with-gemini-and-mcp-1fl6</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-i-turned-notion-into-an-ai-powered-crm-with-gemini-and-mcp-1fl6</guid>
      <description>&lt;p&gt;A revenue operations platform where Gemini 2.5 Flash autonomously manages pipeline, scores leads, and drafts sales emails through Notion MCP. No CRM vendor lock-in. No proprietary databases. Just Notion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;RevOps AI turns Notion into a full revenue operations platform. No Salesforce. No HubSpot. No proprietary database sitting between your team and their data.&lt;/p&gt;

&lt;p&gt;Four Notion databases hold every contact, deal, activity, and company record. Google Gemini 2.5 Flash sits on top as an autonomous sales team member. It queries your pipeline, scores leads, drafts emails, generates pre-call briefings, and creates follow-up activities. All through natural language. All written back to Notion in real time.&lt;/p&gt;

&lt;p&gt;The critical difference from other MCP integrations? Gemini doesn't follow hardcoded scripts. Through &lt;code&gt;mcpToTool()&lt;/code&gt; from Google's GenAI SDK, the model receives all 22 Notion MCP tools and decides at runtime which ones to call. It reasons through multi-step operations the same way a senior sales ops analyst would, chaining searches, queries, and updates in a single response.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem That Started This
&lt;/h3&gt;

&lt;p&gt;Every sales team I've worked with runs into the same wall. Their CRM lives in one system. Their docs, meeting notes, and project management live in Notion. Two systems. Two sources of truth. Data drifts apart within weeks.&lt;/p&gt;

&lt;p&gt;Salesforce runs $150/user/month. HubSpot locks you into their ecosystem. Meanwhile, the team already collaborates in Notion every day.&lt;/p&gt;

&lt;p&gt;So I asked a simple question. What if Notion &lt;em&gt;was&lt;/em&gt; the CRM? And what if an AI agent could operate it autonomously?&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager.&lt;/strong&gt; Ask &lt;em&gt;"What's my pipeline health?"&lt;/em&gt; in plain English. Gemini queries your Notion databases, calculates win rates, and surfaces the insights that matter. Say &lt;em&gt;"Create a follow-up for the Acme deal"&lt;/em&gt; and it creates the activity, linked to the right deal, without you touching a form. Think of it as a senior sales ops analyst available around the clock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard.&lt;/strong&gt; Total pipeline value, win rate, deal breakdown by stage, top deals, and recent team activity. Every metric pulls from Notion through MCP in real time. No stale data. No manual report building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Deal Pipeline.&lt;/strong&gt; Drag-and-drop Kanban board with six stages, from Lead through Closed Won and Closed Lost. Every move syncs to Notion instantly via MCP. What your team sees in the app matches what they see in Notion. Always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Lead Scoring.&lt;/strong&gt; One click triggers Gemini to evaluate a contact's role seniority, company size, and engagement history, then assign a 0-100 score with written reasoning. That score persists back to the Notion Contacts database so your entire team sees it everywhere Notion surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-Call Briefing Generator.&lt;/strong&gt; Before any sales call, the AI pulls deal context, activity history, and contact data from Notion to build a structured brief. Talking points, likely objections, relationship timeline, and recommended next steps. All generated autonomously from your existing data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email Ghostwriter.&lt;/strong&gt; Gemini drafts personalized sales emails based on the deal's stage, contact history, and recent activity. No generic templates. Each email reflects the actual context of the relationship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contact Management.&lt;/strong&gt; Full CRUD with search, filtering by lead source, sortable columns, and inline AI scoring. Add new contacts directly from the UI. Everything writes to Notion through MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nK-Te39bsS0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Pipeline (Kanban)&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" alt="Pipeline" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Detail + AI Email Draft&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" alt="Deal Detail" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" alt="AI Email Draft" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leads &amp;amp; AI Scoring&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" alt="Leads" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companies&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" alt="Companies" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" alt="AI Assistant" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsive across desktop, tablet, and mobile&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Desktop" width="800" height="450"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" alt="Tablet" width="768" height="1024"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" alt="Mobile" width="375" height="812"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Us the Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/pooyagolchian/ai-sales-crm" rel="noopener noreferrer"&gt;https://github.com/pooyagolchian/ai-sales-crm&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Application Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" alt="Application Architecture" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Core Innovation, &lt;code&gt;mcpToTool()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Most MCP integrations manually wire each API endpoint into the AI layer. RevOps AI eliminates that entire step with a single function call.&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="c1"&gt;// src/lib/gemini.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mcpToTool&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="s2"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getMcpClient&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="s2"&gt;./mcp-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateWithTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;prompt&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="nx"&gt;systemPrompt&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&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;mcpClient&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;getMcpClient&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;response&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;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;mcpToTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;         &lt;span class="c1"&gt;// All 22 Notion tools, auto-mapped&lt;/span&gt;
      &lt;span class="na"&gt;systemInstruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCrmSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// DB IDs + property names injected&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt; auto-maps all 22 Notion MCP tools into Gemini function declarations. The system prompt injects actual database IDs and property names so Gemini skips the discovery phase entirely.&lt;/p&gt;

&lt;p&gt;The result? Gemini autonomously decides which Notion operations to perform. Ask &lt;em&gt;"What's my pipeline value?"&lt;/em&gt; and it calls &lt;code&gt;API-query-data-source&lt;/code&gt; on Deals. Say &lt;em&gt;"Log a meeting for Acme"&lt;/em&gt; and it calls &lt;code&gt;API-post-page&lt;/code&gt; to create an Activity, then &lt;code&gt;API-patch-page&lt;/code&gt; to update the deal. Zero hardcoded logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Client, Singleton with Auto-Reconnect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/mcp-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&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="s2"&gt;@modelcontextprotocol/sdk/client/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamableHTTPClientTransport&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="s2"&gt;@modelcontextprotocol/sdk/client/streamableHttp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;&amp;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;mcpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&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="s2"&gt;revops-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MCP_SERVER_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3001/mcp&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;requestInit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js 15 (App Router), TypeScript (strict)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4, shadcn/ui, Lucide icons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Google Gemini 2.5 Flash via &lt;code&gt;@google/genai&lt;/code&gt; SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Notion (4 databases) via MCP protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@notionhq/notion-mcp-server&lt;/code&gt; v2 (HTTP) + &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DnD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@dnd-kit/core&lt;/code&gt; + &lt;code&gt;@dnd-kit/sortable&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linting&lt;/td&gt;
&lt;td&gt;Biome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion isn't a supporting tool here. It &lt;em&gt;is&lt;/em&gt; the entire database layer. No Postgres. No SQLite. No local storage. Every record lives in a Notion database, and every read and write goes through MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Notion Databases
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key Properties&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lead &amp;amp; customer profiles&lt;/td&gt;
&lt;td&gt;Name, Email, Company, Role, Lead Score, Lead Score Notes, Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Revenue pipeline items&lt;/td&gt;
&lt;td&gt;Name, Contact (relation), Stage (6 values), Value, Close Date, Priority, Next Action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sales touchpoints&lt;/td&gt;
&lt;td&gt;Type (call/email/meeting/note), Date, Deal (relation), Summary, Raw Notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Companies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Organization profiles&lt;/td&gt;
&lt;td&gt;Name, Industry, Size, Website&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  MCP Operations Used
&lt;/h3&gt;

&lt;p&gt;Every data operation routes through MCP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;API-query-data-source&lt;/code&gt; for querying databases with filters and sorts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-post-page&lt;/code&gt; for creating new contacts, deals, and activities&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-patch-page&lt;/code&gt; for updating deal stages via Kanban drag-and-drop, persisting AI lead scores, and modifying Next Action fields&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-search&lt;/code&gt; for free-text search across the entire Notion workspace&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-retrieve-a-page&lt;/code&gt; for fetching full details used in deal briefings and AI assistant context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Makes This Approach Different
&lt;/h3&gt;

&lt;p&gt;Most MCP integrations hardcode which tools to call. RevOps AI hands the entire toolset to Gemini and lets it choose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt;&lt;/strong&gt; auto-maps all 22 Notion MCP tools into Gemini function declarations with a single call. No manual schema writing. No tool definitions to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autonomous multi-step reasoning&lt;/strong&gt; means Gemini chains multiple MCP calls in one response. Ask &lt;em&gt;"Brief me on the TechCorp deal"&lt;/em&gt; and it searches for the deal, queries related activities, retrieves the contact profile, and synthesizes a briefing. Four operations. One prompt. Zero hardcoded logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System prompt injection&lt;/strong&gt; feeds the AI actual database IDs and exact property names from &lt;code&gt;notion-schema.ts&lt;/code&gt;, so the model never wastes tool calls discovering database structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notion stays the single source of truth.&lt;/strong&gt; The team's data lives where they already collaborate. RevOps AI adds an intelligent layer on top without creating another data silo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Notion MCP Specifically
&lt;/h3&gt;

&lt;p&gt;This solves a real problem that traditional CRM architectures cannot.&lt;/p&gt;

&lt;p&gt;Your data stays where your team already works. A marketing director opens Notion and sees every deal, contact, and activity in familiar views. Tables, kanban boards, calendars, galleries. They edit inline, add comments, share with stakeholders. No separate CRM login required.&lt;/p&gt;

&lt;p&gt;The AI gets full context through a standard protocol. MCP gives structured access to query, create, update, and search, with proper schemas and typed inputs. This is a designed protocol, not screen scraping or brittle API hacks.&lt;/p&gt;

&lt;p&gt;Zero vendor lock-in. Switch from Gemini to Claude or GPT? The MCP layer stays the same. Switch from Notion to another MCP-compatible tool? The AI integration pattern stays the same. MCP decouples the AI from the data source.&lt;/p&gt;

&lt;p&gt;The cost is essentially zero. Notion's free plan supports databases. Gemini 2.5 Flash has a generous free tier via Google AI Studio. The MCP server is open source. A startup's first RevOps stack can run at $0.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Notion MCP, Google Gemini 2.5 Flash, and Next.js 15. Because revenue operations shouldn't require a $150/seat CRM when your team already lives in Notion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>notionchallenge</category>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>RevOps AI. I Turned Notion into an AI-Powered CRM with Gemini and MCP</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Mon, 16 Mar 2026 12:43:06 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-i-turned-notion-into-an-ai-powered-crm-with-gemini-and-mcp-5did</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-i-turned-notion-into-an-ai-powered-crm-with-gemini-and-mcp-5did</guid>
      <description>&lt;p&gt;A revenue operations platform where Gemini 2.5 Flash autonomously manages pipeline, scores leads, and drafts sales emails through Notion MCP. No CRM vendor lock-in. No proprietary databases. Just Notion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;RevOps AI turns Notion into a full revenue operations platform. No Salesforce. No HubSpot. No proprietary database sitting between your team and their data.&lt;/p&gt;

&lt;p&gt;Four Notion databases hold every contact, deal, activity, and company record. Google Gemini 2.5 Flash sits on top as an autonomous sales team member. It queries your pipeline, scores leads, drafts emails, generates pre-call briefings, and creates follow-up activities. All through natural language. All written back to Notion in real time.&lt;/p&gt;

&lt;p&gt;The critical difference from other MCP integrations? Gemini doesn't follow hardcoded scripts. Through &lt;code&gt;mcpToTool()&lt;/code&gt; from Google's GenAI SDK, the model receives all 22 Notion MCP tools and decides at runtime which ones to call. It reasons through multi-step operations the same way a senior sales ops analyst would, chaining searches, queries, and updates in a single response.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem That Started This
&lt;/h3&gt;

&lt;p&gt;Every sales team I've worked with runs into the same wall. Their CRM lives in one system. Their docs, meeting notes, and project management live in Notion. Two systems. Two sources of truth. Data drifts apart within weeks.&lt;/p&gt;

&lt;p&gt;Salesforce runs $150/user/month. HubSpot locks you into their ecosystem. Meanwhile, the team already collaborates in Notion every day.&lt;/p&gt;

&lt;p&gt;So I asked a simple question. What if Notion &lt;em&gt;was&lt;/em&gt; the CRM? And what if an AI agent could operate it autonomously?&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager.&lt;/strong&gt; Ask &lt;em&gt;"What's my pipeline health?"&lt;/em&gt; in plain English. Gemini queries your Notion databases, calculates win rates, and surfaces the insights that matter. Say &lt;em&gt;"Create a follow-up for the Acme deal"&lt;/em&gt; and it creates the activity, linked to the right deal, without you touching a form. Think of it as a senior sales ops analyst available around the clock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard.&lt;/strong&gt; Total pipeline value, win rate, deal breakdown by stage, top deals, and recent team activity. Every metric pulls from Notion through MCP in real time. No stale data. No manual report building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Deal Pipeline.&lt;/strong&gt; Drag-and-drop Kanban board with six stages, from Lead through Closed Won and Closed Lost. Every move syncs to Notion instantly via MCP. What your team sees in the app matches what they see in Notion. Always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Lead Scoring.&lt;/strong&gt; One click triggers Gemini to evaluate a contact's role seniority, company size, and engagement history, then assign a 0-100 score with written reasoning. That score persists back to the Notion Contacts database so your entire team sees it everywhere Notion surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-Call Briefing Generator.&lt;/strong&gt; Before any sales call, the AI pulls deal context, activity history, and contact data from Notion to build a structured brief. Talking points, likely objections, relationship timeline, and recommended next steps. All generated autonomously from your existing data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email Ghostwriter.&lt;/strong&gt; Gemini drafts personalized sales emails based on the deal's stage, contact history, and recent activity. No generic templates. Each email reflects the actual context of the relationship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contact Management.&lt;/strong&gt; Full CRUD with search, filtering by lead source, sortable columns, and inline AI scoring. Add new contacts directly from the UI. Everything writes to Notion through MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nK-Te39bsS0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Pipeline (Kanban)&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" alt="Pipeline" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Detail + AI Email Draft&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" alt="Deal Detail" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" alt="AI Email Draft" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leads &amp;amp; AI Scoring&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" alt="Leads" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companies&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" alt="Companies" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" alt="AI Assistant" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsive across desktop, tablet, and mobile&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Desktop" width="800" height="450"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" alt="Tablet" width="768" height="1024"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" alt="Mobile" width="375" height="812"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Us the Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/pooyagolchian/ai-sales-crm" rel="noopener noreferrer"&gt;https://github.com/pooyagolchian/ai-sales-crm&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Application Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" alt="Application Architecture" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Core Innovation, &lt;code&gt;mcpToTool()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Most MCP integrations manually wire each API endpoint into the AI layer. RevOps AI eliminates that entire step with a single function call.&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="c1"&gt;// src/lib/gemini.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mcpToTool&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="s2"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getMcpClient&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="s2"&gt;./mcp-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateWithTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;prompt&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="nx"&gt;systemPrompt&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&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;mcpClient&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;getMcpClient&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;response&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;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;mcpToTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;         &lt;span class="c1"&gt;// All 22 Notion tools, auto-mapped&lt;/span&gt;
      &lt;span class="na"&gt;systemInstruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCrmSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// DB IDs + property names injected&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt; auto-maps all 22 Notion MCP tools into Gemini function declarations. The system prompt injects actual database IDs and property names so Gemini skips the discovery phase entirely.&lt;/p&gt;

&lt;p&gt;The result? Gemini autonomously decides which Notion operations to perform. Ask &lt;em&gt;"What's my pipeline value?"&lt;/em&gt; and it calls &lt;code&gt;API-query-data-source&lt;/code&gt; on Deals. Say &lt;em&gt;"Log a meeting for Acme"&lt;/em&gt; and it calls &lt;code&gt;API-post-page&lt;/code&gt; to create an Activity, then &lt;code&gt;API-patch-page&lt;/code&gt; to update the deal. Zero hardcoded logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Client, Singleton with Auto-Reconnect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/mcp-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&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="s2"&gt;@modelcontextprotocol/sdk/client/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamableHTTPClientTransport&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="s2"&gt;@modelcontextprotocol/sdk/client/streamableHttp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;&amp;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;mcpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&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="s2"&gt;revops-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MCP_SERVER_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3001/mcp&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;requestInit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js 15 (App Router), TypeScript (strict)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4, shadcn/ui, Lucide icons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Google Gemini 2.5 Flash via &lt;code&gt;@google/genai&lt;/code&gt; SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Notion (4 databases) via MCP protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@notionhq/notion-mcp-server&lt;/code&gt; v2 (HTTP) + &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DnD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@dnd-kit/core&lt;/code&gt; + &lt;code&gt;@dnd-kit/sortable&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linting&lt;/td&gt;
&lt;td&gt;Biome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion isn't a supporting tool here. It &lt;em&gt;is&lt;/em&gt; the entire database layer. No Postgres. No SQLite. No local storage. Every record lives in a Notion database, and every read and write goes through MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Notion Databases
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key Properties&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lead &amp;amp; customer profiles&lt;/td&gt;
&lt;td&gt;Name, Email, Company, Role, Lead Score, Lead Score Notes, Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Revenue pipeline items&lt;/td&gt;
&lt;td&gt;Name, Contact (relation), Stage (6 values), Value, Close Date, Priority, Next Action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sales touchpoints&lt;/td&gt;
&lt;td&gt;Type (call/email/meeting/note), Date, Deal (relation), Summary, Raw Notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Companies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Organization profiles&lt;/td&gt;
&lt;td&gt;Name, Industry, Size, Website&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  MCP Operations Used
&lt;/h3&gt;

&lt;p&gt;Every data operation routes through MCP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;API-query-data-source&lt;/code&gt; for querying databases with filters and sorts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-post-page&lt;/code&gt; for creating new contacts, deals, and activities&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-patch-page&lt;/code&gt; for updating deal stages via Kanban drag-and-drop, persisting AI lead scores, and modifying Next Action fields&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-search&lt;/code&gt; for free-text search across the entire Notion workspace&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API-retrieve-a-page&lt;/code&gt; for fetching full details used in deal briefings and AI assistant context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Makes This Approach Different
&lt;/h3&gt;

&lt;p&gt;Most MCP integrations hardcode which tools to call. RevOps AI hands the entire toolset to Gemini and lets it choose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt;&lt;/strong&gt; auto-maps all 22 Notion MCP tools into Gemini function declarations with a single call. No manual schema writing. No tool definitions to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autonomous multi-step reasoning&lt;/strong&gt; means Gemini chains multiple MCP calls in one response. Ask &lt;em&gt;"Brief me on the TechCorp deal"&lt;/em&gt; and it searches for the deal, queries related activities, retrieves the contact profile, and synthesizes a briefing. Four operations. One prompt. Zero hardcoded logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System prompt injection&lt;/strong&gt; feeds the AI actual database IDs and exact property names from &lt;code&gt;notion-schema.ts&lt;/code&gt;, so the model never wastes tool calls discovering database structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notion stays the single source of truth.&lt;/strong&gt; The team's data lives where they already collaborate. RevOps AI adds an intelligent layer on top without creating another data silo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Notion MCP Specifically
&lt;/h3&gt;

&lt;p&gt;This solves a real problem that traditional CRM architectures cannot.&lt;/p&gt;

&lt;p&gt;Your data stays where your team already works. A marketing director opens Notion and sees every deal, contact, and activity in familiar views. Tables, kanban boards, calendars, galleries. They edit inline, add comments, share with stakeholders. No separate CRM login required.&lt;/p&gt;

&lt;p&gt;The AI gets full context through a standard protocol. MCP gives structured access to query, create, update, and search, with proper schemas and typed inputs. This is a designed protocol, not screen scraping or brittle API hacks.&lt;/p&gt;

&lt;p&gt;Zero vendor lock-in. Switch from Gemini to Claude or GPT? The MCP layer stays the same. Switch from Notion to another MCP-compatible tool? The AI integration pattern stays the same. MCP decouples the AI from the data source.&lt;/p&gt;

&lt;p&gt;The cost is essentially zero. Notion's free plan supports databases. Gemini 2.5 Flash has a generous free tier via Google AI Studio. The MCP server is open source. A startup's first RevOps stack can run at $0.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Notion MCP, Google Gemini 2.5 Flash, and Next.js 15. Because revenue operations shouldn't require a $150/seat CRM when your team already lives in Notion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>RevOps AI - Build Your AI Sales Team on Notion with Gemini + MCP</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Mon, 16 Mar 2026 12:16:21 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g</link>
      <guid>https://dev.to/pooyagolchian/revops-ai-build-your-ai-sales-team-on-notion-with-gemini-mcp-4k0g</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;RevOps AI&lt;/strong&gt; — a revenue operations platform that lets marketing directors and company managers &lt;strong&gt;build, deploy, and manage an AI-powered sales team&lt;/strong&gt;, with Notion as the single source of truth.&lt;/p&gt;

&lt;p&gt;This isn't just a CRM. It's a RevOps command center where &lt;strong&gt;Google Gemini 2.5 Flash acts as an autonomous sales team member&lt;/strong&gt; that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyze your pipeline and tell you where revenue is leaking&lt;/li&gt;
&lt;li&gt;Score leads using AI reasoning and write the scores back to Notion&lt;/li&gt;
&lt;li&gt;Generate pre-call briefings before your team picks up the phone&lt;/li&gt;
&lt;li&gt;Draft personalized sales emails based on deal context&lt;/li&gt;
&lt;li&gt;Create contacts, log activities, and update deal stages — all through natural language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI doesn't follow hardcoded scripts. Using &lt;code&gt;mcpToTool()&lt;/code&gt; from Google's GenAI SDK, Gemini receives &lt;strong&gt;all 22 Notion MCP tools&lt;/strong&gt; and decides at runtime which to call. It reasons, chains multi-step operations, and executes — like a real sales ops analyst would.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why RevOps on Notion?
&lt;/h3&gt;

&lt;p&gt;Every sales team I've worked with faces the same problem: their CRM is a data silo. Salesforce costs $150/user/month. HubSpot locks you into their ecosystem. Meanwhile, the team already lives in Notion for docs, meeting notes, and project management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if Notion &lt;em&gt;was&lt;/em&gt; the CRM?&lt;/strong&gt; And what if an AI agent could operate it autonomously?&lt;/p&gt;

&lt;p&gt;That's RevOps AI. Your pipeline data stays in Notion where your whole team can see it. The AI layer adds intelligence on top — scoring, briefing, drafting, analyzing — without moving data into yet another proprietary database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager&lt;/strong&gt; — Chat with your revenue data in natural language. Ask &lt;em&gt;"What's my pipeline health?"&lt;/em&gt; and the AI queries Notion, calculates win rates, and surfaces insights. Say &lt;em&gt;"Create a follow-up for the Acme deal"&lt;/em&gt; and it creates the activity linked to the right deal. It's like having a senior sales ops analyst available 24/7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard&lt;/strong&gt; — Pipeline metrics at a glance: total value, win rate, deal breakdown by stage, top deals, and recent activity. All data flows from Notion through MCP in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Deal Pipeline (Kanban)&lt;/strong&gt; — Drag-and-drop deals between 6 stages (Lead → Qualified → Proposal → Negotiation → Closed Won → Closed Lost). Every move syncs to Notion instantly via MCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Lead Scoring&lt;/strong&gt; — One-click AI analysis evaluates each contact (role seniority, company size, engagement history) and assigns a 0-100 score with written reasoning. Scores persist back to the Notion Contacts database so your team sees them everywhere Notion surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-Call Briefing Generator&lt;/strong&gt; — Before any sales call, the AI generates a structured brief: talking points, objections to expect, relationship history, and recommended next steps. All sourced autonomously from deal and activity data in Notion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email Ghostwriter&lt;/strong&gt; — AI drafts personalized sales emails based on the deal's context, stage, and contact history. No generic templates — each email is contextually unique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contact Management&lt;/strong&gt; — Full contact CRUD with search, filtering by lead source, sortable columns, and inline AI scoring. Add new contacts directly from the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nK-Te39bsS0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Revenue Dashboard&lt;/strong&gt; — KPIs, pipeline by stage, top deals, recent activity:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Pipeline (Kanban)&lt;/strong&gt; — Drag &amp;amp; drop deals between stages:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flizvxxtnmgfrrc07s2tu.png" alt="Pipeline" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deal Detail + AI Email Draft&lt;/strong&gt; — Edit deals, generate briefings, draft emails:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ewqhl0o8csce0nlohgz.png" alt="Deal Detail" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnoez5adjppgukoiwv3u.png" alt="AI Email Draft" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leads &amp;amp; AI Scoring&lt;/strong&gt; — Contact table with AI-powered lead scores:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn1njg0qrmxbm1cbwhm5.png" alt="Leads" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companies&lt;/strong&gt; — Organization profiles linked to contacts and deals:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwka2x2kl2rfas6z4wluz.png" alt="Companies" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Sales Team Manager&lt;/strong&gt; — Natural language chat with your CRM data:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1r7zwbk8l5cesc0vgf.png" alt="AI Assistant" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsive Design&lt;/strong&gt; — Works on desktop, tablet, and mobile:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7inq0l5pir15r4zwi8.png" alt="Desktop" width="800" height="450"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8k99vryxf26mp2ojuw.png" alt="Tablet" width="768" height="1024"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ufqbbna1c9fshey9rw.png" alt="Mobile" width="375" height="812"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Us the Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/pooyagolchian/ai-sales-crm" rel="noopener noreferrer"&gt;https://github.com/pooyagolchian/ai-sales-crm&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Application Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2vbijwa8smbfianaiu8.png" alt="Application Architecture" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Core Innovation — &lt;code&gt;mcpToTool()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of manually wiring each Notion API endpoint into the AI, one function call changes everything:&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="c1"&gt;// src/lib/gemini.ts — The entire AI-to-Notion bridge&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mcpToTool&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="s2"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getMcpClient&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="s2"&gt;./mcp-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateWithTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;prompt&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="nx"&gt;systemPrompt&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&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;mcpClient&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;getMcpClient&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;response&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;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;mcpToTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;         &lt;span class="c1"&gt;// ← All 22 Notion tools, auto-mapped&lt;/span&gt;
      &lt;span class="na"&gt;systemInstruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCrmSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// ← DB IDs + property names injected&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt; auto-maps &lt;strong&gt;all 22 Notion MCP tools&lt;/strong&gt; into Gemini function declarations. The system prompt injects the actual database IDs and property names so Gemini doesn't waste tool calls discovering them.&lt;/p&gt;

&lt;p&gt;The result: Gemini &lt;strong&gt;autonomously decides&lt;/strong&gt; which Notion operations to perform. Ask &lt;em&gt;"What's my pipeline value?"&lt;/em&gt; → it calls &lt;code&gt;API-query-data-source&lt;/code&gt; on Deals. Say &lt;em&gt;"Log a meeting for Acme"&lt;/em&gt; → it calls &lt;code&gt;API-post-page&lt;/code&gt; to create an Activity and &lt;code&gt;API-patch-page&lt;/code&gt; to update the deal. No hardcoded logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Client — Singleton with Auto-Reconnect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/mcp-client.ts — Singleton pattern&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&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="s2"&gt;@modelcontextprotocol/sdk/client/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamableHTTPClientTransport&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="s2"&gt;@modelcontextprotocol/sdk/client/streamableHttp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;&amp;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;mcpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&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="s2"&gt;revops-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MCP_SERVER_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3001/mcp&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;requestInit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mcpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js 15 (App Router), TypeScript (strict)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4, shadcn/ui, Lucide icons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Google Gemini 2.5 Flash via &lt;code&gt;@google/genai&lt;/code&gt; SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Notion (4 databases) via MCP protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@notionhq/notion-mcp-server&lt;/code&gt; v2 (HTTP) + &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DnD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@dnd-kit/core&lt;/code&gt; + &lt;code&gt;@dnd-kit/sortable&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linting&lt;/td&gt;
&lt;td&gt;Biome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion is not a supporting tool here — it &lt;strong&gt;is&lt;/strong&gt; the entire database layer. There is no Postgres, no SQLite, no local storage. Every record (contacts, deals, activities, companies) lives in a Notion database, and every read/write goes through MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Notion Databases
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key Properties&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lead &amp;amp; customer profiles&lt;/td&gt;
&lt;td&gt;Name, Email, Company, Role, Lead Score, Lead Score Notes, Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Revenue pipeline items&lt;/td&gt;
&lt;td&gt;Name, Contact (relation), Stage (6 values), Value, Close Date, Priority, Next Action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sales touchpoints&lt;/td&gt;
&lt;td&gt;Type (call/email/meeting/note), Date, Deal (relation), Summary, Raw Notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Companies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Organization profiles&lt;/td&gt;
&lt;td&gt;Name, Industry, Size, Website&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  MCP Operations Used
&lt;/h3&gt;

&lt;p&gt;Every data operation goes through MCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;API-query-data-source&lt;/code&gt;&lt;/strong&gt; — Querying databases with filters and sorts (&lt;em&gt;"all deals where Stage = Proposal, sorted by Value desc"&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;API-post-page&lt;/code&gt;&lt;/strong&gt; — Creating new contacts, deals, activities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;API-patch-page&lt;/code&gt;&lt;/strong&gt; — Updating deal stages (Kanban DnD), persisting AI lead scores, updating Next Action fields&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;API-search&lt;/code&gt;&lt;/strong&gt; — Free-text search across the entire Notion workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;API-retrieve-a-page&lt;/code&gt;&lt;/strong&gt; — Fetching full details for deal briefings and AI assistant context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Makes This Approach Fundamentally Different
&lt;/h3&gt;

&lt;p&gt;Most MCP integrations hardcode which tools to call. RevOps AI lets the AI choose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpToTool(mcpClient)&lt;/code&gt;&lt;/strong&gt; — One function call auto-maps all 22 Notion MCP tools into Gemini function declarations. No manual schema writing. No maintaining tool definitions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Autonomous multi-step reasoning&lt;/strong&gt; — Gemini chains multiple MCP calls in a single response. Ask &lt;em&gt;"Brief me on the TechCorp deal"&lt;/em&gt; and it autonomously: (1) searches for the deal → (2) queries related activities → (3) retrieves the contact profile → (4) synthesizes a briefing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System prompt injection&lt;/strong&gt; — The AI system prompt includes actual database IDs and exact property names from &lt;code&gt;notion-schema.ts&lt;/code&gt;, so the AI doesn't waste tool calls discovering database structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notion as single source of truth&lt;/strong&gt; — The team's data stays in Notion where they already collaborate. RevOps AI is an intelligent layer on top, not another data silo.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Practical Benefit of Notion MCP
&lt;/h3&gt;

&lt;p&gt;Why Notion MCP specifically? Because it solves a real problem that traditional CRM architectures can't:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your data stays where your team already works.&lt;/strong&gt; A marketing director can open Notion and see every deal, contact, and activity in familiar views — tables, kanban, calendars, galleries. They can edit inline, add comments, share with stakeholders. No CRM login required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI gets full context through a standard protocol.&lt;/strong&gt; MCP gives the AI structured access to query, create, update, and search — with proper schemas and typed inputs. This isn't screen scraping or brittle API hacks. It's a designed protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero vendor lock-in.&lt;/strong&gt; Switch from Gemini to Claude or GPT? The MCP layer stays the same. Switch from Notion to another MCP-compatible tool? The AI integration pattern stays the same. MCP decouples the AI from the data source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost: essentially free.&lt;/strong&gt; Notion's free plan supports databases. Gemini 2.5 Flash has a generous free tier via Google AI Studio. The MCP server is open source. For a startup's first RevOps stack, total cost can be $0.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Notion MCP, Google Gemini 2.5 Flash, Next.js 15, and the belief that revenue operations shouldn't require a $150/seat CRM when your team's already in Notion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>notionchallenge</category>
    </item>
    <item>
      <title>https://www.npmjs.com/package/use-local-llm</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Fri, 13 Mar 2026 06:59:35 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/httpswwwnpmjscompackageuse-local-llm-25ll</link>
      <guid>https://dev.to/pooyagolchian/httpswwwnpmjscompackageuse-local-llm-25ll</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pooyagolchian/use-local-llm-react-hooks-for-ai-that-actually-work-locally-4ghc" class="crayons-story__hidden-navigation-link"&gt;use-local-llm: React Hooks for AI That Actually Work Locally&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pooyagolchian" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" alt="pooyagolchian profile" class="crayons-avatar__image" width="800" height="1071"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pooyagolchian" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pooya Golchian
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pooya Golchian
                
              
              &lt;div id="story-author-preview-content-3340869" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pooyagolchian" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78949%2Fcb6a9990-c5ed-4158-ab22-6b65396dabc0.jpeg" class="crayons-avatar__image" alt="" width="800" height="1071"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pooya Golchian&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pooyagolchian/use-local-llm-react-hooks-for-ai-that-actually-work-locally-4ghc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 11&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pooyagolchian/use-local-llm-react-hooks-for-ai-that-actually-work-locally-4ghc" id="article-link-3340869"&gt;
          use-local-llm: React Hooks for AI That Actually Work Locally
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/pooyagolchian/use-local-llm-react-hooks-for-ai-that-actually-work-locally-4ghc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.npmjs.com/package/use-local-llm" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;npmjs.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>react</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Detailed Comparison of Next.js, Astro, and Remix</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Fri, 02 Aug 2024 22:37:35 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/boost-your-online-presence-expert-website-development-and-consultancy-3lmp</link>
      <guid>https://dev.to/pooyagolchian/boost-your-online-presence-expert-website-development-and-consultancy-3lmp</guid>
      <description>&lt;h4&gt;
  
  
  Introduction
&lt;/h4&gt;

&lt;p&gt;Creating a robust and fast website is essential in today's digital landscape. This post compares three popular frontend frameworks—Next.js, Astro, and Remix—highlighting why developers choose them, and introduces the expert website development and consultancy services available at Pooya Blog.&lt;/p&gt;

&lt;h4&gt;
  
  
  Detailed Comparison of Next.js, Astro, and Remix
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Overview of Frameworks&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Remix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static site generation&lt;/td&gt;
&lt;td&gt;Server-rendered React apps&lt;/td&gt;
&lt;td&gt;Full-stack JavaScript framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal JavaScript, fast load times&lt;/td&gt;
&lt;td&gt;Efficient with static and server-side rendering&lt;/td&gt;
&lt;td&gt;Fast data retrieval and loading&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;UI-agnostic, supports various frameworks&lt;/td&gt;
&lt;td&gt;Built-in CSS/Sass support, configurable&lt;/td&gt;
&lt;td&gt;Unifies client-server, web standards-focused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero-config, supports multiple UI libraries&lt;/td&gt;
&lt;td&gt;Async components, file-based routing&lt;/td&gt;
&lt;td&gt;File-based routing, nested components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hydration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial hydration&lt;/td&gt;
&lt;td&gt;No partial hydration, supports static pages&lt;/td&gt;
&lt;td&gt;No partial hydration, potential with React 18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Performance Benchmarks&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Remix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fastest&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JavaScript Payload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SEO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Remix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Content-heavy Static Sites&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dynamic Applications&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client-server Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Adoption and Community Support&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Remix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Stars&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25,000+&lt;/td&gt;
&lt;td&gt;100,000+&lt;/td&gt;
&lt;td&gt;15,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;npm Downloads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500,000/month&lt;/td&gt;
&lt;td&gt;3 million/month&lt;/td&gt;
&lt;td&gt;200,000/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Comprehensive&lt;/td&gt;
&lt;td&gt;Comprehensive&lt;/td&gt;
&lt;td&gt;Comprehensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Community Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Very Active&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Why Developers Choose These Frameworks
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance and Flexibility:&lt;/strong&gt; SSR, SSG, built-in CSS/Sass support, file-based routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust Ecosystem:&lt;/strong&gt; Extensive documentation, large community, and seamless integration with Vercel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance-First Approach:&lt;/strong&gt; Minimal JavaScript, partial hydration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility and Simplicity:&lt;/strong&gt; UI-agnostic, zero-config setup, supports various frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full-Stack Capabilities:&lt;/strong&gt; Client-server unification, fast data loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern Development Features:&lt;/strong&gt; Nested routes, web standards focus.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Expert Website Development and Consultancy
&lt;/h4&gt;

&lt;p&gt;At Pooya Golchian Website, we specialize in creating robust and fast websites tailored to your needs. Our consultancy services provide insights into the latest trends, best practices, and essential tools for building and maintaining high-performance websites. Whether starting from scratch or enhancing your existing site, our expertise will guide you to success.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Choosing the right framework depends on your project needs. Astro excels in static site generation, Next.js is versatile for dynamic and static content, and Remix offers a seamless full-stack experience. For professional website development and consultancy services, visit &lt;a href="https://pooya.blog/" rel="noopener noreferrer"&gt;Pooya Golchian Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>astro</category>
      <category>nextjs</category>
      <category>remix</category>
    </item>
    <item>
      <title>Improving Next.js website performance and get a 100% rate of optimization from Lighthouse on desktop and mobile</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Tue, 14 Feb 2023 14:09:57 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/improve-nextjs-website-performance-and-get-a-100-rate-of-optimization-from-lighthouse-on-desktop-and-mobile-1bf4</link>
      <guid>https://dev.to/pooyagolchian/improve-nextjs-website-performance-and-get-a-100-rate-of-optimization-from-lighthouse-on-desktop-and-mobile-1bf4</guid>
      <description>&lt;p&gt;I want to write about the website's performance that lighthouse provides us. After I developed the website of the Cashew company, the performance of the website was so terrible. The percentage was about 20%. I improved it with some tips and tricks about the image format and also upgraded the Next.js version 12 to 13. &lt;/p&gt;




&lt;h3&gt;
  
  
  Image format! (the important part)
&lt;/h3&gt;

&lt;p&gt;Don't forget to use the best image resolution also the next-generation format like Webp format.&lt;/p&gt;

&lt;p&gt;Firstly, we should convert all high-resolution images of our website to the WebP format. I found one of the best command line tools that name is &lt;a href="https://developers.google.com/speed/webp/docs/cwebp" rel="noopener noreferrer"&gt;CWebP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Secondly, we can convert it in all bulk! For this action, we need to install the &lt;code&gt;parallel&lt;/code&gt; package in our OS.&lt;/p&gt;

&lt;p&gt;For example, I convert all jpeg files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parallel -eta cwebp -q 20 -lossless {} -o {.}.webp ::: *.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upgrade Next.js version 12 to version 13
&lt;/h3&gt;

&lt;p&gt;In this part, we should change some parts of the code that were deprecated in the old version. Next.js has provided very well &lt;a href="https://nextjs.org/docs/upgrading" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for upgrading the old version to the new one. Every single native component in Next.js should refactor after upgrading.&lt;br&gt;
I mentioned some the important ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image&lt;/li&gt;
&lt;li&gt;Scripts (third-party scripts)&lt;/li&gt;
&lt;li&gt;Link&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  How to get the report in Lighthouse with the command line
&lt;/h3&gt;

&lt;p&gt;Firstly you should install the &lt;code&gt;lighthouse&lt;/code&gt; NPM package on your local machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g lighthouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly, you can separate desktop and mobile report&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command for desktop
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lighthouse [WEBSITE] --form-factor "desktop" --preset=desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Command for mobile
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lighthouse [WEBSITE] --form-factor "desktop" --preset=mobile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nextjs</category>
      <category>lighthouse</category>
      <category>react</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to pull the AWS ECR docker image and extract all things in the docker image?</title>
      <dc:creator>Pooya Golchian</dc:creator>
      <pubDate>Wed, 21 Apr 2021 17:34:37 +0000</pubDate>
      <link>https://dev.to/pooyagolchian/how-to-pull-the-aws-ecr-docker-image-and-extract-all-things-in-the-docker-image-4ioo</link>
      <guid>https://dev.to/pooyagolchian/how-to-pull-the-aws-ecr-docker-image-and-extract-all-things-in-the-docker-image-4ioo</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Maybe you faced the problem that you have been needed the old docker file in the ECR repository.&lt;br&gt;
I solved this problem with the docker command to pull and extract the docker image and access all I need.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pulling Docker image file
&lt;/h2&gt;

&lt;p&gt;You can use the below command to pull the docker image that you push in the ECR repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull YOUR_DOCKER_IMAGE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run your desire command in docker image
&lt;/h2&gt;

&lt;p&gt;You can run and execute a command in your docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/bash YOUR_DOCKER_IMAGE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/bash YOUR_DOCKER_IMAGE_URL &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extract Docker image and searching in the files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; docker image save YOUR_DOCKER_IMAGE_URL &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; img.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you extract the docker image and find your target files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvf&lt;/span&gt; img.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>ecs</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
