psephos-mcp 0.0.1 → 0.1.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/LICENSE +21 -0
- package/README.md +84 -5
- package/dist/server.js +117 -0
- package/package.json +27 -6
- package/bin.js +0 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamit Çevik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,11 +1,90 @@
|
|
|
1
1
|
# psephos-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for Psephos —
|
|
3
|
+
MCP server for **Psephos** — let your AI agents ask for your approval on your
|
|
4
|
+
phone before they do something risky, and wait for your answer.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
The agent calls `ask_approval`; your iPhone gets a notification with
|
|
7
|
+
**Approve** and **Reject** buttons; your tap goes straight back to the agent.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
1. Install the Psephos app on your iPhone and open it once. It creates your
|
|
12
|
+
identity and shows your **API key** (`ak_...`).
|
|
13
|
+
2. Add the server to your MCP client with that key.
|
|
14
|
+
|
|
15
|
+
**Claude Code**
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
claude mcp add psephos -e PSEPHOS_API_KEY=ak_your_key -- npx -y psephos-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Any MCP client** (JSON config)
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"psephos": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "psephos-mcp"],
|
|
29
|
+
"env": { "PSEPHOS_API_KEY": "ak_your_key" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Node.js 18 or newer.
|
|
36
|
+
|
|
37
|
+
## Tools
|
|
38
|
+
|
|
39
|
+
### `ask_approval`
|
|
40
|
+
|
|
41
|
+
Sends an approval request to your phone and waits for the answer.
|
|
42
|
+
|
|
43
|
+
| Parameter | Type | Description |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `title` | string, required | Short, clear question |
|
|
46
|
+
| `body` | string | Details: what is being approved |
|
|
47
|
+
| `wait_seconds` | integer 0–600 | How long to wait (default 55) |
|
|
48
|
+
|
|
49
|
+
Returns `status`: `approved`, `rejected`, or `pending` if the wait ran out
|
|
50
|
+
before you answered. On `pending`, the agent keeps waiting with
|
|
51
|
+
`check_approval`.
|
|
52
|
+
|
|
53
|
+
### `check_approval`
|
|
54
|
+
|
|
55
|
+
Returns the current state of an earlier request, optionally waiting for it.
|
|
56
|
+
|
|
57
|
+
| Parameter | Type | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `request_id` | UUID, required | From `ask_approval` |
|
|
60
|
+
| `wait_seconds` | integer 0–600 | How long to wait (default 0 = check now) |
|
|
61
|
+
|
|
62
|
+
`status` can also be `expired`: the request timed out and no longer accepts
|
|
63
|
+
an answer.
|
|
64
|
+
|
|
65
|
+
## Environment
|
|
66
|
+
|
|
67
|
+
| Variable | Required | Description |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `PSEPHOS_API_KEY` | yes | Your API key from the Psephos app |
|
|
70
|
+
| `PSEPHOS_SERVER_URL` | no | Override the server address |
|
|
71
|
+
|
|
72
|
+
## Good to know
|
|
73
|
+
|
|
74
|
+
- **The buttons appear when you press and hold the notification.** You can
|
|
75
|
+
answer without opening the app.
|
|
76
|
+
- Your key only ever reaches your own devices, and it can send requests but
|
|
77
|
+
cannot answer them — only your phone can.
|
|
78
|
+
- Limits per key: 30 requests per minute and 1000 per day; status checks up
|
|
79
|
+
to 120 per minute.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
7
84
|
|
|
8
85
|
---
|
|
9
86
|
|
|
10
|
-
Psephos
|
|
11
|
-
|
|
87
|
+
**Türkçe:** Psephos, yapay zekâ ajanlarının riskli bir işlemden önce
|
|
88
|
+
telefonundan onay istemesini sağlar. Uygulamayı açınca görünen API anahtarını
|
|
89
|
+
`PSEPHOS_API_KEY` olarak yukarıdaki yapılandırmaya ekle. Onayla/Reddet
|
|
90
|
+
butonları bildirime basılı tutunca açılır.
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Psephos MCP sunucusu (npm: psephos-mcp). Kaynak bu dosya; npm paketi bunun
|
|
3
|
+
// derlenmiş hâlini (dist/) taşır, çünkü Node node_modules içindeki .ts
|
|
4
|
+
// dosyalarını çalıştırmaz. Sahip ise repodan doğrudan çalıştırır
|
|
5
|
+
// (.mcp.json: node --env-file-if-exists=.env mcp/server.ts).
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
// package.json'daki sürümle aynı tutulmalı
|
|
10
|
+
const VERSION = '0.1.0';
|
|
11
|
+
// PSEPHOS_* herkes için; diğer adlar sahibin repodaki kurulumu çalışsın diye
|
|
12
|
+
// (kök .env'i --env-file ile paylaşıyor).
|
|
13
|
+
const SERVER_URL = process.env.PSEPHOS_SERVER_URL ??
|
|
14
|
+
process.env.EXPO_PUBLIC_SERVER_URL ??
|
|
15
|
+
process.env.SERVER_URL ??
|
|
16
|
+
'https://173-212-253-154.sslip.io';
|
|
17
|
+
const API_KEY = process.env.PSEPHOS_API_KEY ?? process.env.EXPO_PUBLIC_API_KEY ?? process.env.API_KEY ?? '';
|
|
18
|
+
const POLL_INTERVAL_MS = 2000;
|
|
19
|
+
const DEFAULT_WAIT_SECONDS = 55;
|
|
20
|
+
// DİKKAT: stdio taşımasında stdout protokole aittir — log SADECE console.error ile.
|
|
21
|
+
if (!API_KEY) {
|
|
22
|
+
console.error('[psephos] PSEPHOS_API_KEY tanımlı değil. Psephos uygulamasındaki API anahtarını ' +
|
|
23
|
+
'MCP yapılandırmasına "env": { "PSEPHOS_API_KEY": "ak_..." } olarak ekle.\n' +
|
|
24
|
+
'[psephos] PSEPHOS_API_KEY is not set. Add the API key from the Psephos app to your ' +
|
|
25
|
+
'MCP config as "env": { "PSEPHOS_API_KEY": "ak_..." }.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
async function api(path, init) {
|
|
29
|
+
const res = await fetch(`${SERVER_URL}${path}`, {
|
|
30
|
+
...init,
|
|
31
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },
|
|
32
|
+
});
|
|
33
|
+
const json = (await res.json().catch(() => ({})));
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new Error(`Onay sunucusu ${res.status} döndü: ${JSON.stringify(json)}`);
|
|
36
|
+
}
|
|
37
|
+
return json;
|
|
38
|
+
}
|
|
39
|
+
function sleep(ms) {
|
|
40
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
+
}
|
|
42
|
+
function toolResult(payload) {
|
|
43
|
+
return { content: [{ type: 'text', text: JSON.stringify(payload) }] };
|
|
44
|
+
}
|
|
45
|
+
async function waitForAnswer(id, deadlineMs) {
|
|
46
|
+
let last;
|
|
47
|
+
do {
|
|
48
|
+
last = await api(`/requests/${id}`);
|
|
49
|
+
if (last.status !== 'pending')
|
|
50
|
+
return last;
|
|
51
|
+
if (Date.now() >= deadlineMs)
|
|
52
|
+
return last;
|
|
53
|
+
await sleep(Math.min(POLL_INTERVAL_MS, Math.max(0, deadlineMs - Date.now())));
|
|
54
|
+
} while (true);
|
|
55
|
+
}
|
|
56
|
+
const server = new McpServer({ name: 'psephos', version: VERSION });
|
|
57
|
+
server.registerTool('ask_approval', {
|
|
58
|
+
title: 'Kullanıcıdan onay iste',
|
|
59
|
+
description: "Kullanıcının telefonuna Onayla/Reddet butonlu push bildirimi gönderir ve cevabı bekler. " +
|
|
60
|
+
"Dönen status: 'approved' (onaylandı), 'rejected' (reddedildi) ya da 'pending' (süre doldu, " +
|
|
61
|
+
"henüz cevap yok). 'pending' dönerse aynı request_id ile check_approval çağırarak beklemeye devam et; " +
|
|
62
|
+
"kullanıcı reddetmedikçe işlemi iptal etme.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
title: z.string().min(1).describe('Bildirim başlığı — kısa, net bir soru/özet'),
|
|
65
|
+
body: z.string().optional().describe('Detay metni (ne onaylanıyor, kapsamı ne)'),
|
|
66
|
+
wait_seconds: z
|
|
67
|
+
.number()
|
|
68
|
+
.int()
|
|
69
|
+
.min(0)
|
|
70
|
+
.max(600)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe(`Cevap için beklenecek süre, saniye (varsayılan ${DEFAULT_WAIT_SECONDS})`),
|
|
73
|
+
},
|
|
74
|
+
}, async ({ title, body, wait_seconds }) => {
|
|
75
|
+
const created = await api('/requests', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: JSON.stringify({ title, body: body ?? '' }),
|
|
78
|
+
});
|
|
79
|
+
console.error(`[psephos] Onay isteği açıldı: ${created.id} "${title}"`);
|
|
80
|
+
const waitMs = (wait_seconds ?? DEFAULT_WAIT_SECONDS) * 1000;
|
|
81
|
+
const finalState = await waitForAnswer(created.id, Date.now() + waitMs);
|
|
82
|
+
console.error(`[psephos] Sonuç: ${created.id} → ${finalState.status}`);
|
|
83
|
+
return toolResult({
|
|
84
|
+
request_id: finalState.id,
|
|
85
|
+
status: finalState.status,
|
|
86
|
+
responded_at: finalState.respondedAt,
|
|
87
|
+
...(finalState.status === 'pending'
|
|
88
|
+
? { note: 'Süre doldu, kullanıcı henüz cevaplamadı. check_approval ile tekrar sorgula.' }
|
|
89
|
+
: {}),
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
server.registerTool('check_approval', {
|
|
93
|
+
title: 'Onay durumunu sorgula',
|
|
94
|
+
description: "Daha önce açılmış bir onay isteğinin durumunu döndürür (approved/rejected/pending/expired). " +
|
|
95
|
+
"ask_approval 'pending' döndüyse bununla beklemeye devam edebilirsin. " +
|
|
96
|
+
"'expired': isteğin süresi doldu ve artık cevap kabul etmiyor; gerekiyorsa ask_approval ile yeniden sor.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
request_id: z.string().uuid().describe('ask_approval çıktısındaki request_id'),
|
|
99
|
+
wait_seconds: z
|
|
100
|
+
.number()
|
|
101
|
+
.int()
|
|
102
|
+
.min(0)
|
|
103
|
+
.max(600)
|
|
104
|
+
.optional()
|
|
105
|
+
.describe('Cevap bekleme süresi; 0 = anlık durum (varsayılan 0)'),
|
|
106
|
+
},
|
|
107
|
+
}, async ({ request_id, wait_seconds }) => {
|
|
108
|
+
const finalState = await waitForAnswer(request_id, Date.now() + (wait_seconds ?? 0) * 1000);
|
|
109
|
+
return toolResult({
|
|
110
|
+
request_id: finalState.id,
|
|
111
|
+
title: finalState.title,
|
|
112
|
+
status: finalState.status,
|
|
113
|
+
responded_at: finalState.respondedAt,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
await server.connect(new StdioServerTransport());
|
|
117
|
+
console.error(`[psephos] MCP sunucusu hazır (stdio) — API: ${SERVER_URL}`);
|
package/package.json
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "psephos-mcp",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "MCP server for Psephos
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Psephos: agents ask for your approval on your phone and wait for the answer.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
5
7
|
"bin": {
|
|
6
|
-
"psephos-mcp": "
|
|
8
|
+
"psephos-mcp": "dist/server.js"
|
|
7
9
|
},
|
|
8
10
|
"files": [
|
|
9
|
-
"
|
|
10
|
-
"README.md"
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
11
14
|
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
12
18
|
"keywords": [
|
|
13
19
|
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"approval",
|
|
22
|
+
"human-in-the-loop",
|
|
23
|
+
"notifications",
|
|
14
24
|
"psephos"
|
|
15
25
|
],
|
|
16
|
-
"
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.build.json",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
+
"zod": "^3.25.76"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.1.1",
|
|
36
|
+
"typescript": "~6.0.3"
|
|
37
|
+
}
|
|
17
38
|
}
|
package/bin.js
DELETED