Skip to main content
Glama

Python MCP servers that are read-only until you say otherwise. One command gives you a working server on the official MCP Python SDK v2, with guardrails that are tested and on by default.

Test template PyPI sett-mcp MCP server – quality and maintenance score on Glama

uvx sett-mcp new my-server

A sett is a badger's den, dug deep and hard to break into. Setts are also the square stones that pave a solid road.

Why

Connecting an AI assistant to a real system is easy. Being comfortable leaving it connected is the hard part: what happens when the model decides to "clean up" some records? Most MCP starters give you a tool decorator and leave the rest to you. This one starts from the question your ops or security lead will ask.

Related MCP server: MCP Mingdao

What you get

Guardrail

How it works

Write gate

Write tools aren't registered unless <PREFIX>_ENABLE_WRITES=true. The model can't call what it can't see. The policy file can't flip this; only whoever runs the server can.

Confirmations

Destructive tools ask the user first ("Permanently delete contact c001?"), using multi-round-trip requests on the 2026-07-28 protocol and elicitation on older ones. Clients that can't show a prompt are refused, not waved through. See client support.

Allow / deny lists

Tools the policy excludes are never registered.

Field masking

email, phone or any field you list becomes [masked] before results reach the model or the audit log. A write that passes [masked] back is refused, so the placeholder can't overwrite real data.

Audit log

One JSON line per call: tool, kind, outcome, duration, masked arguments.

Tool hints

Every tool is marked read-only or destructive so clients can warn users.

Strict policy file

Unknown keys fail at startup, so a typo can't silently turn masking off.

Plus the plumbing that usually takes an afternoon:

  • A runnable example (a small contacts store) you replace with your real API client

  • install <client> for Claude Desktop, Cursor, Claude Code and Codex, with --writes to turn write tools on, and a troubleshooting guide in every project

  • Stdio by default, stateless Streamable HTTP with serve --http, tested against the edge cases that break real clients

  • Remote in one command: deploy vercel --auth supabase hosts the server on your own Vercel account with Supabase for sign-in: a consent page, invite-only users, a writer role for write tools, confirmations that work across server instances, and an audit table. Remote mode also works with any OAuth provider on any host

  • Setup instructions for Claude Desktop, Claude Code, Cursor, the MCP Inspector and Docker

  • Tests that drive the server through a real MCP client on both protocol versions

  • GitHub Actions for CI and PyPI publishing, so users can run uvx your-server

  • A Dockerfile, glama.json and a publishing guide for PyPI, Glama, the awesome-mcp-servers list and the official MCP Registry

  • A CLAUDE.md that keeps Claude Code from bypassing the guardrails when it adds tools, and shows it how to describe tools well

Quick start

uvx sett-mcp new my-server
cd my-server
uv sync
uv run --extra dev pytest                  # the guardrail test suite
uv run my-server install claude-desktop    # or cursor, claude-code, codex, all; then restart it

To host it for others, with sign-in, on your own Vercel and Supabase accounts:

uv run my-server deploy vercel --auth supabase --supabase-project <ref>
uv run my-server users add you@example.com --writer

Copier asks for a name, tool prefix and environment-variable prefix. Everything else is filled in. New to this? docs/getting-started.md walks through it from installing uv, on Windows and macOS.

Adding a tool looks like this:

@guard.read(name="acme_list_invoices")
async def list_invoices(status: str = "open") -> list[dict]:
    """List invoices by status."""
    return await api.invoices(status=status)

@guard.write(name="acme_void_invoice", destructive=True,
             confirm_message="Void invoice {invoice_id}? This can't be undone.")
async def void_invoice(invoice_id: str) -> dict:
    """Void an invoice."""
    return await api.void(invoice_id)

Client support

Tested in November 2026 with the example server this template generates:

Client

Tools and masking

Confirmation prompts

Claude Code

✅

✅ Shows an Accept / Decline prompt, including in auto mode

Claude on the web (claude.ai, remote connector)

✅

✅ Shows a "requests your input" card with Submit / Decline. Tested against a server deployed with deploy vercel --auth supabase

Cursor

✅

✅ Shows a Submit / Cancel prompt. Answer it promptly: Cursor gives up on a call that waits too long, then retries

Codex CLI 0.167

✅

✅ Shows an Allow / Deny / Cancel prompt, after Codex's own approval for the tool call. Sett 0.2 and earlier sent a form schema Codex rejects, so it cancelled without asking (openai/codex#60158)

Claude Desktop 4.x (Windows)

✅

✅ for remote servers (a connector such as one made with deploy vercel): shows the same Submit / Decline card as the web. ⛔ for local servers: the app reaches them through a bridge that doesn't carry prompts, so tools that need confirmation are refused and nothing changes

What that means in practice:

  • A confirmation is only as strong as the client. Sett can't tell a person clicking Accept from a client that accepts automatically. The write gate and the allow / deny lists don't depend on the client at all, so treat those as the hard limits.

  • Where prompts can't reach (local servers in Claude Desktop today), keep the default confirm: destructive, or deploy the server remotely so the prompt can be shown: deploy vercel --auth supabase --supabase-project <ref> --writes, give the person the writer role with users add <email> --writer, and add the /mcp address as a connector. Without --writes, write tools aren't registered at all. Ordinary writes still work, and destructive ones are refused. Setting confirm: off makes them run without asking.

  • Claude may refuse on its own. In our tests Claude sometimes declined to call a tool described as permanently deleting data, or asked in chat first. That's a welcome extra layer, but it depends on the model and the conversation, which is why the server enforces confirmation itself.

Tried another client? Open an issue with what you saw and we'll add it to the table.

Keeping generated projects current

Guardrail fixes and SDK updates land here. Pull them into any project made from the template with:

uvx sett-mcp update

sett-mcp is a thin wrapper around Copier, so uvx copier copy gh:kclarkedesign/sett-mcp my-server and copier update work too.

Migrating from FastMCP v1

SDK v2 renamed FastMCP to MCPServer and changed a dozen other things. See docs/migrating-from-v1.md for the checklist and how to move a hand-rolled write gate onto the guardrails.

Roadmap

  • A preflight sett-mcp check that tests a running server against real clients' quirks

  • One-click .mcpb bundles for Claude Desktop

  • More deploy targets: Cloudflare Workers, Cloud Run, Fly

  • Example connectors, starting with a read-only Salesforce analyst server

Contributing

Issues and pull requests are welcome. To work on the template itself:

uvx copier copy --trust --defaults --vcs-ref HEAD . /tmp/check
cd /tmp/check && uv run --extra dev pytest

License

Apache-2.0. Projects you generate can choose Apache-2.0 or MIT. Logo and icon files are in assets/, including an animated reveal and a looping loading mark (rebuild them with node scripts/build_motion.mjs).

Available Tools

2 tools
acme_get_contactA
Read-only

Get every field of one contact by its id.

Use this when you already have an id, for example from search results. If you only know a name or company, use search instead.

NameRequiredDescriptionDefault
contact_idYesThe contact's id, as returned by search (for example c001).

Output Schema

NameRequiredDescription

No output parameters

acme_search_contactsA
Read-only

Find contacts whose name, company or any other field contains the query (case-insensitive).

Use this first when you don't know a contact's id. Each result includes the id to pass to the other contact tools. Masked fields show as "[masked]".

NameRequiredDescriptionDefault
limitNoMost results to return, from 1 to 100.
queryYesText to look for, such as a name, company or email domain.

Output Schema

NameRequiredDescription
resultYes

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.1
    • Changedacme_get_contact1 field changed
      • addedInput schema / properties / contact_id / description
        Added value: +"The contact's id, as returned by search (for example c001)."
    • Changedacme_search_contacts4 fields changed
      • addedInput schema / properties / limit / description
        Added value: +"Most results to return, from 1 to 100."
      • addedInput schema / properties / limit / maximum
        Added value: +100
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • addedInput schema / properties / query / description
        Added value: +"Text to look for, such as a name, company or email domain."
  2. 2 tool updates
    • First observedacme_get_contact
    • First observedacme_search_contacts

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

The two tools are cleanly separated: search by fuzzy field match versus get by exact id. Descriptions explicitly tell the agent which to use and reference each other, leaving no realistic chance of misselection.

Naming Consistency4/5

Both names follow the same `acme_verb_noun` convention, which is predictable. The only nit is `search_contacts` (plural) versus `get_contact` (singular), a minor deviation that does not hinder readability.

Tool Count3/5

Two tools is thin for a contact-management surface, though it is plausible for a deliberately read-only gateway. It sits at the borderline where the surface feels minimal rather than well-scoped.

Completeness2/5

The surface covers only read access: search and retrieve by id. There is no way to create, update, delete, or enumerate contacts, so any write workflow or bulk listing is a dead end.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A minimal demonstration MCP server showcasing basic tools, resources, and prompts functionality. Built as a template for learning and starting new MCP server projects with Smithery SDK.
    8 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A minimal MCP server template demonstrating basic tools, resources, and prompts functionality built with Smithery SDK.
    ISC
  • A
    license
    Not graded
    quality
    D
    maintenance
    A production-ready Python template for building MCP servers with enterprise features including registry integration, configuration management, structured logging, and extensible patterns for tools, resources, and prompts.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A minimal template MCP server demonstrating basic tools, resources, and prompts functionality, built with Smithery SDK for quick prototyping and deployment.
    9 npm
    ISC