Skip to content

Commit c89d346

Browse files
Follow MCP best practices: add isError flag to tool responses
- Updated make-paystack-request tool to return isError: true on failures - Updated get-paystack-operation tool to return isError: true on failures - Enhanced error messages to include HTTP status codes - Added comprehensive test suite for tool-level error handling - All 9 tests passing (4 new tool tests + 5 existing client tests) Co-authored-by: Andrew-Paystack <78197464+Andrew-Paystack@users.noreply.github.com>
1 parent 6074b70 commit c89d346

3 files changed

Lines changed: 167 additions & 6 deletions

File tree

src/tools/get-paystack-operation.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export function registerGetPaystackOperationTool(
3434
type: "text",
3535
text: `Operation with ID ${operation_id} not found.`,
3636
},
37-
]
37+
],
38+
isError: true
3839
}
3940
}
4041

@@ -47,14 +48,16 @@ export function registerGetPaystackOperationTool(
4748
},
4849
]
4950
}
50-
} catch {
51+
} catch (error) {
52+
const errorMessage = error instanceof Error ? error.message : String(error);
5153
return {
5254
content: [
5355
{
5456
type: "text",
55-
text: `Operation with ID ${operation_id} not found.`,
57+
text: `Unable to retrieve operation: ${errorMessage}`,
5658
},
57-
]
59+
],
60+
isError: true
5861
}
5962
}
6063
}

src/tools/make-paystack-request.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,23 @@ export function registerMakePaystackRequestTool(server: McpServer) {
3838
]
3939
}
4040
} catch(error) {
41+
// Follow MCP best practices: return isError flag instead of throwing
42+
const errorMessage = error instanceof Error ? error.message : String(error);
43+
const statusCode = (error as any).statusCode;
44+
45+
let detailedMessage = `Unable to make request: ${errorMessage}`;
46+
if (statusCode) {
47+
detailedMessage = `Unable to make request (HTTP ${statusCode}): ${errorMessage}`;
48+
}
49+
4150
return {
4251
content: [
4352
{
4453
type: "text",
45-
text: `Unable to make request. ${error}`,
54+
text: detailedMessage,
4655
},
47-
]
56+
],
57+
isError: true
4858
}
4959
}
5060
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import assert from "node:assert";
2+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import { registerMakePaystackRequestTool } from "../src/tools/make-paystack-request.js";
4+
5+
describe("MakePaystackRequestTool", () => {
6+
describe("Error handling with isError flag", () => {
7+
let server: McpServer;
8+
let toolHandler: any;
9+
10+
before(() => {
11+
// Create a mock MCP server
12+
server = {
13+
registerTool: (name: string, config: any, handler: any) => {
14+
if (name === "make_paystack_request") {
15+
toolHandler = handler;
16+
}
17+
}
18+
} as any;
19+
20+
registerMakePaystackRequestTool(server);
21+
});
22+
23+
it("should return isError: true for non-JSON responses", async () => {
24+
// Mock fetch to return HTML error page
25+
const originalFetch = global.fetch;
26+
global.fetch = async () => {
27+
return {
28+
status: 502,
29+
text: async () => "<html><body><h1>502 Bad Gateway</h1></body></html>",
30+
} as Response;
31+
};
32+
33+
try {
34+
const result = await toolHandler({
35+
request: {
36+
method: "GET",
37+
path: "/test-endpoint",
38+
}
39+
});
40+
41+
// Verify isError flag is set
42+
assert.strictEqual(result.isError, true);
43+
44+
// Verify error message content
45+
assert.ok(result.content);
46+
assert.strictEqual(result.content.length, 1);
47+
assert.strictEqual(result.content[0].type, "text");
48+
assert.ok(result.content[0].text.includes("Unable to make request"));
49+
assert.ok(result.content[0].text.includes("HTTP 502"));
50+
assert.ok(result.content[0].text.includes("non-JSON response"));
51+
} finally {
52+
global.fetch = originalFetch;
53+
}
54+
});
55+
56+
it("should return isError: false (omitted) for successful responses", async () => {
57+
// Mock fetch to return valid JSON
58+
const originalFetch = global.fetch;
59+
const validJsonResponse = {
60+
status: true,
61+
message: "Success",
62+
data: { id: 123 }
63+
};
64+
65+
global.fetch = async () => {
66+
return {
67+
status: 200,
68+
text: async () => JSON.stringify(validJsonResponse),
69+
} as Response;
70+
};
71+
72+
try {
73+
const result = await toolHandler({
74+
request: {
75+
method: "GET",
76+
path: "/test-endpoint",
77+
}
78+
});
79+
80+
// Verify isError is not set (or false) for successful responses
81+
assert.ok(!result.isError);
82+
83+
// Verify success content
84+
assert.ok(result.content);
85+
assert.strictEqual(result.content.length, 1);
86+
assert.strictEqual(result.content[0].type, "text");
87+
assert.strictEqual(result.content[0].mimeType, "application/json");
88+
89+
// Parse and verify the response data
90+
const parsedResponse = JSON.parse(result.content[0].text);
91+
assert.strictEqual(parsedResponse.status, true);
92+
assert.strictEqual(parsedResponse.message, "Success");
93+
} finally {
94+
global.fetch = originalFetch;
95+
}
96+
});
97+
98+
it("should include HTTP status code in error message", async () => {
99+
// Mock fetch to return a 504 Gateway Timeout
100+
const originalFetch = global.fetch;
101+
global.fetch = async () => {
102+
return {
103+
status: 504,
104+
text: async () => "Gateway Timeout",
105+
} as Response;
106+
};
107+
108+
try {
109+
const result = await toolHandler({
110+
request: {
111+
method: "POST",
112+
path: "/transaction/initialize",
113+
data: { amount: 1000 }
114+
}
115+
});
116+
117+
// Verify error response structure
118+
assert.strictEqual(result.isError, true);
119+
assert.ok(result.content[0].text.includes("HTTP 504"));
120+
} finally {
121+
global.fetch = originalFetch;
122+
}
123+
});
124+
125+
it("should handle network errors with isError flag", async () => {
126+
// Mock fetch to simulate network error
127+
const originalFetch = global.fetch;
128+
global.fetch = async () => {
129+
throw new Error("Network connection failed");
130+
};
131+
132+
try {
133+
const result = await toolHandler({
134+
request: {
135+
method: "GET",
136+
path: "/customer/list",
137+
}
138+
});
139+
140+
// Verify error is properly handled
141+
assert.strictEqual(result.isError, true);
142+
assert.ok(result.content[0].text.includes("Unable to make request"));
143+
} finally {
144+
global.fetch = originalFetch;
145+
}
146+
});
147+
});
148+
});

0 commit comments

Comments
 (0)