Scriptable Blog

Implementation-focused tutorials for engineers. Every article ships with a companion project that was actually built, run, and tested on macOS.

Running a Local Package Registry on macOS: Rating the Options for npm, Python, and More

You want a package registry running on your own Mac. The reasons are usually one of these: hosting private packages that should not go to a public index, rehearsing a publish before it becomes irreversible, working offline or air-gapped, caching upstream packages so CI is not at the mercy of npmjs.com or PyPI, or sharing internal libraries across a small team without paying for SaaS. The options range from a one-process container that does exactly one thing to a multi-gigabyte Java application that hosts a dozen package formats. This article rates the realistic contenders, then proves the top picks by actually publishing and installing real npm and Python packages against them. Docker is on the table throughout; every registry here runs as a container. ...

15 min

Build a Python CLI Tool and Distribute It on GitHub with uvx

A sibling article, Publish a FastMCP Server to PyPI and Run It Anywhere with uvx, ships a package to the public PyPI index. This one skips the index entirely. If your code is a Git repository, uvx can install and run its console script straight from the repo: uvx --from git+https://github.com/your-username/textkit textkit --help That command clones the repo into a cached, throwaway environment, builds the package, and runs its console script — no git clone, no virtualenv, no pip install on the user’s side. For a personal utility, an internal tool, or anything you are not ready to name on PyPI, a GitHub repo is the whole distribution channel. ...

14 min

Dockerize an MCP Server on macOS

Packaging an MCP server as a Docker image gives clients one launch command with no Python, no uv, and no virtual environment to manage on the host: they run the container. This tutorial builds a small, non-root image for a FastMCP server with a multi-stage build, runs it over stdio the way a client does, and wires it into a client’s .mcp.json with docker run. The build uses uv in the builder stage and copies only the finished virtual environment into a slim runtime stage, so the final image carries the app and its dependencies — not the build toolchain. ...

14 min

CI/CD for an MCP Server: Lint, Test, Build, and Publish with GitHub Actions on macOS

An installable MCP server needs the same release discipline as any Python package: lint and test every change, build the artifact, and publish on a tagged release. This tutorial wires that pipeline with GitHub Actions and uv — ruff for linting and formatting, pytest for tests, uv build for the wheel/sdist, and PyPI trusted publishing (OIDC, no stored token) on a version tag. It is the CI/CD companion to Publish a FastMCP Server to PyPI and Run It Anywhere with uvx. ...

13 min

Harden an MCP Server: A Threat Model and Defenses on macOS

An MCP server is an attack surface. It runs with real privileges (a filesystem, API credentials, a database), it accepts arguments chosen by a model that may be under an attacker’s influence, and its results flow straight back into a model’s context. Most MCP defenses are the server author’s responsibility — the client cannot enforce them for you. This tutorial builds one hardened FastMCP server, a sandboxed “knowledge base,” and demonstrates a defense for each of the common MCP threats: input validation, the confused-deputy problem, prompt injection carried in tool results, tool poisoning, and the token-passthrough anti-pattern. Every defense is backed by a test, so you can see it hold. ...

17 min

Debug an MCP Server with the MCP Inspector on macOS

The MCP Inspector is the official developer tool for MCP servers. It has two faces: a web UI for clicking through tools, resources, and prompts interactively, and a CLI mode that drives the same server headlessly and prints JSON — scriptable, diffable, and easy to drop into a Makefile or CI. This tutorial uses both against a small FastMCP server, then reproduces and diagnoses a broken stdio handshake, the most common failure when a client refuses to connect. ...

13 min

Build an MCP Server in Rust with the Official SDK

This is the Rust companion to Build an MCP Server in Go with the Official SDK and Build an MCP Server in TypeScript with the Official SDK. It builds the same kind of server — two tools and a resource — with the official Rust SDK (rmcp), served over the stdio transport so a local client launches it as a subprocess. The result is a single compiled binary with no runtime beyond itself. ...

18 min

Fine-Grained Authorization for a FastMCP Server on macOS

An email allowlist answers one question: is this caller allowed in at all? Real servers need a second answer: which of my tools may this particular caller use? A reader should be able to list notes but never delete one; an admin should be able to delete anyone’s. That is authorization, and FastMCP gives you two levers for it: a declarative require_scopes(...) on each tool, and the caller’s identity inside a handler for checks a static scope cannot express. ...

17 min

A Testing Strategy for MCP Servers on macOS

Most MCP servers get a handful of assert result == ... tests bolted on and nothing more. That misses the failures that actually bite: a tool quietly renamed, a parameter that changed type, a result that grew a field, an error that stopped being an error. A server is a contract a model depends on, and the contract needs its own tests. This tutorial builds a small, deterministic FastMCP server and then a layered test suite around it with four kinds of tests, each catching a different class of regression: ...

14 min

Wrap a Local CLI as MCP Tools on macOS

A command-line tool you already trust makes a good MCP tool: it has a stable interface, it is installed on the box, and its behavior is documented. The work is not reimplementing it, it is exposing it safely — mapping subcommands and flags to typed inputs, feeding untrusted input as data rather than shell, bounding runtime, and turning a non-zero exit into a clean tool error instead of a stack trace. ...

15 min