Skip to content

Commit 83088e8

Browse files
Merge pull request #19 from PaystackOSS/feat/npm-publish
setup npm distribution
2 parents 6774a1a + 038afad commit 83088e8

13 files changed

Lines changed: 259 additions & 78 deletions

.github/workflows/release.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish MCP to npmjs
2+
on:
3+
release:
4+
types: [published]
5+
6+
jobs:
7+
build:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
contents: read
11+
steps:
12+
- uses: actions/checkout@v5
13+
14+
- name: Setup Node.js
15+
uses: actions/setup-node@v4
16+
with:
17+
node-version: '20.x'
18+
registry-url: 'https://registry.npmjs.org'
19+
20+
- name: Install dependencies
21+
run: npm ci
22+
23+
- name: Sync version with release tag
24+
run: npm version ${{ github.event.release.tag_name }} --no-git-tag-version
25+
26+
- name: Build package
27+
run: npm run build
28+
29+
- name: Publish to npmjs
30+
run: npm publish --access public
31+
env:
32+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
77
88
## Quick Start
99

10-
Clone the repo and build locally:
10+
Install and run via npm (recommended):
11+
12+
```bash
13+
npx @paystack/mcp-server --api-key sk_test_your_key_here
14+
```
15+
16+
Or for local development, clone and build:
1117

1218
```bash
1319
git clone https://github.com/PaystackOSS/paystack-mcp-server.git
@@ -16,7 +22,7 @@ npm install
1622
npm run build
1723
```
1824

19-
Then configure your MCP client to use the built server (see [Client Integration](#client-integration)).
25+
Then configure your MCP client to use the server (see [Client Integration](#client-integration)).
2026

2127
## Requirements
2228

@@ -28,14 +34,33 @@ Then configure your MCP client to use the built server (see [Client Integration]
2834

2935
| Environment Variable | Purpose |
3036
| -------------------------- | ------------------------------------------------------ |
31-
| `PAYSTACK_TEST_SECRET_KEY` | Your Paystack test secret key **(required)** |
37+
| `PAYSTACK_TEST_SECRET_KEY` | Your Paystack test secret key (fallback if no CLI arg) |
38+
39+
You can provide your API key in two ways:
40+
1. **CLI argument (recommended):** `--api-key sk_test_...`
41+
2. **Environment variable:** Set `PAYSTACK_TEST_SECRET_KEY`
3242

3343
> **Security note:** Only test keys (`sk_test_*`) are allowed. The server validates this at startup and will reject live keys.
3444
3545
## Client Integration
3646

3747
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.).
3848

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+
3964
### Using a local build
4065

4166
If you've cloned and built the server locally:
@@ -91,7 +116,6 @@ The Paystack MCP Server exposes the **entire Paystack API** to AI assistants by
91116
| Tool | Description |
92117
| ------------------------ | ------------------------------------------------------------------ |
93118
| `get_paystack_operation` | Fetch operation details (method, path, parameters) by operation ID |
94-
| `get_paystack_operation_guided` | Infers the operation ID from prompt |
95119
| `make_paystack_request` | Execute a Paystack API request |
96120
97121
### Available Resources

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: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
{
2-
"name": "paystack-mcp",
2+
"name": "@paystack/mcp-server",
33
"version": "0.0.1",
4-
"description": "",
4+
"description": "Model Context Protocol (MCP) server for Paystack API integration",
5+
"mcpName": "io.github.PaystackOSS/paystack",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/PaystackOSS/paystack-mcp-server.git"
9+
},
510
"bin": {
6-
"paystack": "./build/index.js"
11+
"paystack-mcp-server": "./build/index.js"
712
},
813
"scripts": {
914
"build": "tsc && cp -r src/data build/",
@@ -12,10 +17,15 @@
1217
"inspect": "set DANGEROUSLY_OMIT_AUTH=true && CLIENT_PORT=8090 SERVER_PORT=9000 npx @modelcontextprotocol/inspector npm run dev",
1318
"test": "mocha"
1419
},
15-
"files": [
16-
"build"
20+
"files": [ "build", "README.md" ],
21+
"keywords": [
22+
"paystack",
23+
"mcp",
24+
"model-context-protocol",
25+
"api",
26+
"payment",
27+
"integration"
1728
],
18-
"keywords": [],
1929
"author": "Andrew-Paystack",
2030
"license": "MIT",
2131
"dependencies": {

src/config.ts

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
11
import dotenv from 'dotenv';
2-
import { z } from 'zod';
32

43
// Load environment variables from .env file
5-
dotenv.config();
4+
dotenv.config({quiet: true});
65

7-
// Define schema for required environment variables
8-
const envSchema = z.object({
9-
PAYSTACK_TEST_SECRET_KEY: z.string().min(30, 'PAYSTACK_TEST_SECRET_KEY is required').refine(val => val.startsWith('sk_test_'), {
10-
message: 'PAYSTACK_TEST_SECRET_KEY must begin with "sk_test_. No live keys allowed."',
11-
}),
12-
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
13-
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
14-
});
15-
16-
// Validate environment variables
17-
function validateEnv() {
18-
try {
19-
return envSchema.parse({
20-
PAYSTACK_TEST_SECRET_KEY: process.env.PAYSTACK_TEST_SECRET_KEY,
21-
NODE_ENV: process.env.NODE_ENV || 'development',
22-
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
23-
});
24-
} catch (error) {
25-
if (error instanceof z.ZodError) {
26-
// Environment validation failed - exit silently
27-
process.exit(1);
28-
}
29-
throw error;
6+
// Get configuration with optional CLI API key
7+
export function getConfig(cliApiKey?: string) {
8+
const apiKey = cliApiKey || process.env.PAYSTACK_TEST_SECRET_KEY;
9+
10+
if (!apiKey) {
11+
console.error('Error: PAYSTACK_TEST_SECRET_KEY is required');
12+
process.exit(1);
13+
}
14+
15+
if (!apiKey.startsWith('sk_test_')) {
16+
console.error('Error: PAYSTACK_TEST_SECRET_KEY must begin with "sk_test_". No live keys allowed.');
17+
process.exit(1);
18+
}
19+
20+
if (apiKey.length < 30) {
21+
console.error('Error: PAYSTACK_TEST_SECRET_KEY appears to be too short');
22+
process.exit(1);
3023
}
24+
25+
return {
26+
PAYSTACK_TEST_SECRET_KEY: apiKey,
27+
NODE_ENV: (process.env.NODE_ENV as 'development' | 'production' | 'test') || 'development',
28+
LOG_LEVEL: (process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'info',
29+
};
3130
}
3231

33-
// Export validated configuration
34-
export const config = validateEnv();
35-
36-
// Paystack API configuration
37-
export const paystackConfig = {
38-
baseURL: 'https://api.paystack.co',
39-
secretKey: config.PAYSTACK_TEST_SECRET_KEY,
40-
timeout: 30000, // 30 seconds
41-
} as const;
32+
// Paystack API configuration factory
33+
export function createPaystackConfig(cliApiKey?: string) {
34+
const cfg = getConfig(cliApiKey);
35+
return {
36+
baseURL: 'https://api.paystack.co',
37+
secretKey: cfg.PAYSTACK_TEST_SECRET_KEY,
38+
timeout: 30000, // 30 seconds
39+
} as const;
40+
}

src/index.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,59 @@
1-
import { startServer } from "./server";
1+
2+
// Simple CLI argument parsing
3+
function parseApiKey(): string | undefined {
4+
const args = process.argv;
5+
const apiKeyIndex = args.findIndex(arg => arg === '--api-key');
6+
7+
if (apiKeyIndex !== -1 && apiKeyIndex + 1 < args.length) {
8+
return args[apiKeyIndex + 1];
9+
}
10+
11+
return undefined;
12+
}
13+
14+
// Show help message
15+
function showHelp() {
16+
console.log(`
17+
Paystack MCP Server
18+
19+
Usage:
20+
npx @paystack/mcp-server --api-key <your-test-secret-key>
21+
22+
Options:
23+
--api-key <key> Your Paystack test secret key (starts with sk_test_)
24+
--help, -h Show this help message
25+
26+
Environment Variables:
27+
PAYSTACK_TEST_SECRET_KEY Fallback if --api-key not provided
28+
29+
Examples:
30+
npx @paystack/mcp-server --api-key sk_test_1234567890abcdef
31+
PAYSTACK_TEST_SECRET_KEY=sk_test_... npx @paystack/mcp-server
32+
`);
33+
}
234

335
async function main() {
4-
await startServer();
36+
// Handle help flag
37+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
38+
showHelp();
39+
process.exit(0);
40+
}
41+
42+
const { startServer } = await import("./server");
43+
44+
// Parse API key from CLI
45+
const cliApiKey = parseApiKey();
46+
47+
48+
// Check if we have an API key from CLI or environment
49+
if (!cliApiKey && !process.env.PAYSTACK_TEST_SECRET_KEY) {
50+
console.error('Error: Paystack API key required.');
51+
console.error('Provide via --api-key argument or PAYSTACK_TEST_SECRET_KEY environment variable.');
52+
showHelp();
53+
process.exit(1);
54+
}
55+
56+
await startServer(cliApiKey);
557
}
658

759
main().catch((error) => {

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();

0 commit comments

Comments
 (0)