Skip to main content

Overview

PromptRepo’s MCP server uses API key authentication to verify caller identity and enforce access control. API keys are cryptographically hashed and stored in the database — the plaintext key is shown only once during generation.
Security-critical: API keys grant full access to your private prompts. Never commit keys to Git, share them publicly, or embed them in client-side code.

Generating an API Key

1

Navigate to Profile

Log in to PromptRepo and go to /profile in your browser.
2

Create API Key

Click “Generate New API Key” in the API Keys section.
3

Copy Key Immediately

The plaintext key is displayed once. Copy it to a secure location (password manager, environment variable file, etc.).
Once you close the modal, the plaintext key cannot be retrieved — you’ll need to generate a new one.
4

Store Securely

Add the key to your AI client’s configuration (see Configuration for examples).

Authentication Headers

When making requests to POST /api/mcp, include your API key in one of two headers:
POST /api/mcp HTTP/1.1
Host: your-app-url.com
Content-Type: application/json
Authorization: Bearer pr_live_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_prompts",
    "arguments": { "limit": 10 }
  }
}
Most MCP clients (Claude Desktop, Claude Code) use the x-api-key header by default. The Authorization: Bearer format is preferred for HTTP clients and SDKs.

How Verification Works

The authentication flow is handled in src/app/api/mcp/route.ts:
1

Extract Key from Headers

The route handler reads Authorization: Bearer <key> or x-api-key: <key>.
const authHeader = request.headers.get('Authorization');
const xApiKeyHeader = request.headers.get('x-api-key');

const rawKey =
  authHeader?.startsWith('Bearer ')
    ? authHeader.slice('Bearer '.length).trim()
    : (xApiKeyHeader?.trim() ?? null);
2

Hash and Verify

The plaintext key is hashed using SHA-256 and compared against the database:
// src/lib/api-keys/verify.ts
const keyHash = await hashApiKey(plaintext);

const { data } = await supabase
  .from('user_api_keys')
  .select('user_id')
  .eq('key_hash', keyHash)
  .is('revoked_at', null)
  .maybeSingle();
3

Set User Context

If valid, the userId is extracted and passed to tool handlers for access control.If invalid or revoked, a JSON-RPC error is returned:
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32001,
    "message": "Invalid API key."
  }
}

Anonymous Access

If no API key is provided in the request:
  • userId is set to null
  • All tools return public prompts only
  • Private prompts are inaccessible
Anonymous access is useful for testing or read-only integrations where you only need public prompts.

Revoking API Keys

1

Go to Profile

Navigate to /profile in your browser.
2

Find the Key

Locate the key in the API Keys table (identified by creation date and last 8 characters).
3

Click Revoke

Click “Revoke” to immediately invalidate the key.
Revocation is immediate — any active MCP client using this key will start receiving authentication errors.
Revoked keys cannot be un-revoked. Generate a new key if needed.

Access Control Rules

Once authenticated, tool handlers enforce these access policies:
ToolAuthenticated UserAnonymous User
list_promptsOwn prompts + public promptsPublic prompts only
get_promptOwn prompts (public/private) + others’ publicPublic prompts only
resolve_promptSame as get_promptPublic prompts only
search_promptsOwn prompts + public promptsPublic prompts only
To test access control, try calling the MCP endpoint without an API key — you should only see prompts marked as public.

Security Best Practices

Store API keys in environment variables or secret management tools — never hardcode them:
# .env.local (local development)
PROMPTREPO_API_KEY=pr_live_abc123...
// claude_desktop_config.json (reference env var)
{
  "mcpServers": {
    "my-prompts": {
      "url": "https://app.promptrepo.dev/api/mcp",
      "headers": {
        "x-api-key": "${PROMPTREPO_API_KEY}"
      }
    }
  }
}
Generate new keys every 90 days and revoke old ones. This limits the damage if a key is compromised.
Generate different API keys for each AI client (Claude Desktop, Claude Code, CI/CD) to enable granular revocation.
Check the “Last Used” timestamp in /profile to detect anomalies or unused keys that should be revoked.

Error Responses

ScenarioHTTP StatusJSON-RPC Error CodeMessage
No key provided200N/ARequest succeeds (anonymous access)
Invalid key format200-32001”Invalid API key.”
Revoked key200-32001”Invalid API key.”
Unknown key200-32001”Invalid API key.”
Why HTTP 200? JSON-RPC 2.0 requires all responses (including errors) to use HTTP 200. Errors are transmitted inside the JSON envelope.

Implementation Reference

API key verification is implemented in src/lib/api-keys/verify.ts:
src/lib/api-keys/verify.ts
export async function verifyApiKey(plaintext: string): Promise<VerifyApiKeyResult> {
  let keyHash: string;

  try {
    keyHash = await hashApiKey(plaintext);
  } catch (err) {
    console.error('[verifyApiKey] hashing failed:', err);
    return { valid: false };
  }

  const supabase = createServiceClient();

  const { data, error } = await supabase
    .from('user_api_keys')
    .select('user_id')
    .eq('key_hash', keyHash)
    .is('revoked_at', null)
    .maybeSingle();

  if (error || !data) {
    return { valid: false };
  }

  return { valid: true, userId: data.user_id as string };
}
See src/app/api/mcp/route.ts:71-96 for the full route handler integration.

Next Steps

Configuration

Configure Claude Desktop, Claude Code, and other MCP clients with your API key

Build docs developers (and LLMs) love