CRITICAL_FINDINGS.md•7.87 kB
# Critical Findings: OAuth Authentication Comparison
## The Core Issue: Why ChatGPT Reports "Connection Problem"
### Summary
The **mcp** implementation uses **opaque (unvalidatable) tokens**, while **mcp2** uses **JWT tokens with proper OAuth infrastructure**. ChatGPT likely rejects mcp because it cannot validate tokens without connecting to the server every time.
---
## Critical Difference #1: Token Format
### mcp2 (Correct for ChatGPT):
```typescript
// JWT token - self-validating with cryptographic signature
const accessToken = jwt.sign(
{
sub: privyUserId,
scope: scopes.join(' '),
aud: audience, // AUDIENCE CLAIM
client_id: clientId,
},
config.jwt.privateKey,
{
algorithm: 'RS256', // RSA with SHA-256
expiresIn: '1h',
issuer: serverUrl, // ISSUER CLAIM
keyid: 'key-1',
}
);
```
**Result**: Token is a signed JWT that ChatGPT can validate independently using the public key from JWKS endpoint.
### mcp (Problem for ChatGPT):
```typescript
// Opaque random token - cannot be validated without server
const accessToken = randomBytes(32).toString("base64url");
// "Q3d4...Jk2=" <- just random bytes, no claims, no signature
```
**Result**: Token is meaningless without server. ChatGPT has NO WAY to validate it.
---
## Critical Difference #2: JWKS Endpoint
### mcp2 Has:
```
GET /.well-known/jwks.json
```
Returns:
```json
{
"keys": [{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "key-1",
"n": "...",
"e": "AQAB"
}]
}
```
**What ChatGPT does**: Downloads the public key, validates JWT signature offline.
### mcp Missing:
**No JWKS endpoint at all.**
**What ChatGPT tries**:
1. Look for JWKS endpoint ✗ (not found)
2. Fall back to introspection ✗ (not implemented)
3. Give up: "connection problem"
---
## Critical Difference #3: Token Validation at MCP Endpoint
### mcp2 (Proper JWT Validation):
```typescript
// Middleware validates JWT cryptographically
const decoded = jwt.verify(token, config.jwt.publicKey, {
algorithms: ['RS256'],
issuer: config.server.baseUrl, // CHECKED
audience: config.server.baseUrl, // CHECKED - CRITICAL!
});
// Can validate without touching token store
req.auth = { token, decoded, userId: decoded.sub };
```
**Security**: JWT is self-validating. Public key proves authenticity. Cannot be forged.
### mcp (Insecure Validation):
```typescript
// Middleware looks up token in memory map
const validation = validateAccessToken(token);
// Just checks: is token in map? is it expired?
// NO cryptographic validation!
```
**Security Problem**: Any client can send any opaque token. If server crashes/restarts, all tokens become valid (empty map!).
---
## Critical Difference #4: Token Introspection
### mcp2:
```typescript
// Implements RFC 7662 token introspection
tokenRouter.post('/introspect', async (req, res) => {
const decoded = jwt.verify(token, config.jwt.publicKey, ...);
res.json({
active: true,
sub: decoded.sub,
scope: decoded.scope,
client_id: decoded.client_id,
exp: decoded.exp,
iat: decoded.iat,
iss: decoded.iss,
aud: decoded.aud,
});
});
```
**Use case**: When ChatGPT receives 401, it can ask "is this token still valid?" without making MCP request.
### mcp:
**Not implemented.**
---
## Root Cause: The "Connection Problem"
### ChatGPT OAuth Client Flow with mcp2:
```
1. User logs in via OAuth
2. ChatGPT receives JWT access token
3. ChatGPT calls GET /.well-known/jwks.json
4. ChatGPT gets server's public key
5. ChatGPT validates token signature: ✓ Valid!
6. ChatGPT calls /mcp with token
7. Server validates JWT: ✓ Matches!
8. Tools execute successfully
```
### ChatGPT OAuth Client Flow with mcp:
```
1. User logs in via OAuth
2. ChatGPT receives opaque token "Q3d4...Jk2="
3. ChatGPT calls GET /.well-known/jwks.json
4. 404 Not Found ✗
5. ChatGPT calls POST /.well-known/oauth-authorization-server
6. Returns metadata WITHOUT jwks_uri ✗
7. ChatGPT tries token introspection endpoint
8. 404 Not Found ✗
9. ChatGPT cannot validate token
10. ChatGPT rejects connection: "connection problem"
```
---
## The Exact Problem Lines
### mcp Missing Lines:
**File: `/Users/jahnik/index-network/mcp/src/oauth.ts`**
This function returns metadata:
```typescript
export function authorizationServerMetadata() {
return {
issuer,
authorization_endpoint: ...,
token_endpoint: ...,
registration_endpoint: ...,
// ❌ MISSING: jwks_uri
scopes_supported: ...,
// ... other fields
};
}
```
**What it should have**:
```typescript
jwks_uri: `${issuer}/.well-known/jwks.json`,
```
And then implement the endpoint:
```typescript
export function getJWKS() {
return {
keys: [
{
kty: "RSA",
use: "sig",
alg: "RS256",
kid: "key-1",
n: base64urlEncode(publicKey.n),
e: base64urlEncode(publicKey.e)
}
]
};
}
```
---
## The Fix Options
### Option A: Upgrade mcp to mcp2 Architecture (Recommended)
- Switch from opaque tokens to JWT
- Add JWKS endpoint
- Add token introspection
- Proper OAuth metadata
**Effort**: Rewrite token handling layer (medium)
**Benefit**: Full ChatGPT compatibility, industry standard
### Option B: Add Missing Endpoints to mcp
1. Convert opaque tokens to JWT (see mcp2 for reference)
2. Add `/.well-known/jwks.json` endpoint
3. Add `/token/introspect` endpoint
4. Update metadata to include `jwks_uri`
**Effort**: Add OAuth infrastructure (high)
**Benefit**: Maintains opaque token approach but fixes immediate issues
### Option C: Hybrid Approach
- Keep mcp's opaque tokens for now
- Implement JWKS endpoint (fake JWT wrapping)
- Add introspection for immediate ChatGPT fix
- Plan migration to mcp2 architecture
**Effort**: Quick patch (low)
**Benefit**: ChatGPT works while planning upgrade
---
## Verification Checklist
To verify which implementation ChatGPT can actually use:
### For mcp2:
- [ ] GET /.well-known/jwks.json returns public key? (Should see `kty: RSA`)
- [ ] POST /token returns JWT token? (Should start with `eyJ...`)
- [ ] JWT header has `kid: key-1`?
- [ ] JWT payload has `aud` claim?
- [ ] POST /mcp with JWT token works?
### For mcp:
- [ ] GET /.well-known/oauth-authorization-server returns `jwks_uri`?
- [ ] POST /token returns what format? (Random bytes vs JWT?)
- [ ] GET /.well-known/jwks.json exists?
- [ ] POST /token/introspect endpoint exists?
---
## Key Code Differences
### Where mcp2 wins - Token Endpoint:
**mcp2**: `src/server/oauth/token.ts` lines 236-275
```typescript
function issueAccessToken({...}) {
const accessToken = jwt.sign({...}, config.jwt.privateKey, {
algorithm: 'RS256',
expiresIn: '1h',
issuer: config.jwt.issuer,
keyid: 'key-1',
});
// Returns: accessToken (JWT)
}
```
**mcp**: `src/oauth.ts` lines 549-593
```typescript
function issueTokens(payload) {
const accessToken = randomBytes(32).toString("base64url");
// Returns: accessToken (opaque)
}
```
### Where mcp2 wins - Authentication Middleware:
**mcp2**: `src/server/middleware/auth.ts` lines 27-82
```typescript
const decoded = jwt.verify(token, config.jwt.publicKey, {
algorithms: [config.jwt.algorithm],
issuer: config.jwt.issuer,
audience: config.server.baseUrl, // ← This validates who token is for
});
```
**mcp**: `src/auth.ts` lines 58
```typescript
const validation = validateAccessToken(token); // Just a Map lookup
```
---
## Recommendation
**Use mcp2 for production ChatGPT integration.**
The architectural differences are fundamental:
- mcp2 uses industry-standard JWT tokens
- mcp uses custom opaque tokens with server-side storage
- For ChatGPT compatibility, you need jwks_uri + introspection
- mcp2 already has both
The "connection problem" is not a bug - it's correct behavior from ChatGPT refusing unvalidatable tokens.