Last month, a team behind a niche Japanese SaaS called tasteck made a bold move: they transformed a decade-old dispatch management platform into one that ChatGPT could call directly. What started as a two-week OAuth sprint ended up taking just a single day—and along the way, they uncovered 12 subtle but critical issues that could derail any MCP integration.
From legacy dispatch to ChatGPT-compatible tools
tasteck serves the Japanese night-leisure industry, managing driver dispatch and cast shifts for nearly 100 venues across eight years of operational data. The team decided to expose three core functions through the Model Context Protocol (MCP), a standard that lets AI assistants invoke external tools securely.
The tools they implemented were:
list_available_drivers– Returns real-time availability for tonight’s shiftslist_cast_shifts– Pulls the scheduled cast roster for any venuelist_assignable_casts– Combines availability with venue rules to suggest assignable staff
A fourth helper, resolveBusinessDate, handles natural-language date parsing like “today” or “tomorrow,” respecting tenant-specific business calendars that might flip at 4:00 AM instead of midnight. This nuance is crucial for industries where shifts start early or end late.
The backend runs on NestJS, using the official @modelcontextprotocol/sdk to expose tools via Server-Sent Events (SSE). Each company in the multi-tenant system gets its own MCP server instance, mapped to a unique session ID to prevent cross-tenant data leaks.
OAuth 2.1 in under 24 hours—what really happened
The team estimated a two-week OAuth 2.1 implementation sprint beginning June 16th. But after carefully reading the specification first—and avoiding premature coding—they completed it in a single day.
Their flow followed seven tightly defined steps:
- Protected Resource Metadata endpoint (RFC 9728)
- Authorization endpoint with PKCE and consent screen
- Token endpoint with PKCE verification and JWT issuance
- Custom
OAuthAccessTokenGuardsupporting RS256 and HS256 fallbacks - Streamable HTTP transport over SSE for JSON-RPC payloads
- Business date resolver with tenant-aware fallbacks
- Quality assurance and live ChatGPT demo
Despite the speed, the real challenge emerged during integration testing—ChatGPT’s behavior didn’t match their assumptions.
Twelve traps that almost blocked ChatGPT’s access
The team documented 12 distinct issues between “OAuth issuance works” and “ChatGPT actually invokes the tool.” Here are the most surprising:
- Discovery path mismatch. ChatGPT expected
.well-known/oauth-protected-resourceat the server root, but tasteck published it under/v1/api/staff/mcp/.... ChatGPT simply couldn’t find the protected resource descriptor.
- Transport protocol mismatch. ChatGPT requires Streamable HTTP (POST
/ssewith JSON-RPC), but the team initially used a legacy SSE transport. QA logs showed POST/sse/1returning 404—not because the route was wrong, but because the transport layer wasn’t compatible.
- OAuth cache illusion. ChatGPT cached the result of
tools/listacross fixes, falsely reporting “no tools available.” Disconnecting and reconnecting through the OAuth flow cleared the cache and allowed discovery to refresh.
- Guard identity crisis. The existing
StaffJwtGuardvalidated staff-login tokens signed with one secret, while OAuth tokens were signed with another. Every SSE POST returned 401 until a newOAuthAccessTokenGuardwas added with dual-algorithm support.
- Undefined value crash. The schema marked the date parameter as optional. ChatGPT invoked the tool with no arguments, and the handler called
naturalText.trim(), causing a TypeError. Fix:(naturalText || "today").trim().
- Empty tools list ≠ broken tools. After fixing the guard,
tools/liststill returned an empty array. The root cause wasn’t the tools—the transport handshake had failed earlier in the stack. Empty tools are often a symptom, not the disease.
- Single-cause fallacy. The team initially blamed either caching or the guard, but QA logs revealed both issues were real and overlapping. Avoid assuming one root cause when multiple layers interact.
- Naming convention confusion. ChatGPT expected tools named like
get_available_drivers, but tasteck usedlist_available_drivers. Once the transport and guard were fixed, naming conventions turned out to be irrelevant.
- Missing schema visibility. The
shop_idparameter wasn’t surfaced ininputSchema, so ChatGPT couldn’t pass it. The team is now preparing B3 to exposeshop_idexplicitly.
- Tenant-aware calendar logic. Hardcoding ISO date strings would have broken venues whose business day rolls at 4:00 AM or 5:00 AM.
resolveBusinessDatereadsCompany.changeDateTimeto resolve dates correctly.
- Multi-tenant isolation. A single SSE process serves multiple companies. The team keys each
McpServerinstance bycompanyIdfrom the validated OAuth claim, preventing cross-tenant data leakage.
- Curl ≠ reality. Manual curl tests returned 200 OK long before ChatGPT could invoke any tool. Curl doesn’t simulate OAuth state, discovery caching, or session persistence—only a live AI demo validates the full integration.
The breakthrough moment
After deploying the guard fix, clearing the cache, and resolving the undefined fallback, the team triggered a live demo in ChatGPT:
ChatGPT: “Tools have been called ✓. Available drivers for 2026-06-10: 0 people. The returned payload is drivers: [], so there are no assignable drivers for tonight.”Moments later, list_cast_shifts and list_assignable_casts executed cleanly, confirming all three tools were live with real data paths and natural-language synthesis.
Lessons for teams building MCP OAuth tomorrow
- Read the spec first. Speed came from reading, not coding. The one-day win started with specification review, not keyboard tapping.
- Separate transport from discovery. They fail differently and produce different logs. Treat them as distinct layers.
- Log every MCP UA hit. The presence or absence of
openai-mcpuser-agent requests is the fastest way to diagnose cache versus server issues.
- Support two guards from day one. Staff JWT and OAuth access tokens use different signing materials. Never reuse authentication logic.
- Don’t trust empty tools lists. An empty
tools/listresponse usually means the transport handshake failed upstream, not that the tools are broken.
What’s next: B3 and beyond
The team is now preparing B3 to expose additional parameters like shop_id in the MCP tool schema, enabling AI to target specific venues directly. Following that, B4 will introduce mutation tools for assignments, using the same OAuth flow but with updated consent and audit requirements.
For any SaaS team considering MCP integration, tasteck’s journey shows that preparation beats speed—and that the toughest bugs often hide in the gap between specification and real-world AI behavior.
AI summary
Learn how a Japanese B2B SaaS integrated with ChatGPT’s MCP protocol in a single day, uncovering 12 critical OAuth and MCP traps along the way.