{
  "$schema": "https://modelcontextprotocol.io/schema/2025/well-known/mcp.json",
  "name": "SpotMe",
  "description": "Mexico's marketplace for storage and parking spaces. Read-only discovery tools for finding mini-bodegas, estacionamiento, bodegas comerciales, and naves industriales across 30+ Mexican cities. SpotMe also operates Almacenamiento a Domicilio (door-to-door storage with pickup and delivery) in Monterrey and CDMX — surfaced via the regular landing pages, not via dedicated MCP tools at this time. An optional admin tier (Firebase-bound OAuth) exposes incident-debugging tools for SpotMe staff.",
  "publisher": {
    "name": "Northern Land Domain S.A.P.I. de C.V.",
    "url": "https://www.spotme.mx",
    "country": "MX"
  },
  "endpoints": [
    {
      "url": "https://server.spotme.mx/mcp",
      "transport": "streamable-http",
      "protocolVersion": "2025-06-18"
    }
  ],
  "auth": {
    "type": "oauth2",
    "description": "OAuth 2.1 with PKCE (S256). Grant types: authorization_code + refresh_token. Two scopes: `mcp:read` for the public discovery surface (auto-approve, no consent screen) and `mcp:admin` for SpotMe-staff-only debugging tools (Firebase Google sign-in, isAnyAdmin verified). Anonymous bearer access is also accepted as a fallback for crawlers and curl tests on the read scope, with stricter per-IP limits.",
    "grant_types_supported": ["authorization_code", "refresh_token"],
    "authorization_servers": ["https://server.spotme.mx"],
    "metadata_endpoint": "https://server.spotme.mx/.well-known/oauth-authorization-server",
    "protected_resource_endpoint": "https://server.spotme.mx/.well-known/oauth-protected-resource/mcp",
    "scopes_supported": ["mcp:read", "mcp:admin"],
    "dynamic_client_registration": "https://server.spotme.mx/oauth/register",
    "anonymousLimits": {
      "requestsPerMinutePerIp": 20,
      "listingsPerCall": 3,
      "listingsPerDayPerIp": 30
    },
    "keyedLimits": {
      "requestsPerMinutePerVendor": 60,
      "listingsPerCall": 5,
      "listingsPerDayPerVendor": 200
    },
    "adminTier": {
      "scope": "mcp:admin",
      "trigger": "Append `?admin=1` to the MCP URL when adding the connector (e.g. `https://server.spotme.mx/mcp?admin=1`). The OAuth flow then renders a Firebase Google sign-in page; only Firebase UIDs with `isAnyAdmin === true` get an admin-bound token.",
      "tokenBinding": "Tokens carry the verified `firebaseUid`. Token storage is DynamoDB-backed (oauthtoken table) so tokens survive deploys. Access tokens live MCP_TOKEN_TTL_DAYS days (default 30); refresh tokens (rotated, ~90 days) silently re-mint access tokens with no Google re-sign-in, and re-verify isAnyAdmin + the revocation list on every refresh.",
      "perAdminDailyCaps": "Raised for primary daily debugging + env-tunable (no redeploy): default 5000 total tool calls/day, 120 req/min, 5000 event rows/day, and per-resource distinct-ID caps in the 300–2000 range. Override via MCP_ADMIN_TOTAL_CALLS_PER_DAY / MCP_ADMIN_EVENTS_PER_DAY / MCP_ADMIN_REQ_PER_MIN / MCP_ADMIN_DISTINCT_ID_CAP. The audit trail + kill switch + revocation list are the real backstop; caps are a leaked-token blast-radius limiter.",
      "audit": "Every admin tool call writes an `mcp.admin.audit` log entry (firebaseUid, email, tool, entityId, args, found, durationMs, ip).",
      "killSwitch": "`MCP_ADMIN_DISABLED=true` → all admin tools 503 immediately. `MCP_ADMIN_REVOKED_UIDS=<csv>` → block specific admin UIDs from new sign-ins."
    }
  },
  "tools": {
    "public": [
      {
        "name": "search_listings",
        "description": "Find rentable spaces in a Mexican city by space type. Returns up to 5 listings (3 anonymous) with short URLs to spotme.mx.",
        "inputs": {
          "city": "string (required) — city slug or name",
          "spaceType": "self-storage | parking | warehouse | industrial-warehouse (optional)",
          "maxPriceMxn": "number (optional)",
          "minSizeM2": "number (optional)"
        }
      },
      {
        "name": "get_city_overview",
        "description": "Summary of SpotMe inventory in a Mexican city: counts by space type, top neighborhoods, and price ranges.",
        "inputs": {
          "city": "string (required)"
        }
      },
      {
        "name": "get_pricing_guide",
        "description": "Estimated monthly price range (MXN) for a space type in a Mexican city. Pure benchmark — does not return individual listings.",
        "inputs": {
          "city": "string (required)",
          "spaceType": "self-storage | parking | warehouse | industrial-warehouse (required)",
          "sizeM2": "number (optional)"
        }
      },
      {
        "name": "get_listing_summary",
        "description": "Look up a single SpotMe listing by short ID (the segment after spotme.mx/c/).",
        "inputs": {
          "nanoId": "string (required)"
        }
      }
    ],
    "admin": [
      {
        "name": "admin_resolve_id",
        "description": "Identify what kind of SpotMe entity an opaque string is (Firebase UID, email, UUID, Stripe ID, listing nanoId, HubSpot ID). Returns suggested next admin_get_* tool. Cheap router — use this first when you have a raw string.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_search_users",
        "description": "Find users WITHOUT a known UID — search name/email/phone text, or pull a cohort (KYC verification queue, leads, admins, recent signups). Returns summaries + UIDs to chain into admin_get_user. The symptom→records entry point for users.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_search_checkouts",
        "description": "List checkouts scoped to a host / team / location / configuration, optionally filtered by lifecycle (PENDING/UP_COMING/ACTIVE/PAST/EXPIRED). Returns summaries + checkoutIds. Requires at least one scope ID (no unbounded platform-wide dump).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_search_closed_deals",
        "description": "List off-platform broker / traditional deals by dealType + billingStatus (NONE/ACTIVE/OVERDUE/CANCELLED/ANY). Returns summaries + closedDealIds. Use OVERDUE to find deals with a past-due invoice.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_search_locations",
        "description": "Pull location cohorts: verification_pending (listings awaiting review) or commercial (bodega comercial / nave industrial inventory). Returns summaries + locationIds. For one host's listings use admin_get_user.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_user",
        "description": "Internal user record for a Firebase UID — full record (admin sees unredacted via hideConfidentialUserInfo), recent listing/reservation/rent IDs, fiscal context (RFC, regimenFiscal, default fiscal type, retention pct), notification state (notiPreferences with per-genre disabled methods + preferedNotiMethod + pending email/phone changes via userChanges), teamMemberships [{teamId, teamName, role, addedOn}], hubspotId. Stripe IDs hidden by default.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_team",
        "description": "Internal team record (corporate / broker host group) — members [{userId, role, addedOn}] with role counts (admins/members), listings, reservations, fiscal info, Stripe Connect account. Set includeMemberContacts=true to additionally batch-load each member's email/phone/name/isVerified/notiPreferences (useful for debugging notification delivery issues).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_location",
        "description": "Internal location (listing) record — address, configurations, host, team, attribution, hubspot/zoho IDs, draft/deactivated/deleted flags.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_configuration",
        "description": "Internal configuration (rentable unit) record by UUID or short nanoId — resolves the parent location and returns the raw config (admin shape).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_checkout",
        "description": "Internal checkout (rental) record — full status, dates, pricing, commission override, Stripe IDs, grace period / scheduled cancellation, attribution. Returns IDs for landlord/renter/teams/location/configuration/chat to chain into.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_closed_deal",
        "description": "Internal closed deal record — off-platform broker / traditional rental, includes renter contact details and matched configuration.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_subscription",
        "description": "Stripe subscription + recent invoices, cross-referenced with the SpotMe checkout that owns it. Provide either stripeSubscriptionId or checkoutId.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_events",
        "description": "Query analytics events by userId or sessionId, optionally filtered by EventAction. Use to debug attribution, funnel drop-off, or what a session actually did.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_chat",
        "description": "Pull a LangGraph WhatsApp / in-app chat thread for chatbot debugging. Returns participant IDs, recent messages with text content (older than 90 days = metadata only).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_hubspot_links",
        "description": "Translate a SpotMe userId to its HubSpot contact + owner mapping. Returns hubspotId + portal URL so you can hop into the HubSpot MCP for the actual deal/contact record.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_auth_status",
        "description": "Firebase Auth state for a user (by userId or email): disabled flag, emailVerified, sign-in providers, last sign-in / creation time, custom claims, tokensValidAfterTime. Answers 'why can't this user log in?' — distinct from admin_get_user (the DynamoDB record).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_email_activity",
        "description": "SendGrid delivery state for an email address — answers 'did user X get email Y, and if not why?'. Suppression lists (bounce / block / spam-report / invalid / unsubscribe with reasons, all plans) + per-message activity feed (delivered/bounced/deferred/dropped; needs the Email Activity add-on, degrades gracefully).",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_fiscal_document",
        "description": "CFDI / factura / nota de crédito / retenciones lookup by fiscalDocumentId, checkoutId, or closedDealId. Returns type/status/flow/period/folio-fiscal + (for checkoutId) the periods still missing a factura. Answers 'where is the host's CFDI / what's the folio fiscal / which periods are unbilled?'.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_fiscal_breakdown",
        "description": "Fiscal calculator for a checkout or host (user/team): is the host a user or team, is their fiscal info complete (standard vs increased retention), and what are the CFDI line items (Tarifa de Servicios = SpotMe commission, Tarifa de Procesamiento = Stripe fee, sin IVA) + transfer %? Complements admin_get_fiscal_document (which shows CFDIs that exist) by computing what the amounts should be. Pairs with the Stripe MCP.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_analysis",
        "description": "The LangGraph chatbot's decision records for a threadId (newest first): per-turn intent, nextAction, confidence, escalateToHuman, leadCreated, qualifiedLeadEventSent, customerType, spaceType, attribution. The 'why' behind admin_get_chat's messages — debug escalations, missed leads, B2B classification, attribution.",
        "scope": "mcp:admin"
      },
      {
        "name": "admin_get_followup_attempts",
        "description": "The autonomous follow-up agent's attempts for a threadId (newest first): attemptedAt, status (sent / failed / skipped_* with reason), attemptNumber, intent, dealType, urgency. Answers 'did we follow up with this lead, when, and why was it skipped?'.",
        "scope": "mcp:admin"
      }
    ]
  },
  "policies": {
    "readOnly": true,
    "noPagination": true,
    "redactsExactAddresses": true,
    "redactsHostContacts": true,
    "fundsAttribution": "Every public-tool result includes a spotme.mx URL — the LLM should surface it so users complete their rental on the platform.",
    "adminPiiHandling": "Admin tier returns full PII to authorized SpotMe staff for incident debugging. The MCP system prompt instructs the LLM to never expose raw renter/host PII (RFC, full names, phone, address) when relaying admin-tool output to a chat user — summarize or redact."
  },
  "languages": ["es", "en"],
  "contact": {
    "email": "contacto@spotme.mx",
    "issues": "https://www.spotme.mx/contacto"
  },
  "lastUpdated": "2026-06-14",
  "version": "0.7.0"
}
