Skip to content
Merged
4 changes: 2 additions & 2 deletions src/tools/feedback.test.ts → src/feedback.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { http, HttpResponse } from 'msw';
import { server } from '../../mocks/node';
import { StackOneError } from '../utils/errors';
import { server } from '../mocks/node';
import { StackOneError } from './utils/errors';
import { createFeedbackTool } from './feedback';

interface FeedbackResultItem {
Expand Down
8 changes: 4 additions & 4 deletions src/tools/feedback.ts → src/feedback.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { z } from 'zod';
import { DEFAULT_BASE_URL } from '../consts';
import { BaseTool } from '../tool';
import type { ExecuteConfig, ExecuteOptions, JsonDict, ToolParameters } from '../types';
import { StackOneError } from '../utils/errors';
import { DEFAULT_BASE_URL } from './consts';
import { BaseTool } from './tool';
import type { ExecuteConfig, ExecuteOptions, JsonDict, ToolParameters } from './types';
import { StackOneError } from './utils/errors';

interface FeedbackToolOptions {
baseUrl?: string;
Expand Down
File renamed without changes.
20 changes: 18 additions & 2 deletions src/utils/headers.ts → src/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { type StackOneHeaders, stackOneHeadersSchema } from '../schemas/headers';
import type { JsonDict } from '../types';
import { z } from 'zod/mini';
import type { JsonDict } from './types';

/**
* Known StackOne API header keys that are forwarded as HTTP headers
*/
export const STACKONE_HEADER_KEYS = ['x-account-id'] as const;

/**
* Zod schema for StackOne API headers (branded)
* These headers are forwarded as HTTP headers in API requests
*/
export const stackOneHeadersSchema = z.record(z.string(), z.string()).brand<'StackOneHeaders'>();

/**
* Branded type for StackOne API headers
*/
export type StackOneHeaders = z.infer<typeof stackOneHeadersSchema>;

/**
* Normalises header values from JsonDict to StackOneHeaders (branded type)
Expand Down
21 changes: 0 additions & 21 deletions src/index.test.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

export { DEFAULT_BASE_URL, DEFAULT_HYBRID_ALPHA } from './consts';
export { BaseTool, StackOneTool, Tools } from './tool';
export { createFeedbackTool } from './tools/feedback';
export { createFeedbackTool } from './feedback';
export { StackOneAPIError, StackOneError } from './utils/errors';

export {
Expand Down
68 changes: 68 additions & 0 deletions src/mcp-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* MCP client factory tests.
* Tests the createMCPClient function for creating MCP protocol clients.
*/
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { createMCPClient } from './mcp-client';

test('createMCPClient creates client with required options', async () => {
const mcpClient = await createMCPClient({
baseUrl: 'https://api.example.com/mcp',
});

expect(mcpClient.client).toBeInstanceOf(Client);
expect(mcpClient.transport).toBeInstanceOf(StreamableHTTPClientTransport);
expect(typeof mcpClient[Symbol.asyncDispose]).toBe('function');
});

test('createMCPClient creates client with custom headers', async () => {
const mcpClient = await createMCPClient({
baseUrl: 'https://api.example.com/mcp',
headers: {
Authorization: 'Bearer test-token',
'x-custom-header': 'custom-value',
},
});

expect(mcpClient.client).toBeInstanceOf(Client);
expect(mcpClient.transport).toBeInstanceOf(StreamableHTTPClientTransport);
});

test('createMCPClient provides asyncDispose for cleanup', async () => {
const mcpClient = await createMCPClient({
baseUrl: 'https://api.example.com/mcp',
});

// Spy on close methods
const clientCloseSpy = vi.spyOn(mcpClient.client, 'close').mockResolvedValue(undefined);
const transportCloseSpy = vi.spyOn(mcpClient.transport, 'close').mockResolvedValue(undefined);

// Call asyncDispose
await mcpClient[Symbol.asyncDispose]();

expect(clientCloseSpy).toHaveBeenCalledOnce();
expect(transportCloseSpy).toHaveBeenCalledOnce();
});

test('createMCPClient can connect and list tools from MCP server', async () => {

@cubic-dev-ai cubic-dev-ai Bot Dec 10, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This test makes real network calls to an external server without mocking, making it flaky and inconsistent with other tests in the project that use MSW. Consider mocking the MCP server using MSW's server.use() pattern as done in toolsets.test.ts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp-client.test.ts, line 48:

<comment>This test makes real network calls to an external server without mocking, making it flaky and inconsistent with other tests in the project that use MSW. Consider mocking the MCP server using MSW&#39;s `server.use()` pattern as done in `toolsets.test.ts`.</comment>

<file context>
@@ -0,0 +1,68 @@
+	expect(transportCloseSpy).toHaveBeenCalledOnce();
+});
+
+test(&#39;createMCPClient can connect and list tools from MCP server&#39;, async () =&gt; {
+	await using mcpClient = await createMCPClient({
+		baseUrl: &#39;https://api.stackone-dev.com/mcp&#39;,
</file context>
Fix with Cubic

await using mcpClient = await createMCPClient({
baseUrl: 'https://api.stackone-dev.com/mcp',
headers: {
Authorization: `Basic ${Buffer.from('test-key:').toString('base64')}`,
'x-account-id': 'test-account',
},
});

await mcpClient.client.connect(mcpClient.transport);
const result = await mcpClient.client.listTools();

expect(result.tools).toBeDefined();
expect(Array.isArray(result.tools)).toBe(true);
expect(result.tools.length).toBeGreaterThan(0);

const tool = result.tools[0];
expect(tool.name).toBe('dummy_action');
expect(tool.description).toBe('Dummy tool');
expect(tool.inputSchema).toBeDefined();
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { http, HttpResponse } from 'msw';
import { server } from '../../mocks/node';
import { type HttpExecuteConfig, ParameterLocation } from '../types';
import { StackOneAPIError } from '../utils/errors';
import { server } from '../mocks/node';
import { type HttpExecuteConfig, ParameterLocation } from './types';
import { StackOneAPIError } from './utils/errors';
import { RequestBuilder } from './requestBuilder';

describe('RequestBuilder', () => {
Expand Down Expand Up @@ -62,8 +62,7 @@ describe('RequestBuilder', () => {
server.events.removeAllListeners('request:start');
});

it('should initialize with correct properties', () => {
expect(builder).toBeDefined();
it('should initialise with headers from constructor', () => {
expect(builder.getHeaders()).toEqual({ 'Initial-Header': 'test' });
});

Expand Down
4 changes: 2 additions & 2 deletions src/modules/requestBuilder.ts → src/requestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
type HttpExecuteConfig,
type JsonDict,
ParameterLocation,
} from '../types';
import { StackOneAPIError } from '../utils/errors';
} from './types';
import { StackOneAPIError } from './utils/errors';

interface SerializationOptions {
maxDepth?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/rpc-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RpcClient } from './rpc-client';
import { stackOneHeadersSchema } from './schemas/headers';
import { stackOneHeadersSchema } from './headers';
import { StackOneAPIError } from './utils/errors';

test('should successfully execute an RPC action', async () => {
Expand Down
6 changes: 3 additions & 3 deletions src/rpc-client.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { STACKONE_HEADER_KEYS } from './schemas/headers';
import { STACKONE_HEADER_KEYS } from './headers';
import {
type RpcActionRequest,
type RpcActionResponse,
type RpcClientConfig,
rpcActionRequestSchema,
rpcActionResponseSchema,
rpcClientConfigSchema,
} from './schemas/rpc';
} from './schema';
import { StackOneAPIError } from './utils/errors';

// Re-export types for consumers and to make types portable
export type { RpcActionResponse } from './schemas/rpc';
export type { RpcActionResponse } from './schema';

/**
* Custom RPC client for StackOne API.
Expand Down
File renamed without changes.
17 changes: 0 additions & 17 deletions src/schemas/headers.ts

This file was deleted.

160 changes: 0 additions & 160 deletions src/tool.json-schema.test.ts

This file was deleted.

Loading
Loading