<?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: Moksh Gupta</title>
    <description>The latest articles on DEV Community by Moksh Gupta (@moksh).</description>
    <link>https://dev.to/moksh</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%2F2457679%2F32485ee6-614e-4050-bd8e-e22536e1f2b5.png</url>
      <title>DEV Community: Moksh Gupta</title>
      <link>https://dev.to/moksh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moksh"/>
    <language>en</language>
    <item>
      <title>How to Get Your Developer Site Cited by AI Search Engines in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sat, 04 Jul 2026 13:59:23 +0000</pubDate>
      <link>https://dev.to/moksh/how-to-get-your-developer-site-cited-by-ai-search-engines-in-2026-12ga</link>
      <guid>https://dev.to/moksh/how-to-get-your-developer-site-cited-by-ai-search-engines-in-2026-12ga</guid>
      <description>&lt;p&gt;AI search engines - ChatGPT, Perplexity, Claude, and Google AI Overviews - now handle roughly 12 to 18 percent of English-language informational queries as of early 2026. That share was below 2 percent a year ago. Traditional search still dominates overall traffic volume, but AI-driven visits convert at higher rates and follow entirely different rules.&lt;/p&gt;

&lt;p&gt;This discipline has a name: Generative Engine Optimization (GEO). Unlike conventional SEO, which targets ranked positions on a results page, GEO focuses on getting your content included in synthesized AI answers. The clearest, most structured response to a specific question wins - not necessarily the highest-authority domain.&lt;/p&gt;

&lt;p&gt;Here are the four technical layers every developer needs to address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1 - Controlling AI Crawl Access via robots.txt
&lt;/h2&gt;

&lt;p&gt;Before anything else, make sure the right crawlers can reach your content. There is a critical distinction most tutorials skip: retrieval crawlers and training crawlers are separate agents with different purposes.&lt;/p&gt;

&lt;p&gt;Retrieval crawlers power real-time query responses and drive citation traffic. These include &lt;code&gt;OAI-SearchBot&lt;/code&gt; (ChatGPT), &lt;code&gt;Claude-Web&lt;/code&gt; (Claude.ai), &lt;code&gt;PerplexityBot&lt;/code&gt;, and &lt;code&gt;Google-Extended&lt;/code&gt;. You want these allowed.&lt;/p&gt;

&lt;p&gt;Training crawlers - such as &lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, and &lt;code&gt;CCBot&lt;/code&gt; - scrape content to build future model weights. They do not generate citations. Blocking them is optional but widely done. Note: &lt;code&gt;ClaudeBot&lt;/code&gt; is Anthropic's training scraper, separate from &lt;code&gt;Claude-Web&lt;/code&gt; which handles live retrieval. Blocking one does not block the other.&lt;/p&gt;

&lt;p&gt;Also block &lt;code&gt;Bytespider&lt;/code&gt; at both the robots.txt level and your CDN - it has a history of ignoring disallow rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2 - Publishing an llms.txt File
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a plain Markdown file placed at your domain root. It gives AI agents a curated index of your most important pages, cutting through the navigation menus, cookie banners, and ad scripts that clutter normal HTML pages.&lt;/p&gt;

&lt;p&gt;By mid-2026, companies like Stripe, Vercel, Cloudflare, and Anthropic all publish one. Cursor and similar AI coding tools actively read it when answering questions about developer products.&lt;/p&gt;

&lt;p&gt;Keep it focused - 20 to 50 priority links with plain-language descriptions. Avoid dumping your full sitemap. Write each link description to answer the question "what will someone learn here?" not to stuff keywords. If your site is documentation-heavy, consider also publishing &lt;code&gt;llms-full.txt&lt;/code&gt; - a single complete Markdown export of your key pages so agents can answer detailed questions without fetching each page separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3 - Structured Data and JSON-LD
&lt;/h2&gt;

&lt;p&gt;This is the highest-return investment in GEO. JSON-LD structured data tells AI systems exactly what your content is and which questions it answers. FAQPage schema alone correlates with citation rates more than three times higher than the same content written as plain prose.&lt;/p&gt;

&lt;p&gt;Add Organization schema to your root layout. It establishes brand identity across your entire domain and links your site to verified social profiles via the &lt;code&gt;sameAs&lt;/code&gt; property - helping AI systems reconcile brand mentions across different sources.&lt;/p&gt;

&lt;p&gt;FAQPage schema is the most cited schema type in AI search. Each question-answer pair becomes a standalone citation candidate. Write answers as complete, self-contained sentences - the AI extracts just the answer text without surrounding context.&lt;/p&gt;

&lt;p&gt;Also add Article schema to every blog post and keep &lt;code&gt;dateModified&lt;/code&gt; current. Perplexity treats freshness as a top-tier ranking signal. A stale date can suppress citations even on accurate, high-quality content.&lt;/p&gt;

&lt;p&gt;Note: Google deprecated FAQ rich results in standard search as of May 2026, but FAQPage schema remains highly effective specifically for AI citation engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4 - Content Structure for AI Extraction
&lt;/h2&gt;

&lt;p&gt;Technical configuration gets AI crawlers to your pages. Content structure determines whether they actually cite you.&lt;/p&gt;

&lt;p&gt;Start every important page with a direct 40 to 60 word answer to the primary question it addresses. Both ChatGPT and Perplexity prioritize content where the answer leads.&lt;/p&gt;

&lt;p&gt;Publish original data when you can. Proprietary benchmarks, survey findings, and real measurements are among the most-cited content types across AI engines. Format information for extraction: tables, numbered steps, and code blocks parse reliably. Pricing in a table gets cited more often than the same pricing buried in paragraphs.&lt;/p&gt;

&lt;p&gt;Cite your sources. AI systems cross-reference content and show a measurable trust preference for pages that link to primary sources like research papers and official documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and Validating Your Setup
&lt;/h2&gt;

&lt;p&gt;The most direct test is manual: search for questions your content should answer in ChatGPT, Perplexity, and Claude, and check whether your domain appears in the citations.&lt;/p&gt;

&lt;p&gt;Monitor AI referral traffic in GA4 by filtering for referral sources matching &lt;code&gt;chatgpt.com&lt;/code&gt;, &lt;code&gt;perplexity.ai&lt;/code&gt;, and &lt;code&gt;claude.ai&lt;/code&gt;. Validate your structured data at &lt;code&gt;validator.schema.org&lt;/code&gt; - a single malformed JSON-LD block fails silently with no visible page error. Confirm your &lt;code&gt;llms.txt&lt;/code&gt; returns plain Markdown at &lt;code&gt;yourdomain.com/llms.txt&lt;/code&gt;, not HTML or a redirect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Update &lt;code&gt;robots.txt&lt;/code&gt; - allow retrieval agents, decide on training crawlers, block Bytespider.&lt;/li&gt;
&lt;li&gt;Publish &lt;code&gt;llms.txt&lt;/code&gt; - write a precise one-paragraph brand summary and curate 20 to 40 priority links.&lt;/li&gt;
&lt;li&gt;Add Organization schema to your root layout and validate it.&lt;/li&gt;
&lt;li&gt;Add Article schema to all blog posts and docs - keep &lt;code&gt;dateModified&lt;/code&gt; current.&lt;/li&gt;
&lt;li&gt;Add FAQPage schema to any page with genuine question-and-answer content.&lt;/li&gt;
&lt;li&gt;Audit top pages - ensure the primary answer appears within the first 100 words.&lt;/li&gt;
&lt;li&gt;Test - validate schema, verify &lt;code&gt;llms.txt&lt;/code&gt; accessibility, query AI engines directly, monitor referral traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;GEO is not a replacement for SEO - it is an additional layer that takes a few hours to implement and pays off in a fast-growing channel. robots.txt updates take 15 minutes. Publishing &lt;code&gt;llms.txt&lt;/code&gt; takes an hour. Adding Organization schema is a one-time change that propagates automatically.&lt;/p&gt;

&lt;p&gt;The harder and more valuable work is content quality: writing direct answers, publishing original data, and keeping dates fresh. That work compounds across both traditional search and AI citations at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/generative-engine-optimization-guide" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/generative-engine-optimization-guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agent Readiness Scanner: &lt;a href="https://devtoollab.com/tools/agent-readiness-scanner" rel="noopener noreferrer"&gt;https://devtoollab.com/tools/agent-readiness-scanner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LLMs.txt Generator: &lt;a href="https://devtoollab.com/tools/llms-txt-generator" rel="noopener noreferrer"&gt;https://devtoollab.com/tools/llms-txt-generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Schema Markup Generator: &lt;a href="https://devtoollab.com/tools/schema-markup-generator" rel="noopener noreferrer"&gt;https://devtoollab.com/tools/schema-markup-generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Robots.txt Generator: &lt;a href="https://devtoollab.com/tools/robots-txt-generator" rel="noopener noreferrer"&gt;https://devtoollab.com/tools/robots-txt-generator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>API Testing Tools Worth Knowing in 2026 - Alternatives to Postman</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Mon, 29 Jun 2026 18:05:19 +0000</pubDate>
      <link>https://dev.to/moksh/api-testing-tools-worth-knowing-in-2026-alternatives-to-postman-1b5h</link>
      <guid>https://dev.to/moksh/api-testing-tools-worth-knowing-in-2026-alternatives-to-postman-1b5h</guid>
      <description>&lt;p&gt;On March 1, 2026, Postman dropped free team collaboration. The Free plan now supports only a single user - any second teammate accessing a shared workspace breaks the plan. That change sent developers searching for alternatives, and Bruno's GitHub stars jumped from 30,000 to over 41,000 in about three months. If you have 200 saved requests to migrate and need a straight answer, this article is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Schools of API Clients
&lt;/h2&gt;

&lt;p&gt;The API client market split into two camps around 2023, and that divide has only grown wider.&lt;/p&gt;

&lt;p&gt;Cloud-first tools like Postman and Insomnia (cloud sync mode) store your collections on their servers. Collaboration is built in, but your API tests live outside your codebase in a separate system with its own permissions model.&lt;/p&gt;

&lt;p&gt;File-first tools like Bruno and Thunder Client store everything locally as plain files. Your request collections live alongside your code in Git, get reviewed in pull requests, and travel with the repo. The tradeoff is that real-time collaboration requires extra setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman
&lt;/h2&gt;

&lt;p&gt;Postman remains the most full-featured API client available. It covers mock servers, automated test scripts, monitoring, documentation generation, and team governance - all in one interface.&lt;/p&gt;

&lt;p&gt;The pricing shift is the main issue for smaller teams. The Free plan is now solo-only. The Solo plan is $9/month, and Team plans start at $19 per user per month. If your team needs shared collections with governance and monitoring built in, Postman still justifies the cost. For individual developers or small teams doing basic request testing, it is likely overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bruno
&lt;/h2&gt;

&lt;p&gt;Bruno is the standout alternative for teams that care about Git workflows. Requests are stored as &lt;code&gt;.bru&lt;/code&gt; files on disk, meaning your entire API test suite lives in your repository and gets versioned with your code. No cloud account required, no sync dependencies.&lt;/p&gt;

&lt;p&gt;The free open-source tier is unlimited for individuals. Pro plans are $6/user/month. Bruno lacks built-in monitoring and mocking, but for backend teams that already have CI pipelines, it fits naturally into existing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Insomnia
&lt;/h2&gt;

&lt;p&gt;Insomnia offers the most flexible storage model of any tool in this category. You can choose between local-only storage, Git sync, or cloud sync depending on your team's needs - and you can switch between them.&lt;/p&gt;

&lt;p&gt;Its strongest differentiator is GraphQL support. Insomnia handles schema introspection and provides autocompletion for GraphQL queries in a way that other tools do not match. The free Essentials tier covers most individual needs. Pro starts at $12/user/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hoppscotch
&lt;/h2&gt;

&lt;p&gt;Hoppscotch is browser-based and fully open source, making it the preferred choice for teams with on-premise or compliance requirements. You can self-host the entire platform via Docker in under ten minutes and keep all API traffic within your own infrastructure.&lt;/p&gt;

&lt;p&gt;For regulated industries such as finance or healthcare, where sending test requests through a third-party cloud is not acceptable, Hoppscotch is often the only practical option. The hosted version is free with unlimited users. The enterprise self-hosted plan is $45/user/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thunder Client
&lt;/h2&gt;

&lt;p&gt;Thunder Client is a VS Code extension, which means it lives directly inside your editor. There is nothing extra to install or open. Collections are stored as JSON files in your project, so they can be checked into Git.&lt;/p&gt;

&lt;p&gt;It is intentionally lightweight - it does not try to replicate Postman's full feature set. For developers who do most of their work in VS Code and want a fast, no-friction way to test endpoints without switching windows, it is an excellent fit. Free for individual use; Team plan is $10/user/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requestly
&lt;/h2&gt;

&lt;p&gt;Requestly is different from the other tools in this list. It is an HTTP interceptor rather than a request builder. It runs as a browser extension or desktop app and intercepts outgoing requests in real time.&lt;/p&gt;

&lt;p&gt;This makes it useful for mocking API responses, redirecting endpoints to staging or local servers, and testing error states without touching production code or deploying anything. Free for up to 10 collaborators; Pro is $9/user/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose
&lt;/h2&gt;

&lt;p&gt;The right tool depends on your team's specific constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git-native workflows:&lt;/strong&gt; Bruno is the best option. Your API collections live in the repo and behave like code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL:&lt;/strong&gt; Insomnia provides the best GraphQL developer experience with schema introspection and autocompletion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-premise or compliance requirements:&lt;/strong&gt; Hoppscotch self-hosted keeps all traffic inside your infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code-first developers:&lt;/strong&gt; Thunder Client integrates directly into the editor with no context switching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise governance, mocking, and monitoring:&lt;/strong&gt; Postman remains the most complete solution if the cost is justifiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request interception and mocking:&lt;/strong&gt; Requestly fills a specific niche that the others do not cover.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Postman's pricing change forced a useful reexamination of what teams actually need from an API client. Most teams do not need monitoring, mock servers, and documentation in a single tool. For those teams, Bruno or Insomnia offers a better fit at a much lower cost.&lt;/p&gt;

&lt;p&gt;The best approach is to trial the tool that matches your primary workflow before migrating your full collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/best-api-testing-tools" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/best-api-testing-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bruno official site: &lt;a href="https://www.usebruno.com" rel="noopener noreferrer"&gt;https://www.usebruno.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Insomnia docs: &lt;a href="https://docs.insomnia.rest" rel="noopener noreferrer"&gt;https://docs.insomnia.rest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hoppscotch: &lt;a href="https://hoppscotch.io" rel="noopener noreferrer"&gt;https://hoppscotch.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Thunder Client: &lt;a href="https://www.thunderclient.com" rel="noopener noreferrer"&gt;https://www.thunderclient.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Requestly: &lt;a href="https://requestly.com" rel="noopener noreferrer"&gt;https://requestly.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why uv Became the Go-To Python Package Manager in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:09:17 +0000</pubDate>
      <link>https://dev.to/moksh/why-uv-became-the-go-to-python-package-manager-in-2026-2kag</link>
      <guid>https://dev.to/moksh/why-uv-became-the-go-to-python-package-manager-in-2026-2kag</guid>
      <description>&lt;p&gt;Installing 23 packages from a warm cache takes pip about 6.6 seconds, while &lt;strong&gt;uv&lt;/strong&gt; handles the same task in just 0.12 seconds. On larger projects involving Django, Celery, Pandas, and scikit-learn, pip needs around 90 seconds, whereas uv finishes in roughly 8 seconds.&lt;/p&gt;

&lt;p&gt;uv is a single binary that consolidates five separate tools into one: pip, pip-tools, virtualenv, pyenv, and pipx. In March 2026, OpenAI acquired Astral, the company behind uv, to bring it into their Codex AI platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tools Does uv Replace?
&lt;/h2&gt;

&lt;p&gt;The traditional Python project setup required juggling five different tools, each with its own configuration format. uv streamlines this into a single, unified workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pyenv install&lt;/code&gt; becomes &lt;code&gt;uv python install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python -m venv&lt;/code&gt; becomes &lt;code&gt;uv venv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip install&lt;/code&gt; becomes &lt;code&gt;uv add&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip-compile&lt;/code&gt; becomes &lt;code&gt;uv lock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pipx install&lt;/code&gt; becomes &lt;code&gt;uv tool install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started with uv
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh

&lt;span class="c"&gt;# Windows&lt;/span&gt;
powershell &lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt; ByPass &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"irm https://astral.sh/uv/install.ps1 | iex"&lt;/span&gt;

&lt;span class="c"&gt;# Self-update&lt;/span&gt;
uv self update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Core Workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;New Project:&lt;/strong&gt; Running &lt;code&gt;uv init&lt;/code&gt; automatically updates &lt;code&gt;pyproject.toml&lt;/code&gt;, regenerates &lt;code&gt;uv.lock&lt;/code&gt;, and installs your dependencies. The &lt;code&gt;uv run&lt;/code&gt; command handles execution, so you no longer need to manually activate virtual environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python Version Management:&lt;/strong&gt; uv can install multiple Python versions side by side (for example, &lt;code&gt;uv python install 3.11 3.12 3.13&lt;/code&gt;) and automatically respects existing &lt;code&gt;.python-version&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global CLI Tools:&lt;/strong&gt; uv replaces &lt;code&gt;pipx&lt;/code&gt; for running standalone CLI utilities like &lt;code&gt;ruff&lt;/code&gt; or &lt;code&gt;cowsay&lt;/code&gt; without polluting your global environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;On a benchmark with a 200-package lockfile, uv completes the full resolve and install cycle in 1.5 seconds total (0.4s for resolving, 1.1s for installing). By comparison, pip takes 20.5 seconds and Poetry takes 16.0 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to uv
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From pip:&lt;/strong&gt; &lt;code&gt;uv pip&lt;/code&gt; works as a drop-in replacement, supporting all standard pip flags so you can transition without changing your existing commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From Poetry:&lt;/strong&gt; The &lt;code&gt;migrate-to-uv&lt;/code&gt; utility converts your &lt;code&gt;[tool.poetry]&lt;/code&gt; sections to the standard &lt;code&gt;[project]&lt;/code&gt; format, making migration straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From pyenv:&lt;/strong&gt; No changes are needed. uv reads your existing &lt;code&gt;.python-version&lt;/code&gt; files directly, so the switch is seamless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using uv in CI/CD
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install uv&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;astral-sh/setup-uv@v5&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latest"&lt;/span&gt;
    &lt;span class="na"&gt;enable-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uv sync --frozen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--frozen&lt;/code&gt; flag ensures that dependencies match exactly what is in &lt;code&gt;uv.lock&lt;/code&gt;, and &lt;code&gt;enable-cache: true&lt;/code&gt; ensures that subsequent CI builds finish in seconds rather than minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conda:&lt;/strong&gt; uv does not replace Conda if your workflow depends on non-Python dependencies such as CUDA libraries or C extensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugins:&lt;/strong&gt; There is currently no plugin system available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy support:&lt;/strong&gt; Some &lt;code&gt;setup.py&lt;/code&gt;-based packages may require &lt;code&gt;uv pip install --no-build-isolation&lt;/code&gt; to build correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;uv replaces five separate tools with a single binary that runs 10 to 100 times faster. With backing from OpenAI and over 45,000 GitHub stars, it has become the go-to standard for Python dependency management. If you are still running &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; by hand, it is time to make the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source article: &lt;a href="https://devtoollab.com/blog/uv-python-package-manager" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/uv-python-package-manager&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uv documentation: &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;https://docs.astral.sh/uv/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Astral (uv) GitHub: &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;https://github.com/astral-sh/uv&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>devtools</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Text to Morse Code - How Optical Signals Carry Information</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Thu, 25 Jun 2026 17:47:08 +0000</pubDate>
      <link>https://dev.to/moksh/from-text-to-morse-code-how-optical-signals-carry-information-1k1c</link>
      <guid>https://dev.to/moksh/from-text-to-morse-code-how-optical-signals-carry-information-1k1c</guid>
      <description>&lt;p&gt;Morse code is one of the oldest encoding systems still worth understanding today. It predates modern networking by over a century, yet it maps directly onto the same core ideas - binary states, serialization, and signal transmission. Whether you are studying communication protocols or just curious about how a flashlight can send a message across a valley, the mechanics behind this system are worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Morse Code Encoding
&lt;/h2&gt;

&lt;p&gt;At its core, encoding is the process of transforming data from one representation into another using a defined rule set. In Morse code, every letter and number gets replaced by a sequence of dots and dashes - short and long signals. A dash is precisely three times the duration of a dot, and this fixed ratio is what makes the system portable across different transmission mediums.&lt;/p&gt;

&lt;p&gt;The international standard ensures consistency. Whether you are transmitting via radio waves or a flashlight, the character mapping stays identical. That universality is what kept this protocol alive long past the telegraph era.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Text Gets Converted Into Morse Code
&lt;/h2&gt;

&lt;p&gt;Conversion relies on a character lookup table engineered around letter frequency. The most common letters in English get the shortest sequences - "E" is just a single dot - while less frequent characters carry longer chains. Numbers always use five-element sequences, and punctuation requires even longer patterns to avoid collision with alphanumeric codes.&lt;/p&gt;

&lt;p&gt;For developers, automating this is straightforward. A script takes an input string, normalizes it to uppercase, filters out unsupported characters, and replaces each character with its corresponding dot-dash sequence from a key-value map. For quick validation during development, an &lt;a href="https://devtoollab.com/tools/morse-code-translator" rel="noopener noreferrer"&gt;online Morse code translator&lt;/a&gt; lets you verify character mappings instantly without writing any code.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;morseMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-.-.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;morseMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;h2&gt;
  
  
  Decoding Morse Code - Reading Patterns in Real Time
&lt;/h2&gt;

&lt;p&gt;Decoding is the reverse operation, but it adds a variable that encoding does not have to deal with: time. The system depends on precise spacing between elements. A dot lasts one time unit; a dash lasts three. The gap between elements within a letter is one unit, between letters is three units, and between words is seven units. Collapse those gaps and the message becomes noise.&lt;/p&gt;

&lt;p&gt;Human error and signal interference introduce the biggest decoding challenges. A dash shortened by atmospheric haze or a hesitant finger can turn one letter into two. Similar-looking sequences - like "A" (dot-dash) being mistaken for "E" and "T" separately - are common failure modes when timing drifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transmission Methods - Sound, Radio, and Light
&lt;/h2&gt;

&lt;p&gt;One of Morse code's most practical qualities is that the transmission layer does not matter. The encoding stays the same whether the medium is audio, radio, or optical.&lt;/p&gt;

&lt;p&gt;Sound-based transmission uses audible tones, typically around 700 Hz. Operators develop muscle memory for patterns rather than consciously counting elements. Radio transmission - specifically Continuous Wave (CW) - drives a carrier wave on and off, cutting through heavy static that would render voice communications unintelligible. Visual transmission replaces tone duration with light flash duration, using anything from naval signal lanterns to basic LED indicators on microcontroller boards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Light to Transmit Morse Code
&lt;/h2&gt;

&lt;p&gt;Optical signaling is especially useful when radio silence is required or no electronic infrastructure is available. A short flash maps to a dot; a long flash maps to a dash. The catch is physical consistency - if a short flash is 250ms, every long flash must hold at 750ms. Any deviation creates translation errors for the person watching on the receiving end.&lt;/p&gt;

&lt;p&gt;Flashlights, signal lanterns, and even onboard LEDs can all serve as transmission devices. For developers building embedded systems, blinking an LED in Morse patterns is a practical way to output debug information or status codes without requiring a serial console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications Today
&lt;/h2&gt;

&lt;p&gt;The SOS signal (three dots, three dashes, three dots) remains the globally recognized distress call - transmittable by anyone with a light source or a whistle, with no shared language required. Amateur radio operators maintain active CW networks worldwide, providing a decentralized communication grid that runs independently of internet infrastructure.&lt;/p&gt;

&lt;p&gt;For computer science and electrical engineering students, Morse code is an ideal entry point into serialization, error correction, and timing-based protocols. It strips away the complexity of modern networking stacks and exposes the raw logic underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Learning This System Pays Off
&lt;/h2&gt;

&lt;p&gt;Working through Morse code trains real-time pattern recognition - the ability to process incoming data streams and decode them into meaning under time pressure. It also builds an appreciation for information density. When every character costs physical effort to transmit, you naturally learn to communicate with precision rather than verbosity.&lt;/p&gt;

&lt;p&gt;More broadly, the system connects historical communication design to modern binary logic. The fundamental principles - encoding, latency, bandwidth, signal-to-noise ratio - have not changed significantly since the telegraph. Studying this legacy protocol gives you a cleaner mental model of how all communication systems actually work at their lowest layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source article: &lt;a href="https://devtoollab.com/blog/text-to-morse-code-optical-signals" rel="noopener noreferrer"&gt;Encoding Text to Morse Code: A Guide to Optical Signals - DevToolLab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevToolLab Morse Code Translator: &lt;a href="https://devtoollab.com/tools/morse-code-translator" rel="noopener noreferrer"&gt;https://devtoollab.com/tools/morse-code-translator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flashlight Morse code signaling guide: &lt;a href="https://morsetranslator.net/resource/modern-uses/send-morse-code-with-flashlight" rel="noopener noreferrer"&gt;https://morsetranslator.net/resource/modern-uses/send-morse-code-with-flashlight&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MorseTranslator.net: &lt;a href="https://morsetranslator.net" rel="noopener noreferrer"&gt;https://morsetranslator.net&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>morsecode</category>
      <category>webdev</category>
      <category>programming</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Top Open Source Coding Agents to Replace Claude Code in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Thu, 25 Jun 2026 03:07:34 +0000</pubDate>
      <link>https://dev.to/moksh/top-open-source-coding-agents-to-replace-claude-code-in-2026-10f7</link>
      <guid>https://dev.to/moksh/top-open-source-coding-agents-to-replace-claude-code-in-2026-10f7</guid>
      <description>&lt;p&gt;Claude Code is a genuinely powerful CLI coding agent. Its context window handling and multi-file reasoning set a high bar in 2026. But it comes with real constraints - it requires an Anthropic API key, charges per token, locks you into Claude models only, and its source code is closed. For developers running local-first workflows, working in air-gapped environments, or simply preferring auditable tooling, those limitations are dealbreakers.&lt;/p&gt;

&lt;p&gt;The good news: the open-source ecosystem has matured significantly. Nine production-ready alternatives now cover every major workflow pattern - from terminal-first pair programming to fully autonomous task execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Source Matters for AI Coding Agents
&lt;/h2&gt;

&lt;p&gt;AI coding agents operate at a high level of system trust. They write files, run commands, and modify your repository. That makes transparency genuinely important - not just philosophically. Open-source licensing lets you read the code, audit its behavior, self-host without sending data to a third party, and customize it for your team's needs.&lt;/p&gt;

&lt;p&gt;Beyond trust, the practical advantages are real. Open-source agents are model-agnostic by design. They connect to whichever LLM you prefer - Claude, GPT, Gemini, DeepSeek, or a local model via Ollama - letting you optimize for cost and capability on a per-task basis rather than being locked to one pricing tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenCode - The Closest Open-Source Drop-In for Claude Code
&lt;/h2&gt;

&lt;p&gt;OpenCode has emerged as the de facto open-source answer to Claude Code in 2026, crossing 161,000 GitHub stars under an MIT license. It connects to over 75 LLM providers via Models.dev - including local Ollama models - and lets you switch providers mid-session.&lt;/p&gt;

&lt;p&gt;Internally it uses a dual-agent architecture: a Plan agent handles task decomposition while a Build agent executes changes. LSP integration brings symbol resolution into the terminal. Multi-session support lets you run parallel agents on the same project simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI Codex CLI - Auditable and Sandbox-First
&lt;/h2&gt;

&lt;p&gt;OpenAI Codex CLI is built primarily in Rust under Apache 2.0, with 74,000+ GitHub stars and 14 million npm downloads in the last 30 days. Its defining characteristic is sandboxed execution - agent-suggested code runs in an isolated environment before touching your actual filesystem.&lt;/p&gt;

&lt;p&gt;In February 2026, OpenAI added native GitHub integration for working directly with repos, issues, and pull requests. It also supports MCP extensions, web search, and subagent parallelism. Note: an OpenAI API key is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenHands - Fully Autonomous Engineering Tasks
&lt;/h2&gt;

&lt;p&gt;OpenHands (formerly OpenDevin) takes the most autonomous approach of any tool on this list. You describe an outcome, and the agent plans and executes the entire sequence of operations without step-by-step guidance. At 74,400+ GitHub stars and version 1.7.0, it is the most-starred fully autonomous coding agent framework available.&lt;/p&gt;

&lt;p&gt;The agent runs inside a Docker sandbox with access to a full dev environment - editing files, running commands, browsing documentation, interacting with external services, and submitting pull requests. The OpenHands Index, launched in January 2026, provides a continuously updated model leaderboard across five real engineering task categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cline - IDE-Native with Plan and Act Separation
&lt;/h2&gt;

&lt;p&gt;Cline is the most widely installed open-source AI coding agent, with 61,000+ GitHub stars and 8 million+ active developers. It runs as a sidebar agent in VS Code, JetBrains, Cursor, Windsurf, Zed, and Neovim, and now ships a full CLI as well.&lt;/p&gt;

&lt;p&gt;Its core workflow separates Plan Mode (think and plan) from Act Mode (execute). This two-phase approach gives you meaningful oversight without micromanaging every edit. A Kanban board view surfaces all running agent tasks at a glance. Per-session token counting keeps cost visibility front and center.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aider - Git-Native Pair Programming
&lt;/h2&gt;

&lt;p&gt;Aider pioneered the Git-native terminal pair programming category with over 4.1 million installations and 42,000+ GitHub stars. Every change it makes is automatically committed with a descriptive message, creating a clean, reviewable history. If a change breaks something, a simple &lt;code&gt;git revert&lt;/code&gt; gets you back immediately.&lt;/p&gt;

&lt;p&gt;Aider's internal tree-sitter repomap builds a compressed symbol map of your repository, letting it identify relevant files across 100+ languages without loading the entire codebase into context. It also runs your linter and test suite automatically after each change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goose - General-Purpose Agent Under Linux Foundation Governance
&lt;/h2&gt;

&lt;p&gt;Goose was built internally at Block (Jack Dorsey's fintech company) and open-sourced in 2025. In December 2025, Block donated it to the Agentic AI Foundation (AAIF) under the Linux Foundation - alongside Anthropic's MCP and OpenAI's AGENTS.md spec. This foundation-governed structure gives Goose vendor-neutral oversight that most open-source AI tools lack.&lt;/p&gt;

&lt;p&gt;Built in Rust, Goose connects to 70+ MCP extensions (databases, GitHub, Slack, cloud APIs, browser automation) and supports 15+ LLM providers. If your work extends beyond code editing into system automation, data tasks, or infrastructure operations, Goose has the widest surface area of any tool on this list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qwen Code - First-Class Harness for Qwen3-Coder
&lt;/h2&gt;

&lt;p&gt;Qwen Code is Alibaba's open-source terminal agent, optimized for the Qwen3-Coder model series under Apache 2.0. It mirrors the Claude Code interaction model while remaining provider-flexible - you can point it at Alibaba Cloud, any OpenAI-compatible endpoint, or a local Ollama instance.&lt;/p&gt;

&lt;p&gt;One important note: the Qwen OAuth free tier was discontinued in April 2026. Users now need an API key or a local Ollama setup running &lt;code&gt;qwen3-coder&lt;/code&gt;. Optional integrations with VS Code, Zed, and JetBrains round out the surface area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue.dev - AI Agents with CI-Enforceable Code Review Rules
&lt;/h2&gt;

&lt;p&gt;Continue.dev integrates AI agent capabilities directly into VS Code and JetBrains, then extends them into CI/CD pipelines via an open-source CLI. Its 2026 Agent Mode handles multi-step tasks autonomously - analyzing requirements, modifying files, running tests, and iterating on failures.&lt;/p&gt;

&lt;p&gt;The standout feature is PR Review Automation: every time a developer submits a pull request, a Continue agent applies review rules stored as Markdown files inside the repository itself. Because rules live in version control, they are auditable, evolvable, and systematically enforceable across the team - not just advisory suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pi - Minimal and Fully Customizable Agent Harness
&lt;/h2&gt;

&lt;p&gt;Pi is the outlier on this list by design. Built by Mario Zechner under MIT license, it ships a four-tool core (Read, Write, Edit, Bash) with no built-in opinions about workflow. No default plan mode, no automatic Git commits, no preset TUI.&lt;/p&gt;

&lt;p&gt;Extensibility is the point: skills are on-demand Markdown capability files, and extensions are TypeScript files that add new tools. Pi runs in four modes - interactive, print/JSON, RPC, and SDK - making it the most automation-friendly and embeddable agent on the list. It gained traction in 2026 among teams who want to fully control how their agent behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose the Right Tool
&lt;/h2&gt;

&lt;p&gt;With nine options, the right pick depends on your workflow constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenCode&lt;/strong&gt; - best zero-cost drop-in for Claude Code with broadest model support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex CLI&lt;/strong&gt; - best for teams on OpenAI with sandboxed execution requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenHands&lt;/strong&gt; - best for long-horizon autonomous tasks without step-by-step supervision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cline&lt;/strong&gt; - best for IDE-first teams wanting wide model compatibility and task oversight&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aider&lt;/strong&gt; - best when Git discipline and reversibility are non-negotiable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goose&lt;/strong&gt; - best for work spanning code, infrastructure, and multi-system automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen Code&lt;/strong&gt; - best if you are already running Qwen3-Coder workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continue.dev&lt;/strong&gt; - best for teams wanting CI-enforced review rules alongside AI coding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pi&lt;/strong&gt; - best if you want to build your own agent workflow from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The gap between Claude Code and open-source alternatives has narrowed considerably in 2026. OpenCode is the fastest drop-in replacement for daily terminal use. Aider is the safest option when Git accountability matters. OpenHands handles fully autonomous task execution. Cline's 8 million active developers make it the most broadly validated across IDE and terminal workflows. Every tool here gives you model flexibility and code transparency that Claude Code cannot offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/open-source-alternatives-claude-code" rel="noopener noreferrer"&gt;Best Open Source Alternatives to Claude Code in 2026 - DevToolLab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/opencode-ai/opencode" rel="noopener noreferrer"&gt;OpenCode on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;OpenAI Codex CLI on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/OpenHands/OpenHands" rel="noopener noreferrer"&gt;OpenHands on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cline/cline" rel="noopener noreferrer"&gt;Cline on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Aider-AI/aider" rel="noopener noreferrer"&gt;Aider on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aaif-goose/goose" rel="noopener noreferrer"&gt;Goose on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/QwenLM/qwen-code" rel="noopener noreferrer"&gt;Qwen Code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/continuedev/continue" rel="noopener noreferrer"&gt;Continue.dev on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/earendil-works/pi" rel="noopener noreferrer"&gt;Pi on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>AI Code Refactoring Tools in 2026: A Practical Developer's Guide</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:10:36 +0000</pubDate>
      <link>https://dev.to/moksh/ai-code-refactoring-tools-in-2026-a-practical-developers-guide-4lc1</link>
      <guid>https://dev.to/moksh/ai-code-refactoring-tools-in-2026-a-practical-developers-guide-4lc1</guid>
      <description>&lt;p&gt;AI tools promised to make developers write cleaner code faster. The data tells a different story: developers are refactoring 60% less than before AI coding tools arrived, while duplicate code blocks in AI-generated codebases rose 8x year-over-year in 2024. The tools that were supposed to eliminate technical debt are creating more of it.&lt;/p&gt;

&lt;p&gt;The root cause is scope. Most developers use AI refactoring by pasting a function into a chat and asking for improvements. That approach misses everything that makes refactoring hard - how the function is used elsewhere, what invariants it must preserve, and what the surrounding modules expect. The best AI refactoring tools in 2026 solve this by working at the right level of abstraction with full codebase context.&lt;/p&gt;

&lt;p&gt;This guide covers the leading tools, what each one is genuinely good at, and how to build a workflow that measurably improves your codebase instead of just changing it faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Refactoring Paradox in AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;AI accelerates code generation, not architecture. When a developer ships a feature in 30 minutes instead of two hours, the refactoring pass that would have happened during hand-written development gets skipped. Over weeks, this compounds into a codebase full of structural shortcuts.&lt;/p&gt;

&lt;p&gt;Context is the critical missing piece. When you paste a single function into an AI chat, the model has no idea how that function connects to the rest of the codebase, what contracts it must honor, or what modules depend on it. The result is locally plausible code that may be globally broken. This is why codebase-level indexing separates useful refactoring tools from overconfident code rewriters.&lt;/p&gt;

&lt;p&gt;Duplicate code is the leading symptom. When an AI generates a utility function without knowing a similar one already exists two modules over, duplication grows. The best 2026 tools address this by indexing the whole repository before suggesting anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Approaches: Agentic vs Inline Refactoring
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Agentic Refactoring
&lt;/h3&gt;

&lt;p&gt;Agentic tools take a natural language goal, analyze the codebase, and apply changes across multiple files. You describe the desired outcome - "extract this shared logic into a utility" or "migrate from CommonJS to ES Modules" - and the agent figures out which files to touch, what order to apply changes in, and whether the result breaks existing tests.&lt;/p&gt;

&lt;p&gt;You review the diff and adjust the goal if needed. Examples include Cursor Agent Mode, Windsurf Cascade, GitHub Copilot Agent, and Claude Code CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inline Refactoring
&lt;/h3&gt;

&lt;p&gt;Inline tools give suggestions as you write or on demand for the code in front of you. They are faster for small improvements - making a function more idiomatic, simplifying a conditional chain, or extracting a named variable. The scope is narrow by design, which makes them reliable and fast but insufficient for cross-file changes.&lt;/p&gt;

&lt;p&gt;Examples include GitHub Copilot inline suggestions, JetBrains AI completions, Sourcery, and Tabnine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Top AI Code Refactoring Tools in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Cursor - Best Overall for Multi-File Refactoring
&lt;/h3&gt;

&lt;p&gt;Cursor is the benchmark for agentic refactoring in 2026, handling complex multi-file edits 30% faster than GitHub Copilot on timed benchmarks. As a full VS Code fork, it imports your existing settings, extensions, and keybindings on first launch with no productivity cliff.&lt;/p&gt;

&lt;p&gt;The core refactoring feature is Agent Mode (formerly Composer). You describe the change, Cursor reads the relevant files, builds a plan, applies changes across the codebase, and shows a file-by-file diff for review. The &lt;code&gt;.cursorrules&lt;/code&gt; file lets you encode project conventions so the agent's output matches your existing style.&lt;/p&gt;

&lt;p&gt;Best for: Multi-file refactoring with visual review, teams that want one tool for generation and cleanup. Pricing: Free (limited), Pro $20/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. GitHub Copilot - Best for Teams with Mixed Editors
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot works natively in VS Code, JetBrains, Visual Studio, Neovim, and on GitHub.com itself - no IDE switch required. Agent Mode reached general availability in March 2026 and handles multi-file refactoring with results comparable to Cursor for most real-world tasks.&lt;/p&gt;

&lt;p&gt;The standout differentiator is GitHub integration. You can reference &lt;code&gt;#issue:1234&lt;/code&gt; in a refactoring chat and Copilot connects the bug description to the affected code and proposes targeted fixes. For teams that track work in GitHub Issues and PRs, this cross-referencing is genuinely useful.&lt;/p&gt;

&lt;p&gt;Best for: Teams already on GitHub, mixed-editor environments. Pricing: Free (2,000 completions/month), Pro $10/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Claude Code - Best for Large-Scale Batch Refactoring
&lt;/h3&gt;

&lt;p&gt;Claude Code is a CLI-first agentic system designed for refactoring tasks too large or systematic to handle interactively. With a 1-million token context window (roughly 25,000 lines of code), it can hold an entire mid-sized codebase in a single session.&lt;/p&gt;

&lt;p&gt;Rather than making isolated changes that break things elsewhere, Claude Code reads the dependency graph, understands module relationships, modifies them in sequence, and runs your test suite to validate each step. It achieves 80.8% on SWE-bench Verified - the highest reported score for autonomous code modification.&lt;/p&gt;

&lt;p&gt;Practical use cases include migrating a codebase from CommonJS to ES Modules, replacing deprecated APIs across 40+ files, or enabling TypeScript strict mode and fixing all resulting type errors. Tasks that would take a developer a full day can often be run unattended with a well-scoped prompt.&lt;/p&gt;

&lt;p&gt;Best for: Large-scale migrations, systematic batch refactoring, CI/CD-triggered automation. Pricing: Included with Claude Pro ($20/month).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Windsurf - Best Cursor Alternative with Generous Free Tier
&lt;/h3&gt;

&lt;p&gt;Windsurf (now owned by the Devin/Cognition team) is the closest competitor to Cursor in the agentic IDE category. Its Cascade agent handles multi-file refactoring in the same model: describe the task, it reads the codebase, builds a plan, applies changes, and runs tests.&lt;/p&gt;

&lt;p&gt;The SWE-1.6 model (released April 2026) runs 13x faster than Claude Sonnet 4.5 at comparable quality. The free tier includes unlimited Tab completions and inline edits with a limited agent quota - enough to evaluate before committing to a subscription.&lt;/p&gt;

&lt;p&gt;Best for: Developers evaluating alternatives to Cursor, teams on a tighter budget. Pricing: Free (unlimited Tab completions, limited agent quota), Pro $20/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. JetBrains AI with Junie - Best for IntelliJ and PyCharm Users
&lt;/h3&gt;

&lt;p&gt;JetBrains AI Assistant with the Junie agent answers the question: what if AI understood the IDE as deeply as the IDE understands the code? Junie (the 2026 flagship feature) explores project structure, makes changes, runs tests, and asks for clarification on ambiguous decisions - directly analogous to Cursor's Agent Mode, but native to every JetBrains IDE.&lt;/p&gt;

&lt;p&gt;The key improvement is Junie's access to JetBrains' built-in static analysis engine instead of text search. When renaming or moving symbols, Junie uses the same semantic understanding that powers IntelliJ's F2 rename - making refactoring faster and architecturally sound rather than text-replacement-based.&lt;/p&gt;

&lt;p&gt;Best for: Java, Kotlin, and Python developers committed to JetBrains IDEs. Pricing: Free (3 AI credits/month), AI Pro $10/month, AI Ultimate $30/month (IDE subscription sold separately).&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Sourcery - Best for Python Teams
&lt;/h3&gt;

&lt;p&gt;Sourcery has a narrower focus than every other tool here, and that narrowness is the point. Built specifically for Python, it finds code that works but is not Pythonic and shows you the improvement with one-click application. It targets concrete, local improvements - replacing a manual loop with a list comprehension, simplifying nested conditionals into guard clauses, catching antipatterns that pass code review but slow down future maintainers.&lt;/p&gt;

&lt;p&gt;The Sentry integration is unique in this category. Sourcery can monitor production errors from Sentry, identify the responsible code, and generate targeted fixes - closing the loop between code that passes tests and code that handles real-world inputs correctly.&lt;/p&gt;

&lt;p&gt;Best for: Python teams focused on code quality and Pythonic style. Pricing: Open Source free (3 repos), Pro $12/seat/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. CodeScene ACE - Best for Measuring Technical Debt
&lt;/h3&gt;

&lt;p&gt;CodeScene ACE (Auto Code Evolution) stands apart because it validates that a suggested refactoring actually improves the code health score before surfacing it to you. Every other tool generates a suggestion and hopes it is better. CodeScene runs the suggestion through a measurement pipeline and only shows it if the metrics confirm improvement.&lt;/p&gt;

&lt;p&gt;The CodeHealth metric scores code on a 1-10 scale. ACE targets specific, measurable complexity indicators - Large Method, Deep Nested Logic, Complex Conditional - that correlate with bug density in CodeScene's behavioral analysis database. This makes technical debt reduction systematic and justifiable to stakeholders.&lt;/p&gt;

&lt;p&gt;Best for: Teams that need to demonstrate and track ROI on refactoring work. Pricing: From $21/month, Enterprise custom.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose the Right Tool
&lt;/h2&gt;

&lt;p&gt;For a solo developer doing mixed-language work, Cursor Pro at $20/month covers the vast majority of real-world refactoring needs. Start here unless you have a specific constraint.&lt;/p&gt;

&lt;p&gt;For a team on GitHub with mixed editors, GitHub Copilot Business at $19/seat/month works in every editor, integrates with GitHub project tracking, and has Agent Mode for multi-file work without requiring an IDE change.&lt;/p&gt;

&lt;p&gt;For large-scale batch refactors on a mature codebase - migrating the whole project rather than fixing a module - Claude Code is the right tool. The 1M token context, test-driven autonomy, and headless mode handle scope that IDE agents cannot reach.&lt;/p&gt;

&lt;p&gt;For Python-heavy teams, Sourcery Pro combined with an agentic IDE covers the full refactoring stack. Sourcery handles Pythonic improvements and production-error diagnosis; the IDE agent handles multi-file structural work.&lt;/p&gt;

&lt;p&gt;For teams that need to measure and report technical debt, add CodeScene ACE on top of whatever IDE tool you are already using. It layers in as a plugin and quantifies debt as a real metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an AI Refactoring Workflow That Produces Results
&lt;/h2&gt;

&lt;p&gt;The most common mistake with AI-assisted refactoring is treating it as a strategy rather than an accelerator for one. Here is the workflow pattern that produces the best outcomes.&lt;/p&gt;

&lt;p&gt;Measure before touching anything. Use a code health tool to identify which parts of the codebase have the highest bug correlation and change failure rate. Refactoring high-entropy modules in active development paths is the highest-ROI work.&lt;/p&gt;

&lt;p&gt;Scope the task to one unit of behavior. The red-green-refactor loop applied at the function or module level is the most reliable pattern. Write a failing test for the behavior you want to preserve, confirm it fails for the right reason, then hand the refactoring to the AI with a clear "preserve this contract" instruction. Keep PRs under 200 lines - review time and regression rates both drop 60% compared to large-scope rewrites.&lt;/p&gt;

&lt;p&gt;Use the right tool for the right scope. For single-file changes, inline suggestions are faster than agentic tools. For 2-10 file changes, IDE Agent Mode is the right level. For entire-codebase migrations, Claude Code CLI is the right choice.&lt;/p&gt;

&lt;p&gt;Always gate with CI/CD. AI-generated refactors should pass the same checks as human-written code: linting, type checking, unit tests, and integration tests. 45% of AI-generated code contains security vulnerabilities in the initial version - quality gates reduce this sharply.&lt;/p&gt;

&lt;p&gt;Track outcomes, not just output. The goal of a refactoring session is measurable improvement - lower complexity, fewer code smell violations, better test coverage. If you are not measuring before and after, you cannot tell whether the AI's suggestions actually improved the code or just changed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI Code Refactoring in 2026: Tools, Workflows, and What Actually Works - DevToolLab: &lt;a href="https://devtoollab.com/blog/ai-code-refactoring-tools" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/ai-code-refactoring-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Best AI Code Review Tools - DevToolLab: &lt;a href="https://devtoollab.com/blog/ai-code-review-tools" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/ai-code-review-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Best AI Coding Assistants in 2026 - DevToolLab: &lt;a href="https://devtoollab.com/blog/best-ai-coding-assistants" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/best-ai-coding-assistants&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Top CLI AI Coding Agents - DevToolLab: &lt;a href="https://devtoollab.com/blog/top-cli-ai-coding-agents" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/top-cli-ai-coding-agents&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Top Static Code Analysis Tools Every Developer Should Know in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Mon, 22 Jun 2026 07:20:12 +0000</pubDate>
      <link>https://dev.to/moksh/top-static-code-analysis-tools-every-developer-should-know-in-2026-hi8</link>
      <guid>https://dev.to/moksh/top-static-code-analysis-tools-every-developer-should-know-in-2026-hi8</guid>
      <description>&lt;p&gt;Catching a bug during development costs a fraction of what it costs in production. That gap is exactly why static code analysis matters - and in 2026, the tooling available to engineers has never been more capable or faster.&lt;/p&gt;

&lt;p&gt;The landscape has split into three clear layers: blazing-fast Rust-based linters, cross-file SAST tools for security, and multi-language platforms for organizational code quality. This article walks through each layer, the best tools in each category, and how to combine them in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Static Analysis Actually Covers
&lt;/h2&gt;

&lt;p&gt;Before picking a tool, it helps to understand what you are actually choosing between. "Static analysis" is an umbrella term for three distinct categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linters&lt;/strong&gt; run per-file in milliseconds. They catch style violations, anti-patterns, and syntax errors. They are fast, surgical, and designed to run on every commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAST tools&lt;/strong&gt; (Static Application Security Testing) perform cross-file analysis to trace how untrusted input flows through your codebase - detecting SQL injection, XSS, and command injection that linters would never surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code quality platforms&lt;/strong&gt; aggregate linting, complexity metrics, duplication, coverage, and dependency scanning into dashboards with trend tracking and policy enforcement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruff - The New Python Linting Default
&lt;/h2&gt;

&lt;p&gt;Ruff is a Rust-based Python linter that consolidates five tools into one binary: Flake8, Black, isort, pyupgrade, and flake8-bugbear. It runs 100-155x faster than its predecessors, ships over 900 built-in rules, and was named the most-admired developer tool in the 2025 Stack Overflow Developer Survey.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ruff
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;       &lt;span class="c"&gt;# lint&lt;/span&gt;
ruff format &lt;span class="nb"&gt;.&lt;/span&gt;      &lt;span class="c"&gt;# format&lt;/span&gt;
ruff check &lt;span class="nt"&gt;--fix&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="c"&gt;# auto-fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free, MIT-licensed, and used by FastAPI, Pydantic, Pandas, Django, and 154,000+ other projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint - JavaScript and TypeScript Ecosystem Standard
&lt;/h2&gt;

&lt;p&gt;ESLint pulls over 132 million npm downloads per week. Version 10 (released February 2026) standardized on flat config (&lt;code&gt;eslint.config.js&lt;/code&gt;) and added the &lt;code&gt;@eslint/mcp&lt;/code&gt; package - which exposes ESLint as an MCP server for AI coding assistants to query rules and results directly.&lt;/p&gt;

&lt;p&gt;ESLint's real strength is its plugin ecosystem: React, Vue, Next.js, accessibility, and dozens of framework-specific rule sets. If you depend on specialized plugins, ESLint remains the only option with full coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Biome - One Rust Binary for Linting and Formatting
&lt;/h2&gt;

&lt;p&gt;Biome replaces both ESLint and Prettier with a single Rust binary. It runs 15x faster than ESLint, 35x faster than Prettier, and achieves 97% compatibility with Prettier's output - meaning most codebases can switch with minimal disruption.&lt;/p&gt;

&lt;p&gt;Biome 2.0 (sponsored by Vercel, June 2025) added type inference without running the TypeScript compiler, covering roughly 75% of what &lt;code&gt;typescript-eslint&lt;/code&gt; catches without the compilation overhead in CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; @biomejs/biome
npx @biomejs/biome init
npx @biomejs/biome check &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For new JS/TS projects starting in 2026, Biome is the recommended modern default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oxlint - 50-100x Faster Than ESLint
&lt;/h2&gt;

&lt;p&gt;Oxlint is part of the OXC Rust-based JavaScript toolchain. It delivers 50-100x speed gains over ESLint with 695 built-in rules. As of March 2026, its JavaScript plugin system hit alpha, allowing teams to run Oxlint alongside ESLint - Oxlint handles the core rules in seconds while ESLint covers remaining specialized plugins.&lt;/p&gt;

&lt;p&gt;Production users include Shopify, Airbnb, Mercedes-Benz, and Zalando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkstyle and RuboCop - Java and Ruby
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Checkstyle&lt;/strong&gt; (v13.5.0) is the standard Java style enforcer. It supports Google Java Style Guide and Sun Code Conventions, validates Javadoc, and integrates with Maven and Gradle. Most teams pair it with SpotBugs and SonarQube.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RuboCop&lt;/strong&gt; (v1.85.0) is the uncontested Ruby linter with 580+ built-in cops. It ships a built-in LSP server for real-time editor feedback. Teams wanting zero config can wrap it with StandardRB. Both are free and MIT-licensed.&lt;/p&gt;

&lt;h2&gt;
  
  
  PHPStan vs Psalm for PHP
&lt;/h2&gt;

&lt;p&gt;Both tools perform static analysis on PHP code and are free and MIT-licensed. The choice depends on your priority:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHPStan&lt;/strong&gt; (v2.2.1) has wider adoption, a large plugin ecosystem including Larastan for Laravel, and a low false positive rate with 10 configurable strictness levels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Psalm&lt;/strong&gt; is stricter by default and includes built-in taint analysis - tracing untrusted user input to dangerous sinks to catch SQL injection and XSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many PHP teams run both in CI: PHPStan for type correctness, Psalm for security.&lt;/p&gt;

&lt;h2&gt;
  
  
  SonarQube - Enterprise Quality Gates
&lt;/h2&gt;

&lt;p&gt;SonarQube supports 40+ languages and is used by over 7 million developers. Its core feature is the &lt;strong&gt;Quality Gate&lt;/strong&gt; - a configurable pass/fail threshold that blocks pull requests from merging when they fail defined standards. In 2026, this matters especially when teams are shipping AI-generated code at scale.&lt;/p&gt;

&lt;p&gt;Security scanning covers OWASP Top 10, CWE, STIG, NIST SSDF, and PCI DSS. The 2026 AI CodeFix feature generates fix suggestions directly in the interface.&lt;/p&gt;

&lt;p&gt;Pricing: Free self-hosted Community edition; Cloud Team from $32/month; Enterprise custom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semgrep and Opengrep - Pattern-Based SAST
&lt;/h2&gt;

&lt;p&gt;Semgrep is the leading pattern-based SAST tool supporting 30+ languages. Its rule syntax mirrors the code pattern it is targeting, so security engineers can write custom rules in minutes. Cross-file taint analysis (available in the Pro tier) tracks multi-hop vulnerability flows across modules.&lt;/p&gt;

&lt;p&gt;In December 2024, Semgrep relicensed its vendor-maintained rules, restricting commercial reuse. A consortium of 10+ security vendors launched &lt;strong&gt;Opengrep&lt;/strong&gt; as an LGPL-2.1 fork - fully backward-compatible with Semgrep's rule format, with complete taint analysis and Windows support. Teams using Semgrep rules in commercial products should evaluate Opengrep.&lt;/p&gt;

&lt;h2&gt;
  
  
  DeepSource - Best False Positive Rate
&lt;/h2&gt;

&lt;p&gt;DeepSource consistently achieves a sub-5% false positive rate across 16 GA languages with 5,000+ analysis rules. Its Autofix AI generates context-aware code fixes for nearly all detected issues. PR report cards score changes across Security, Reliability, Complexity, Hygiene, and Coverage.&lt;/p&gt;

&lt;p&gt;Setup requires only installing the GitHub app - no YAML configuration for core analysis.&lt;/p&gt;

&lt;p&gt;Pricing: Free for public repos; Team at $24/user/month; Enterprise custom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qlty - Open CLI with Optional Cloud Dashboards
&lt;/h2&gt;

&lt;p&gt;Qlty (formerly Code Climate Quality) is an open-source Rust CLI that integrates 60+ linter plugins covering 40+ languages and 20,000+ rules. The cloud dashboards are an optional add-on, not a requirement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://qlty.sh/install | bash
qlty init
qlty check &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free tier includes 1,000 analysis minutes/month and 100 AI autofixes/month - genuinely useful for solo developers and small open-source projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rust Revolution in Linting Performance
&lt;/h2&gt;

&lt;p&gt;The shift from interpreted runtimes to natively compiled Rust binaries is not an incremental improvement - it is architectural. Ruff runs 100-155x faster than Flake8. Biome runs 15-35x faster than ESLint and Prettier. Oxlint runs 50-100x faster than ESLint.&lt;/p&gt;

&lt;p&gt;A linting step that previously took 2-3 minutes on a large codebase now completes in 2-5 seconds. That speed changes behavior: tasks that previously ran only on PR merges can now run as pre-commit hooks with no workflow penalty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Layered CI Pipeline
&lt;/h2&gt;

&lt;p&gt;The most effective static analysis setups layer tools at different stages of the development cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit&lt;/strong&gt; - Ruff, Biome, or Oxlint. Completes in under 5 seconds. Catches style and syntax before pushing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR checks&lt;/strong&gt; - Full type checking (&lt;code&gt;tsc --noEmit&lt;/code&gt;), complete lint pass, SAST with Semgrep or Opengrep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR or nightly&lt;/strong&gt; - SonarQube or DeepSource platform scan plus dependency vulnerability checks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach lets fast linting fail immediately on style issues, saving compute for deeper checks that only run when code is already syntactically clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Stack for Your Team
&lt;/h2&gt;

&lt;p&gt;The right combination depends on your team size and language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solo developer or OSS maintainer&lt;/strong&gt; - Ruff (Python) or Biome (JS/TS) plus Semgrep Community tier. Total cost: $0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small team (2-10 devs)&lt;/strong&gt; - Same linter stack plus SonarQube Cloud Team or DeepSource Team for trend dashboards and policy enforcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP with security requirements&lt;/strong&gt; - Run PHPStan and Psalm together in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise with compliance needs&lt;/strong&gt; - SonarQube Enterprise covering OWASP, CWE, STIG, and NIST SSDF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating from legacy JS/TS tooling&lt;/strong&gt; - Start with Biome for formatting, add Oxlint alongside ESLint, drop ESLint rules incrementally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security-first team&lt;/strong&gt; - Semgrep or Opengrep with custom rule libraries for organization-specific vulnerability patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/best-static-code-analysis-tools" rel="noopener noreferrer"&gt;Static Code Analysis in 2026: Best Linters, SAST Tools, and Platforms Compared - DevToolLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astral-sh/ruff" rel="noopener noreferrer"&gt;Ruff GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://biomejs.dev/" rel="noopener noreferrer"&gt;Biome Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oxc-project/oxc" rel="noopener noreferrer"&gt;Oxlint / OXC Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sonarsource.com/products/sonarqube/" rel="noopener noreferrer"&gt;SonarQube by Sonar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semgrep.dev/" rel="noopener noreferrer"&gt;Semgrep Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opengrep.dev/" rel="noopener noreferrer"&gt;Opengrep Community Fork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepsource.com/" rel="noopener noreferrer"&gt;DeepSource Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qlty.sh/" rel="noopener noreferrer"&gt;Qlty CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>GitHub Copilot vs Cursor vs Windsurf: Top AI Coding Assistants Every Developer Should Know in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:07:14 +0000</pubDate>
      <link>https://dev.to/moksh/github-copilot-vs-cursor-vs-windsurf-top-ai-coding-assistants-every-developer-should-know-in-2026-4pgi</link>
      <guid>https://dev.to/moksh/github-copilot-vs-cursor-vs-windsurf-top-ai-coding-assistants-every-developer-should-know-in-2026-4pgi</guid>
      <description>&lt;p&gt;By 2026, the question isn't whether to use an AI coding assistant - it's which one actually fits how you work. The gap between a well-chosen tool and a poorly matched one shows up directly in your output: one makes you faster at real tasks, the other distracts you with confident-sounding hallucinations. This guide cuts through the marketing noise by comparing the leading tools across real TypeScript, Python, Go, and Rust codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI Coding Assistants Took Off in 2026
&lt;/h2&gt;

&lt;p&gt;Three things shifted simultaneously in 2026 to make this tooling category genuinely useful. First, context windows grew large enough to hold entire repositories rather than single files. Second, models became more specialized for code generation rather than relying on general-purpose LLMs. Third, agentic patterns matured - tools can now run tests, read failure logs, and iterate on fixes without constant human hand-holding. The result is a set of tools that actually belong in a professional developer's workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Evaluate an AI Coding Tool
&lt;/h2&gt;

&lt;p&gt;The right tool depends on four factors specific to your setup. Your editor preference matters first - some tools are plugins that augment your current IDE, while others are full AI-native editors that replace it. Then consider what kind of coding help you actually need: quick autocomplete, or multi-file reasoning that spans your whole codebase. Third, check compliance requirements - some environments (defense, finance) mandate air-gapped or on-premises deployment. Finally, consider your ecosystem: whether you live in GitHub, AWS, or a specific IDE like JetBrains shapes which tool integrates most naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot remains the default choice for teams already centered on GitHub. Its standout advantage is deep integration with your repository history - it can reference PR context, past commits, and issue threads when generating suggestions. At $10/month for individuals with a free tier available, it's accessible to most developers. The tradeoff is that it functions as an IDE plugin rather than an AI-native editor, so it augments rather than transforms your environment. Best suited for: GitHub-centric teams wanting tight repository awareness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor
&lt;/h2&gt;

&lt;p&gt;Cursor is a VS Code fork rebuilt from the ground up as an AI-first editor. Its defining feature is Composer mode - a natural language interface for making coordinated changes across multiple files simultaneously. It also supports a .cursorrules file, letting teams encode project-specific conventions that the AI follows consistently. Model selection is flexible: you can configure it to use Claude Sonnet, Claude Opus, or GPT-4o depending on the task. At $20/month for the Pro tier, it's the best option for complex multi-file architecture work. The learning curve is minimal for anyone already using VS Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Windsurf (Devin Desktop)
&lt;/h2&gt;

&lt;p&gt;Windsurf competes directly with Cursor but differentiates on agentic terminal behavior. Its Cascade agent doesn't just suggest code - it can run your test suite, read the failure output, and loop through fixes without you driving each step. It also maintains persistent session memory, which helps when working across long debugging sessions. The free tier is notably more generous than Cursor's, making it an excellent starting point before committing to a paid plan. Pro tier is also $20/month. Best for: developers who want hands-off iteration on failing tests and complex debugging tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabnine
&lt;/h2&gt;

&lt;p&gt;Tabnine occupies a distinct niche: it's the primary choice for organizations with strict data security requirements. Unlike all the other tools here, Tabnine can run entirely on-premises in an air-gapped environment, meaning your code never leaves your network. This makes it the standard option for regulated industries like defense contracting and financial services where any cloud-connected code completion is a non-starter. The tradeoff is cost - there's no free tier and pricing starts at $39 per user per month, which is steep for individuals but justified for enterprise compliance teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon Q Developer
&lt;/h2&gt;

&lt;p&gt;Amazon Q Developer is the obvious choice if your stack is AWS-heavy. It provides deep context awareness for AWS services including Lambda, DynamoDB, and CDK, so it generates code that actually matches how those services work rather than producing generic boilerplate. Its unique capability is code transformation - particularly upgrading legacy Java 8 codebases to modern Java 17. A generous free tier is available, with the Pro plan at $19/month. Outside of AWS-focused development, its advantages diminish significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue.dev
&lt;/h2&gt;

&lt;p&gt;Continue.dev is an open-source VS Code and JetBrains extension that takes a bring-your-own-model approach. You can connect it to any LLM - Claude, GPT-4o, Gemini, Mistral, or a locally running model via Ollama for fully offline operation. This makes it the strongest option for privacy-first teams who want full control over their AI stack without any vendor lock-in. The base extension is free; costs only arise if you use paid hosted model tokens. It's also the most flexible tool for teams evaluating different models against each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  JetBrains AI Assistant
&lt;/h2&gt;

&lt;p&gt;JetBrains AI Assistant integrates natively into IntelliJ, PyCharm, and the rest of the JetBrains suite. Its advantage is awareness of project module structure, build system configurations, and IDE-specific refactoring patterns that generic tools miss. At $10/month, it's reasonable in price. The honest recommendation: only use it if you're already committed to JetBrains IDEs. If you're open to switching editors, Cursor or Windsurf offer more powerful AI capabilities regardless of language stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;IDE Plugin&lt;/td&gt;
&lt;td&gt;$10/mo&lt;/td&gt;
&lt;td&gt;GitHub teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;AI-native IDE&lt;/td&gt;
&lt;td&gt;$20/mo&lt;/td&gt;
&lt;td&gt;Multi-file reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;AI-native IDE&lt;/td&gt;
&lt;td&gt;$20/mo&lt;/td&gt;
&lt;td&gt;Agentic terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabnine&lt;/td&gt;
&lt;td&gt;IDE Plugin&lt;/td&gt;
&lt;td&gt;$39+/mo&lt;/td&gt;
&lt;td&gt;Air-gapped/enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Q&lt;/td&gt;
&lt;td&gt;IDE Plugin&lt;/td&gt;
&lt;td&gt;$19/mo&lt;/td&gt;
&lt;td&gt;AWS stacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continue.dev&lt;/td&gt;
&lt;td&gt;IDE Extension&lt;/td&gt;
&lt;td&gt;Free+&lt;/td&gt;
&lt;td&gt;Custom model control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JetBrains AI&lt;/td&gt;
&lt;td&gt;IDE Plugin&lt;/td&gt;
&lt;td&gt;$10/mo&lt;/td&gt;
&lt;td&gt;JetBrains users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which Tool Should You Pick?
&lt;/h2&gt;

&lt;p&gt;If you're testing for the first time with no paid commitment, start with Windsurf - its free tier is the most capable of any tool in this category. GitHub Copilot is the safe default for teams whose workflow is centered on GitHub PRs and issue tracking. For serious multi-file development or feature implementation, Cursor and Windsurf are both strong; Cursor leads on autocomplete speed and ecosystem, while Windsurf wins on autonomous terminal iteration. Tabnine is the only real choice when compliance or air-gapping is non-negotiable. Amazon Q Developer is the no-brainer pick for AWS-heavy engineering teams. Continue.dev is ideal for anyone who wants to avoid model vendor lock-in entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source article: &lt;a href="https://devtoollab.com/blog/best-ai-coding-assistants" rel="noopener noreferrer"&gt;GitHub Copilot vs Cursor vs Windsurf: Best AI Coding Assistants Ranked for 2026 - DevToolLab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot" rel="noopener noreferrer"&gt;GitHub Copilot Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeium.com/windsurf" rel="noopener noreferrer"&gt;Windsurf (Codeium) Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tabnine.com" rel="noopener noreferrer"&gt;Tabnine Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/q/developer/" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/continuedev/continue" rel="noopener noreferrer"&gt;Continue.dev GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.jetbrains.com/ai/" rel="noopener noreferrer"&gt;JetBrains AI Assistant&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>devex</category>
    </item>
    <item>
      <title>Passkeys in 2026: A Practical Engineering Guide to Passwordless Auth</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:31:08 +0000</pubDate>
      <link>https://dev.to/moksh/passkeys-in-2026-a-practical-engineering-guide-to-passwordless-auth-15pj</link>
      <guid>https://dev.to/moksh/passkeys-in-2026-a-practical-engineering-guide-to-passwordless-auth-15pj</guid>
      <description>&lt;p&gt;Authentication is broken at its foundation - not just inconvenient. Passwords are shared secrets: hand one to a server, and you have instantly doubled your attack surface. With over 5 billion passkeys now active globally and Google reporting a 99.9% lower account compromise rate compared to passwords, the industry has already moved. This guide covers how passkeys work cryptographically, how to implement them in TypeScript, and the pitfalls to avoid before going to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Passwords Are Structurally Broken
&lt;/h2&gt;

&lt;p&gt;The core issue isn't that users pick weak passwords - it's that passwords require a shared secret stored on both sides. The Verizon 2025 DBIR found that 22% of all breaches started with stolen credentials, and 88% of web app attacks relied on them. In 2024, infostealer malware alone harvested 548 million passwords. Adding 2FA helps but doesn't fix the root problem: SMS codes are SIM-swap targets, and TOTP tokens can be phished in real time by proxy attackers who replay codes within their validity window.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Passkeys Actually Are
&lt;/h2&gt;

&lt;p&gt;A passkey is a credential built on public-key cryptography, standardized through the WebAuthn spec and FIDO2. When you register, your device generates a public-private key pair - the private key stays locked in hardware (Secure Enclave, StrongBox, or a hardware key), and the server only receives the public key. At login, the server sends a random challenge, your device signs it with the private key after biometric or PIN verification, and the server verifies the signature. No secret is ever transmitted. This eliminates credential stuffing, server-side breach exposure, and phishing - because passkeys are cryptographically bound to a specific origin domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cryptography Worth Understanding
&lt;/h2&gt;

&lt;p&gt;The standard algorithm is ES256 - ECDSA with the P-256 curve and SHA-256. Each credential is tied to a specific relying party ID (your app's domain). A passkey created for yourapp.com cannot be used on yourapp-phishing.com because the origin is embedded in clientDataJSON and verified by the authenticator before it signs anything. The registration flow involves your server generating a one-time challenge, the client calling navigator.credentials.create(), the authenticator signing an attestation with the new private key, and your server verifying and storing the public key and credential ID. Authentication mirrors this with navigator.credentials.get().&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Registration with SimpleWebAuthn
&lt;/h2&gt;

&lt;p&gt;Don't implement raw WebAuthn from scratch - CBOR decoding and signature verification are easy to misconfigure. The standard library for TypeScript/Node.js is SimpleWebAuthn, which handles cryptographic complexity through a clean API and sees 800K+ weekly npm downloads. Install both packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @simplewebauthn/server @simplewebauthn/browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Server side - Registration options endpoint (Express):&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;generateRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;verifyRegistrationResponse&lt;/span&gt;&lt;span class="p"&gt;,&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;@simplewebauthn/server&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RegistrationResponseJSON&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;@simplewebauthn/types&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;rpName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your App Name&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;rpID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yourapp.com&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;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rpID&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/register/options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;options&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;generateRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;rpName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userDisplayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;excludeCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passkeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pk&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;residentKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registrationChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/register/verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RegistrationResponseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registrationChallenge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;verification&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;verifyRegistrationResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;response&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="nx"&gt;expectedChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expectedOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expectedRPID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rpID&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verification&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;verified&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;credentialDeviceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;credentialBackedUp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;registrationInfo&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passkeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;deviceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentialDeviceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;backedUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentialBackedUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transports&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registrationChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;verified&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;strong&gt;Client side - Registration:&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;startRegistration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;browserSupportsWebAuthn&lt;/span&gt;&lt;span class="p"&gt;,&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;@simplewebauthn/browser&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;registerPasskey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;browserSupportsWebAuthn&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your browser does not support passkeys.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/register/options&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;options&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;optionsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attResp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attResp&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;startRegistration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;optionsJSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;InvalidStateError&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This device already has a passkey registered.&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifyRes&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/register/verify&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attResp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;verifyRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Passkey registered successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing Authentication
&lt;/h2&gt;

&lt;p&gt;The login flow mirrors registration - generate a challenge, have the authenticator sign it, and verify server-side. Registration and login together clock in at roughly 150 lines using SimpleWebAuthn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server side - Authentication:&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;generateAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;verifyAuthenticationResponse&lt;/span&gt;&lt;span class="p"&gt;,&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;@simplewebauthn/server&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthenticationResponseJSON&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;@simplewebauthn/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login/options&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&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;generateAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login/verify&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthenticationResponseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authChallenge&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;passkey&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passkeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByCredentialID&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="nx"&gt;id&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;passkey&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown credential&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;verification&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;verifyAuthenticationResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;response&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="nx"&gt;expectedChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expectedOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expectedRPID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;publicKey&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authenticationInfo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verification&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;verified&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passkeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;authenticationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newCounter&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;user&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;verified&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;strong&gt;Client side - Authentication:&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;startAuthentication&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;@simplewebauthn/browser&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;signInWithPasskey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login/options&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;options&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;optionsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;assnResp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;assnResp&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;startAuthentication&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;optionsJSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authentication failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifyRes&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login/verify&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assnResp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;verifyRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where Adoption Stands Right Now
&lt;/h2&gt;

&lt;p&gt;FIDO Alliance research from April 2026 across 11,000 consumers in 10 countries shows 75% of people globally have at least one passkey enabled, up from 69% just six months prior. Awareness hit 90%, up from 39% two years ago. Google serves 800 million passkey-enabled accounts with sign-in success rates 4x higher than passwords. Amazon has 175 million passkey users, and TikTok reports a 97% auth success rate. A February 2026 arXiv study found 11.3% of the Tranco top-100K websites now support passkeys - 62 times more than manual directory audits suggested. If your app doesn't support passkeys yet, you're behind the median curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Common Implementation Bugs
&lt;/h2&gt;

&lt;p&gt;These show up repeatedly in developer forums and production incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge reuse&lt;/strong&gt; - Challenges must be single-use, server-generated, and stored in the session between options generation and verification. Any deviation opens a replay attack window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping counter updates&lt;/strong&gt; - The authenticator increments a counter on every auth operation. Comparing incoming vs. stored counters lets you detect cloned credentials. Always call updateCounter after a successful login.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No account recovery path&lt;/strong&gt; - A user who loses their device loses their passkey. Shipping passkeys without a recovery mechanism - backup email codes, support-verified identity, or recovery keys - means locking users out permanently. Build this before launching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-device UX dropoff&lt;/strong&gt; - Same-device passkey completion rates run 79-98%. Cross-device flows on Windows web (QR code + Bluetooth) drop to 52-67%. Keep a password fallback during migration and watch your device mix in analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating From Passwords Without Breaking Things
&lt;/h2&gt;

&lt;p&gt;Treat the migration as three additive stages, not a hard cutover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1 - Opt-in:&lt;/strong&gt; Prompt users to register a passkey after a successful password login. Nothing changes for those who skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2 - Flip the default:&lt;/strong&gt; Once 60-70% of active users have passkeys, make the passkey flow primary with password as a secondary option. Conversion follows naturally because the UX is better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3 - Require for new signups:&lt;/strong&gt; New accounts register a passkey as part of onboarding. Apple's OS 26 includes Automatic Passkey Upgrade, which silently creates a passkey on every eligible password sign-in - a useful reference for aggressive migration strategies.&lt;/p&gt;

&lt;p&gt;Note for regulated environments: NIST guidelines updated in 2025 require phishing-resistant MFA (explicitly citing WebAuthn/FIDO2) for all US federal agency systems. Syncable passkeys satisfy AAL2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser and Platform Support
&lt;/h2&gt;

&lt;p&gt;WebAuthn has approximately 95% global browser coverage in 2026. Chrome (v67+), Edge (v18+), Safari (v13+ and iOS v14.5+), and all major Chromium forks support it. Platform authenticators cover every major consumer OS: Touch ID and Face ID on Apple devices, Windows Hello on Windows, and Android biometrics on Android 9+. Hardware keys like YubiKey and Google Titan work via USB or NFC for high-assurance enterprise scenarios. Use browserSupportsWebAuthn() from SimpleWebAuthn to gate the passkey UI at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source article: &lt;a href="https://devtoollab.com/blog/passkeys-passwordless-auth-guide" rel="noopener noreferrer"&gt;Passkeys Explained: The Developer's Complete Guide to Passwordless Authentication in 2026 - DevToolLab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simplewebauthn.dev/" rel="noopener noreferrer"&gt;SimpleWebAuthn Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webauthn.guide/" rel="noopener noreferrer"&gt;WebAuthn Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fidoalliance.org/passkeys/" rel="noopener noreferrer"&gt;FIDO Alliance Passkey Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/webauthn-3/" rel="noopener noreferrer"&gt;W3C WebAuthn Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pages.nist.gov/800-63-3/sp800-63b.html" rel="noopener noreferrer"&gt;NIST SP 800-63B Digital Identity Guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Building a Python MCP Server from Scratch - A Practical GitHub API Guide</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:53:46 +0000</pubDate>
      <link>https://dev.to/moksh/building-a-python-mcp-server-from-scratch-a-practical-github-api-guide-397k</link>
      <guid>https://dev.to/moksh/building-a-python-mcp-server-from-scratch-a-practical-github-api-guide-397k</guid>
      <description>&lt;p&gt;The Model Context Protocol has gone from a niche Anthropic project to industry-standard infrastructure in under two years - hitting 97 million monthly SDK downloads and earning a permanent home under the Linux Foundation. Every major AI coding tool now speaks MCP natively, yet most tutorials either list pre-built servers to install or recite the spec without building anything real.&lt;/p&gt;

&lt;p&gt;This guide walks you through writing a Python MCP server from zero: defining tools, resources, and prompts, testing with the MCP Inspector, and wiring it into Claude Desktop or Claude Code. The working example targets the GitHub API - a practical starting point you can extend for any project that needs live external data inside an AI assistant.&lt;/p&gt;

&lt;p&gt;Prerequisites: Python 3.10+, uv or pip, and Claude Desktop or Claude Code installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MCP Server Actually Does
&lt;/h2&gt;

&lt;p&gt;MCP is a JSON-RPC protocol that gives an AI client a standardized way to call external services. The client - Claude, Cursor, or any compliant tool - sends a request and your server handles it, regardless of what language it is written in.&lt;/p&gt;

&lt;p&gt;Every MCP server exposes three core primitives. Tools are callable functions the AI can invoke to take action or fetch data. Resources are read-only data endpoints the AI can pull from - similar to files or database records. Prompts are reusable instruction templates stored on the server and referenced by name, useful for standardizing workflows across a team.&lt;/p&gt;

&lt;p&gt;For transport, this guide uses stdio - the server runs as a subprocess and communicates over stdin/stdout. This works out of the box with Claude Desktop and Claude Code. For production or remote deployments, Streamable HTTP is the alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;Create a fresh directory and install the MCP SDK with the &lt;code&gt;[cli]&lt;/code&gt; extra, which includes the dev server and inspector launcher:&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;mkdir &lt;/span&gt;github-mcp-server
&lt;span class="nb"&gt;cd &lt;/span&gt;github-mcp-server
uv init &lt;span class="nb"&gt;.&lt;/span&gt;
uv add &lt;span class="s2"&gt;"mcp[cli]"&lt;/span&gt; httpx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer pip, run &lt;code&gt;pip install "mcp[cli]" httpx&lt;/code&gt; instead. Your project needs just three files: &lt;code&gt;server.py&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt; (if using uv), and an optional &lt;code&gt;.env&lt;/code&gt; for your GitHub token.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal Tool in 10 Lines
&lt;/h2&gt;

&lt;p&gt;Before diving into the full server, start with the simplest possible working example. This lets you confirm the SDK is wired up correctly before adding any real logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello-mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a personalized greeting. Use this when asked to greet someone.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Your MCP server is working.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;uv run mcp dev server.py&lt;/code&gt; to launch the MCP Inspector at &lt;code&gt;http://localhost:5173&lt;/code&gt;. Navigate to the Tools tab, call &lt;code&gt;greet&lt;/code&gt; with any name, and confirm the response. Three things matter here: &lt;code&gt;FastMCP&lt;/code&gt; handles all JSON-RPC plumbing, the &lt;code&gt;@mcp.tool()&lt;/code&gt; decorator auto-generates a schema from your type hints, and the docstring is what the AI reads to decide whether to call this tool - write it clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the GitHub Tools Server
&lt;/h2&gt;

&lt;p&gt;Now replace that minimal example with a real server. This version exposes two tools: one that fetches repository metadata and one that lists open issues, both backed by live GitHub API calls via httpx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github-tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This server provides tools to interact with the GitHub API. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use get_repo_info to fetch repository metadata. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use list_open_issues to retrieve open issues for a repository.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_github_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/vnd.github+json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-GitHub-Api-Version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-11-28&lt;/span&gt;&lt;span class="sh"&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;if&lt;/span&gt; &lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;forks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;open_issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_repo_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RepoInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Fetch metadata for a GitHub repository including stars, forks, and open issue count.
    Args:
        owner: GitHub username or organization (e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anthropics&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
        repo: Repository name (e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;claude-code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_github_headers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RepoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stargazers_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;forks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;forks_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;open_issues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open_issues_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html_url&lt;/span&gt;&lt;span class="sh"&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;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_open_issues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    List open issues for a GitHub repository, ordered by most recently updated.
    Args:
        owner: GitHub username or organization
        repo: Repository name
        limit: Max issues to return (1-30, default 10)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/issues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_github_headers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No open issues found for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open issues in **&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;** (showing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- #&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&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 few deliberate design choices are worth noting. Returning a Pydantic model from a tool gives the AI a typed, structured response it can reference field by field - far more reliable than parsing a formatted string. For string-return tools, catching exceptions and returning an error message is safer than letting them propagate, since an unhandled exception under stdio can kill the entire connection. Always clamp numeric inputs like &lt;code&gt;limit&lt;/code&gt; - the AI will occasionally send &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;100&lt;/code&gt;, or a string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Resources and Prompts
&lt;/h2&gt;

&lt;p&gt;Resources let the AI read data passively. Here is one that reports whether a GitHub token is configured, and a dynamic one that fetches a repo's README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config://github-tools/status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Report whether the server has a GitHub token configured.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;auth_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authenticated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unauthenticated (rate-limited to 60 req/hr)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GitHub Tools MCP Server&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;auth_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github://repos/{owner}/{repo}/readme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_readme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch the raw README content for a repository.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/readme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nf"&gt;_github_headers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/vnd.github.raw+json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No README found for this repository.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompts are stored instruction templates any MCP client can call by name. This one structures a code review request around the GitHub tools we defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;review_pull_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pr_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prompt template for reviewing a GitHub pull request.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please review pull request #&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pr_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start by fetching the repository info with get_repo_info, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;then list the open issues to understand the project context. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Focus your review on correctness, performance, and adherence to the project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s patterns.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing with the MCP Inspector
&lt;/h2&gt;

&lt;p&gt;The fastest way to validate your server is with the built-in inspector - no Claude required. Run &lt;code&gt;uv run mcp dev server.py&lt;/code&gt; and open &lt;code&gt;http://localhost:5173&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set your &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; in the Environment Variables section before connecting. Then test tools in the Tools tab, verify resources in the Resources tab, and confirm prompts show up in the Prompts tab. If anything fails, the Logs panel shows the raw JSON-RPC exchange, which is the most direct way to pinpoint the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Claude Desktop
&lt;/h2&gt;

&lt;p&gt;Open the Claude Desktop config file - on macOS at &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;, on Windows at &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;. Add your server under &lt;code&gt;mcpServers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"github-tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--with"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp[cli]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--with"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"httpx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/absolute/path/to/github-mcp-server/server.py"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"PYTHONUNBUFFERED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GITHUB_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_github_token_here"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;uv run&lt;/code&gt; rather than a bare &lt;code&gt;python&lt;/code&gt; command - Claude Desktop spawns its own shell environment without your PATH, so bare &lt;code&gt;python&lt;/code&gt; will often fail. Fully quit and restart Claude Desktop after saving. A plug icon in the input box confirms the server connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Claude Code
&lt;/h2&gt;

&lt;p&gt;For Claude Code, use the &lt;code&gt;claude mcp add&lt;/code&gt; CLI command. The &lt;code&gt;--&lt;/code&gt; separator is required to separate the server name from the launch command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add github-tools &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PYTHONUNBUFFERED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uv run &lt;span class="nt"&gt;--with&lt;/span&gt; &lt;span class="s2"&gt;"mcp[cli]"&lt;/span&gt; &lt;span class="nt"&gt;--with&lt;/span&gt; httpx python /absolute/path/to/server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To share the server config with your team, use &lt;code&gt;--scope project&lt;/code&gt;. This writes an &lt;code&gt;.mcp.json&lt;/code&gt; file to the repository root and prompts team members to activate it when they open the project. Run &lt;code&gt;claude mcp list&lt;/code&gt; to confirm registration.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/build-mcp-server-python" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/build-mcp-server-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP Python SDK: &lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol/python-sdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FastMCP documentation: &lt;a href="https://gofastmcp.com" rel="noopener noreferrer"&gt;https://gofastmcp.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP specification (Linux Foundation): &lt;a href="https://spec.modelcontextprotocol.io" rel="noopener noreferrer"&gt;https://spec.modelcontextprotocol.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;httpx documentation: &lt;a href="https://www.python-httpx.org" rel="noopener noreferrer"&gt;https://www.python-httpx.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub REST API reference: &lt;a href="https://docs.github.com/en/rest" rel="noopener noreferrer"&gt;https://docs.github.com/en/rest&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>mcp</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Node.js vs Bun vs Deno 2 in 2026: Which JavaScript Runtime Should You Actually Use?</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Thu, 18 Jun 2026 05:34:55 +0000</pubDate>
      <link>https://dev.to/moksh/nodejs-vs-bun-vs-deno-2-in-2026-which-javascript-runtime-should-you-actually-use-260e</link>
      <guid>https://dev.to/moksh/nodejs-vs-bun-vs-deno-2-in-2026-which-javascript-runtime-should-you-actually-use-260e</guid>
      <description>&lt;p&gt;In 2026, the JavaScript runtime landscape looks very different from just two years ago. Trigger.dev switched to Bun and reported a 5x throughput improvement on identical hardware. Anthropic acquired Bun and uses it in Claude Code's CLI. Deno 2 shipped real Node.js compatibility, bringing enterprise teams on board. And Node.js 24 quietly added native TypeScript execution alongside a production-ready built-in test runner.&lt;/p&gt;

&lt;p&gt;The question developers now face is not "should I switch?" - it is "which runtime fits which job?" This guide covers where each runtime actually wins in 2026, backed by numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Runtime Shipped Recently
&lt;/h2&gt;

&lt;p&gt;Node.js 24 reached Active LTS status with a major quality-of-life upgrade: &lt;code&gt;--experimental-strip-types&lt;/code&gt; is now stable, letting you run &lt;code&gt;.ts&lt;/code&gt; files directly without a separate build step. The &lt;code&gt;require(esm)&lt;/code&gt; interop is also stable in v24, ending years of CommonJS and ESM friction. The built-in &lt;code&gt;node:test&lt;/code&gt; module gained coverage reporting, mocking support, and &lt;code&gt;describe&lt;/code&gt;/&lt;code&gt;it&lt;/code&gt; APIs. V8 13.6 makes JSON serialization up to 2x faster on large objects.&lt;/p&gt;

&lt;p&gt;Bun 1.3 is still the all-in-one runtime - it packs a package manager, bundler, and test runner into a single binary, running on JavaScriptCore (Safari's engine) for lower memory and faster cold starts than V8. It added a built-in Redis client in 1.3, joining a native SQLite driver. The &lt;code&gt;bun compile&lt;/code&gt; command produces single-file executables, making it the cleanest option for distributing CLI tools. Anthropic's acquisition signals long-term institutional backing.&lt;/p&gt;

&lt;p&gt;Deno 2.8 completed the Node.js compatibility work started in v2.0. It now handles &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, npm workspaces, and most CommonJS packages with little friction. New additions include &lt;code&gt;deno audit&lt;/code&gt;, a &lt;code&gt;dx&lt;/code&gt; command similar to npx, self-extracting compiled binaries, and stabilization of the Temporal API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;On a plain REST endpoint with no database, Bun with Hono handles around 110,000 requests per second. Deno reaches roughly 85,000 req/s, while Node.js with Express lands near 50,000 req/s. In realistic apps with database calls and middleware, the gap narrows but Bun still leads at around 14,320 req/s versus Node's 5,240.&lt;/p&gt;

&lt;p&gt;Cold start times tell an equally clear story. A hello-world process starts in 8-15ms with Bun, 40-60ms with Deno, and 60-120ms with Node.js. On AWS Lambda, Bun averages a 156ms cold start compared to Node's 245ms - a 35% difference that adds up quickly in pay-per-invocation billing.&lt;/p&gt;

&lt;p&gt;Memory usage follows the same pattern. An idle Bun process uses around 18MB; Deno uses roughly 30MB; Node.js around 40MB. At 5,000 concurrent WebSocket connections, Bun uses 620MB versus Node's 890MB - a meaningful difference when running many processes per server.&lt;/p&gt;

&lt;p&gt;Package install speed is where Bun is the clear standout. An 847-package monorepo installs in 1.2 seconds with &lt;code&gt;bun install&lt;/code&gt; versus 32 seconds with npm. On a 1,847-dependency project, the gap grows to 47 seconds versus 17-20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript Support - The Real Practical Difference
&lt;/h2&gt;

&lt;p&gt;All three runtimes can execute TypeScript without a separate compile step in 2026, but their handling of TypeScript features differs significantly.&lt;/p&gt;

&lt;p&gt;Bun and Deno support enums, decorators, and TypeScript namespaces natively out of the box. Node.js 24's strip-types mode only removes type annotations - it does not transform TypeScript syntax, so enums and decorators still require a bundler like tsx or esbuild.&lt;/p&gt;

&lt;p&gt;Deno also offers something Bun and Node.js do not: &lt;code&gt;deno check server.ts&lt;/code&gt; runs a full tsc pass and surfaces type errors before runtime. Both Bun and Node.js strip types silently, meaning type errors go undetected until they crash at runtime - unless you run a separate &lt;code&gt;tsc&lt;/code&gt; step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in Tooling Comparison
&lt;/h2&gt;

&lt;p&gt;Bun and Deno both cut down the toolchain setup required for new projects. A fresh Bun project comes with TypeScript support, a test runner, and a bundler - no installs needed. Deno ships a linter, formatter, test runner, doc generator, and compiler as built-in subcommands.&lt;/p&gt;

&lt;p&gt;Node.js 24 has been closing this gap. Its built-in &lt;code&gt;node:test&lt;/code&gt; module now supports coverage, mocking, and &lt;code&gt;describe&lt;/code&gt;/&lt;code&gt;it&lt;/code&gt; APIs - making it a practical Jest replacement. But there is still no built-in bundler or linter, so tools like esbuild and ESLint remain part of the standard workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing an HTTP Server in Each Runtime
&lt;/h2&gt;

&lt;p&gt;Bun and Deno both use the web-standard &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt; API, making code portable to Cloudflare Workers, edge functions, and browser Service Workers. Node.js uses its own HTTP module by default.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 24:&lt;/strong&gt; &lt;code&gt;createServer()&lt;/code&gt; from &lt;code&gt;node:http&lt;/code&gt;, run with &lt;code&gt;node --experimental-strip-types server.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bun:&lt;/strong&gt; &lt;code&gt;Bun.serve({ fetch(req) {...} })&lt;/code&gt;, run with &lt;code&gt;bun run server.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deno:&lt;/strong&gt; &lt;code&gt;Deno.serve((req) =&amp;gt; {...})&lt;/code&gt;, run with &lt;code&gt;deno run --allow-net server.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Bun and Deno APIs align with how you write handlers in edge environments, which makes code more portable across deployment targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Readiness - Known Gaps
&lt;/h2&gt;

&lt;p&gt;Bun's main production concern is stability in long-running services. Memory leaks have been reported and partially patched through Bun 1.1.x, but developers continue to flag instability in processes running for days. There is no formal LTS program, so breaking changes can ship at any version. The ongoing Zig-to-Rust rewrite adds short-term uncertainty.&lt;/p&gt;

&lt;p&gt;Deno's main challenge is ecosystem coverage. Developer adoption sits at roughly 2.4% versus Node.js's 42.65%. npm compatibility covers about 90-95% of packages, but native addons and Node-internal-dependent packages remain hit-or-miss. Next.js and Remix are not fully supported.&lt;/p&gt;

&lt;p&gt;Node.js's limitations are more familiar: slower cold starts, no built-in bundler, and slower package installs. ESM/CommonJS migration friction, while improved in v24, still causes headaches on large codebases. TypeScript enum and decorator support still requires a build tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Which Runtime
&lt;/h2&gt;

&lt;p&gt;Here is a practical decision guide based on use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Serverless and edge functions&lt;/strong&gt; - Bun (8ms cold start, 35% faster Lambda)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-throughput REST APIs&lt;/strong&gt; - Bun (2-3x req/s in real apps, lower memory per process)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI tools and scripts&lt;/strong&gt; - Bun for single-file compiled binaries; Deno for sandboxed execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise and regulated workloads&lt;/strong&gt; - Node.js (30-month LTS, OpenJS Foundation governance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing Node.js codebases&lt;/strong&gt; - Node.js (ecosystem lock-in is real; switch only with measurable benefit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript-first greenfield projects&lt;/strong&gt; - Deno (built-in type checking, web standards first)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security-sensitive applications&lt;/strong&gt; - Deno (default-deny permissions; Bun has no sandbox; Node's model is opt-in)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time and WebSocket servers&lt;/strong&gt; - Bun (2.8x more messages/sec at 5,000 connections)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo tooling&lt;/strong&gt; - Bun (1.2s vs 32s install on an 847-package repo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge CDN deployment&lt;/strong&gt; - Deno (Deno Deploy is first-class; no Bun equivalent)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Install and Try Each Runtime Today
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Node.js via nvm&lt;/span&gt;
curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;

&lt;span class="c"&gt;# Bun&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://bun.sh/install | bash

&lt;span class="c"&gt;# Deno&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deno.land/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a TypeScript file in each:&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="c"&gt;# Node.js 24 - strips types only, no enums or decorators&lt;/span&gt;
node &lt;span class="nt"&gt;--experimental-strip-types&lt;/span&gt; script.ts

&lt;span class="c"&gt;# Bun - full TypeScript support&lt;/span&gt;
bun run script.ts

&lt;span class="c"&gt;# Deno - runs without type checking&lt;/span&gt;
deno run script.ts

&lt;span class="c"&gt;# Deno - with full type checking before run&lt;/span&gt;
deno check script.ts &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; deno run script.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To benchmark your actual server, install &lt;code&gt;autocannon&lt;/code&gt; and test under real load before committing to a runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The JavaScript runtime space in 2026 is no longer a single-winner conversation. Bun, Node.js, and Deno have each carved out distinct strengths - and the ecosystem is better for it. Bun pushed the industry to take cold start times and install speed seriously. Deno proved that TypeScript-first and security-by-default were viable design choices. Node.js responded by shipping a native test runner, built-in fetch, and TypeScript stripping.&lt;/p&gt;

&lt;p&gt;Pick the runtime that removes the most friction for your specific workflow. If you are running a high-throughput API that starts frequently, Bun's numbers are hard to argue with. If you are maintaining a large enterprise codebase with strict audit requirements, Node.js LTS is the path of least resistance. If you are writing TypeScript-heavy serverless functions or CLIs, Deno's zero-config type checking is genuinely pleasant.&lt;/p&gt;

&lt;p&gt;Run the benchmarks that matter for your actual application - five minutes of &lt;code&gt;autocannon&lt;/code&gt; on your real endpoint is worth more than any comparison table.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/javascript-runtime-comparison" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/javascript-runtime-comparison&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node.js official documentation: &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;https://nodejs.org/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bun official documentation: &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;https://bun.sh/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deno official documentation: &lt;a href="https://deno.com/" rel="noopener noreferrer"&gt;https://deno.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nvm (Node Version Manager): &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;https://github.com/nvm-sh/nvm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>DevToolLab Launches a Free Discord Community for Developers</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:05:25 +0000</pubDate>
      <link>https://dev.to/moksh/devtoollab-launches-a-free-discord-community-for-developers-5ggl</link>
      <guid>https://dev.to/moksh/devtoollab-launches-a-free-discord-community-for-developers-5ggl</guid>
      <description>&lt;p&gt;Building a developer toolkit is one thing, but creating a community around it is a different challenge. DevToolLab has expanded to 500+ browser-based utilities, and now there is a dedicated space where developers can connect, ask questions, and share what they are working on. The DevToolLab Discord server is open and free to join today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Discord Server Is Actually For
&lt;/h2&gt;

&lt;p&gt;The server has four core purposes. You can get direct help when a tool behaves unexpectedly. You can suggest new tools or request features - the best ideas have always come from developers who hit a real workflow gap. You can share what you are building and get feedback from peers. And you get early announcements: updates get posted in the server before they appear anywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Join the DevToolLab Discord Server
&lt;/h2&gt;

&lt;p&gt;Joining takes under a minute. Go to discord.gg/zzNzd9ssG in your browser. If you do not yet have a Discord account, creating one is free and does not require email verification to start reading. Once inside, accept the server rules and post a brief intro in the introductions channel. All channels are open to every member with no paid tiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Discord Works Well for a Developer Community
&lt;/h2&gt;

&lt;p&gt;Discord was chosen over Slack and forums for practical reasons. Threaded replies keep technical discussions organized without cluttering the main channel. Search is fully open with no login required, so useful answers from months ago remain accessible. And Discord is already part of the daily routine for a large percentage of developers, which reduces friction to participate.&lt;/p&gt;

&lt;h2&gt;
  
  
  About DevToolLab
&lt;/h2&gt;

&lt;p&gt;DevToolLab is a free collection of 500+ browser-based utilities built for developers who need quick results without installing anything. The toolset covers JSON formatting, base64 encoding, URL tracking, password generation, webhook inspection, and dozens of other everyday tasks. The Discord server extends the platform into a community - a place where users and the team can actually communicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Worth Bookmarking Before You Join
&lt;/h2&gt;

&lt;p&gt;Several DevToolLab tools come up regularly in community help threads. The Webhook Receiver generates a live URL for inspecting HTTP payloads without writing handling code. The Pastebin tool creates shareable links with syntax highlighting for 30+ languages. The Code to Image Converter produces clean PNGs from snippets for posting in Discord. The JSON Formatter cleans up API responses before you paste them into a help request.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://devtoollab.com/blog/devtoollab-discord-community" rel="noopener noreferrer"&gt;https://devtoollab.com/blog/devtoollab-discord-community&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevToolLab Discord Server invite: &lt;a href="https://discord.gg/zzNzd9ssG" rel="noopener noreferrer"&gt;https://discord.gg/zzNzd9ssG&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevToolLab X Account: &lt;a href="https://x.com/devtoollab" rel="noopener noreferrer"&gt;https://x.com/devtoollab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevToolLab GitHub Account: &lt;a href="https://github.com/DevToolLab" rel="noopener noreferrer"&gt;https://github.com/DevToolLab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevToolLab LinkedIn Account: &lt;a href="https://www.linkedin.com/company/devtoollab" rel="noopener noreferrer"&gt;https://www.linkedin.com/company/devtoollab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>community</category>
      <category>discord</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
