Skip to content

Commit c3ca7ae

Browse files
clean up and add tests for missing API key
1 parent b85b54f commit c3ca7ae

9 files changed

Lines changed: 101 additions & 40 deletions

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,22 @@ You can provide your API key in two ways:
4646

4747
The Paystack MCP Server works with any MCP-compatible client. Below is the standard configuration schema used by most clients (Claude Desktop, ChatGPT Desktop, Cursor, Windsurf, etc.).
4848

49-
### Using npm (recommended)\n\nFor npm-installed server:\n\n```json\n{\n \"mcpServers\": {\n \"paystack\": {\n \"command\": \"npx\",\n \"args\": [\"@paystack/mcp-server\", \"--api-key\", \"sk_test_...\"]\n }\n }\n}\n```\n\n### Using a local build
49+
### Using npm (recommended)
50+
51+
For npm-installed server:
52+
53+
```json
54+
{
55+
"mcpServers": {
56+
"paystack": {
57+
"command": "npx",
58+
"args": ["@paystack/mcp-server", "--api-key", "sk_test_..."]
59+
}
60+
}
61+
}
62+
```
63+
64+
### Using a local build
5065

5166
If you've cloned and built the server locally:
5267

package-lock.json

Lines changed: 4 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"url": "https://github.com/PaystackOSS/paystack-mcp-server.git"
99
},
1010
"bin": {
11-
"paystack-mcp": "./build/index.js"
11+
"paystack-mcp-server": "./build/index.js"
1212
},
1313
"scripts": {
1414
"build": "tsc && cp -r src/data build/",
@@ -33,7 +33,8 @@
3333
"dependencies": {
3434
"@modelcontextprotocol/inspector": "^0.18.0",
3535
"@modelcontextprotocol/sdk": "^1.26.0",
36-
"dotenv": "^17.2.3"
36+
"dotenv": "^17.2.3",
37+
"zod": "^4.3.6"
3738
},
3839
"devDependencies": {
3940
"@apidevtools/swagger-parser": "^12.1.0",

src/config.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ export function getConfig(cliApiKey?: string) {
2929
};
3030
}
3131

32-
// Export validated configuration (for backward compatibility, will use env var)
33-
export const config = getConfig();
34-
3532
// Paystack API configuration factory
3633
export function createPaystackConfig(cliApiKey?: string) {
3734
const cfg = getConfig(cliApiKey);
@@ -41,6 +38,3 @@ export function createPaystackConfig(cliApiKey?: string) {
4138
timeout: 30000, // 30 seconds
4239
} as const;
4340
}
44-
45-
// Default paystack config (for backward compatibility)
46-
export const paystackConfig = createPaystackConfig();

src/index.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#!/usr/bin/env node
2-
import { startServer } from "./server";
31

42
// Simple CLI argument parsing
53
function parseApiKey(): string | undefined {
@@ -41,15 +39,12 @@ async function main() {
4139
process.exit(0);
4240
}
4341

42+
const { startServer } = await import("./server");
43+
4444
// Parse API key from CLI
4545
const cliApiKey = parseApiKey();
4646

47-
// Validate API key format if provided via CLI
48-
if (cliApiKey && !cliApiKey.startsWith('sk_test_')) {
49-
console.error('Error: API key must start with "sk_test_". Only test keys are allowed.');
50-
process.exit(1);
51-
}
52-
47+
5348
// Check if we have an API key from CLI or environment
5449
if (!cliApiKey && !process.env.PAYSTACK_TEST_SECRET_KEY) {
5550
console.error('Error: Paystack API key required.');

src/logger.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { config } from './config.js';
1+
import { getConfig } from './config.js';
22
// Define log levels
33
export enum LogLevel {
44
DEBUG = 'debug',
@@ -92,8 +92,8 @@ function redactSensitiveData(obj: any): any {
9292
class Logger {
9393
private currentLogLevel: LogLevel;
9494

95-
constructor() {
96-
this.currentLogLevel = config.LOG_LEVEL as LogLevel;
95+
constructor(logLevel?: LogLevel) {
96+
this.currentLogLevel = logLevel || LogLevel.INFO;
9797
}
9898

9999
private shouldLog(level: LogLevel): boolean {
@@ -175,4 +175,13 @@ class Logger {
175175
}
176176
}
177177

178+
/**
179+
* Create a logger instance with configuration
180+
*/
181+
export function createLogger(cliApiKey?: string): Logger {
182+
const config = getConfig(cliApiKey);
183+
return new Logger(config.LOG_LEVEL as LogLevel);
184+
}
185+
186+
// Default logger instance for backward compatibility (uses environment variable)
178187
export const logger = new Logger();

src/paystack-client.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { PaystackResponse, PaystackError } from "./types";
2-
import { paystackConfig } from "./config";
2+
import { createPaystackConfig } from "./config";
33

4-
const PAYSTACK_BASE_URL = paystackConfig.baseURL;
4+
const PAYSTACK_BASE_URL = 'https://api.paystack.co';
55
const USER_AGENT = process.env.USER_AGENT || 'Paystack-MCP-Server/0.0.1';
66

77
export class PaystackClient {
@@ -92,7 +92,10 @@ export class PaystackClient {
9292
}
9393
}
9494

95-
// Export singleton instance for backward compatibility
96-
export const paystackClient = new PaystackClient(
97-
paystackConfig.secretKey
98-
);
95+
/**
96+
* Create a PaystackClient instance with configuration
97+
*/
98+
export function createPaystackClient(cliApiKey?: string): PaystackClient {
99+
const config = createPaystackConfig(cliApiKey);
100+
return new PaystackClient(config.secretKey, config.baseURL, undefined, config.timeout);
101+
}

test/make-paystack-request-tool.spec.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ describe("MakePaystackRequestTool", () => {
1717
}
1818
} as any;
1919

20-
registerMakePaystackRequestTool(server);
20+
// Pass a test API key to avoid environment variable requirement
21+
registerMakePaystackRequestTool(server, "sk_test_1234567890abcdef1234567890abcdef12345678");
2122
});
2223

2324
it("should return isError: true for non-JSON responses", async () => {
@@ -145,4 +146,52 @@ describe("MakePaystackRequestTool", () => {
145146
}
146147
});
147148
});
149+
describe("Missing API Key Validation", () => {
150+
let server: McpServer;
151+
let originalExit: typeof process.exit;
152+
let exitCode: number | undefined;
153+
let consoleErrors: string[] = [];
154+
let originalConsoleError: typeof console.error;
155+
156+
beforeEach(() => {
157+
// Mock process.exit to capture exit calls
158+
exitCode = undefined;
159+
originalExit = process.exit;
160+
process.exit = ((code?: number) => {
161+
exitCode = code || 0;
162+
throw new Error(`Process would exit with code ${exitCode}`);
163+
}) as any;
164+
165+
// Mock console.error to capture error messages
166+
consoleErrors = [];
167+
originalConsoleError = console.error;
168+
console.error = (...args: any[]) => {
169+
consoleErrors.push(args.join(' '));
170+
};
171+
172+
server = {
173+
registerTool: (name: string, config: any, handler: any) => { }
174+
} as any;
175+
});
176+
177+
afterEach(() => {
178+
process.exit = originalExit;
179+
console.error = originalConsoleError;
180+
delete process.env.PAYSTACK_TEST_SECRET_KEY;
181+
});
182+
183+
it("should fail when no API key provided via CLI or environment", () => {
184+
// Ensure no environment variable is set
185+
delete process.env.PAYSTACK_TEST_SECRET_KEY;
186+
187+
try {
188+
registerMakePaystackRequestTool(server); // No cliApiKey parameter
189+
assert.fail("Expected registerMakePaystackRequestTool to throw an error");
190+
} catch (error: any) {
191+
assert.ok(error.message.includes("Process would exit with code 1"));
192+
assert.strictEqual(exitCode, 1);
193+
assert.ok(consoleErrors.some(msg => msg.includes("PAYSTACK_TEST_SECRET_KEY is required")));
194+
}
195+
});
196+
});
148197
});

test/paystack-client.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import assert from "node:assert";
2-
import { paystackClient } from "../src/paystack-client.js";
2+
import { createPaystackClient } from "../src/paystack-client.js";
33

44
describe("PaystackClient", () => {
5+
// Use a test API key for the test client
6+
const paystackClient = createPaystackClient("sk_test_1234567890abcdef1234567890abcdef12345678");
7+
58
describe("makeRequest - Non-JSON Response Handling", () => {
69
it("should throw a descriptive error for HTML error responses", async () => {
710
// This test validates that non-JSON responses (like HTML error pages)

0 commit comments

Comments
 (0)