From Access Keys to OAuth: Practical Notes from Building Halo Lumin's MCP Server
Audience: builders who want to expose a small, safe product capability to AI agents through MCP.
This is not official MCP guidance. It is a practical note about how I worked with ChatGPT and Codex to build Halo Lumin's MCP server, from access-key testing toward OAuth/DCR-ready behavior. It is not a full MCP specification. Use it as a field note, then verify the details against the current MCP protocol and client docs for your target environment.
Big-Picture Mental Model
Think of a remote MCP server as a narrow bridge from an AI agent into your product:
Product capability -> MCP tool -> authenticated agent access -> safe runtime checks
The MCP server should expose small, useful product actions. It should not expose the whole database, internal admin power, or every API route just because those things exist.
A good first MCP tool answers a simple question:
- What useful thing can an agent do for a signed-in user?
- Can the server prove the agent is allowed to do it?
- Can the server prevent cross-user access?
- Can the response be stable and structured enough for another system to use?
For Halo Lumin, that meant exposing owned Halo actions, not raw table access.
Choose the Protocol and Client Docs First
MCP gives the broad tool and server model, but real clients may have their own connection, auth, discovery, and metadata expectations.
Pick the target client early: ChatGPT, Codex, Claude, or another MCP client. Follow the official docs for that target client and the MCP protocol or specification. Avoid building only from assumptions or copied examples.
For Halo Lumin, ChatGPT and Codex were my first practical targets, so I validated against their remote MCP behavior instead of treating local tests as the whole story.
Start Simple with Access Keys
Access keys are useful for early developer testing. They are easier to reason about than full OAuth while the tool design is still changing.
The real starter step is not the first product tool. The real starter step is a minimal access-key lifecycle:
- create an access key
- revoke an access key
- verify an access key
That lifecycle gives you a safe way to test MCP behavior before OAuth/DCR is ready. Once create, revoke, and verify are working, you can use access-key auth to answer the important product questions:
- Does the tool shape make sense?
- Are scopes narrow enough?
- Are owned-record checks working?
- Are error responses clear?
- Does
tools/listshow only what the caller can actually use?
Keep scopes narrow. Start with read-only tools before write tools. Do not begin with admin, update, delete, import, export, or file upload tools.
Add Read-Only Tools First
Read-only tools are the safest way to prove the MCP surface.
Useful first tools include:
verify_access: confirms the caller has valid MCP access.search_owned_halosorlist_owned_records: searches or lists records owned by the authenticated user.get_owned_haloorget_owned_record: fetches one owned record by id.get_environment_status: reports which runtime the server is using and whether writes would touch shared data.
These tools should still enforce auth and ownership. Read-only does not mean public.
Add One Narrow Write Tool
After read-only behavior is stable, add one narrow write tool.
For Halo Lumin, the right first write was:
- Create a text Halo.
It was intentionally limited:
- No file upload at first.
- No update tool at first.
- No delete tool at first.
- No admin tool.
- No cross-user access.
- Return stable structured results.
The goal is to prove one useful write path end to end without opening broad product mutation through MCP.
Good write responses should include stable ids, ownership-safe URLs or references, and enough structured fields for the client to understand what happened. They should not include secrets, raw tokens, or internal-only database details.
Add OAuth/DCR After Tool Behavior Is Stable
OAuth and Dynamic Client Registration are for real remote client onboarding. They are important, but they add moving parts:
- Authorization server metadata.
- Protected resource metadata.
- Redirect URI validation.
- Authorization-code exchange.
- Token storage and expiry.
- Scope mapping.
- Client registration rules.
- Client-specific discovery behavior.
Do not make OAuth the first problem unless you already know the tool surface is correct.
For Halo Lumin, the safer path was:
- Prove access-key MCP behavior.
- Stabilize read-only tools.
- Add one narrow create tool.
- Add OAuth/DCR only after the tool behavior was understandable.
Keep production fail-closed until intentionally enabled. Preview or staging OAuth/DCR should be explicit, configured on purpose, and easy to test.
Runtime vs Data Environment
A key Halo Lumin lesson: runtime environment and data environment are different things.
mcp-preview.halolumin.io is a staging MCP runtime. It is not staging data.
That preview runtime shares production Supabase data. Writes through preview create real production records.
That distinction matters because a tool can be running from a preview URL while still reading and writing real user data. Agents and developers need a clear way to know that before running write tests.
This is why environment-status tooling matters.
Environment/Status Tool
A tool like get_environment_status is useful because it gives the agent safe context before it acts.
It should help answer:
- Which runtime am I calling?
- Is this production, preview, or local?
- Does this runtime share production data?
- Are write tests safe here?
- Is this a read-only or write-capable environment?
The tool should be read-only and expose no secrets. It should not return tokens, raw connection strings, service keys, full env dumps, or private infrastructure details.
For shared production data, the response should be blunt. Agents should not have to infer that a preview runtime writes real records.
Real Client Binding Verification
MCP server behavior can be correct while the real client binding is stale.
We saw this pattern:
- Server
tools/listwas correct. - Direct MCP calls worked.
- OAuth/DCR metadata was correct.
- But ChatGPT, Codex, or Claude client bindings still needed reconnect, refresh, or cleanup.
Duplicate app bindings, draft connectors, old registrations, or cached tool discovery can create confusing results. The server may be fixed while the client still sees an older tool list.
Before write tests, verify from the actual client that will run the tool:
- The intended server URL is connected.
- The expected auth path is active.
tools/listshows the expected tools.- Tool descriptions and schemas look current.
- There are no duplicate stale bindings pointing at older deployments.
Do not rely only on direct curl or local MCP calls when the real goal is ChatGPT, Codex, Claude, or another remote client.
Safety Checklist
Before treating a remote MCP server as ready, check:
- No secrets in tool responses.
- No broad admin tools.
- No cross-user data access.
- No bulk destructive actions.
- No write smoke against shared production data unless the record is disposable.
tools/listreturns only tools the caller is allowed to use.- Protected resource metadata is correct.
- Actual client binding has been verified.
- Production stays fail-closed until intentionally enabled.
Suggested MCP Tool Maturity Path
A simple maturity path:
- Add the repo-side access-key lifecycle: create, revoke, and verify.
- Add
get_environment_status. - Add
verify_accessas the first MCP-facing auth check. - Read or list owned records.
- Get one owned record.
- Create one narrow content type.
- Add update later, after create behavior is safe.
- Add delete last, if ever.
Delete tools deserve extra caution. Many products never need remote-agent delete access.
The Build Loop I Used
I worked in small steps.
I stated the goal, constraints, and what felt risky or unclear. ChatGPT helped discuss the approach, narrow the scope, identify guardrails, and write a precise Codex prompt. Codex implemented the change and returned a summary with files changed, validation, assumptions, and follow-up notes.
I pasted Codex's summary back into ChatGPT. ChatGPT reviewed the result, called out risks or missing verification, and generated the next focused prompt. I handled external decisions and reality checks such as GitHub PRs, ChatGPT connector bindings, and production or staging verification.
The loop repeated in small steps instead of one large risky change.
Takeaway: a good AI-assisted engineering loop is not "ask once and trust the result." It is goal -> scoped prompt -> implementation summary -> review -> verification -> next scoped prompt.
Use This Prompt From the Project Repo
This starter prompt is meant to be used from inside the actual product repo, not as a generic prompt in an empty chat.
The coding agent should inspect the repo before making changes. That means reading the relevant project docs, route structure, auth helpers, database/schema files, environment examples, tests, and tracked checklists. If the repo already has a standard engineering or Codex starter prompt, use that shape and fill it with the MCP-specific goal, requirements, validation, and scope controls below.
Do not paste secrets into the prompt. Reference environment variable names and configuration expectations, but keep keys, tokens, service-role credentials, and production secrets out of the conversation.
Starter Prompt
Copyable prompt for an AI coding agent running inside your project repo:
Build a small remote MCP server for this product, but start safely.
You are working inside the [PRODUCT / REPO NAME] project repository.
Before making changes, create a new feature branch for this work.
Use the repo as the source of truth. Inspect the relevant README files, project notes, architecture docs, auth helpers, API routes, schema or migration files, environment examples, tests, and tracked product checklists before implementing anything. If this repo has a standard engineering or Codex starter prompt, follow that structure and fill it with the MCP-specific details below.
Goal:
Build a small remote MCP server for this product, starting with a safe, narrow authenticated tool surface.
Context:
This product has useful user-owned data or actions that an AI agent may help with. The MCP server should expose small product capabilities, not the whole database or internal admin API.
Use the current official MCP protocol/client documentation for the target environment. Identify the first target client before implementation, such as ChatGPT, Codex, Claude, or another MCP-compatible client.
Use simple access-key authentication first so tool behavior can be tested before OAuth. The first foundation is the access-key lifecycle: create, revoke, and verify access keys. Add OAuth and Dynamic Client Registration only after the tool behavior, scopes, metadata, and client discovery expectations are stable.
Requirements:
1. Inspect the existing product architecture before implementing tools.
2. Implement or verify the minimal access-key lifecycle:
- create access key
- revoke access key
- verify access key
3. Define a narrow MCP tool surface for authenticated agents only.
4. Start with read-only MCP tools:
- get_environment_status
- verify_access
- list/search records owned by the authenticated user
- get one owned record by id
5. Enforce ownership checks on every record access.
6. Use narrow scopes.
7. Return stable structured responses.
8. Do not expose secrets, raw tokens, service keys, connection strings, internal database details, or full environment dumps.
9. Do not expose broad admin actions, cross-user reads, bulk actions, imports, exports, update, delete, or file upload in the first pass.
10. After read-only tools are stable, add one narrow create tool for a single content type.
11. Keep production fail-closed until intentionally enabled.
Environment and safety guidance:
- Add a read-only environment/status tool early.
- The environment/status tool should report which runtime is being called.
- It should clearly say whether preview/staging shares production data.
- If preview/staging writes touch real production data, make that explicit.
- Before any write test, verify:
- server tools/list
- protected resource metadata
- actual client binding in the target client
- no duplicate or stale client connections
- Do not run write smoke tests against shared production data unless the created record is disposable and cleanup is verified.
Implementation guidance:
- Keep tools boring, explicit, and agent-friendly.
- Prefer clear names like:
- product.get_environment_status
- product.verify_access
- product.search_owned_records
- product.get_owned_record
- product.create_text_record
- Keep access-key management boring and auditable.
- Hash stored access keys if keys are persisted.
- Never return full access keys after creation except at the one-time creation moment, if that is part of the product design.
- Keep helper functions small and testable.
- Do not add database migrations unless required.
- Do not broaden existing product permissions.
- Do not make production OAuth/DCR available until intentionally planned.
Validation:
- Confirm access keys can be created, revoked, and verified.
- Confirm revoked keys fail safely.
- Confirm tools/list returns only tools the caller is allowed to use.
- Confirm read-only tools perform no product mutations.
- Confirm ownership checks prevent cross-user access.
- Confirm no secrets are exposed.
- Confirm unauthenticated requests fail safely.
- Confirm production remains fail-closed.
- Test with the actual target client, not only local curl or direct HTTP calls.
Scope control:
For this first pass, do not add:
- update tools
- delete tools
- file upload tools
- admin tools
- cross-user tools
- bulk import/export
- analytics dashboards
- broad database access
- production OAuth/DCR rollout
Documentation:
Update tracked repo docs, checklists, or session notes if they exist so this work is reflected in the project record.
Work process:
Work in small branches. After each change, stop and report back before continuing.
When done, provide:
- branch name used
- summary of changes
- files changed
- tools added
- scopes added or changed
- auth/OAuth/DCR changes
- schema or migration changes
- documentation updated
- validation performed
- assumptions made
- follow-up recommendations
- confirmation that no secrets are exposed
- confirmation that no broad admin/cross-user behavior was added
- confirmation that production remains fail-closed