Back to DevLog

Building a Tiered License Gate for MemStack: From Free to Pro with Grace Periods

4 min read

Just wrapped up a marathon 90-minute session building out the complete license validation system for MemStack. What started as a simple "check if they have a pro key" turned into a full tiered access system with grace periods and tamper detection.

The Journey from Simple to Sophisticated

I began with a straightforward goal: gate MemStack's MCP tools behind a Pro license. But as I worked through the implementation with my multi-agent setup (Manager, Builder, Reviewer via agent-bridge), the requirements evolved into something much more nuanced.

Version 1: Basic Pro Gate

First iteration was clean and simple:

  • Built license.py with async validation against my AdminStack API
  • Added in-memory caching with server-controlled TTL
  • Implemented per-key locks to prevent duplicate API calls
  • License key sourced from env var or ~/.memstack/license.json
  • Graceful degradation for network issues, immediate rejection for HTTP 4xx errors

The Reviewer caught 6 issues including a nasty async race condition. All 11 unit tests passed after fixes.

Version 2: Tiered Access Strategy

Then I realized a binary pro/no-pro gate was too harsh. Users need a taste before they buy:

  • No key: Fully blocked with "Get your free key at memstack.pro" message
  • Free key: Access to 77 out of 80 skills
  • Pro key: Full access to all 80 skills

The three Pro-exclusive skills I held back: consolidate, context-db, and api-docs - the power user features.

I also added email capture to the activate_license tool for contact tracking in AdminStack. The Reviewer caught some skill name leakage issues for unauthorized users that I had to patch up.

Version 3: The Grace Period Challenge

Here's where it got interesting. Existing users had been using MemStack for free, and I didn't want to rug-pull them. Solution: 14-day grace period with full Pro access.

The implementation details got tricky:

  • Grace state stored in ~/.memstack/grace-period.json
  • In-memory caching, refreshed daily (not on every tool call)
  • Threading locks to prevent file creation races
  • Explicit grace_expired field to avoid sentinel value ambiguity

Reviewer caught two critical bugs here that could have caused nasty race conditions.

Version 4: HMAC Tamper Detection

Because this is a local file system, what's stopping someone from just editing the grace period JSON? Enter HMAC-SHA256 signing:

  • Machine-specific secret: HMAC(hostname + username, "memstack-grace-v1")
  • Legacy files auto-migrated on first read
  • Tampered files immediately blocked with integrity failure
  • Cached machine secret via @functools.lru_cache

Of course, the Reviewer found a crash bug where I was unpacking a 3-tuple into 2 variables. Classic.

The Architecture Decisions

Some key choices I made:

Gate at MCP server level: Most robust approach. Skills are completely inaccessible without proper licensing, not just warned about.

Graceful degradation strategy: Network errors allow stale cache usage, but HTTP 4xx errors (invalid keys, etc.) are immediate rejections.

Machine-tied secrets: No external key management needed. The HMAC secret is deterministic but machine-specific.

Grace period = Pro access: Since existing users had full access before, the grace period grants Pro-level access to all 80 skills.

What's Next

The core system is done and pushed, but there's more to build:

  • Landing page at memstack.pro with free key signup
  • AdminStack UI for license management (create, revoke, upgrade)
  • Move the Pro-exclusive skill list to config instead of hardcoded
  • End-to-end testing of the email capture flow
  • Usage analytics to see which skills people actually use by tier

This was one of those sessions where the scope creep was actually good creep. What started as a simple gate turned into a thoughtful user experience that respects existing users while creating a clear upgrade path.

Now I need to go fix that python3python bug in the memstack-pro hooks that I somehow left uncommitted. The little things always get you.

Share this post