Skip to content

Commit 0ad6f59

Browse files
committed
fix(rpc): send x-account-id as HTTP header in RPC requests
The RPC client was sending x-account-id only in the request body's headers field, but the StackOne API expects it as an actual HTTP header. This caused 400 "Missing x-account-id header" errors when executing tools via the RPC endpoint. The fix extracts x-account-id from the request headers and includes it as an HTTP header on the fetch request while preserving it in the body for backwards compatibility. Uses type narrowing (typeof === 'string') to satisfy TypeScript since the headers schema uses Record<string, unknown>.
1 parent 5df53c0 commit 0ad6f59

File tree

3 files changed

+38
-5
lines changed

3 files changed

+38
-5
lines changed

mocks/handlers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ export const handlers = [
233233
// ============================================================
234234
http.post('https://api.stackone.com/actions/rpc', async ({ request }) => {
235235
const authHeader = request.headers.get('Authorization');
236+
const accountIdHeader = request.headers.get('x-account-id');
236237

237238
// Check for authentication
238239
if (!authHeader || !authHeader.startsWith('Basic ')) {
@@ -258,6 +259,16 @@ export const handlers = [
258259
);
259260
}
260261

262+
// Test action to verify x-account-id is sent as HTTP header
263+
if (body.action === 'test_account_id_header') {
264+
return HttpResponse.json({
265+
data: {
266+
httpHeader: accountIdHeader,
267+
bodyHeader: body.headers?.['x-account-id'],
268+
},
269+
});
270+
}
271+
261272
// Return mock response based on action
262273
if (body.action === 'hris_get_employee') {
263274
return HttpResponse.json({

src/rpc-client.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,20 @@ test('should work with only action parameter', async () => {
102102
// Response has data field (server returns { data: { action, received } })
103103
expect(response).toHaveProperty('data');
104104
});
105+
106+
test('should send x-account-id as HTTP header', async () => {
107+
const client = new RpcClient({
108+
security: { username: 'test-api-key' },
109+
});
110+
111+
const response = await client.actions.rpcAction({
112+
action: 'test_account_id_header',
113+
headers: { 'x-account-id': 'test-account-123' },
114+
});
115+
116+
// Verify x-account-id is sent both as HTTP header and in request body
117+
expect(response.data).toMatchObject({
118+
httpHeader: 'test-account-123',
119+
bodyHeader: 'test-account-123',
120+
});
121+
});

src/rpc-client.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,18 @@ export class RpcClient {
5353
query: validatedRequest.query,
5454
} as const satisfies RpcActionRequest;
5555

56+
// Extract x-account-id from request headers to send as HTTP header
57+
const accountId = validatedRequest.headers?.['x-account-id'];
58+
const httpHeaders = {
59+
'Content-Type': 'application/json',
60+
Authorization: this.authHeader,
61+
'User-Agent': 'stackone-ai-node',
62+
...(typeof accountId === 'string' ? { 'x-account-id': accountId } : {}),
63+
} satisfies Record<string, string>;
64+
5665
const response = await fetch(url, {
5766
method: 'POST',
58-
headers: {
59-
'Content-Type': 'application/json',
60-
Authorization: this.authHeader,
61-
'User-Agent': 'stackone-ai-node',
62-
},
67+
headers: httpHeaders,
6368
body: JSON.stringify(requestBody),
6469
});
6570

0 commit comments

Comments
 (0)