<?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: Sangyoon Lee</title>
    <description>The latest articles on DEV Community by Sangyoon Lee (@globalmatchhub_b7ad9cba).</description>
    <link>https://dev.to/globalmatchhub_b7ad9cba</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%2F4133533%2F792bf7a7-4835-4751-8944-848601fe60e3.png</url>
      <title>DEV Community: Sangyoon Lee</title>
      <link>https://dev.to/globalmatchhub_b7ad9cba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/globalmatchhub_b7ad9cba"/>
    <language>en</language>
    <item>
      <title>Figma plugin UI has a null origin: how I read a Google Sheet without a server or API key</title>
      <dc:creator>Sangyoon Lee</dc:creator>
      <pubDate>Thu, 15 Oct 2026 02:27:40 +0000</pubDate>
      <link>https://dev.to/globalmatchhub_b7ad9cba/figma-plugin-ui-has-a-null-origin-how-i-read-a-google-sheet-without-a-server-or-api-key-idf</link>
      <guid>https://dev.to/globalmatchhub_b7ad9cba/figma-plugin-ui-has-a-null-origin-how-i-read-a-google-sheet-without-a-server-or-api-key-idf</guid>
      <description>&lt;p&gt;I was building a Figma plugin that pulls rows from a public Google Sheet. It looked easy: fetch the CSV, parse it, done. It did not work, and the reason is easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A Figma plugin UI runs in an iframe with a null origin. The Figma docs say this means the plugin can only call APIs that return Access-Control-Allow-Origin: *.&lt;/p&gt;

&lt;p&gt;I built a sandboxed iframe with a null origin and tested each way to read the sheet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;docs.google.com gviz/tq?tqx=out:csv&lt;/td&gt;
&lt;td&gt;blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docs.google.com export?format=csv&lt;/td&gt;
&lt;td&gt;blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sheets.googleapis.com v4&lt;/td&gt;
&lt;td&gt;works, but needs an API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gviz/tq?tqx=out:json;responseHandler:cb loaded with a script tag&lt;/td&gt;
&lt;td&gt;works, no key, no server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why the export URL fails
&lt;/h2&gt;

&lt;p&gt;The final response of the export URL does have the allow header. But the first answer is a 307 redirect, and that redirect has no allow header. The browser checks the first one and stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;A script tag is not subject to CORS. So I ask Google for the JSON-with-callback format and load it as a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* data.table.rows */&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://docs.google.com/spreadsheets/d/&amp;lt;ID&amp;gt;/gviz/tq?tqx=out:json;responseHandler:cb&amp;amp;headers=0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add headers=0, otherwise Google guesses which row is the header and may eat your first data row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson
&lt;/h2&gt;

&lt;p&gt;Before writing any code that reads someone else's API from a browser extension or plugin, check with curl whether the API sends Access-Control-Allow-Origin: *. If there is a redirect, look at the headers of the first response, not the last one.&lt;/p&gt;

&lt;p&gt;The built-in browser pane of my tool could not run nested sandbox iframes, so I had to do this test in a real Chrome. Testing in the real environment saved me from shipping a plugin that fails on first load.&lt;/p&gt;

&lt;p&gt;Have you hit a null-origin problem in another plugin platform, and how did you get around it?&lt;/p&gt;




&lt;p&gt;I'm a full-stack developer, 13 years, based in Korea (UTC+9). I work in writing only, and use AI translation, so my English is simple. CV: &lt;a href="https://cv.namubase.com" rel="noopener noreferrer"&gt;https://cv.namubase.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>figma</category>
      <category>cors</category>
      <category>javascript</category>
      <category>googlesheets</category>
    </item>
    <item>
      <title>Available for remote contract work: 13-year full-stack dev (billing, admin, payments)</title>
      <dc:creator>Sangyoon Lee</dc:creator>
      <pubDate>Wed, 07 Oct 2026 00:10:56 +0000</pubDate>
      <link>https://dev.to/globalmatchhub_b7ad9cba/available-for-remote-contract-work-13-year-full-stack-dev-billing-admin-payments-3djf</link>
      <guid>https://dev.to/globalmatchhub_b7ad9cba/available-for-remote-contract-work-13-year-full-stack-dev-billing-admin-payments-3djf</guid>
      <description>&lt;p&gt;I'm Sangyoon Lee, a full-stack developer in Seoul with 13 years of experience, and I'm available for remote contract work starting now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note up front:&lt;/strong&gt; I don't speak English well. I communicate in writing and use AI translation for every message, so please expect written replies (within 24 hours, weekends included), not calls or video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I build and fix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin dashboards and back-office tools (members, orders, settlement, permissions, change history)&lt;/li&gt;
&lt;li&gt;Billing, settlement and payment integrations (PG, recurring payments, kiosk/card terminals)&lt;/li&gt;
&lt;li&gt;Backends for web and mobile apps, plus Flutter apps shipped to iOS and Android&lt;/li&gt;
&lt;li&gt;Stack: Laravel and CodeIgniter, Spring Boot, Node.js/TypeScript, React, Vue, MySQL/PostgreSQL/Redis, AWS, Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Things you can check&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I replaced a production app's backend (serverless to Spring Boot WebFlux) without forcing a user app update.&lt;/li&gt;
&lt;li&gt;I built a multi-channel settlement reconciliation engine (Java 17, idempotent reprocessing, rollback).&lt;/li&gt;
&lt;li&gt;I shipped three new services in two months as the only developer, all on schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How I work&lt;/strong&gt;&lt;br&gt;
Remote only. Scope and acceptance criteria are agreed in a document before I start, progress is written up every week, and I reply within 24 hours, weekends included. Everything stays in writing, so nothing gets lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of an interview, the first small task is free&lt;/strong&gt;&lt;br&gt;
We agree on the scope in writing first (up to about 10 hours of work), and I deliver within a week with a short written update every day. If you like the result, we continue on a paid basis. If not, you've lost nothing. One free task per company.&lt;/p&gt;

&lt;p&gt;If you have a backend, admin or integration task that keeps slipping, send me a short description and I'll reply with a plan.&lt;/p&gt;

&lt;p&gt;Portfolio: &lt;a href="https://cv.namubase.com" rel="noopener noreferrer"&gt;https://cv.namubase.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>freelance</category>
      <category>remote</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I ran my own SEO checker on my own sites. It called every Korean page "thin"</title>
      <dc:creator>Sangyoon Lee</dc:creator>
      <pubDate>Mon, 05 Oct 2026 07:57:54 +0000</pubDate>
      <link>https://dev.to/globalmatchhub_b7ad9cba/i-ran-my-own-seo-checker-on-my-own-sites-it-called-every-korean-page-thin-213b</link>
      <guid>https://dev.to/globalmatchhub_b7ad9cba/i-ran-my-own-seo-checker-on-my-own-sites-it-called-every-korean-page-thin-213b</guid>
      <description>&lt;p&gt;I built SearchLink, a small tool that checks a web page for SEO problems. A few weeks ago I ran it on my own 21 sites as a QA pass. All 21 answered with 200 OK, but the checker itself had three bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Korean pages were marked "not enough text".&lt;/strong&gt;&lt;br&gt;
It counted words by splitting on spaces. Korean, Chinese and Japanese don't work like that, so a full Korean page looked almost empty. Fix: for CJK text, count every 2 characters as one word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Title and description limits were English only.&lt;/strong&gt;&lt;br&gt;
Korean characters are wider, so they get cut off in search results earlier. I split the limits: 33 characters for titles and 85 for descriptions when the page is Korean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It nagged pages I hid on purpose.&lt;/strong&gt;&lt;br&gt;
If a page is noindex or blocked by robots.txt, the checker now just says "hidden on purpose" and stops scoring it.&lt;/p&gt;

&lt;p&gt;What I took from this: the best test data was my own real sites, in my own language. English samples would never have shown me bug #1. After the fixes, 110 tests on the paid build and 18 on the free build pass.&lt;/p&gt;

&lt;p&gt;If you build anything that reads text, try it on a language that doesn't use spaces. It is a cheap way to find assumptions you didn't know you had.&lt;/p&gt;




&lt;p&gt;About me: full-stack developer in South Korea, 13 years, open to remote contracts. I work fully in writing (async), so everything stays documented.&lt;br&gt;
CV: &lt;a href="https://cv.namubase.com" rel="noopener noreferrer"&gt;https://cv.namubase.com&lt;/a&gt;&lt;br&gt;
SearchLink: &lt;a href="https://searchlink.namubase.com" rel="noopener noreferrer"&gt;https://searchlink.namubase.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>testing</category>
      <category>webdev</category>
      <category>i18n</category>
    </item>
    <item>
      <title>I ship backends alone, in writing: 12 years, 6 products live right now</title>
      <dc:creator>Sangyoon Lee</dc:creator>
      <pubDate>Fri, 25 Sep 2026 00:38:29 +0000</pubDate>
      <link>https://dev.to/globalmatchhub_b7ad9cba/i-ship-backends-alone-in-writing-12-years-6-products-live-right-now-92p</link>
      <guid>https://dev.to/globalmatchhub_b7ad9cba/i-ship-backends-alone-in-writing-12-years-6-products-live-right-now-92p</guid>
      <description>&lt;p&gt;I am Lee Sangyoon, a full-stack engineer in South Korea. Twelve years, mostly in small teams where one person takes a feature from the spec to production and then answers for it afterwards. I am open to remote contract work and remote roles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One condition, stated first so nobody wastes time: I work in writing only.&lt;/strong&gt; Email, chat, issue trackers, pull requests. No calls, no video interviews. If that rules me out, it rules me out early, and that is fair to both sides. What I offer in exchange is a written trail of every decision, an answer within 24 hours, and a plan you can read before you pay for anything.&lt;/p&gt;

&lt;p&gt;And the written trail is the part I would keep even if calls were easy. Every request you make and every answer I give stays on the record. A change of scope and what it costs is written down the moment it is agreed, so it is not a memory contest three weeks later. You can search the whole history of the project, and hand it to whoever takes over from you. In my experience most disputes in contract work start with "but we said" about something nobody wrote down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work I was paid for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Children's news app (Flutter, iOS and Android), 2024 to 2026.&lt;/strong&gt; Shipped to both stores. Then rebuilt the backend from AWS Amplify to Spring Boot WebFlux while the app was live, as the only engineer. Because the API boundary was GraphQL, the app shipped no forced update during the migration. Recurring subscription billing through a payment gateway, Google and Apple sign-in, refresh-token auth. After launch: realtime ratings over GraphQL subscriptions, streak rewards, article translation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unmanned study cafe system, 2023.&lt;/strong&gt; The whole thing: web seat booking and passes, an Electron kiosk in the store, an admin for both, card payment with recurring billing, and a door controller wired to pass state so access reflects payment in realtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Healthcare platform, 2020 to 2023.&lt;/strong&gt; Rebuilt a hospital, patient and operator platform on Laravel 8 API plus React. Three role boundaries and SSO, API documented in Swagger so the app team could work in parallel, AWS setup and deploys run by me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Realtime chat app, 2019 to 2020.&lt;/strong&gt; Flutter and Firebase, FCM push, SMS-verified signup, in-app purchase with server-side receipt validation. Scaled from one server to four when traffic outgrew the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before that, 2014 to 2019.&lt;/strong&gt; A gold trading platform and its app, realtime chat and browser games, an eCRF solution for academic conferences, public sector sites, and a lot of commerce builds. PHP, jQuery and the habit of running my own servers came from here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side projects, all live, all built and operated solo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SearchLink&lt;/strong&gt; (&lt;a href="https://searchlink.namubase.com" rel="noopener noreferrer"&gt;https://searchlink.namubase.com&lt;/a&gt;). Connects Google Search Console to Claude and ChatGPT over MCP, mails a weekly report with three things to fix, and alerts when clicks drop. Cloudflare Workers, D1, OAuth, billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public auction price statistics site.&lt;/strong&gt; A daily cron pulls a government open API and generates about 350 statistics pages. Workers plus D1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlassian Marketplace apps.&lt;/strong&gt; A Jira markdown editor and dashboard gadgets, through Atlassian's security review and publicly installable. Forge and React.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketplace extensions&lt;/strong&gt; published on WordPress.org, JetBrains, VS Code, Figma and Wix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source.&lt;/strong&gt; An MCP server for Search Console on npm, listed in several MCP directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;TypeScript and Node, Python, PHP and Laravel, Java and Spring Boot (WebFlux), React, Next.js, Vue, Flutter, React Native, Electron. PostgreSQL, MySQL, Redis, SQLite, D1. AWS, Cloudflare Workers, Docker, Nginx, Linux. GraphQL and REST. Payment gateways, in-app purchase receipt validation, social login, FCM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am looking for
&lt;/h2&gt;

&lt;p&gt;Remote contract work or a remote role, backend or full-stack, with written communication. Data pipelines, API integrations, LLM-backed tooling and "this thing must keep running unattended" work are what I enjoy most and what I am fastest at.&lt;/p&gt;

&lt;p&gt;Reach me at &lt;a href="mailto:globalmatchhub@gmail.com"&gt;globalmatchhub@gmail.com&lt;/a&gt;. Tell me what is broken or what you want built, and you will get a written plan back, not a meeting invite.&lt;/p&gt;

</description>
      <category>career</category>
      <category>remote</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why your Search Console API impressions don't match the UI</title>
      <dc:creator>Sangyoon Lee</dc:creator>
      <pubDate>Mon, 21 Sep 2026 04:38:18 +0000</pubDate>
      <link>https://dev.to/globalmatchhub_b7ad9cba/why-your-search-console-api-impressions-dont-match-the-ui-2cef</link>
      <guid>https://dev.to/globalmatchhub_b7ad9cba/why-your-search-console-api-impressions-dont-match-the-ui-2cef</guid>
      <description>&lt;p&gt;I was building a small tool that pulls Google Search Console data into Claude, and the first thing that broke was trust. My impressions total was higher than the number in the Search Console UI. Not by a rounding error. Noticeably higher.&lt;/p&gt;

&lt;p&gt;Here is what was going on, in case you hit the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake: summing page rows
&lt;/h2&gt;

&lt;p&gt;The obvious way to get totals is to query by &lt;code&gt;page&lt;/code&gt;, then add up clicks and impressions across all rows.&lt;/p&gt;

&lt;p&gt;Clicks come out close. Impressions do not.&lt;/p&gt;

&lt;p&gt;The reason: Search Console counts impressions differently depending on how you group the data. Grouped by property, one results page counts once. Grouped by page, every one of your URLs on that results page gets its own impression. If two of your pages show up for the same search, that is one impression for the site but two when you add up the page rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: take totals from the date dimension
&lt;/h2&gt;

&lt;p&gt;If you want numbers that match the UI, query with &lt;code&gt;dimensions: ["date"]&lt;/code&gt; and sum those rows instead. Each day is counted once at the property level, so impressions line up with what Search Console shows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use page or query dimensions for breakdowns ("which pages lost clicks"), and the date dimension for the headline totals. Don't mix the two in the same number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two smaller gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data lag.&lt;/strong&gt; The most recent two or three days are usually incomplete. If you compare "last 7 days" to "the 7 before", end both ranges a few days back, or the latest week always looks worse than it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average position is not additive.&lt;/strong&gt; You can't take a plain average of the position column across rows. Weight it by impressions, or take it from the same date-level query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I ended up building
&lt;/h2&gt;

&lt;p&gt;I wrapped this into a small MCP server so I can ask Claude or Cursor things like "which pages lost clicks this month" without exporting a CSV first. It is read-only, runs locally, and takes totals from the date dimension.&lt;/p&gt;

&lt;p&gt;Disclosure: I built it. It's free and MIT: &lt;a href="https://go.namubase.com/slite?s=devto-0921" rel="noopener noreferrer"&gt;https://go.namubase.com/slite?s=devto-0921&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've found other places where the API and the UI disagree, I'd like to hear them.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>api</category>
      <category>webdev</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
