Skip to content

Commit 97f9722

Browse files
RahulHereRahulHere
authored andcommitted
Implement Error Message Management (#130)
- Added thread-local error context storage for detailed debugging - Implemented set_error_with_context for enhanced error information - Added client-specific error tracking with last_error_context - Created mcp_auth_get_last_error_full with context details - Added mcp_auth_client_get_last_error for per-client errors - Enhanced validation errors with contextual information - Improved JWKS fetch error reporting with URI and status - Added token expiration context with timestamps - Fixed error_to_string to use switch statement - Moved set_client_error after struct definition
1 parent f23f77d commit 97f9722

File tree

1 file changed

+125
-51
lines changed

1 file changed

+125
-51
lines changed

src/c_api/mcp_c_auth_api.cc

Lines changed: 125 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
#include <openssl/bn.h>
2727
#include <curl/curl.h>
2828

29-
// Thread-local error storage
29+
// Thread-local error storage with context
3030
static thread_local std::string g_last_error;
31+
static thread_local std::string g_last_error_context; // Additional context information
3132
static thread_local mcp_auth_error_t g_last_error_code = MCP_AUTH_SUCCESS;
3233

3334
// Global initialization state
@@ -38,12 +39,22 @@ static std::mutex g_init_mutex;
3839
static void set_error(mcp_auth_error_t code, const std::string& message) {
3940
g_last_error_code = code;
4041
g_last_error = message;
42+
g_last_error_context.clear();
43+
}
44+
45+
// Set error with additional context
46+
static void set_error_with_context(mcp_auth_error_t code, const std::string& message,
47+
const std::string& context) {
48+
g_last_error_code = code;
49+
g_last_error = message;
50+
g_last_error_context = context;
4151
}
4252

4353
// Clear error state
4454
static void clear_error() {
4555
g_last_error_code = MCP_AUTH_SUCCESS;
4656
g_last_error.clear();
57+
g_last_error_context.clear();
4758
}
4859

4960
// ========================================================================
@@ -931,6 +942,10 @@ struct mcp_auth_client {
931942
std::string last_alg;
932943
std::string last_kid;
933944

945+
// Error context for debugging
946+
std::string last_error_context;
947+
mcp_auth_error_t last_error_code = MCP_AUTH_SUCCESS;
948+
934949
// JWKS cache
935950
std::vector<jwks_key> cached_keys;
936951
std::chrono::steady_clock::time_point cache_timestamp;
@@ -947,6 +962,16 @@ struct mcp_auth_validation_options {
947962
int64_t clock_skew = 60;
948963
};
949964

965+
// Store error in client structure with context (moved after struct definition)
966+
static void set_client_error(mcp_auth_client_t client, mcp_auth_error_t code,
967+
const std::string& message, const std::string& context = "") {
968+
if (client) {
969+
client->last_error_code = code;
970+
client->last_error_context = context.empty() ? message : message + " (" + context + ")";
971+
}
972+
set_error_with_context(code, message, context);
973+
}
974+
950975
struct mcp_auth_token_payload {
951976
std::string subject;
952977
std::string issuer;
@@ -1109,11 +1134,17 @@ static void invalidate_cache(mcp_auth_client_t client) {
11091134
static bool fetch_and_cache_jwks(mcp_auth_client_t client) {
11101135
std::string jwks_json;
11111136
if (!fetch_jwks_json(client->jwks_uri, jwks_json)) {
1137+
set_client_error(client, MCP_AUTH_ERROR_JWKS_FETCH_FAILED,
1138+
"Failed to fetch JWKS",
1139+
"URI: " + client->jwks_uri + ", Error: " + g_last_error);
11121140
return false;
11131141
}
11141142

11151143
std::vector<jwks_key> keys;
11161144
if (!parse_jwks(jwks_json, keys)) {
1145+
set_client_error(client, MCP_AUTH_ERROR_JWKS_FETCH_FAILED,
1146+
"Failed to parse JWKS response",
1147+
"URI: " + client->jwks_uri);
11171148
return false;
11181149
}
11191150

@@ -1289,7 +1320,12 @@ mcp_auth_error_t mcp_auth_client_create(
12891320
}
12901321

12911322
if (!client || !jwks_uri || !issuer) {
1292-
set_error(MCP_AUTH_ERROR_INVALID_PARAMETER, "Invalid parameter");
1323+
std::string context = "Missing: ";
1324+
if (!client) context += "client ptr, ";
1325+
if (!jwks_uri) context += "jwks_uri, ";
1326+
if (!issuer) context += "issuer";
1327+
set_error_with_context(MCP_AUTH_ERROR_INVALID_PARAMETER,
1328+
"Invalid parameters", context);
12931329
return MCP_AUTH_ERROR_INVALID_PARAMETER;
12941330
}
12951331

@@ -1510,7 +1546,18 @@ mcp_auth_error_t mcp_auth_validate_token(
15101546
}
15111547

15121548
if (!client || !token || !result) {
1513-
set_error(MCP_AUTH_ERROR_INVALID_PARAMETER, "Invalid parameter");
1549+
std::string context = "Missing: ";
1550+
if (!client) context += "client, ";
1551+
if (!token) context += "token, ";
1552+
if (!result) context += "result";
1553+
1554+
if (result) {
1555+
result->valid = false;
1556+
result->error_code = MCP_AUTH_ERROR_INVALID_PARAMETER;
1557+
result->error_message = "Invalid parameters";
1558+
}
1559+
set_client_error(client, MCP_AUTH_ERROR_INVALID_PARAMETER,
1560+
"Invalid parameters for token validation", context);
15141561
return MCP_AUTH_ERROR_INVALID_PARAMETER;
15151562
}
15161563

@@ -1606,9 +1653,13 @@ mcp_auth_error_t mcp_auth_validate_token(
16061653

16071654
if (payload_data.expiration > 0) {
16081655
if (now > payload_data.expiration + clock_skew) {
1609-
set_error(MCP_AUTH_ERROR_EXPIRED_TOKEN, "JWT has expired");
1656+
std::string context = "Token expired at " + std::to_string(payload_data.expiration) +
1657+
", current time: " + std::to_string(now) +
1658+
", clock skew: " + std::to_string(clock_skew) + "s";
1659+
set_client_error(client, MCP_AUTH_ERROR_EXPIRED_TOKEN,
1660+
"JWT has expired", context);
16101661
result->error_code = MCP_AUTH_ERROR_EXPIRED_TOKEN;
1611-
result->error_message = safe_strdup("JWT has expired");
1662+
result->error_message = safe_strdup(("JWT has expired [" + context + "]").c_str());
16121663
return MCP_AUTH_ERROR_EXPIRED_TOKEN;
16131664
}
16141665
}
@@ -1954,10 +2005,46 @@ mcp_auth_error_t mcp_auth_get_last_error_code(void) {
19542005
return g_last_error_code;
19552006
}
19562007

2008+
// Get last error with full context information
2009+
mcp_auth_error_t mcp_auth_get_last_error_full(char** error_message) {
2010+
if (!error_message) {
2011+
return MCP_AUTH_ERROR_INVALID_PARAMETER;
2012+
}
2013+
2014+
std::string full_message = g_last_error;
2015+
if (!g_last_error_context.empty()) {
2016+
full_message += " [Context: " + g_last_error_context + "]";
2017+
}
2018+
2019+
*error_message = safe_strdup(full_message);
2020+
return g_last_error_code;
2021+
}
2022+
2023+
// Get error details from client
2024+
mcp_auth_error_t mcp_auth_client_get_last_error(mcp_auth_client_t client, char** error_message) {
2025+
if (!client || !error_message) {
2026+
return MCP_AUTH_ERROR_INVALID_PARAMETER;
2027+
}
2028+
2029+
*error_message = safe_strdup(client->last_error_context);
2030+
return client->last_error_code;
2031+
}
2032+
19572033
void mcp_auth_clear_error(void) {
19582034
clear_error();
19592035
}
19602036

2037+
// Clear error for a specific client
2038+
mcp_auth_error_t mcp_auth_client_clear_error(mcp_auth_client_t client) {
2039+
if (!client) {
2040+
return MCP_AUTH_ERROR_INVALID_PARAMETER;
2041+
}
2042+
2043+
client->last_error_code = MCP_AUTH_SUCCESS;
2044+
client->last_error_context.clear();
2045+
return MCP_AUTH_SUCCESS;
2046+
}
2047+
19612048
bool mcp_auth_has_error(void) {
19622049
return g_last_error_code != MCP_AUTH_SUCCESS;
19632050
}
@@ -2018,53 +2105,40 @@ bool mcp_auth_validate_scopes(
20182105
}
20192106

20202107
const char* mcp_auth_error_to_string(mcp_auth_error_t error_code) {
2021-
// Thread-safe static strings for error descriptions
2022-
static const char* const error_strings[] = {
2023-
[MCP_AUTH_SUCCESS] = "Success",
2024-
[MCP_AUTH_ERROR_INVALID_TOKEN] = "Invalid or malformed JWT token",
2025-
[MCP_AUTH_ERROR_EXPIRED_TOKEN] = "JWT token has expired",
2026-
[MCP_AUTH_ERROR_INVALID_SIGNATURE] = "JWT signature verification failed",
2027-
[MCP_AUTH_ERROR_INVALID_ISSUER] = "Token issuer does not match expected value",
2028-
[MCP_AUTH_ERROR_INVALID_AUDIENCE] = "Token audience does not match expected value",
2029-
[MCP_AUTH_ERROR_INSUFFICIENT_SCOPE] = "Token lacks required scopes for operation",
2030-
[MCP_AUTH_ERROR_JWKS_FETCH_FAILED] = "Failed to fetch JWKS from authorization server",
2031-
[MCP_AUTH_ERROR_INVALID_KEY] = "No valid signing key found in JWKS",
2032-
[MCP_AUTH_ERROR_NETWORK_ERROR] = "Network communication error",
2033-
[MCP_AUTH_ERROR_INVALID_CONFIG] = "Invalid authentication configuration",
2034-
[MCP_AUTH_ERROR_OUT_OF_MEMORY] = "Memory allocation failed",
2035-
[MCP_AUTH_ERROR_INVALID_PARAMETER] = "Invalid parameter passed to function",
2036-
[MCP_AUTH_ERROR_NOT_INITIALIZED] = "Authentication library not initialized",
2037-
[MCP_AUTH_ERROR_INTERNAL_ERROR] = "Internal library error"
2038-
};
2039-
2040-
// Bounds checking
2041-
if (error_code >= 0) {
2042-
return error_strings[MCP_AUTH_SUCCESS];
2043-
}
2044-
2045-
int index = -error_code;
2046-
if (index >= 1000 && index <= 1014) {
2047-
// Map negative error codes to array indices
2048-
switch (error_code) {
2049-
case MCP_AUTH_ERROR_INVALID_TOKEN: return error_strings[MCP_AUTH_ERROR_INVALID_TOKEN];
2050-
case MCP_AUTH_ERROR_EXPIRED_TOKEN: return error_strings[MCP_AUTH_ERROR_EXPIRED_TOKEN];
2051-
case MCP_AUTH_ERROR_INVALID_SIGNATURE: return error_strings[MCP_AUTH_ERROR_INVALID_SIGNATURE];
2052-
case MCP_AUTH_ERROR_INVALID_ISSUER: return error_strings[MCP_AUTH_ERROR_INVALID_ISSUER];
2053-
case MCP_AUTH_ERROR_INVALID_AUDIENCE: return error_strings[MCP_AUTH_ERROR_INVALID_AUDIENCE];
2054-
case MCP_AUTH_ERROR_INSUFFICIENT_SCOPE: return error_strings[MCP_AUTH_ERROR_INSUFFICIENT_SCOPE];
2055-
case MCP_AUTH_ERROR_JWKS_FETCH_FAILED: return error_strings[MCP_AUTH_ERROR_JWKS_FETCH_FAILED];
2056-
case MCP_AUTH_ERROR_INVALID_KEY: return error_strings[MCP_AUTH_ERROR_INVALID_KEY];
2057-
case MCP_AUTH_ERROR_NETWORK_ERROR: return error_strings[MCP_AUTH_ERROR_NETWORK_ERROR];
2058-
case MCP_AUTH_ERROR_INVALID_CONFIG: return error_strings[MCP_AUTH_ERROR_INVALID_CONFIG];
2059-
case MCP_AUTH_ERROR_OUT_OF_MEMORY: return error_strings[MCP_AUTH_ERROR_OUT_OF_MEMORY];
2060-
case MCP_AUTH_ERROR_INVALID_PARAMETER: return error_strings[MCP_AUTH_ERROR_INVALID_PARAMETER];
2061-
case MCP_AUTH_ERROR_NOT_INITIALIZED: return error_strings[MCP_AUTH_ERROR_NOT_INITIALIZED];
2062-
case MCP_AUTH_ERROR_INTERNAL_ERROR: return error_strings[MCP_AUTH_ERROR_INTERNAL_ERROR];
2063-
default: break;
2064-
}
2108+
switch (error_code) {
2109+
case MCP_AUTH_SUCCESS:
2110+
return "Success";
2111+
case MCP_AUTH_ERROR_INVALID_TOKEN:
2112+
return "Invalid or malformed JWT token";
2113+
case MCP_AUTH_ERROR_EXPIRED_TOKEN:
2114+
return "JWT token has expired";
2115+
case MCP_AUTH_ERROR_INVALID_SIGNATURE:
2116+
return "JWT signature verification failed";
2117+
case MCP_AUTH_ERROR_INVALID_ISSUER:
2118+
return "Token issuer does not match expected value";
2119+
case MCP_AUTH_ERROR_INVALID_AUDIENCE:
2120+
return "Token audience does not match expected value";
2121+
case MCP_AUTH_ERROR_INSUFFICIENT_SCOPE:
2122+
return "Token lacks required scopes for operation";
2123+
case MCP_AUTH_ERROR_JWKS_FETCH_FAILED:
2124+
return "Failed to fetch JWKS from authorization server";
2125+
case MCP_AUTH_ERROR_INVALID_KEY:
2126+
return "No valid signing key found in JWKS";
2127+
case MCP_AUTH_ERROR_NETWORK_ERROR:
2128+
return "Network communication error";
2129+
case MCP_AUTH_ERROR_INVALID_CONFIG:
2130+
return "Invalid authentication configuration";
2131+
case MCP_AUTH_ERROR_OUT_OF_MEMORY:
2132+
return "Memory allocation failed";
2133+
case MCP_AUTH_ERROR_INVALID_PARAMETER:
2134+
return "Invalid parameter passed to function";
2135+
case MCP_AUTH_ERROR_NOT_INITIALIZED:
2136+
return "Authentication library not initialized";
2137+
case MCP_AUTH_ERROR_INTERNAL_ERROR:
2138+
return "Internal library error";
2139+
default:
2140+
return "Unknown error code";
20652141
}
2066-
2067-
return "Unknown error code";
20682142
}
20692143

20702144
int mcp_auth_error_to_http_status(mcp_auth_error_t error_code) {

0 commit comments

Comments
 (0)