Skip to content

Commit faeed0b

Browse files
committed
refactor(toolsets): stop reading STACKONE_ACCOUNT_ID
Match the Python SDK (stackone-ai-python#23): the account id must be passed as accountId/accountIds, and STACKONE_API_KEY stays the only credential read from the environment. In 3.0 an unset account means every active account, so an environment variable silently setting it changed a toolset's scope without the code saying so. The README and examples now read STACKONE_ACCOUNT_ID themselves and pass it as accountId; unset, it is undefined and the toolset discovers the key's accounts. MIGRATION.md shows the before and after. BREAKING CHANGE: STACKONE_ACCOUNT_ID is no longer read. Pass the account id as accountId or accountIds; without one, the toolset discovers every active account linked to the API key.
1 parent f953281 commit faeed0b

14 files changed

Lines changed: 62 additions & 37 deletions

‎.env.example‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Required for all examples
22
STACKONE_API_KEY=your-stackone-api-key
33

4-
# Optional: without it, the SDK uses every active account linked to the API key
4+
# Optional: the examples read this and pass it as accountId (the SDK itself never reads it).
5+
# Without it, the SDK uses every active account linked to the API key
56
STACKONE_ACCOUNT_ID=your-account-id
67

78
# Required for OpenAI examples (openai-integration, openai-responses-integration, ai-sdk-integration)

‎MIGRATION.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ await toolset.execute('linear_list_comments', { headers: { 'x-request-id': 'abc'
179179

180180
**With no account id, the SDK discovers your accounts.** In 2.x, calling `fetchTools()` with no account listed tools without an `x-account-id`, which the API refuses. In 3.0 it asks `GET /accounts` (also available as `toolset.fetchAccounts()`) and lists the catalog of every active account. If you have many accounts, pass `accountId`, `accountIds` or call `setAccounts()` so the SDK does not fetch every catalog.
181181

182+
**`STACKONE_ACCOUNT_ID` is no longer read.** Pass the account id as `accountId` or `accountIds`. Since an unset account now means every active account, an environment variable that set it implicitly could widen or narrow a toolset's scope without the code saying so. `STACKONE_API_KEY` is still read. To keep the variable, read it yourself; unset, it is `undefined` and the toolset discovers your accounts:
183+
184+
```typescript
185+
// 2.x: STACKONE_ACCOUNT_ID picked up implicitly
186+
const toolset = new StackOneToolSet();
187+
188+
// 3.0
189+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
190+
```
191+
182192
**Listings are merged in sorted account order.** When two accounts serve the same tool name, `getTool()` returns the first one listed — now the one on the lowest account id, where 2.x followed the order you passed. A warning names the clashing tools. Pass `accountIds` to choose the account yourself.
183193

184194
**`fetchTools()` returns fresh tool instances on every call**, never the cached `Tools`, so `setAccountId()` on one tool no longer changes what later callers get. An account whose listing fails is skipped with a warning, unless every account fails.

‎README.md‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ for (const account of await toolset.fetchAccounts()) {
7777
7878
### Account IDs
7979

80-
StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels (a per-call `accountIds` wins, then `setAccounts()`, then the constructor's `accountIds`, then `accountId` or `STACKONE_ACCOUNT_ID`, then discovery):
80+
StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels (a per-call `accountIds` wins, then `setAccounts()`, then the constructor's `accountIds`, then `accountId`, then discovery). The SDK never reads an account id from the environment, so an environment variable cannot silently change which accounts a toolset reaches. To keep yours in `STACKONE_ACCOUNT_ID`, read it yourself:
8181

8282
```typescript
8383
import { StackOneToolSet } from '@stackone/ai';
8484

85-
// Simplest: set STACKONE_ACCOUNT_ID environment variable
86-
const toolset = new StackOneToolSet();
85+
// Single account, read from your own environment variable. Unset means discovery
86+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
8787
const tools = await toolset.fetchTools();
8888

8989
// Explicit single account
@@ -125,8 +125,8 @@ npm install @stackone/ai openai # or: yarn/pnpm/bun add
125125
import { OpenAI } from 'openai';
126126
import { StackOneToolSet } from '@stackone/ai';
127127

128-
// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environment
129-
const toolset = new StackOneToolSet();
128+
// Reads STACKONE_API_KEY from the environment. Without an account id, every active account is used
129+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
130130

131131
const tools = await toolset.fetchTools({ actions: ['workday_*'] });
132132
const openai = new OpenAI();
@@ -163,8 +163,8 @@ npm install @stackone/ai openai # or: yarn/pnpm/bun add
163163
import OpenAI from 'openai';
164164
import { StackOneToolSet } from '@stackone/ai';
165165

166-
// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environment
167-
const toolset = new StackOneToolSet();
166+
// Reads STACKONE_API_KEY from the environment. Without an account id, every active account is used
167+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
168168

169169
const tools = await toolset.fetchTools();
170170

@@ -193,8 +193,8 @@ npm install @stackone/ai @anthropic-ai/sdk # or: yarn/pnpm/bun add
193193
import Anthropic from '@anthropic-ai/sdk';
194194
import { StackOneToolSet } from '@stackone/ai';
195195

196-
// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environment
197-
const toolset = new StackOneToolSet();
196+
// Reads STACKONE_API_KEY from the environment. Without an account id, every active account is used
197+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
198198

199199
const tools = await toolset.fetchTools();
200200

@@ -232,8 +232,8 @@ import { openai } from '@ai-sdk/openai';
232232
import { generateText, stepCountIs } from 'ai';
233233
import { StackOneToolSet } from '@stackone/ai';
234234

235-
// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environment
236-
const toolset = new StackOneToolSet();
235+
// Reads STACKONE_API_KEY from the environment. Without an account id, every active account is used
236+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
237237

238238
const tools = await toolset.fetchTools();
239239

@@ -259,8 +259,8 @@ npm install @stackone/ai @anthropic-ai/claude-agent-sdk zod # or: yarn/pnpm/bun
259259
import { query } from '@anthropic-ai/claude-agent-sdk';
260260
import { StackOneToolSet } from '@stackone/ai';
261261

262-
// Reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from environment
263-
const toolset = new StackOneToolSet();
262+
// Reads STACKONE_API_KEY from the environment. Without an account id, every active account is used
263+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
264264

265265
// Fetch tools and convert to Claude Agent SDK format
266266
const tools = await toolset.fetchTools();

‎examples/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Set your credentials using either approach:
1616

1717
```bash
1818
export STACKONE_API_KEY=your-stackone-api-key
19-
export STACKONE_ACCOUNT_ID=your-account-id # optional: accounts are discovered otherwise
19+
export STACKONE_ACCOUNT_ID=your-account-id # optional: the examples pass it as accountId
2020
export OPENAI_API_KEY=your-openai-api-key # for OpenAI/AI SDK examples
2121
export ANTHROPIC_API_KEY=your-anthropic-api-key # for Anthropic examples
2222
```
@@ -79,6 +79,6 @@ Walks through every way to configure API keys and account IDs: reading from envi
7979
| Variable | Required | Used By |
8080
| --------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
8181
| `STACKONE_API_KEY` | Yes | All examples |
82-
| `STACKONE_ACCOUNT_ID` | Optional | All examples (read automatically; without it, accounts are discovered from the key) |
82+
| `STACKONE_ACCOUNT_ID` | Optional | Read by the examples and passed as `accountId`; without it, accounts are discovered |
8383
| `OPENAI_API_KEY` | For OpenAI / AI SDK examples | `openai-integration.ts`, `openai-responses-integration.ts`, `ai-sdk-integration.ts` |
8484
| `ANTHROPIC_API_KEY` | For Anthropic examples | `anthropic-integration.ts`, `claude-agent-sdk-integration.ts` |

‎examples/ai-sdk-integration.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ if (!process.env.OPENAI_API_KEY) {
2828
}
2929

3030
const aiSdkIntegration = async (): Promise<void> => {
31-
// Initialize StackOne — reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from env
32-
const toolset = new StackOneToolSet();
31+
// Initialize StackOne — reads STACKONE_API_KEY from env. The account id is passed explicitly;
32+
// without one, the toolset uses every active account linked to the key
33+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
3334

3435
// Filter to specific tools to keep token usage manageable
3536
const tools = await toolset.fetchTools({

‎examples/anthropic-integration.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ if (!process.env.ANTHROPIC_API_KEY) {
1717
}
1818

1919
const anthropicIntegration = async (): Promise<void> => {
20-
// Initialize StackOne — reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from env
21-
const toolset = new StackOneToolSet();
20+
// Initialize StackOne — reads STACKONE_API_KEY from env. The account id is passed explicitly;
21+
// without one, the toolset uses every active account linked to the key
22+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
2223

2324
// Filter for any relevant tools
2425
const tools = await toolset.fetchTools({

‎examples/auth-management.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* Run with:
77
* STACKONE_API_KEY=xxx npx tsx examples/auth-management.ts
88
*
9-
* STACKONE_ACCOUNT_ID is optional: without it, accounts are discovered from the API key.
9+
* STACKONE_ACCOUNT_ID is optional, and only read by this example: the SDK never reads it. Without
10+
* it, accounts are discovered from the API key.
1011
*/
1112

1213
import process from 'node:process';
@@ -21,7 +22,7 @@ if (!process.env.STACKONE_API_KEY) {
2122
const apiKeyFromEnv = async (): Promise<void> => {
2223
console.log('=== 1a. API Key from environment ===\n');
2324

24-
// Reads STACKONE_API_KEY (and STACKONE_ACCOUNT_ID, if set) from env automatically
25+
// Reads STACKONE_API_KEY from env automatically. No account is configured, so it discovers them
2526
const toolset = new StackOneToolSet();
2627
const tools = await toolset.fetchTools();
2728
console.log(` Loaded ${tools.toOpenAI().length} tools using env API key\n`);
@@ -51,11 +52,12 @@ const apiKeyAlone = async (): Promise<void> => {
5152
const accountIdFromEnv = async (): Promise<void> => {
5253
console.log('=== 2. Account ID from environment ===\n');
5354

54-
// The Node SDK reads STACKONE_ACCOUNT_ID from env automatically
55+
// The SDK only reads the API key from env; read the account id yourself and pass it.
56+
// Unset, it is undefined and the toolset discovers every active account instead.
5557
const accountId = process.env.STACKONE_ACCOUNT_ID;
5658
console.log(` STACKONE_ACCOUNT_ID is ${accountId ? 'set' : 'not set'}`);
5759

58-
const toolset = new StackOneToolSet();
60+
const toolset = new StackOneToolSet({ accountId });
5961
const tools = await toolset.fetchTools();
6062
console.log(` Loaded ${tools.toOpenAI().length} tools for account from env\n`);
6163
};

‎examples/claude-agent-sdk-integration.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ if (!process.env.ANTHROPIC_API_KEY) {
2323
}
2424

2525
const claudeAgentSdkIntegration = async (): Promise<void> => {
26-
// Initialize StackOne — reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from env
27-
const toolset = new StackOneToolSet();
26+
// Initialize StackOne — reads STACKONE_API_KEY from env. The account id is passed explicitly;
27+
// without one, the toolset uses every active account linked to the key
28+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
2829

2930
// Fetch tools from StackOne and convert to Claude Agent SDK format
3031
const tools = await toolset.fetchTools();

‎examples/openai-integration.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ if (!process.env.OPENAI_API_KEY) {
1717
}
1818

1919
const openaiIntegration = async (): Promise<void> => {
20-
// Initialize StackOne — reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from env
21-
const toolset = new StackOneToolSet();
20+
// Initialize StackOne — reads STACKONE_API_KEY from env. The account id is passed explicitly;
21+
// without one, the toolset uses every active account linked to the key
22+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
2223

2324
// Filter to specific tools to stay within OpenAI's 128-tool limit
2425
const tools = await toolset.fetchTools({

‎examples/openai-responses-integration.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ if (!process.env.OPENAI_API_KEY) {
1717
}
1818

1919
const openaiResponsesIntegration = async (): Promise<void> => {
20-
// Initialize StackOne — reads STACKONE_API_KEY and STACKONE_ACCOUNT_ID from env
21-
const toolset = new StackOneToolSet();
20+
// Initialize StackOne — reads STACKONE_API_KEY from env. The account id is passed explicitly;
21+
// without one, the toolset uses every active account linked to the key
22+
const toolset = new StackOneToolSet({ accountId: process.env.STACKONE_ACCOUNT_ID });
2223

2324
// Fetch tools via MCP
2425
const tools = await toolset.fetchTools({

0 commit comments

Comments
 (0)