<?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: Marek Cziba</title>
    <description>The latest articles on DEV Community by Marek Cziba (@marekcziba).</description>
    <link>https://dev.to/marekcziba</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%2F4151454%2F2271e9c6-08f8-428e-b846-794e5f612944.png</url>
      <title>DEV Community: Marek Cziba</title>
      <link>https://dev.to/marekcziba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marekcziba"/>
    <language>en</language>
    <item>
      <title>Building a 16-Server MCP Monorepo in 48 Hours</title>
      <dc:creator>Marek Cziba</dc:creator>
      <pubDate>Wed, 30 Sep 2026 08:19:15 +0000</pubDate>
      <link>https://dev.to/marekcziba/building-a-16-server-mcp-monorepo-in-48-hours-2958</link>
      <guid>https://dev.to/marekcziba/building-a-16-server-mcp-monorepo-in-48-hours-2958</guid>
      <description>&lt;p&gt;Sixteen MCP servers. One repository. Two days. 197 tests, 65 tools, and a CI pipeline that fails loudly the moment any single server breaks.&lt;/p&gt;

&lt;p&gt;This is the story of how the &lt;a href="https://github.com/MarekCziba/mcp-servers" rel="noopener noreferrer"&gt;mcp-servers monorepo&lt;/a&gt; came together â€” and why I deliberately chose &lt;em&gt;many small servers&lt;/em&gt; over &lt;em&gt;one big framework&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 16 small servers instead of one
&lt;/h2&gt;

&lt;p&gt;The default instinct when building MCP tooling is to create a monolith: one server, a giant tool namespace, dozens of parameters. In practice that hurts in three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context bloat.&lt;/strong&gt; Every tool a server exposes is tokens an LLM pays for on every turn. A client that connects to a server advertising 65 tools burns context even if you only need &lt;code&gt;sha256&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All-or-nothing trust.&lt;/strong&gt; Nobody wants to grant an agent access to "the everything server." &lt;code&gt;hash-checksum-mcp&lt;/code&gt; is obviously harmless; a monolith is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All-or-nothing installs.&lt;/strong&gt; &lt;code&gt;uvx password-generator-mcp&lt;/code&gt; starts in milliseconds. A framework with 40 dependencies does not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So each server does exactly one job: hashing, base64, diffs, RSS reading, YouTube transcripts, SEO checks, unit conversion, and so on. The count of 16 is not a goal â€” it's just where "one tool family per server" ended up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repository layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mcp-servers/
â”œâ”€â”€ pyproject.toml          # workspace root: shared deps + ruff + pytest config
â”œâ”€â”€ uv.lock                 # one lockfile for everything
â”œâ”€â”€ .github/workflows/
â”‚   â”œâ”€â”€ ci.yml              # lint + test matrix + build matrix
â”‚   â””â”€â”€ publish.yml         # PyPI via Trusted Publishing (OIDC)
â””â”€â”€ servers/
    â”œâ”€â”€ hash-checksum-mcp/
    â”‚   â”œâ”€â”€ pyproject.toml        # its own package, entry point, deps
    â”‚   â”œâ”€â”€ hash_checksum_mcp/
    â”‚   â”‚   â””â”€â”€ main.py           # the MCP server
    â”‚   â”œâ”€â”€ tests/
    â”‚   â”‚   â””â”€â”€ test_hash-checksum-mcp.py
    â”‚   â”œâ”€â”€ Dockerfile
    â”‚   â””â”€â”€ README.md
    â”œâ”€â”€ regex-tester-mcp/
    â”œâ”€â”€ website-to-markdown-mcp/
    â””â”€â”€ ... (16 total)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract is simple: &lt;strong&gt;every directory under &lt;code&gt;servers/&lt;/code&gt; is an independently installable package, and the root is just glue.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The workspace contract
&lt;/h2&gt;

&lt;p&gt;The root &lt;code&gt;pyproject.toml&lt;/code&gt; never gets published. It exists to give every server the same dependencies, the same lint rules, and the same test configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mcp-servers-workspace"&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="py"&gt;"mcp&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"httpx&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.27&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"beautifulsoup4&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.12&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="c"&gt;# ... shared third-party libs&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[dependency-groups]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["pytest&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="py"&gt;pytest-asyncio&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.24&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="py"&gt;ruff&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="py"&gt;package&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;   &lt;span class="c"&gt;# root is not a package â€” only glue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each server declares its &lt;em&gt;own&lt;/em&gt; &lt;code&gt;pyproject.toml&lt;/code&gt; with a console entry point, which is what makes &lt;code&gt;uvx&lt;/code&gt; work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hash-checksum-mcp"&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["mcp&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[project.scripts]&lt;/span&gt;
&lt;span class="py"&gt;hash-checksum-mcp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hash_checksum_mcp.main:main"&lt;/span&gt;

&lt;span class="nn"&gt;[build-system]&lt;/span&gt;
&lt;span class="py"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"hatchling"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hatchling.build"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;code&gt;uvx hash-checksum-mcp&lt;/code&gt; runs with zero configuration, while &lt;code&gt;uv sync&lt;/code&gt; at the repo root installs everything for development in one shot. One lockfile, sixteen packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: 197 tests without a mega-suite
&lt;/h2&gt;

&lt;p&gt;There is no central &lt;code&gt;tests/&lt;/code&gt; directory. Each server ships its own tests next to its code, and the root pytest config wires imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pytest.ini_options]&lt;/span&gt;
&lt;span class="py"&gt;testpaths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;asyncio_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"auto"&lt;/span&gt;
&lt;span class="py"&gt;pythonpath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"servers/hash-checksum-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"servers/website-to-markdown-mcp/src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c"&gt;# ... every server&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests are real, not smoke tests. The hash server validates against published NIST and RFC vectors:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_sha256_nist_vector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generate_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&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;sha256&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="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad&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;CI then runs them as a &lt;strong&gt;matrix of 16&lt;/strong&gt; â€” one job per server â€” so a failure tells you exactly which server broke instead of burying you in one log with 400 tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI pipeline
&lt;/h2&gt;

&lt;p&gt;Three jobs on every push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;uv run ruff check servers&lt;/span&gt;
        &lt;span class="s"&gt;uv run ruff format --check servers&lt;/span&gt;
&lt;span class="na"&gt;test:   matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16 servers â†’ uv run pytest servers/&amp;lt;name&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;build:  matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16 servers â†’ python -m build per package&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;publish.yml&lt;/code&gt;, triggered on GitHub release, which ships selected packages to PyPI using &lt;strong&gt;Trusted Publishing&lt;/strong&gt; â€” OIDC, no API tokens stored anywhere. It defaults to &lt;code&gt;dry_run: true&lt;/code&gt; and takes an explicit &lt;code&gt;packages&lt;/code&gt; input, so publishing one server never risks the other fifteen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually bit me
&lt;/h2&gt;

&lt;p&gt;The build was fast; the fixes were the real work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;pythonpath&lt;/code&gt; and src layouts.&lt;/strong&gt; Fifteen servers use flat layouts, one uses &lt;code&gt;src/&lt;/code&gt;. Listing them individually in &lt;code&gt;pythonpath&lt;/code&gt; was the only reliable fix â€” pytest does not care about your cleverness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python version drift.&lt;/strong&gt; The &lt;code&gt;mcp&lt;/code&gt; SDK's &lt;code&gt;uuid7&lt;/code&gt; helper only exists on newer Pythons. A small fallback keeps servers installable on 3.10+, which is what &lt;code&gt;requires-python = "&amp;gt;=3.10"&lt;/code&gt; promises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;asyncio_mode = "auto"&lt;/code&gt;.&lt;/strong&gt; Forgetting this is the classic MCP testing bug: every test fails with "async def functions are not natively supported" and you waste an hour suspecting the SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding on Windows.&lt;/strong&gt; Writing UTF-8 markdown without a BOM and then reading it with an ANSI codepage mangles every arrow and emoji. Read files with an explicit encoding or pay for it in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Distribution: the part nobody writes tutorials for
&lt;/h2&gt;

&lt;p&gt;Building servers is the easy half. Getting them &lt;em&gt;installed&lt;/em&gt; is the other half:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PyPI&lt;/strong&gt; â€” Trusted Publisher configured, releases cut from GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glama&lt;/strong&gt; â€” listing live: &lt;a href="https://glama.ai/mcp/servers/MarekCziba/website-to-markdown-mcp" rel="noopener noreferrer"&gt;website-to-markdown-mcp&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCPMarket&lt;/strong&gt; â€” submitted to the free review queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smithery&lt;/strong&gt; â€” account registered; publishing requires a hosted Streamable HTTP endpoint, which is the next milestone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry rule learned the hard way:&lt;/strong&gt; Glama allows &lt;em&gt;one listing per repository&lt;/em&gt;. Submitting a second server from the same repo gets rejected â€” monorepo or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;Start the CI matrix from commit number one. I wrote the servers first and retrofitted the pipeline, which meant the first green run was a small celebration rather than a continuous heartbeat. Beyond that, the shape holds: small servers, one workspace, one lockfile, one matrix.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/MarekCziba/mcp-servers" rel="noopener noreferrer"&gt;github.com/MarekCziba/mcp-servers&lt;/a&gt; â€” MIT, &lt;code&gt;uvx&lt;/code&gt;-installable, 65 tools waiting for an agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously: &lt;a href="https://www.linkedin.com/pulse/mcp-vs-apify-custom-agents-when-use-each-marek-cziba-rltzf/" rel="noopener noreferrer"&gt;MCP vs Apify vs Custom Agents: When to Use Each&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>MCP vs Apify vs Custom Agents: When to Use Each</title>
      <dc:creator>Marek Cziba</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:57:30 +0000</pubDate>
      <link>https://dev.to/marekcziba/mcp-vs-apify-vs-custom-agents-when-to-use-each-4pl0</link>
      <guid>https://dev.to/marekcziba/mcp-vs-apify-vs-custom-agents-when-to-use-each-4pl0</guid>
      <description>&lt;h1&gt;
  
  
  MCP vs Apify vs Custom Agents: When to Use Each
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Marek Cziba — Open-source MCP Server Developer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every team building with LLMs eventually hits the same wall: the model can reason, but it cannot &lt;em&gt;do&lt;/em&gt;. Three solutions compete for that gap — MCP servers, Apify actors, and custom agents. They are not interchangeable. Choosing wrong costs weeks of rework; choosing right ships a working product in days.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three options, honestly defined
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; is a protocol, not a product. An MCP server exposes your tools — functions, APIs, data sources — to any MCP-compatible client (Claude Desktop, Claude Code, Cursor, and growing list of others) in a standard shape: tools, resources, prompts. You write the server once, and every client that speaks MCP can use it. The value is &lt;em&gt;interoperability&lt;/em&gt;: one integration, many consumers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apify&lt;/strong&gt; is a hosted platform of pre-built automation units called actors — mostly web scraping, data extraction, and browser automation — run and monitored in the cloud, priced per compute unit. The value is &lt;em&gt;infrastructure you don't operate&lt;/em&gt;: proxies, browsers, scheduling, storage, and thousands of actors someone else maintains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom agents&lt;/strong&gt; are your own code: a loop that calls an LLM, executes code or API calls, checks results, and iterates. Frameworks vary, but the defining trait is that &lt;em&gt;you own the control flow&lt;/em&gt;. The value is &lt;em&gt;arbitrary complexity&lt;/em&gt; — multi-step workflows, business rules, state, and rollback that neither MCP nor Apify provides out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each one wins
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MCP: you have tools, and clients are the bottleneck
&lt;/h3&gt;

&lt;p&gt;MCP is the right answer when you've already built (or can build) a capability as a simple function call, and the problem is getting it into the hands of AI users across different clients.&lt;/p&gt;

&lt;p&gt;Signals you're in this situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team uses several MCP clients and refuses to maintain one integration per client.&lt;/li&gt;
&lt;li&gt;The capability is deterministic: convert a URL to Markdown, compute a hash, read an RSS feed, query a database. No orchestration needed — one call, one result.&lt;/li&gt;
&lt;li&gt;You want distribution. MCP registries and directories are where AI users look for tools; a well-listed server gets installed by strangers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost profile: low at runtime (your code runs wherever it runs), moderate up front (one server per capability surface). Risk: you own correctness, rate limits, and auth of whatever the server wraps.&lt;/p&gt;

&lt;p&gt;Our 16-server monorepo is exactly this shape — each server does one thing (base64, UUID v7, regex testing, website-to-Markdown conversion) with zero infrastructure. &lt;code&gt;uvx hash-checksum-mcp&lt;/code&gt; and it's live in any client. Nothing to host, nothing to babysit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apify: the hard part is the web itself
&lt;/h3&gt;

&lt;p&gt;Scraping looks easy until you hit rotating IPs, headless browser farms, CAPTCHAs, and HTML that changes weekly. Apify wins when your target is &lt;em&gt;the open web at scale&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need thousands of pages, not ten.&lt;/li&gt;
&lt;li&gt;The site fights back (bot detection, geo-blocking, login walls).&lt;/li&gt;
&lt;li&gt;You want scheduled runs, dataset storage, and proxies without building any of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost profile: predictable per compute unit, but it scales with volume — a heavy crawl is real money. Risk: vendor dependency, and actor quality varies (community actors break when target sites change).&lt;/p&gt;

&lt;p&gt;Custom code can scrape ten pages fine. It cannot cheaply replicate a proxy network and a browser farm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom agents: when the workflow is the product
&lt;/h3&gt;

&lt;p&gt;Build your own agent loop when the value is in the &lt;em&gt;sequence&lt;/em&gt;, not the single call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-step decisions with state: "gather data → validate → reconcile → draft → require human approval → publish."&lt;/li&gt;
&lt;li&gt;Business logic that doesn't fit anyone's API: internal rules, legacy system glue, transactional rollbacks.&lt;/li&gt;
&lt;li&gt;Latency, cost, or compliance constraints that forbid shuffling data through third parties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost profile is inverted: highest up-front, lowest marginal cost — and full control. The risk is that you also own every failure mode: retries, evaluation, prompt drift, monitoring. An agent that "works in the demo" needs engineering to survive production.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision flow that actually resolves
&lt;/h2&gt;

&lt;p&gt;Ask these five questions in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the work a single, deterministic call?&lt;/strong&gt; → MCP server. Stop here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it primarily web scraping or browser automation at scale?&lt;/strong&gt; → Apify actor (or your own crawler only if you have unusual compliance needs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it require multi-step decisions, state, or human-in-the-loop?&lt;/strong&gt; → custom agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do different AI clients need the same capability?&lt;/strong&gt; → whatever the engine is, expose it via MCP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are you about to write plumbing that Apify already sells?&lt;/strong&gt; → buy it, wrap it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Question 4 is the trap people miss: the engine and the interface are orthogonal. The most common production pattern we see is &lt;em&gt;Apify for execution, MCP for delivery&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hybrid: MCP server wrapping an Apify actor
&lt;/h2&gt;

&lt;p&gt;This combination covers 80% of real-world deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI client (Claude, Cursor)
    │  MCP protocol
    ▼
MCP server you control          ← auth, validation, business rules
    │  API call
    ▼
Apify actor                     ← browsers, proxies, storage, scheduling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP server stays thin: it validates input (critical — the LLM will eventually pass garbage), maps results into clean tool responses, and enforces your API keys. Apify does the hostile-web heavy lifting. You get MCP's interoperability and Apify's infrastructure without owning either's downside.&lt;/p&gt;

&lt;p&gt;We use the same shape inside the monorepo: servers wrap libraries (not platforms), but the boundary logic — validate in, normalize out — is identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each one costs you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Build speed&lt;/th&gt;
&lt;th&gt;Runtime cost&lt;/th&gt;
&lt;th&gt;You own&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hours–days&lt;/td&gt;
&lt;td&gt;Your hosting only&lt;/td&gt;
&lt;td&gt;Correctness, auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apify actor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minutes (existing)&lt;/td&gt;
&lt;td&gt;Per compute unit&lt;/td&gt;
&lt;td&gt;Input mapping, budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Days–weeks&lt;/td&gt;
&lt;td&gt;LLM tokens + infra&lt;/td&gt;
&lt;td&gt;Everything: retries, evals, drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apify + MCP hybrid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;td&gt;Compute + your thin server&lt;/td&gt;
&lt;td&gt;Input validation, spend limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; is how tools reach AI clients. If you have a capability, package it as MCP — that's distribution, not architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apify&lt;/strong&gt; is where hard web automation already lives. Buy the crawling, don't build it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom agents&lt;/strong&gt; are for workflows where the sequence itself is your product — and you're willing to own the production engineering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams don't choose one forever. They start with the cheapest thing that works (often a hosted actor or an MCP server wrapping an existing API), and reach for a custom agent only when the workflow — not the tool — becomes the differentiator.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Source code: &lt;a href="https://github.com/MarekCziba/mcp-servers" rel="noopener noreferrer"&gt;github.com/MarekCziba/mcp-servers&lt;/a&gt; — 16 MCP servers, one monorepo, full CI/CD to PyPI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Install the flagship in one line: &lt;code&gt;uvx website-to-markdown-mcp&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills &amp;amp; Keywords
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MCP&lt;/code&gt; &lt;code&gt;ModelContextProtocol&lt;/code&gt; &lt;code&gt;Apify&lt;/code&gt; &lt;code&gt;AIAgents&lt;/code&gt; &lt;code&gt;FastMCP&lt;/code&gt; &lt;code&gt;Automation&lt;/code&gt; &lt;code&gt;Scraping&lt;/code&gt; &lt;code&gt;LLM&lt;/code&gt; &lt;code&gt;OpenSource&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contact
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/marek-cziba-669521372/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/marek-cziba-669521372/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/MarekCziba" rel="noopener noreferrer"&gt;https://github.com/MarekCziba&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/user/marekcziba/" rel="noopener noreferrer"&gt;https://pypi.org/user/marekcziba/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>mcp</category>
      <category>apify</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
