@palpluss/mcp 0.0.0-stage → 0.2.0
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.
- package/EMBEDDING.md +72 -0
- package/README.md +165 -2
- package/dist/api/client.d.ts +154 -0
- package/dist/api/client.js +164 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +65 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.js +119 -0
- package/dist/http.d.ts +42 -0
- package/dist/http.js +141 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/safety/approval.d.ts +34 -0
- package/dist/safety/approval.js +61 -0
- package/dist/safety/audit.d.ts +26 -0
- package/dist/safety/audit.js +40 -0
- package/dist/safety/limits.d.ts +42 -0
- package/dist/safety/limits.js +130 -0
- package/dist/safety/redact.d.ts +12 -0
- package/dist/safety/redact.js +80 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.js +71 -0
- package/dist/tools/common.d.ts +75 -0
- package/dist/tools/common.js +221 -0
- package/dist/tools/guide.d.ts +7 -0
- package/dist/tools/guide.js +146 -0
- package/dist/tools/money.d.ts +13 -0
- package/dist/tools/money.js +360 -0
- package/dist/tools/read.d.ts +2 -0
- package/dist/tools/read.js +148 -0
- package/package.json +59 -4
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { guarded, ok } from './common.js';
|
|
3
|
+
import { maskPhone } from '../safety/redact.js';
|
|
4
|
+
import { GUIDE_TOPICS, guide } from './guide.js';
|
|
5
|
+
const READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
6
|
+
export function registerReadTools(ctx) {
|
|
7
|
+
const { server, client } = ctx;
|
|
8
|
+
server.registerTool('palpluss_list_transactions', {
|
|
9
|
+
title: 'List transactions',
|
|
10
|
+
description: 'List recent PalPluss transactions (STK collections and B2C payouts), newest first. Filter by type or status; page with the returned next_cursor.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: z.enum(['STK', 'B2C']).optional().describe('Only STK collections or only B2C payouts.'),
|
|
13
|
+
status: z
|
|
14
|
+
.enum(['PENDING', 'PROCESSING', 'SUCCESS', 'FAILED', 'REVERSED', 'CANCELLED'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Only transactions in this status.'),
|
|
17
|
+
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 20, max 100).'),
|
|
18
|
+
cursor: z.string().max(500).optional().describe('next_cursor from a previous page.'),
|
|
19
|
+
},
|
|
20
|
+
annotations: { title: 'List transactions', ...READ },
|
|
21
|
+
}, guarded(ctx, 'palpluss_list_transactions', async (args) => ok(await client.listTransactions(args))));
|
|
22
|
+
server.registerTool('palpluss_get_transaction', {
|
|
23
|
+
title: 'Get a transaction',
|
|
24
|
+
description: 'Get one transaction by its PalPluss transaction ID — status, amount, M-Pesa receipt and result. Use it to check whether an STK push, payout or top-up succeeded.',
|
|
25
|
+
inputSchema: { transactionId: z.string().uuid().describe('PalPluss transaction ID (UUID).') },
|
|
26
|
+
annotations: { title: 'Get a transaction', ...READ },
|
|
27
|
+
}, guarded(ctx, 'palpluss_get_transaction', async ({ transactionId }) => ok(await client.getTransaction(transactionId))));
|
|
28
|
+
server.registerTool('palpluss_get_wallet_balances', {
|
|
29
|
+
title: 'Wallet balances',
|
|
30
|
+
description: 'Balances of both PalPluss wallets: the service wallet (pays transaction fees, e.g. KES 3 per STK push) and the B2C wallet (the float that payouts are sent from).',
|
|
31
|
+
inputSchema: {},
|
|
32
|
+
annotations: { title: 'Wallet balances', ...READ },
|
|
33
|
+
}, guarded(ctx, 'palpluss_get_wallet_balances', async () => {
|
|
34
|
+
const [service, b2c] = await Promise.allSettled([client.serviceWalletBalance(), client.b2cWalletBalance()]);
|
|
35
|
+
const pick = (r) => r.status === 'fulfilled' ? r.value : { unavailable: true, reason: r.reason?.message ?? 'unknown' };
|
|
36
|
+
return ok({ serviceWallet: pick(service), b2cWallet: pick(b2c) });
|
|
37
|
+
}));
|
|
38
|
+
server.registerTool('palpluss_list_payment_channels', {
|
|
39
|
+
title: 'List payment channels',
|
|
40
|
+
description: 'List the account\'s payment channels — the Paybills and Tills customer payments are collected into. The default channel is first; STK pushes use it unless a channelId is given.',
|
|
41
|
+
inputSchema: {},
|
|
42
|
+
annotations: { title: 'List payment channels', ...READ },
|
|
43
|
+
}, guarded(ctx, 'palpluss_list_payment_channels', async () => ok(await client.listChannels())));
|
|
44
|
+
server.registerTool('palpluss_get_payment_channel', {
|
|
45
|
+
title: 'Get a payment channel',
|
|
46
|
+
description: 'Get one payment channel by ID.',
|
|
47
|
+
inputSchema: { channelId: z.string().uuid().describe('Payment channel ID (UUID).') },
|
|
48
|
+
annotations: { title: 'Get a payment channel', ...READ },
|
|
49
|
+
}, guarded(ctx, 'palpluss_get_payment_channel', async ({ channelId }) => ok(await client.getChannel(channelId))));
|
|
50
|
+
server.registerTool('palpluss_get_wallet_history', {
|
|
51
|
+
title: 'Wallet history',
|
|
52
|
+
description: 'Charges, top-ups, refunds and payouts on the service and B2C wallets, newest first, with the balance after each entry. Filter by wallet or category; page with nextCursor.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
wallet: z.enum(['service', 'b2c']).optional().describe('service = fees wallet, b2c = payout float. Omit for both.'),
|
|
55
|
+
category: z.enum(['charge', 'topup', 'refund', 'payout', 'bonus', 'other']).optional(),
|
|
56
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
57
|
+
cursor: z.string().max(500).optional().describe('nextCursor from a previous page.'),
|
|
58
|
+
},
|
|
59
|
+
annotations: { title: 'Wallet history', ...READ },
|
|
60
|
+
}, guarded(ctx, 'palpluss_get_wallet_history', async (args) => ok(await client.walletHistory(args))));
|
|
61
|
+
server.registerTool('palpluss_estimate_fee', {
|
|
62
|
+
title: 'Estimate a fee',
|
|
63
|
+
description: 'What PalPluss would charge (from the service wallet) for an STK push or a B2C payout of a given amount, using this account\'s pricing plan.',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
service: z.enum(['STK', 'B2C']),
|
|
66
|
+
amount: z.number().positive().describe('Amount in Kenyan shillings.'),
|
|
67
|
+
},
|
|
68
|
+
annotations: { title: 'Estimate a fee', ...READ },
|
|
69
|
+
}, guarded(ctx, 'palpluss_estimate_fee', async ({ service, amount }) => ok(await client.estimateFee(service, amount))));
|
|
70
|
+
server.registerTool('palpluss_list_payment_links', {
|
|
71
|
+
title: 'List payment links',
|
|
72
|
+
description: 'List the account\'s payment links, newest first, with their URL, amount and status. Page with nextCursor.',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
75
|
+
cursor: z.string().max(500).optional(),
|
|
76
|
+
},
|
|
77
|
+
annotations: { title: 'List payment links', ...READ },
|
|
78
|
+
}, guarded(ctx, 'palpluss_list_payment_links', async (args) => ok(await client.listPaymentLinks(args))));
|
|
79
|
+
server.registerTool('palpluss_get_payment_link', {
|
|
80
|
+
title: 'Get a payment link',
|
|
81
|
+
description: 'One payment link: URL, amount, status, expiry and how many payments it has received.',
|
|
82
|
+
inputSchema: { paymentLinkId: z.string().uuid() },
|
|
83
|
+
annotations: { title: 'Get a payment link', ...READ },
|
|
84
|
+
}, guarded(ctx, 'palpluss_get_payment_link', async ({ paymentLinkId }) => ok(await client.getPaymentLink(paymentLinkId))));
|
|
85
|
+
server.registerTool('palpluss_list_approvals', {
|
|
86
|
+
title: 'List approval requests',
|
|
87
|
+
description: 'Payouts and channel changes this key asked for that PalPluss governs (MCP keys): waiting for approval, approved, done, rejected or expired.',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
status: z.enum(['PENDING_APPROVAL', 'APPROVED', 'REJECTED', 'EXPIRED', 'EXECUTED', 'FAILED']).optional(),
|
|
90
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
91
|
+
cursor: z.string().max(500).optional(),
|
|
92
|
+
},
|
|
93
|
+
annotations: { title: 'List approval requests', ...READ },
|
|
94
|
+
}, guarded(ctx, 'palpluss_list_approvals', async (args) => ok(await client.listApprovals(args))));
|
|
95
|
+
server.registerTool('palpluss_get_guardrails', {
|
|
96
|
+
title: 'Guardrails in force',
|
|
97
|
+
description: 'Show what this PalPluss MCP server is allowed to do: enabled toolsets, whether writes are switched off, how approvals work, the payout limits and how much of today\'s payout limit is left. Call this before planning payouts or channel changes.',
|
|
98
|
+
inputSchema: {},
|
|
99
|
+
annotations: { title: 'Guardrails in force', ...READ, openWorldHint: false },
|
|
100
|
+
}, guarded(ctx, 'palpluss_get_guardrails', async () => {
|
|
101
|
+
const { config } = ctx;
|
|
102
|
+
const key = await ctx.keyInfo();
|
|
103
|
+
const approvalText = key.mode === 'test'
|
|
104
|
+
? 'TEST MODE: this key moves simulated money only, so nothing needs approval.'
|
|
105
|
+
: config.approval === 'elicit'
|
|
106
|
+
? 'Top-ups, STK pushes, payouts and channel changes ask the person to approve them in their app, unless PalPluss already holds them for console approval (see key.policy.approvals).'
|
|
107
|
+
: config.approval === 'backend'
|
|
108
|
+
? 'PalPluss enforces this key\'s permissions and limits and holds payouts / channel changes for console approval when the merchant asked for that.'
|
|
109
|
+
: 'Approval prompts are switched off (PALPLUSS_MCP_APPROVAL=none).';
|
|
110
|
+
return ok({
|
|
111
|
+
key: {
|
|
112
|
+
kind: key.kind,
|
|
113
|
+
mode: key.mode,
|
|
114
|
+
livemode: key.livemode,
|
|
115
|
+
permissions: key.scopes === 'all' ? 'all (standard key)' : key.scopes.map((s) => s.scope),
|
|
116
|
+
expiresAt: key.expiresAt,
|
|
117
|
+
connectedApp: key.connectedApp,
|
|
118
|
+
policy: key.policy,
|
|
119
|
+
},
|
|
120
|
+
toolsets: config.toolsets,
|
|
121
|
+
writesEnabled: !config.readOnly,
|
|
122
|
+
approval: approvalText,
|
|
123
|
+
limits: {
|
|
124
|
+
maxPayoutKes: config.limits.maxPayout,
|
|
125
|
+
dailyPayoutTotalKes: config.limits.dailyPayoutTotal,
|
|
126
|
+
dailyPayoutCount: config.limits.dailyPayoutCount,
|
|
127
|
+
maxTopupKes: config.limits.maxTopup,
|
|
128
|
+
maxStkKes: config.limits.maxStk,
|
|
129
|
+
payoutAllowlist: config.limits.payoutAllowlist.length
|
|
130
|
+
? config.limits.payoutAllowlist.map((p) => maskPhone(p))
|
|
131
|
+
: 'any number',
|
|
132
|
+
},
|
|
133
|
+
payoutsToday: ctx.config.toolsets.includes('payouts') ? ctx.limiter.remaining() : 'payouts toolset is off',
|
|
134
|
+
}, {
|
|
135
|
+
note: key.policy
|
|
136
|
+
? 'key.policy is enforced by PalPluss itself; limits are also enforced by this MCP server. The lower one wins.'
|
|
137
|
+
: 'Limits here are enforced by this MCP server in addition to PalPluss\'s own limits.',
|
|
138
|
+
});
|
|
139
|
+
}));
|
|
140
|
+
server.registerTool('palpluss_integration_guide', {
|
|
141
|
+
title: 'Integration guide',
|
|
142
|
+
description: 'Short, accurate guidance for building PalPluss into an app: STK push, webhooks, B2C payouts, payment channels, wallets, errors and going live. Use it before writing PalPluss integration code.',
|
|
143
|
+
inputSchema: { topic: z.enum(GUIDE_TOPICS).describe('Which part of the integration.') },
|
|
144
|
+
annotations: { title: 'Integration guide', ...READ, openWorldHint: false },
|
|
145
|
+
}, guarded(ctx, 'palpluss_integration_guide', async ({ topic }) => ({
|
|
146
|
+
content: [{ type: 'text', text: guide(topic) }],
|
|
147
|
+
})));
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@palpluss/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "PalPluss MCP server — let AI coding tools check balances, manage payment channels and wallets, collect with M-Pesa STK Push and send B2C payouts, with human approval on anything that moves money.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"palpluss-mcp": "dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"EMBEDDING.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"start": "node dist/cli.js",
|
|
27
|
+
"release:manual": "node scripts/publish.mjs"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"palpluss",
|
|
31
|
+
"mpesa",
|
|
32
|
+
"m-pesa",
|
|
33
|
+
"mcp",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"stk-push",
|
|
36
|
+
"b2c",
|
|
37
|
+
"payments",
|
|
38
|
+
"kenya"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/palpluss/palpluss-sdk.git",
|
|
47
|
+
"directory": "packages/mcp"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/sdk": "1.32.2",
|
|
54
|
+
"zod": "3.25.76"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "20.19.0",
|
|
58
|
+
"typescript": "5.9.3",
|
|
59
|
+
"vitest": "3.2.4"
|
|
60
|
+
}
|
|
61
|
+
}
|