Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.

Commit c39c990

Browse files
authored
Merge pull request #40 from runbasehq/dev
feat: add terminal UI for test observability
2 parents c7fc9de + 269fc66 commit c39c990

File tree

21 files changed

+1490
-240
lines changed

21 files changed

+1490
-240
lines changed

.github/workflows/release.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ jobs:
3939
- name: Build package
4040
run: pnpm build
4141

42-
- name: Publish to npm
43-
run: pnpm changeset publish
42+
- name: Create GitHub Release and Publish to npm
43+
uses: changesets/action@v1
44+
with:
45+
publish: pnpm changeset publish
4446
env:
47+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4548
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

packages/mcp-check/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# mcp-testing-library
22

3+
## 0.2.3
4+
5+
### Patch Changes
6+
7+
- Introduced a terminal UI using Ink to visualize streamed output from different AI models during execution with npx mcp-check.
8+
39
## 0.2.2
410

511
### Patch Changes

packages/mcp-check/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,27 @@ const mcpServer = new McpServer({
5858
});
5959
```
6060

61-
### client(mcpServer, models)
61+
### client(mcpServer, models, config?)
6262

6363
Create a client instance to execute prompts:
6464

6565
```typescript
66-
const agent = client(mcpServer, ["claude-3-haiku-20240307", "gpt-4"])
66+
const agent = client(mcpServer, ["claude-3-haiku-20240307", "gpt-4"], {
67+
// Provider-specific API keys (optional - can also use environment variables)
68+
anthropicApiKey: "your-anthropic-key-here",
69+
openaiApiKey: "your-openai-key-here"
70+
})
6771
.prompt("Your prompt here")
6872
.execute();
6973
```
7074

7175
**Parameters:**
7276
- `mcpServer`: Configured MCP server instance
7377
- `models`: Array of AI model names to use
78+
- `config`: Optional configuration object
79+
- `anthropicApiKey`: API key for Anthropic models
80+
- `openaiApiKey`: API key for OpenAI models
81+
7482

7583
**Supported Models:**
7684
- Claude models: `claude-3-haiku-20240307`, `claude-3-5-sonnet-20240620`, etc.
@@ -114,10 +122,19 @@ const mcpServer = new McpServer({
114122

115123
describe("MCP Server Tests", () => {
116124
test("should use expected tools", async () => {
125+
// Option 1: Use environment variables (no config needed)
117126
const agent = await client(mcpServer, ["claude-3-haiku-20240307"])
118127
.prompt("Update the content using the available tools.")
119128
.execute();
120129

130+
// Option 2: Pass API keys directly in config
131+
const agentWithConfig = await client(mcpServer, ["claude-3-haiku-20240307", "gpt-4"], {
132+
anthropicApiKey: "your-anthropic-key-here",
133+
openaiApiKey: "your-openai-key-here"
134+
})
135+
.prompt("Update the content using the available tools.")
136+
.execute();
137+
121138
// Verify tools were used
122139
expect(agent.usedTools).toHaveProperty("claude-3-haiku-20240307");
123140
expect(agent.usedTools["claude-3-haiku-20240307"]!).toEqual(
@@ -149,4 +166,4 @@ Set the following environment variables for AI model authentication:
149166
ANTHROPIC_API_KEY=your_anthropic_key_here
150167
OPENAI_API_KEY=your_openai_key_here
151168
MCP_TOKEN=your_mcp_server_token_here
152-
```
169+
```

packages/mcp-check/bin/mcp-check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
import('../dist/ui/cli.js');

packages/mcp-check/bin/mcp-check.cjs

Lines changed: 0 additions & 38 deletions
This file was deleted.

packages/mcp-check/package.json

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "mcp-check",
33
"module": "index.ts",
4-
"version": "0.2.2",
4+
"version": "0.2.3",
55
"type": "module",
66
"main": "dist/index.js",
77
"types": "dist/index.d.ts",
88
"devDependencies": {
99
"@types/bun": "latest",
1010
"@types/jest": "^30.0.0",
11+
"@types/react": "^19.1.8",
1112
"jest": "^30.0.5",
1213
"ts-jest": "^29.4.0",
1314
"ts-node": "^10.9.2"
@@ -18,12 +19,11 @@
1819
"dependencies": {
1920
"@anthropic-ai/sdk": "^0.56.0",
2021
"@modelcontextprotocol/sdk": "^1.16.0",
21-
"bun-plugin-dts": "^0.3.0",
22-
"dotenv": "^16.4.7",
2322
"glob": "^11.0.0",
24-
"mcp-check-agents": "^0.1.0",
23+
"ink": "^6.1.0",
2524
"openai": "^5.10.1",
26-
"tsx": "^4.19.2",
25+
"react": "^19.1.1",
26+
"string-width": "^7.2.0",
2727
"zod": "^3.24.4"
2828
},
2929
"description": "",
@@ -35,11 +35,13 @@
3535
}
3636
},
3737
"bin": {
38-
"mcp-check": "./bin/mcp-check.cjs"
38+
"mcp-check": "./bin/mcp-check"
3939
},
4040
"scripts": {
4141
"build": "tsup",
42-
"prepublishOnly": "pnpm build"
42+
"prepublishOnly": "pnpm build",
43+
"test": "./bin/mcp-test",
44+
"test:jest": "jest"
4345
},
4446
"files": [
4547
"dist",
@@ -48,5 +50,10 @@
4850
],
4951
"publishConfig": {
5052
"access": "public"
51-
}
53+
},
54+
"repository": {
55+
"type": "git",
56+
"url": "https://github.com/runbasehq/mcp-check.git"
57+
},
58+
"homepage": "https://github.com/runbasehq/mcp-check#readme"
5259
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export { ChunkNormalizer } from "./normalizer.js";
2+
3+
export type {
4+
ToolCall,
5+
StreamResult,
6+
NormalizedChunk,
7+
NormalizedChunkType,
8+
ChunkHandlerConfig,
9+
ChunkCallback,
10+
ChunkTypeCallback,
11+
} from "./types.js";
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { NormalizedChunk, ChunkHandlerConfig } from "./types.js";
2+
3+
export class ChunkNormalizer {
4+
static async processChunk(normalizedChunk: NormalizedChunk, handlers: ChunkHandlerConfig): Promise<void> {
5+
// Provider-specific
6+
if (normalizedChunk.provider === "anthropic") {
7+
if (handlers.anthropic?.onContentBlockDelta && normalizedChunk.originalChunk?.type === "content_block_delta") {
8+
await handlers.anthropic.onContentBlockDelta(normalizedChunk.originalChunk);
9+
}
10+
if (handlers.anthropic?.onContentBlockStart && normalizedChunk.originalChunk?.type === "content_block_start") {
11+
await handlers.anthropic.onContentBlockStart(normalizedChunk.originalChunk);
12+
}
13+
if (handlers.anthropic?.onContentBlockStop && normalizedChunk.originalChunk?.type === "content_block_stop") {
14+
await handlers.anthropic.onContentBlockStop(normalizedChunk.originalChunk);
15+
}
16+
} else if (normalizedChunk.provider === "openai") {
17+
if (handlers.openai?.onResponseOutputItemAdded && normalizedChunk.originalChunk?.type === "response.output_item.added") {
18+
await handlers.openai.onResponseOutputItemAdded(normalizedChunk.originalChunk);
19+
}
20+
if (handlers.openai?.onResponseOutputItemDone && normalizedChunk.originalChunk?.type === "response.output_item.done") {
21+
await handlers.openai.onResponseOutputItemDone(normalizedChunk.originalChunk);
22+
}
23+
}
24+
25+
switch (normalizedChunk.type) {
26+
case "text_delta":
27+
if (handlers.onTextDelta) {
28+
await handlers.onTextDelta(normalizedChunk.data);
29+
}
30+
break;
31+
case "tool_call_start":
32+
if (handlers.onToolCallStart) {
33+
await handlers.onToolCallStart(normalizedChunk.data);
34+
}
35+
break;
36+
case "tool_call_done":
37+
if (handlers.onToolCallDone) {
38+
await handlers.onToolCallDone(normalizedChunk.data);
39+
}
40+
break;
41+
case "tool_result":
42+
if (handlers.onToolResult) {
43+
await handlers.onToolResult(normalizedChunk.data);
44+
}
45+
break;
46+
case "message_start":
47+
if (handlers.onMessageStart) {
48+
await handlers.onMessageStart(normalizedChunk.data);
49+
}
50+
break;
51+
case "message_done":
52+
if (handlers.onMessageDone) {
53+
await handlers.onMessageDone(normalizedChunk.data);
54+
}
55+
break;
56+
case "thinking_delta":
57+
if (handlers.onThinkingDelta) {
58+
await handlers.onThinkingDelta(normalizedChunk.data);
59+
}
60+
break;
61+
case "error":
62+
if (handlers.onError) {
63+
await handlers.onError(normalizedChunk.data);
64+
}
65+
break;
66+
}
67+
68+
if (handlers.onAnyChunk) {
69+
await handlers.onAnyChunk(normalizedChunk);
70+
}
71+
}
72+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
export interface ToolCall {
2+
args: Record<string, any>;
3+
result?: any;
4+
}
5+
6+
export interface StreamResult {
7+
usedTools: string[];
8+
content: string;
9+
toolCalls: Record<string, ToolCall[]>;
10+
}
11+
12+
export type NormalizedChunkType =
13+
| 'text_delta'
14+
| 'tool_call_start'
15+
| 'tool_call_done'
16+
| 'tool_result'
17+
| 'message_start'
18+
| 'message_done'
19+
| 'thinking_delta'
20+
| 'error';
21+
22+
export interface NormalizedChunk {
23+
type: NormalizedChunkType;
24+
provider: 'anthropic' | 'openai';
25+
timestamp: number;
26+
data: {
27+
text?: string;
28+
toolName?: string;
29+
toolArgs?: Record<string, any>;
30+
toolResult?: any;
31+
error?: string;
32+
[key: string]: any;
33+
};
34+
originalChunk?: any;
35+
}
36+
37+
export type ChunkCallback = (chunk: NormalizedChunk) => void | Promise<void>;
38+
export type ChunkTypeCallback = (data: NormalizedChunk['data']) => void | Promise<void>;
39+
40+
export interface ChunkHandlerConfig {
41+
onTextDelta?: ChunkTypeCallback;
42+
onToolCallStart?: ChunkTypeCallback;
43+
onToolCallDone?: ChunkTypeCallback;
44+
onToolResult?: ChunkTypeCallback;
45+
onMessageStart?: ChunkTypeCallback;
46+
onMessageDone?: ChunkTypeCallback;
47+
onThinkingDelta?: ChunkTypeCallback;
48+
onError?: ChunkTypeCallback;
49+
onAnyChunk?: ChunkCallback;
50+
51+
// Provider-specific handlers
52+
anthropic?: {
53+
onContentBlockDelta?: (chunk: any) => void | Promise<void>;
54+
onContentBlockStart?: (chunk: any) => void | Promise<void>;
55+
onContentBlockStop?: (chunk: any) => void | Promise<void>;
56+
};
57+
openai?: {
58+
onResponseOutputItemAdded?: (chunk: any) => void | Promise<void>;
59+
onResponseOutputItemDone?: (chunk: any) => void | Promise<void>;
60+
};
61+
}

0 commit comments

Comments
 (0)