kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(mcp): guard device-flow polling and project fallback types

Tin 037ece51 d7e3f397

+30 -17
+26 -13
packages/mcp/src/auth/device-flow.ts
··· 24 24 : err instanceof Error && err.name === "AbortError"; 25 25 } 26 26 27 + function isObjectRecord(value: unknown): value is Record<string, unknown> { 28 + return value !== null && typeof value === "object"; 29 + } 30 + 27 31 async function fetchWithTimeout( 28 32 input: string, 29 33 init: RequestInit, ··· 55 59 } 56 60 throw err; 57 61 } 58 - const body = (await res.json().catch(() => ({}))) as 59 - | DeviceCodeResponse 60 - | DeviceTokenErrorBody; 62 + const parsedBody: unknown = await res.json().catch(() => ({})); 63 + if (!isObjectRecord(parsedBody)) { 64 + throw new Error( 65 + `device/code: unexpected response ${JSON.stringify(parsedBody)}`, 66 + ); 67 + } 68 + const body = parsedBody; 61 69 if (!res.ok) { 62 70 throw new Error( 63 71 `device/code failed (${res.status}): ${JSON.stringify(body)}`, 64 72 ); 65 73 } 66 - if (!("device_code" in body) || typeof body.device_code !== "string") { 74 + if (typeof body.device_code !== "string") { 67 75 throw new Error(`device/code: unexpected response ${JSON.stringify(body)}`); 68 76 } 69 77 if ( ··· 74 82 `device/code: missing user_code or verification_uri ${JSON.stringify(body)}`, 75 83 ); 76 84 } 77 - const interval = toFiniteNumber((body as DeviceCodeResponse).interval); 78 - const expiresIn = toFiniteNumber((body as DeviceCodeResponse).expires_in); 85 + const interval = toFiniteNumber(body.interval); 86 + const expiresIn = toFiniteNumber(body.expires_in); 79 87 if (interval === undefined || expiresIn === undefined) { 80 88 throw new Error( 81 89 `device/code: invalid interval or expires_in ${JSON.stringify(body)}`, ··· 120 128 for (let attempt = 0; Date.now() - started < maxWait; attempt++) { 121 129 if (attempt > 0) { 122 130 await sleep(intervalMs); 131 + if (Date.now() - started >= maxWait) { 132 + break; 133 + } 123 134 } 124 135 125 136 let res: Response; ··· 141 152 throw err; 142 153 } 143 154 144 - if (Date.now() - started > maxWait) { 155 + if (Date.now() - started >= maxWait) { 145 156 throw new Error("Device authorization timed out waiting for approval."); 146 157 } 147 158 148 - const body = (await res.json().catch(() => ({}))) as { 149 - access_token?: string; 150 - error?: string; 151 - error_description?: string; 152 - }; 159 + const parsedBody: unknown = await res.json().catch(() => ({})); 160 + if (!isObjectRecord(parsedBody)) { 161 + throw new Error( 162 + `device/token failed (${res.status}): ${JSON.stringify(parsedBody)}`, 163 + ); 164 + } 165 + const body = parsedBody; 153 166 154 167 if (res.ok && typeof body.access_token === "string") { 155 168 return body.access_token; 156 169 } 157 170 158 - const err = body.error; 171 + const err = typeof body.error === "string" ? body.error : undefined; 159 172 if (err === "authorization_pending") { 160 173 log("Waiting for device approval…"); 161 174 continue;
+4 -4
packages/mcp/src/tools/register.ts
··· 153 153 const icon = 154 154 patch.icon !== undefined 155 155 ? patch.icon 156 - : existing.icon != null 157 - ? String(existing.icon) 156 + : typeof existing.icon === "string" 157 + ? existing.icon 158 158 : undefined; 159 159 const slug = 160 160 patch.slug ?? ··· 171 171 const isPublic = 172 172 patch.isPublic !== undefined 173 173 ? patch.isPublic 174 - : existing.isPublic != null 175 - ? Boolean(existing.isPublic) 174 + : typeof existing.isPublic === "boolean" 175 + ? existing.isPublic 176 176 : undefined; 177 177 178 178 const body: Record<string, unknown> = { name, slug };