MCP authentication
Authenticate with the Nomadfiling MCP server using API keys or OAuth 2.1 with PKCE — token formats, lifetimes, and the full authorization flow.
POST /tools/call HTTP/1.1
Host: mcp.nomadfiling.com
Content-Type: application/json
Authorization: Bearer nf_invalid_token_value
{
"jsonrpc": "2.0",
"id": "req_unauth_01",
"method": "tools/call",
"params": {
"name": "get_filing_status",
"arguments": {
"filingId": "fil_9m2x8q1p"
}
}
}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://mcp.nomadfiling.com/.well-known/oauth-protected-resource"
Content-Type: application/json
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Unauthorized"
},
"id": "req_unauth_01"
}
POST /register HTTP/1.1
Host: mcp.nomadfiling.com
Content-Type: application/json
{
"redirect_uris": [
"https://studio.acme.dev/oauth/callback"
],
"client_name": "Acme MCP Studio",
"grant_types": [
"authorization_code",
"refresh_token"
],
"token_endpoint_auth_method": "none"
}
{
"client_id": "mcp_client_example_1234567890abcdef",
"client_id_issued_at": 1734567890,
"redirect_uris": [
"https://studio.acme.dev/oauth/callback"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"token_endpoint_auth_method": "none",
"client_name": "Acme MCP Studio"
}
{
"error": "invalid_redirect_uri"
}
POST /token HTTP/1.1
Host: mcp.nomadfiling.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=mcp_code_example_1234567890abcdef&redirect_uri=https%3A%2F%2Fstudio.acme.dev%2Foauth%2Fcallback&client_id=mcp_client_example_1234567890abcdef&code_verifier=example_code_verifier_value
{
"access_token": "mcp_at_example_7jd3p4n8q2r6s1t5v9w0x3y6",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "mcp_rt_example_1234567890abcdef",
"scope": "mcp"
}
{
"error": "invalid_grant"
}
POST /token HTTP/1.1
Host: mcp.nomadfiling.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=mcp_rt_example_1234567890abcdef
{
"access_token": "mcp_at_example_rotated_7jd3p4n8q2",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "mcp_rt_example_rotated_abcdef1234567890",
"scope": "mcp"
}
Choose an authentication method
Authenticate MCP requests with either a NomadFiling API key or an OAuth 2.1 access token. Use API keys for server-to-server access you control directly, and use OAuth when you are building a client that signs users in and needs delegated access.
initialize, tools/list, and ping do not require authentication. tools/call requires a valid bearer credential and returns 401 Unauthorized when authentication fails.
Use an API key when your MCP client runs in a trusted environment and you do not need an end-user consent flow.
Header format
Authorization: Bearer nf_example_9xmk7q2r4b7m8n2p6t1v5c8a
Server verification
- Checks that the token starts with
nf_ - Computes the SHA-256 hash of the raw token
- Compares that hash to
api_keys.key_hash - Verifies the row exists in
api_keys - Verifies
expires_atis eithernullor in the future - Updates
api_keys.last_used_atasynchronously - Returns
user_idon success, ornullon failure
Use OAuth 2.1 when your client needs user authorization, consent, and refreshable access tokens.
Header format
Authorization: Bearer mcp_at_example_7jd3p4n8q2r6s1t5v9w0x3y6
Server verification
- Checks that the token starts with
mcp_at_ - Computes the SHA-256 hash of the raw token
- Compares that hash to
oauth_tokens.access_token_hash - Verifies the row exists in
oauth_tokens - Verifies
revokedisfalse - Verifies
expires_atis in the future - Updates
oauth_tokens.last_used_atasynchronously - Returns
user_idon success, ornullon failure
Send authenticated requests
Pass the credential in the Authorization header as a bearer token. The MCP server accepts the same header pattern for both authentication methods.
curl https://mcp.nomadfiling.com/tools/call \
-H "Content-Type: application/json" \
-H "Authorization: Bearer nf_example_9xmk7q2r4b7m8n2p6t1v5c8a" \
-d '{
"jsonrpc": "2.0",
"id": "req_7aK2mP",
"method": "tools/call",
"params": {
"name": "get_filing_status",
"arguments": {
"filingId": "fil_9m2x8q1p"
}
}
}'
const response = await fetch("https://mcp.nomadfiling.com/tools/call", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer nf_example_9xmk7q2r4b7m8n2p6t1v5c8a"
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "req_7aK2mP",
method: "tools/call",
params: {
name: "get_filing_status",
arguments: {
filingId: "fil_9m2x8q1p"
}
}
})
});
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://mcp.nomadfiling.com/tools/call",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer nf_example_9xmk7q2r4b7m8n2p6t1v5c8a",
},
json={
"jsonrpc": "2.0",
"id": "req_7aK2mP",
"method": "tools/call",
"params": {
"name": "get_filing_status",
"arguments": {
"filingId": "fil_9m2x8q1p"
}
},
},
)
print(response.json())
If authentication succeeds, the request continues to the selected tool with the authenticated user_id in context. If authentication fails and the method is tools/call, the server returns an authentication challenge.
Use a NomadFiling API key
API keys are the simplest option for backend services, scripts, and private automations. Each key is created once in the NomadFiling app and only its hash is stored on the server.
The plaintext API key is shown once when you create it. Store it in your secret manager before you leave the creation screen.
Create an API key
Generate API keys from the NomadFiling app at /account/api-keys. Enter a name for the key, generate it, then copy the plaintext value before closing the dialog.
A valid API key has the format nf_ followed by 64 hexadecimal characters. The full string is 67 characters long, including the prefix.
nf_example_9xmk7q2r4b7m8n2p6t1v5c8a
API key format and validation
Bearer token in the form Bearer nf_.... The server authenticates only the raw token value after the Bearer prefix.
Must be nf_.
32 random bytes encoded as 64 hexadecimal characters after the prefix.
SHA-256 hash of the full raw token, stored in api_keys.key_hash.
Authentication succeeds only when expires_at is null or later than the current time.
The server updates api_keys.last_used_at asynchronously after successful authentication.
API key authentication result
User identifier associated with the matching api_keys row.
When validation fails, the API key authenticator returns null. For tools/call, the request then fails with the 401 Unauthorized response shown earlier.
Use OAuth 2.1 with PKCE
OAuth 2.1 adds client registration, user consent, short-lived access tokens, and rotating refresh tokens. NomadFiling supports the authorization code grant and refresh token grant.
Use PKCE for every public client. The server supports both S256 and plain, but S256 is the safer choice.
OAuth endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/register | POST | Register an OAuth client |
/authorize | GET | Start the authorization flow |
/approve | POST | Create an authorization code after user approval |
/token | POST | Exchange an authorization code or refresh token |
Complete OAuth flow
Register your client
Send a request to /register with at least one redirect URI. The server creates a client ID with the mcp_client_ prefix and, if needed, a client secret with the mcp_secret_ prefix.
Your client is ready when you receive a 201 response containing client_id. Save that value because you need it for every later step.
Redirect the user to authorize
Send the user to /authorize with the client ID, redirect URI, response type, and PKCE challenge. The server validates the client and then issues a 302 redirect to https://app.nomadfiling.com/oauth/authorize, where the user sees the consent screen.
https://mcp.nomadfiling.com/authorize?client_id=mcp_client_example_1234567890abcdef&redirect_uri=https%3A%2F%2Fstudio.acme.dev%2Foauth%2Fcallback&response_type=code&state=example_state_value&code_challenge=example_code_challenge_value&code_challenge_method=S256&scope=mcp
curl -i "https://mcp.nomadfiling.com/authorize?client_id=mcp_client_example_1234567890abcdef&redirect_uri=https%3A%2F%2Fstudio.acme.dev%2Foauth%2Fcallback&response_type=code&state=example_state_value&code_challenge=example_code_challenge_value&code_challenge_method=S256&scope=mcp"
After approval, the app posts to /approve, creates an authorization code with the mcp_code_ prefix, and redirects the user back to your redirect_uri with code and state query parameters.
Exchange the authorization code for tokens
Call /token with grant_type=authorization_code, the authorization code, the same redirect URI, your client ID, and the PKCE verifier when the flow used PKCE. The server validates the code, marks it as used, and returns an access token plus a refresh token.
Your client is authenticated when you can send the returned access_token as Authorization: Bearer mcp_at_... and the MCP server accepts a tools/call request.
Refresh expired access tokens
Call /token again with grant_type=refresh_token and the current refresh token. The server revokes the old token row and returns a new access token and refresh token pair.
Store the new refresh token immediately. Token rotation is enforced, so the previous refresh token no longer remains valid after a successful refresh.
Dynamic client registration reference
Use /register to create a client before starting the OAuth flow.
Request body
List of allowed redirect URIs for the client. The server rejects the request with invalid_redirect_uri when a redirect URI is missing or invalid.
Human-readable client name stored with the client record.
Grant types for the client. Defaults to authorization_code and refresh_token.
Client authentication method for the token endpoint. Defaults to none. When set to client_secret_post, the server generates a client secret and stores only its SHA-256 hash.
noneclient_secret_postResponse fields
Client identifier with the mcp_client_ prefix.
Client secret with the mcp_secret_ prefix. Returned only when token_endpoint_auth_method is not none.
Unix timestamp for when the client ID was issued.
Registered redirect URIs for the client.
Grant types assigned to the client.
Always returns code.
Configured token endpoint authentication method.
Human-readable client name if provided at registration time.
Authorization endpoint reference
Use /authorize to validate the client request and forward the user into the NomadFiling consent flow.
Query parameters
Registered OAuth client ID.
Redirect URI that exactly matches one of the client's registered redirect URIs.
Opaque value returned to your redirect URI unchanged. Use it to prevent CSRF and correlate browser state.
PKCE code challenge. When present, the token exchange requires a matching code_verifier.
PKCE verification method. Defaults to plain when omitted.
S256plainRequested scope. Defaults to mcp.
Behavior
If the request is valid, the server returns 302 Found and redirects the browser to https://app.nomadfiling.com/oauth/authorize with the request parameters forwarded. After the user approves access, the app calls /approve, generates an authorization code, and redirects the browser to your redirect_uri with code and state.
Token endpoint reference
Use /token for both authorization code exchange and refresh token rotation.
Authorization code grant
Authorization code with the mcp_code_ prefix.
Must match the redirect URI used during authorization and stored with the authorization code.
OAuth client ID that requested the code.
Required when the authorization request included code_challenge. For S256, the server hashes the verifier with SHA-256 and compares the base64url output to code_challenge. For plain, the verifier must exactly equal code_challenge.
Refresh token grant
Refresh token with the mcp_rt_ prefix.
Token response fields
Bearer access token with the mcp_at_ prefix.
Always Bearer.
Access token lifetime in seconds. The current value is 3600.
Refresh token with the mcp_rt_ prefix.
Granted scope. The current value is mcp.
Token endpoint errors
| HTTP status | Error | Meaning |
|---|---|---|
400 | invalid_grant | Authorization code or refresh token is missing, invalid, expired, revoked, already used, or does not match the client request |
400 | unsupported_grant_type | grant_type is not supported |
400 | invalid_request | Request is malformed or missing required parameters |
500 | server_error | Server failed while processing the request |
Token formats and lifetimes
Each credential type has a fixed prefix and storage pattern. The server hashes secrets before lookup except for client IDs and authorization codes.
| Token type | Prefix | Raw size | Lifetime | Storage |
|---|---|---|---|---|
| API key | nf_ | 32 bytes, hex encoded | Configurable | SHA-256 in api_keys.key_hash |
| OAuth client ID | mcp_client_ | 16 bytes, base64url encoded | Permanent | Plaintext in oauth_clients |
| OAuth client secret | mcp_secret_ | 32 bytes, base64url encoded | Permanent | SHA-256 in oauth_clients.client_secret_hash |
| Authorization code | mcp_code_ | 24 bytes, base64url encoded | 5 minutes | Plaintext in oauth_auth_codes |
| Access token | mcp_at_ | 32 bytes, base64url encoded | 1 hour | SHA-256 in oauth_tokens.access_token_hash |
| Refresh token | mcp_rt_ | 32 bytes, base64url encoded | 30 days | SHA-256 in oauth_tokens.refresh_token_hash |
All tokens are generated with crypto.getRandomValues and base64url encoding, except API keys, which are shown as hexadecimal after the nf_ prefix. Refresh token rotation is enforced, so each successful refresh revokes the previous token row and issues a new token pair.
Security notes
Do not log raw bearer tokens, authorization codes, or client secrets. Log token prefixes, request IDs, and user IDs instead.
When you build a public client, treat client_id as public metadata and treat code_verifier, access tokens, and refresh tokens as secrets. Store refresh tokens in encrypted storage and rotate them immediately after refresh.
FAQ
tools/call requires authentication. initialize, tools/list, and ping are allowed without authentication.