1212 * - Full TypeScript support
1313 *
1414 * @module @gitscrum -studio/mcp-server/client
15- * @author GitScrum <hello @gitscrum.com>
15+ * @author GitScrum <support @gitscrum.com>
1616 * @license MIT
1717 */
1818
@@ -29,9 +29,27 @@ export interface ApiResponse<T> {
2929 } ;
3030}
3131
32+ /**
33+ * Standard API error response from GitScrum API
34+ * All errors follow this format with HTTP status codes:
35+ * - 401: Unauthorized (token issues)
36+ * - 402: Payment Required (PRO features)
37+ * - 403: Forbidden (permission denied)
38+ * - 404: Not Found
39+ * - 409: Conflict (validation, business rules)
40+ * - 429: Rate Limited
41+ * - 500: Server Error
42+ */
3243interface ApiError {
44+ error_code ?: string ;
3345 message : string ;
3446 errors ?: Record < string , string [ ] > ;
47+ // 402 specific
48+ feature ?: string ;
49+ upgrade_url ?: string ;
50+ // 429 specific
51+ retry_after ?: number ;
52+ plan ?: string ;
3553}
3654
3755export class GitScrumClient {
@@ -102,56 +120,93 @@ export class GitScrumClient {
102120
103121 if ( ! response . ok ) {
104122 const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as ApiError ;
123+ const errorCode = errorData . error_code || "" ;
105124 const errorMessage = errorData . message || `API Error: ${ response . status } ` ;
106125
107- // Handle 400 Bad Request - validation or invalid input
108- if ( response . status === 400 ) {
109- throw new Error ( `Invalid request: ${ errorMessage } ` ) ;
110- }
111-
112126 // Handle 401 Unauthorized - authentication issue
113127 if ( response . status === 401 ) {
114- throw new Error ( "Session expired. Authentication required. Use login to reconnect." ) ;
128+ // Use error_code for specific auth errors
129+ if ( errorCode === "TOKEN_EXPIRED" || errorCode === "SESSION_EXPIRED" ) {
130+ throw new Error ( "Session expired. Use auth_login to reconnect." ) ;
131+ }
132+ if ( errorCode === "TOKEN_INVALID" || errorCode === "TOKEN_MISSING" ) {
133+ throw new Error ( "Authentication required. Use auth_login to authenticate." ) ;
134+ }
135+ if ( errorCode === "MFA_REQUIRED" ) {
136+ throw new Error ( "Multi-factor authentication required." ) ;
137+ }
138+ throw new Error ( "Authentication required. Use auth_login to authenticate." ) ;
139+ }
140+
141+ // Handle 402 Payment Required - PRO feature or subscription issue
142+ if ( response . status === 402 ) {
143+ const feature = errorData . feature || "this feature" ;
144+ if ( errorCode === "PRO_FEATURE_REQUIRED" ) {
145+ throw new Error ( `PRO feature required: ${ feature } . Upgrade at ${ errorData . upgrade_url || "https://gitscrum.com/pricing" } ` ) ;
146+ }
147+ if ( errorCode === "SUBSCRIPTION_EXPIRED" ) {
148+ throw new Error ( `Subscription expired. Renew at ${ errorData . upgrade_url || "https://gitscrum.com/pricing" } ` ) ;
149+ }
150+ throw new Error ( `Payment required: ${ errorMessage } ` ) ;
115151 }
116152
117- // Handle 403 Forbidden
153+ // Handle 403 Forbidden - permission denied
118154 if ( response . status === 403 ) {
155+ // Use error_code for specific permission messages
156+ if ( errorCode === "CLIENT_READ_ONLY" ) {
157+ throw new Error ( "Clients have read-only access to this resource." ) ;
158+ }
159+ if ( errorCode === "OWNER_ONLY" ) {
160+ throw new Error ( "Only workspace owners can perform this action." ) ;
161+ }
162+ if ( errorCode === "ADMIN_ONLY" || errorCode === "MANAGER_ONLY" ) {
163+ throw new Error ( `Access denied: ${ errorMessage } ` ) ;
164+ }
119165 throw new Error ( `Access denied: ${ errorMessage } ` ) ;
120166 }
121167
122168 // Handle 404 Not Found
123169 if ( response . status === 404 ) {
124- throw new Error ( "Resource not found or was deleted." ) ;
170+ // Use error_code for specific resource type
171+ const resourceMessages : Record < string , string > = {
172+ TASK_NOT_FOUND : "Task not found or was deleted." ,
173+ PROJECT_NOT_FOUND : "Project not found or was deleted." ,
174+ WORKSPACE_NOT_FOUND : "Workspace not found or was deleted." ,
175+ SPRINT_NOT_FOUND : "Sprint not found or was deleted." ,
176+ USER_NOT_FOUND : "User not found." ,
177+ USER_STORY_NOT_FOUND : "User story not found or was deleted." ,
178+ } ;
179+ throw new Error ( resourceMessages [ errorCode ] || "Resource not found or was deleted." ) ;
125180 }
126181
127- // Handle 409 Conflict - resource conflict (e.g., duplicate, pending operation)
182+ // Handle 409 Conflict - validation errors and business rule violations
128183 if ( response . status === 409 ) {
184+ // VALIDATION_FAILED includes field-level errors
185+ if ( errorCode === "VALIDATION_FAILED" && errorData . errors ) {
186+ const fieldErrors = Object . entries ( errorData . errors )
187+ . map ( ( [ field , msgs ] ) => `${ field } : ${ msgs . join ( ", " ) } ` )
188+ . join ( "; " ) ;
189+ throw new Error ( `Validation failed: ${ fieldErrors } ` ) ;
190+ }
191+ // Other conflict errors
129192 throw new Error ( `Conflict: ${ errorMessage } ` ) ;
130193 }
131194
132- // Handle 422 Unprocessable Entity - validation errors
133- if ( response . status === 422 ) {
134- throw new Error ( JSON . stringify ( {
135- error : "validation_failed" ,
136- message : errorMessage ,
137- validation_errors : errorData . errors
138- } ) ) ;
139- }
140-
141- // Handle 429 Too Many Requests - MCP rate limit exceeded
195+ // Handle 429 Too Many Requests - rate limit exceeded
142196 if ( response . status === 429 ) {
197+ const retryAfter = errorData . retry_after || parseInt ( response . headers . get ( "Retry-After" ) || "60" ) ;
143198 throw new Error ( JSON . stringify ( {
144- error : "rate_limit_exceeded ",
145- limit : response . headers . get ( "X-MCP-RateLimit-Limit" ) ,
146- remaining : response . headers . get ( "X-MCP-RateLimit-Remaining" ) ,
147- reset : response . headers . get ( "X-MCP-RateLimit-Reset" ) ,
148- upgrade_url : "https://gitscrum.com/pricing"
199+ error_code : errorCode || "RATE_LIMIT_EXCEEDED ",
200+ message : errorMessage ,
201+ retry_after : retryAfter ,
202+ plan : errorData . plan ,
203+ upgrade_url : errorData . upgrade_url || "https://gitscrum.com/pricing"
149204 } ) ) ;
150205 }
151206
152207 // Handle 500+ Server Errors
153208 if ( response . status >= 500 ) {
154- throw new Error ( `Server error: ${ errorMessage } ` ) ;
209+ throw new Error ( `Server error: ${ errorMessage } . Please try again later. ` ) ;
155210 }
156211
157212 throw new Error ( errorMessage ) ;
0 commit comments