@blocks-network/mcp-server 0.1.55 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { sendTask } from '../src/tools.js';
3
+ import { makeFakeDeps, makeFakeClient, makeFakeSession } from './helpers.js';
4
+
5
+ describe('send_task', () => {
6
+ it('sends a text part with default partId="text" when agent has no declared inputs', async () => {
7
+ const session = makeFakeSession({ taskId: 't1', terminal: { state: 'completed' } });
8
+ const client = makeFakeClient({ session });
9
+ const { deps, mocks } = makeFakeDeps({ client, agentEntry: null });
10
+
11
+ const res = await sendTask(
12
+ { agentName: 'alice', message: 'hello' },
13
+ deps,
14
+ );
15
+
16
+ expect(client.sendMessageMock).toHaveBeenCalledOnce();
17
+ const call = client.sendMessageMock.mock.calls[0][0];
18
+ expect(call.agentName).toBe('alice');
19
+ expect(call.requestParts).toHaveLength(1);
20
+ expect(mocks.textPart).toHaveBeenCalledWith('hello', 'text');
21
+ expect(res.isError).toBeUndefined();
22
+ expect(res.content[0].text).toContain('Task t1 completed');
23
+ });
24
+
25
+ it('uses the agent card declared text input id', async () => {
26
+ const client = makeFakeClient();
27
+ const { deps, mocks } = makeFakeDeps({
28
+ client,
29
+ agentEntry: {
30
+ agentName: 'bob',
31
+ card: { io: { inputs: [{ id: 'prompt', contentType: 'text/plain' }] } },
32
+ },
33
+ });
34
+
35
+ await sendTask({ agentName: 'bob', message: 'hi' }, deps);
36
+
37
+ expect(mocks.textPart).toHaveBeenCalledWith('hi', 'prompt');
38
+ });
39
+
40
+ it('skips the default text part when caller supplies inputs override for the same id', async () => {
41
+ const client = makeFakeClient();
42
+ const { deps, mocks } = makeFakeDeps({
43
+ client,
44
+ agentEntry: {
45
+ agentName: 'bob',
46
+ card: { io: { inputs: [{ id: 'prompt', contentType: 'text/plain' }] } },
47
+ },
48
+ });
49
+
50
+ await sendTask(
51
+ { agentName: 'bob', message: 'ignored', inputs: { prompt: 'override' } },
52
+ deps,
53
+ );
54
+
55
+ expect(mocks.textPart).toHaveBeenCalledTimes(1);
56
+ expect(mocks.textPart).toHaveBeenCalledWith('override', 'prompt');
57
+ });
58
+
59
+ it('rejects file uploads larger than maxUploadBytes', async () => {
60
+ const client = makeFakeClient();
61
+ const { deps } = makeFakeDeps({
62
+ client,
63
+ maxUploadBytes: 1000,
64
+ fileSize: 5000,
65
+ });
66
+
67
+ const res = await sendTask(
68
+ { agentName: 'alice', message: 'hi', filePath: '/tmp/big.bin' },
69
+ deps,
70
+ );
71
+
72
+ expect(res.isError).toBe(true);
73
+ expect(res.content[0].text).toContain('File too large');
74
+ expect(client.sendMessageMock).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('attaches a file part with the agent-declared file partId', async () => {
78
+ const client = makeFakeClient();
79
+ const { deps, mocks } = makeFakeDeps({
80
+ client,
81
+ agentEntry: {
82
+ agentName: 'alice',
83
+ card: {
84
+ io: {
85
+ inputs: [
86
+ { id: 'prompt', contentType: 'text/plain' },
87
+ { id: 'attachment', contentType: 'image/png' },
88
+ ],
89
+ },
90
+ },
91
+ },
92
+ fileSize: 50,
93
+ });
94
+
95
+ await sendTask(
96
+ { agentName: 'alice', message: 'hi', filePath: '/safe/img.png' },
97
+ deps,
98
+ );
99
+
100
+ expect(mocks.validateFilePath).toHaveBeenCalledWith('/safe/img.png');
101
+ expect(mocks.filePartFromPath).toHaveBeenCalledWith('/safe/img.png', {
102
+ partId: 'attachment',
103
+ contentType: 'image/png',
104
+ });
105
+ });
106
+
107
+ it('forwards taskKind and duration to sendMessage', async () => {
108
+ const client = makeFakeClient();
109
+ const { deps } = makeFakeDeps({ client });
110
+
111
+ await sendTask(
112
+ { agentName: 'alice', message: 'hi', taskKind: 'pipe', duration: 30 },
113
+ deps,
114
+ );
115
+
116
+ const call = client.sendMessageMock.mock.calls[0][0];
117
+ expect(call.taskKind).toBe('pipe');
118
+ expect(call.duration).toBe(30);
119
+ });
120
+
121
+ it('uses the billingMode from the agent registry entry', async () => {
122
+ const { deps, mocks } = makeFakeDeps({
123
+ agentEntry: { agentName: 'alice', billingMode: 'paid' },
124
+ });
125
+
126
+ await sendTask({ agentName: 'alice', message: 'hi' }, deps);
127
+
128
+ expect(mocks.getTaskClient).toHaveBeenCalledWith('paid');
129
+ });
130
+
131
+ it('emits progress lines, includes text artifacts inline, and closes the session', async () => {
132
+ const session = makeFakeSession({
133
+ taskId: 't9',
134
+ terminal: { state: 'completed' },
135
+ progressEvents: [{ message: 'starting' }, { message: 'half-done' }],
136
+ artifacts: [
137
+ {
138
+ fileName: 'out.txt',
139
+ mimeType: 'text/plain',
140
+ data: new TextEncoder().encode('hello world'),
141
+ },
142
+ ],
143
+ });
144
+ const client = makeFakeClient({ session });
145
+ const { deps } = makeFakeDeps({ client });
146
+
147
+ const res = await sendTask({ agentName: 'alice', message: 'hi' }, deps);
148
+
149
+ const text = res.content[0].text;
150
+ expect(text).toContain('Task t9 completed');
151
+ expect(text).toContain('[progress] starting');
152
+ expect(text).toContain('[progress] half-done');
153
+ expect(text).toContain('[artifact: out.txt]');
154
+ expect(text).toContain('hello world');
155
+ expect(session.closeMock).toHaveBeenCalledOnce();
156
+ });
157
+
158
+ it('summarises binary artifacts by size rather than embedding their bytes', async () => {
159
+ const session = makeFakeSession({
160
+ terminal: { state: 'completed' },
161
+ artifacts: [
162
+ {
163
+ fileName: 'image.png',
164
+ mimeType: 'image/png',
165
+ data: new Uint8Array(64),
166
+ },
167
+ ],
168
+ });
169
+ const client = makeFakeClient({ session });
170
+ const { deps } = makeFakeDeps({ client });
171
+
172
+ const res = await sendTask({ agentName: 'alice', message: 'hi' }, deps);
173
+
174
+ expect(res.content[0].text).toContain('[artifact: image.png] (image/png, 64 bytes)');
175
+ });
176
+
177
+ it('reports artifact download failures without crashing', async () => {
178
+ const session = makeFakeSession({
179
+ terminal: { state: 'completed' },
180
+ artifacts: [
181
+ {
182
+ fileName: 'broken.bin',
183
+ mimeType: 'application/octet-stream',
184
+ data: new Uint8Array(),
185
+ downloadFails: true,
186
+ },
187
+ ],
188
+ });
189
+ const client = makeFakeClient({ session });
190
+ const { deps } = makeFakeDeps({ client });
191
+
192
+ const res = await sendTask({ agentName: 'alice', message: 'hi' }, deps);
193
+ expect(res.content[0].text).toContain('[artifact: broken.bin] (download failed)');
194
+ });
195
+
196
+ it('returns isError=true when waitForTerminal rejects (timeout)', async () => {
197
+ const session = makeFakeSession({
198
+ taskId: 't_timeout',
199
+ terminalRejects: new Error('timed out after 60000ms'),
200
+ });
201
+ const client = makeFakeClient({ session });
202
+ const { deps } = makeFakeDeps({ client });
203
+
204
+ const res = await sendTask({ agentName: 'alice', message: 'hi' }, deps);
205
+
206
+ expect(res.isError).toBe(true);
207
+ expect(res.content[0].text).toContain('Task t_timeout error: timed out after 60000ms');
208
+ expect(session.closeMock).toHaveBeenCalledOnce();
209
+ });
210
+
211
+ it('appends an "Error:" line for failed terminals', async () => {
212
+ const session = makeFakeSession({
213
+ terminal: { state: 'failed', error: 'boom' },
214
+ });
215
+ const client = makeFakeClient({ session });
216
+ const { deps } = makeFakeDeps({ client });
217
+
218
+ const res = await sendTask({ agentName: 'alice', message: 'hi' }, deps);
219
+ expect(res.content[0].text).toContain('Error: boom');
220
+ });
221
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { pauseTask, resumeTask, retryTask } from '../src/tools.js';
3
+ import { makeFakeDeps, makeFakeClient } from './helpers.js';
4
+
5
+ describe('pause_task', () => {
6
+ it('forwards taskId to client.pauseTask and confirms', async () => {
7
+ const client = makeFakeClient();
8
+ const { deps } = makeFakeDeps({ client });
9
+
10
+ const res = await pauseTask({ taskId: 't_1' }, deps);
11
+
12
+ expect(client.pauseTaskMock).toHaveBeenCalledWith('t_1');
13
+ expect(res.content[0].text).toBe('Task t_1 paused.');
14
+ });
15
+
16
+ it('propagates errors from pauseTask', async () => {
17
+ const client = makeFakeClient();
18
+ client.pauseTaskMock.mockRejectedValueOnce(new Error('already terminal'));
19
+ const { deps } = makeFakeDeps({ client });
20
+
21
+ await expect(pauseTask({ taskId: 't_err' }, deps)).rejects.toThrow('already terminal');
22
+ });
23
+ });
24
+
25
+ describe('resume_task', () => {
26
+ it('forwards taskId to client.resumeTask and confirms', async () => {
27
+ const client = makeFakeClient();
28
+ const { deps } = makeFakeDeps({ client });
29
+
30
+ const res = await resumeTask({ taskId: 't_2' }, deps);
31
+
32
+ expect(client.resumeTaskMock).toHaveBeenCalledWith('t_2');
33
+ expect(res.content[0].text).toBe('Task t_2 resumed.');
34
+ });
35
+
36
+ it('propagates errors from resumeTask', async () => {
37
+ const client = makeFakeClient();
38
+ client.resumeTaskMock.mockRejectedValueOnce(new Error('not paused'));
39
+ const { deps } = makeFakeDeps({ client });
40
+
41
+ await expect(resumeTask({ taskId: 't_err' }, deps)).rejects.toThrow('not paused');
42
+ });
43
+ });
44
+
45
+ describe('retry_task', () => {
46
+ it('forwards taskId to client.retryTask and confirms', async () => {
47
+ const client = makeFakeClient();
48
+ const { deps } = makeFakeDeps({ client });
49
+
50
+ const res = await retryTask({ taskId: 't_3' }, deps);
51
+
52
+ expect(client.retryTaskMock).toHaveBeenCalledWith('t_3');
53
+ expect(res.content[0].text).toBe('Task t_3 retry requested.');
54
+ });
55
+
56
+ it('propagates errors from retryTask', async () => {
57
+ const client = makeFakeClient();
58
+ client.retryTaskMock.mockRejectedValueOnce(new Error('not failed'));
59
+ const { deps } = makeFakeDeps({ client });
60
+
61
+ await expect(retryTask({ taskId: 't_err' }, deps)).rejects.toThrow('not failed');
62
+ });
63
+ });
64
+
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ },
7
+ });