@stratta/mcp 0.9.4 → 0.9.6
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/README.md +30 -0
- package/dist/account.d.ts +16 -0
- package/dist/account.js +81 -0
- package/dist/doctor.d.ts +10 -0
- package/dist/doctor.js +134 -0
- package/dist/index.js +76 -1
- package/dist/update.d.ts +29 -0
- package/dist/update.js +88 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,36 @@ precedence over the saved key):
|
|
|
56
56
|
claude mcp add stratta --scope user --env STRATTA_API_KEY=sk_strt_xxx -- npx -y @stratta/mcp
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
| Command | Purpose |
|
|
62
|
+
| ----------------------------------- | -------------------------------------------------- |
|
|
63
|
+
| `npx -y @stratta/mcp login` | Authorise this machine from your browser |
|
|
64
|
+
| `npx -y @stratta/mcp login --paste` | Paste a key, when there is no browser |
|
|
65
|
+
| `npx -y @stratta/mcp logout` | Remove the key stored on this machine |
|
|
66
|
+
| `npx -y @stratta/mcp whoami` | Which account and workspace this machine speaks as |
|
|
67
|
+
| `npx -y @stratta/mcp doctor` | Diagnose an install that is not responding |
|
|
68
|
+
| `npx -y @stratta/mcp update` | Check your version and fetch the latest |
|
|
69
|
+
| `npx -y @stratta/mcp --help` | All of the above |
|
|
70
|
+
|
|
71
|
+
Run `doctor` first when norms are missing: a revoked key, a stale
|
|
72
|
+
`STRATTA_API_KEY` in your shell and an unreachable backend all look identical
|
|
73
|
+
from inside the agent, and are fixed differently.
|
|
74
|
+
|
|
75
|
+
## Staying up to date
|
|
76
|
+
|
|
77
|
+
`npx -y @stratta/mcp` fetches the published package, but npx caches what it
|
|
78
|
+
resolved, so a server can keep launching a version npm replaced weeks ago with
|
|
79
|
+
nothing to tell you.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx -y @stratta/mcp update
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Prints the version you are running and the one npm serves, and fetches the new
|
|
86
|
+
one if they differ. Restart your agent afterwards. The server also mentions it
|
|
87
|
+
on stderr at startup when it notices it is behind.
|
|
88
|
+
|
|
59
89
|
### Claude Desktop
|
|
60
90
|
|
|
61
91
|
Sign in once from a terminal — the desktop app cannot prompt you interactively:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `whoami` — which account and organisation this machine speaks as.
|
|
3
|
+
*
|
|
4
|
+
* Asks the backend rather than reading the file: a key that was revoked from
|
|
5
|
+
* the dashboard still sits on disk looking perfectly valid, and reporting it
|
|
6
|
+
* as fine is exactly the wrong answer to "why can't I see my norms?".
|
|
7
|
+
*/
|
|
8
|
+
export declare function runWhoami(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* `logout` — remove the stored key.
|
|
11
|
+
*
|
|
12
|
+
* Only touches the file. An environment variable is set by whoever set it,
|
|
13
|
+
* and silently doing nothing about it while claiming to have logged the user
|
|
14
|
+
* out is how someone ends up convinced the command is broken.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runLogout(): void;
|
package/dist/account.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
import { CONFIG_PATH, readStoredApiKey } from './config.js';
|
|
3
|
+
import { resolveApiKey } from './auth.js';
|
|
4
|
+
import { api, createConvexClient } from './client.js';
|
|
5
|
+
/** `sk_strt_abcd…` — enough to tell two keys apart, not enough to use one. */
|
|
6
|
+
function mask(key) {
|
|
7
|
+
return key.length > 12 ? `${key.slice(0, 12)}…` : '…';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Where the key came from, which is half the answer when the wrong one is in
|
|
11
|
+
* use: the environment variable wins over the file, so someone who ran
|
|
12
|
+
* `login` and still sees the old organisation has a stale `STRATTA_API_KEY`
|
|
13
|
+
* in their shell, not a broken login.
|
|
14
|
+
*/
|
|
15
|
+
function keySource() {
|
|
16
|
+
if (process.env.STRATTA_API_KEY?.trim())
|
|
17
|
+
return 'env';
|
|
18
|
+
if (readStoredApiKey())
|
|
19
|
+
return 'config';
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `whoami` — which account and organisation this machine speaks as.
|
|
24
|
+
*
|
|
25
|
+
* Asks the backend rather than reading the file: a key that was revoked from
|
|
26
|
+
* the dashboard still sits on disk looking perfectly valid, and reporting it
|
|
27
|
+
* as fine is exactly the wrong answer to "why can't I see my norms?".
|
|
28
|
+
*/
|
|
29
|
+
export async function runWhoami() {
|
|
30
|
+
const key = resolveApiKey();
|
|
31
|
+
if (!key) {
|
|
32
|
+
console.log('Aucune clé sur cette machine.');
|
|
33
|
+
console.log('\n npx -y @stratta/mcp login');
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const source = keySource();
|
|
38
|
+
console.log(`Clé : ${mask(key)}`);
|
|
39
|
+
console.log(`Source : ${source === 'env' ? 'variable STRATTA_API_KEY' : CONFIG_PATH}`);
|
|
40
|
+
try {
|
|
41
|
+
const client = createConvexClient();
|
|
42
|
+
const info = (await client.action(api.apiKeys.describeApiKey, {
|
|
43
|
+
plaintext: key,
|
|
44
|
+
}));
|
|
45
|
+
if (!info.valid) {
|
|
46
|
+
console.log('\nCette clé n’est plus valide (révoquée, ou compte retiré).');
|
|
47
|
+
console.log('\n npx -y @stratta/mcp login');
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(`Compte : ${info.email ?? '—'}`);
|
|
52
|
+
console.log(`Espace : ${info.organizationName ?? '—'}`);
|
|
53
|
+
if (info.keyName)
|
|
54
|
+
console.log(`Nom : ${info.keyName}`);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.log('\nImpossible de joindre Stratta pour vérifier la clé.');
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `logout` — remove the stored key.
|
|
63
|
+
*
|
|
64
|
+
* Only touches the file. An environment variable is set by whoever set it,
|
|
65
|
+
* and silently doing nothing about it while claiming to have logged the user
|
|
66
|
+
* out is how someone ends up convinced the command is broken.
|
|
67
|
+
*/
|
|
68
|
+
export function runLogout() {
|
|
69
|
+
const hadFile = existsSync(CONFIG_PATH);
|
|
70
|
+
if (hadFile) {
|
|
71
|
+
rmSync(CONFIG_PATH, { force: true });
|
|
72
|
+
console.log(`Clé supprimée de ${CONFIG_PATH}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log('Aucune clé enregistrée sur cette machine.');
|
|
76
|
+
}
|
|
77
|
+
if (process.env.STRATTA_API_KEY?.trim()) {
|
|
78
|
+
console.log('\n⚠ STRATTA_API_KEY est définie dans votre environnement et reste active.\n' +
|
|
79
|
+
' Retirez-la de votre shell ou de la configuration de votre agent.');
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `doctor` — everything that has to be true, checked in the order it breaks.
|
|
3
|
+
*
|
|
4
|
+
* Written because the failures this product produces are all silent from the
|
|
5
|
+
* user's side: the agent says a norm is unavailable, and the cause is four
|
|
6
|
+
* layers away — a revoked key, a stale environment variable, a Python that
|
|
7
|
+
* was never installed. Each check here corresponds to a real support
|
|
8
|
+
* question, and each failure carries the command that fixes it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runDoctor(current: string): Promise<void>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { CONFIG_PATH, readStoredApiKey } from './config.js';
|
|
3
|
+
import { resolveApiKey } from './auth.js';
|
|
4
|
+
import { api, createConvexClient } from './client.js';
|
|
5
|
+
import { latestPublished, isNewer } from './update.js';
|
|
6
|
+
function line(c) {
|
|
7
|
+
console.log(`${c.ok ? '✓' : '✗'} ${c.label}${c.detail ? ` — ${c.detail}` : ''}`);
|
|
8
|
+
if (!c.ok && c.fix)
|
|
9
|
+
console.log(` → ${c.fix}`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `doctor` — everything that has to be true, checked in the order it breaks.
|
|
13
|
+
*
|
|
14
|
+
* Written because the failures this product produces are all silent from the
|
|
15
|
+
* user's side: the agent says a norm is unavailable, and the cause is four
|
|
16
|
+
* layers away — a revoked key, a stale environment variable, a Python that
|
|
17
|
+
* was never installed. Each check here corresponds to a real support
|
|
18
|
+
* question, and each failure carries the command that fixes it.
|
|
19
|
+
*/
|
|
20
|
+
export async function runDoctor(current) {
|
|
21
|
+
const checks = [];
|
|
22
|
+
// Node. The package declares >=20; ingestion tooling assumes a modern one.
|
|
23
|
+
const major = Number(process.versions.node.split('.')[0]);
|
|
24
|
+
checks.push({
|
|
25
|
+
ok: major >= 20,
|
|
26
|
+
label: 'Node.js',
|
|
27
|
+
detail: process.versions.node,
|
|
28
|
+
fix: 'Installez Node 20 ou plus récent : https://nodejs.org',
|
|
29
|
+
});
|
|
30
|
+
// The key, and where it comes from — the env var wins, which surprises
|
|
31
|
+
// people who just ran `login`.
|
|
32
|
+
const key = resolveApiKey();
|
|
33
|
+
const fromEnv = !!process.env.STRATTA_API_KEY?.trim();
|
|
34
|
+
checks.push({
|
|
35
|
+
ok: !!key,
|
|
36
|
+
label: 'Clé API',
|
|
37
|
+
detail: key
|
|
38
|
+
? fromEnv
|
|
39
|
+
? 'variable STRATTA_API_KEY'
|
|
40
|
+
: CONFIG_PATH
|
|
41
|
+
: 'absente',
|
|
42
|
+
fix: 'npx -y @stratta/mcp login',
|
|
43
|
+
});
|
|
44
|
+
if (key && fromEnv && readStoredApiKey()) {
|
|
45
|
+
checks.push({
|
|
46
|
+
ok: false,
|
|
47
|
+
label: 'Deux clés en présence',
|
|
48
|
+
detail: 'la variable d’environnement masque le fichier',
|
|
49
|
+
fix: 'Retirez STRATTA_API_KEY si vous vouliez utiliser celle du login',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Reachability and validity in one call: a revoked key and an unreachable
|
|
53
|
+
// backend look identical from the agent's side, and are fixed differently.
|
|
54
|
+
if (key) {
|
|
55
|
+
try {
|
|
56
|
+
const client = createConvexClient();
|
|
57
|
+
const info = (await client.action(api.apiKeys.describeApiKey, {
|
|
58
|
+
plaintext: key,
|
|
59
|
+
}));
|
|
60
|
+
checks.push({
|
|
61
|
+
ok: true,
|
|
62
|
+
label: 'Connexion à Stratta',
|
|
63
|
+
detail: 'joignable',
|
|
64
|
+
});
|
|
65
|
+
checks.push({
|
|
66
|
+
ok: info.valid,
|
|
67
|
+
label: 'Clé acceptée',
|
|
68
|
+
detail: info.valid
|
|
69
|
+
? `${info.email ?? '—'} · ${info.organizationName ?? '—'}`
|
|
70
|
+
: 'révoquée ou expirée',
|
|
71
|
+
fix: 'npx -y @stratta/mcp login',
|
|
72
|
+
});
|
|
73
|
+
if (info.valid) {
|
|
74
|
+
// Its own try: a corpus read failing is a different diagnosis from an
|
|
75
|
+
// unreachable backend, and folding them together reports "check your
|
|
76
|
+
// network" to someone whose network is fine.
|
|
77
|
+
try {
|
|
78
|
+
const norms = (await client.action(api._mcp.listPublishedNorms, {
|
|
79
|
+
apiKey: key,
|
|
80
|
+
}));
|
|
81
|
+
const n = Array.isArray(norms) ? norms.length : 0;
|
|
82
|
+
checks.push({
|
|
83
|
+
ok: n > 0,
|
|
84
|
+
label: 'Normes accessibles',
|
|
85
|
+
detail: `${n}`,
|
|
86
|
+
fix: 'Votre espace est vide — ingérez une norme, ou vérifiez l’organisation ci-dessus',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
checks.push({
|
|
91
|
+
ok: false,
|
|
92
|
+
label: 'Lecture du corpus',
|
|
93
|
+
detail: err instanceof Error ? err.message.slice(0, 70) : 'échec',
|
|
94
|
+
fix: 'La clé est bonne mais la lecture échoue — signalez-le à hello@stratta.ch',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
checks.push({
|
|
101
|
+
ok: false,
|
|
102
|
+
label: 'Connexion à Stratta',
|
|
103
|
+
detail: err instanceof Error ? err.message.slice(0, 70) : 'échec',
|
|
104
|
+
fix: 'Vérifiez votre connexion réseau et votre proxy',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Python, needed only for ingestion — a warning, not a failure, since most
|
|
109
|
+
// people read norms without ever ingesting one.
|
|
110
|
+
const py = ['python', 'python3'].some((bin) => {
|
|
111
|
+
const r = spawnSync(bin, ['--version'], { stdio: 'ignore', shell: false });
|
|
112
|
+
return r.status === 0;
|
|
113
|
+
});
|
|
114
|
+
checks.push({
|
|
115
|
+
ok: py,
|
|
116
|
+
label: 'Python (ingestion seulement)',
|
|
117
|
+
detail: py ? 'présent' : 'absent',
|
|
118
|
+
fix: 'Requis uniquement pour ingérer vos propres normes : https://python.org',
|
|
119
|
+
});
|
|
120
|
+
const latest = await latestPublished();
|
|
121
|
+
checks.push({
|
|
122
|
+
ok: !latest || !isNewer(latest, current),
|
|
123
|
+
label: 'Version',
|
|
124
|
+
detail: latest ? `${current} (dernière : ${latest})` : current,
|
|
125
|
+
fix: 'npx -y @stratta/mcp update',
|
|
126
|
+
});
|
|
127
|
+
checks.forEach(line);
|
|
128
|
+
const failed = checks.filter((c) => !c.ok);
|
|
129
|
+
console.log(failed.length === 0
|
|
130
|
+
? '\nTout est en ordre.'
|
|
131
|
+
: `\n${failed.length} point${failed.length > 1 ? 's' : ''} à régler.`);
|
|
132
|
+
if (failed.length > 0)
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -92,15 +92,90 @@ for (const def of [...readTools, ...dossierTools, ...ingestTools]) {
|
|
|
92
92
|
// handler receives values that already match the declared shape.
|
|
93
93
|
(args) => call(def, args));
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The help nobody had.
|
|
97
|
+
*
|
|
98
|
+
* Every command here existed and was invisible: `login` shipped in 0.9.0 and
|
|
99
|
+
* did not appear in the app for months, `update` answers a question people
|
|
100
|
+
* were solving with cache-clearing folklore. A CLI whose only discovery path
|
|
101
|
+
* is the documentation site has no discovery path.
|
|
102
|
+
*/
|
|
103
|
+
function printHelp() {
|
|
104
|
+
console.log(`stratta-mcp ${packageVersion()} — vos normes dans votre agent
|
|
105
|
+
|
|
106
|
+
USAGE
|
|
107
|
+
npx -y @stratta/mcp <commande>
|
|
108
|
+
|
|
109
|
+
COMMANDES
|
|
110
|
+
login Autoriser cette machine depuis votre navigateur
|
|
111
|
+
login --paste Coller une clé, quand il n'y a pas de navigateur
|
|
112
|
+
logout Supprimer la clé enregistrée sur cette machine
|
|
113
|
+
whoami Compte et espace de travail utilisés
|
|
114
|
+
doctor Diagnostiquer une installation qui ne répond pas
|
|
115
|
+
update Vérifier la version et récupérer la dernière
|
|
116
|
+
--version Version installée
|
|
117
|
+
--help Cet écran
|
|
118
|
+
|
|
119
|
+
Sans commande, le serveur MCP démarre sur stdio : c'est ce que fait votre
|
|
120
|
+
agent, vous n'avez pas à le lancer vous-même.
|
|
121
|
+
|
|
122
|
+
Installation claude mcp add stratta --scope user -- npx -y @stratta/mcp
|
|
123
|
+
Documentation https://stratta.ch/docs`);
|
|
124
|
+
}
|
|
95
125
|
async function main() {
|
|
96
|
-
|
|
126
|
+
const command = process.argv[2];
|
|
127
|
+
if (command === 'login') {
|
|
97
128
|
const { runLogin } = await import('./login.js');
|
|
98
129
|
await runLogin(process.argv.slice(3));
|
|
99
130
|
return;
|
|
100
131
|
}
|
|
132
|
+
if (command === 'logout') {
|
|
133
|
+
const { runLogout } = await import('./account.js');
|
|
134
|
+
runLogout();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (command === 'whoami') {
|
|
138
|
+
const { runWhoami } = await import('./account.js');
|
|
139
|
+
await runWhoami();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (command === 'doctor') {
|
|
143
|
+
const { runDoctor } = await import('./doctor.js');
|
|
144
|
+
await runDoctor(packageVersion());
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (command === 'update') {
|
|
148
|
+
const { runUpdate } = await import('./update.js');
|
|
149
|
+
await runUpdate(packageVersion());
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// `--version` because that is what everyone types first, and because
|
|
153
|
+
// `update` needs a cheap way to make npx materialise a given version.
|
|
154
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
155
|
+
console.log(packageVersion());
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
159
|
+
printHelp();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// An unknown word is a typo, not a request to start a server on stdio. Left
|
|
163
|
+
// unhandled it looks like a hang: the process waits for MCP frames that
|
|
164
|
+
// never come, and says nothing.
|
|
165
|
+
if (command && !command.startsWith('-')) {
|
|
166
|
+
console.error(`Commande inconnue : ${command}\n`);
|
|
167
|
+
printHelp();
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
101
171
|
await server.connect(new StdioServerTransport());
|
|
102
172
|
// Stderr only; stdout carries the MCP protocol.
|
|
103
173
|
console.error(`[stratta-mcp] v${packageVersion()} listening on stdio`);
|
|
174
|
+
// After connect, never before: the check reaches the network, and a server
|
|
175
|
+
// that starts slowly because of a version lookup is a worse problem than an
|
|
176
|
+
// out-of-date one.
|
|
177
|
+
const { noticeIfOutdated } = await import('./update.js');
|
|
178
|
+
noticeIfOutdated(packageVersion());
|
|
104
179
|
}
|
|
105
180
|
main().catch((err) => {
|
|
106
181
|
console.error('[stratta-mcp] fatal:', err);
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What version npm would serve right now, or null if we could not ask.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately quiet about failures: this runs on a path where being offline,
|
|
5
|
+
* behind a proxy, or on a flaky connection is ordinary, and none of that is
|
|
6
|
+
* worth an error. A missed check is not a problem; a server that refuses to
|
|
7
|
+
* start because a version check failed would be.
|
|
8
|
+
*/
|
|
9
|
+
export declare function latestPublished(timeoutMs?: number): Promise<string | null>;
|
|
10
|
+
/** Numeric compare, so 0.9.10 sorts above 0.9.9. */
|
|
11
|
+
export declare function isNewer(candidate: string, current: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Tell the user, on stderr, when they are running something old.
|
|
14
|
+
*
|
|
15
|
+
* Fire-and-forget from `main`: the server is already listening by the time
|
|
16
|
+
* this resolves, so a slow registry delays nothing. stdout carries the MCP
|
|
17
|
+
* protocol and must stay clean.
|
|
18
|
+
*/
|
|
19
|
+
export declare function noticeIfOutdated(current: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* `npx @stratta/mcp update`.
|
|
22
|
+
*
|
|
23
|
+
* npx caches a resolved package and reuses it, so `npx -y @stratta/mcp` can
|
|
24
|
+
* keep launching a version that npm replaced weeks ago — the user has no way
|
|
25
|
+
* to tell, and nothing tells them. This says which version is running, which
|
|
26
|
+
* one is published, and clears the cache entry so the next launch fetches the
|
|
27
|
+
* new one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runUpdate(current: string): Promise<void>;
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
const REGISTRY = 'https://registry.npmjs.org/@stratta/mcp/latest';
|
|
3
|
+
/**
|
|
4
|
+
* What version npm would serve right now, or null if we could not ask.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately quiet about failures: this runs on a path where being offline,
|
|
7
|
+
* behind a proxy, or on a flaky connection is ordinary, and none of that is
|
|
8
|
+
* worth an error. A missed check is not a problem; a server that refuses to
|
|
9
|
+
* start because a version check failed would be.
|
|
10
|
+
*/
|
|
11
|
+
export async function latestPublished(timeoutMs = 3000) {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(REGISTRY, {
|
|
14
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
15
|
+
headers: { accept: 'application/json' },
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return null;
|
|
19
|
+
const body = (await res.json());
|
|
20
|
+
return body.version ?? null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Numeric compare, so 0.9.10 sorts above 0.9.9. */
|
|
27
|
+
export function isNewer(candidate, current) {
|
|
28
|
+
const a = candidate.split('.').map(Number);
|
|
29
|
+
const b = current.split('.').map(Number);
|
|
30
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
31
|
+
const x = a[i] ?? 0;
|
|
32
|
+
const y = b[i] ?? 0;
|
|
33
|
+
if (x !== y)
|
|
34
|
+
return x > y;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Tell the user, on stderr, when they are running something old.
|
|
40
|
+
*
|
|
41
|
+
* Fire-and-forget from `main`: the server is already listening by the time
|
|
42
|
+
* this resolves, so a slow registry delays nothing. stdout carries the MCP
|
|
43
|
+
* protocol and must stay clean.
|
|
44
|
+
*/
|
|
45
|
+
export function noticeIfOutdated(current) {
|
|
46
|
+
void latestPublished().then((latest) => {
|
|
47
|
+
if (!latest || !isNewer(latest, current))
|
|
48
|
+
return;
|
|
49
|
+
console.error(`[stratta-mcp] version ${latest} disponible (vous avez ${current}) — ` +
|
|
50
|
+
`lancez : npx -y @stratta/mcp update`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `npx @stratta/mcp update`.
|
|
55
|
+
*
|
|
56
|
+
* npx caches a resolved package and reuses it, so `npx -y @stratta/mcp` can
|
|
57
|
+
* keep launching a version that npm replaced weeks ago — the user has no way
|
|
58
|
+
* to tell, and nothing tells them. This says which version is running, which
|
|
59
|
+
* one is published, and clears the cache entry so the next launch fetches the
|
|
60
|
+
* new one.
|
|
61
|
+
*/
|
|
62
|
+
export async function runUpdate(current) {
|
|
63
|
+
console.log(`Version installée : ${current}`);
|
|
64
|
+
const latest = await latestPublished(8000);
|
|
65
|
+
if (!latest) {
|
|
66
|
+
console.log('Impossible de joindre npm. Vérifiez votre connexion et réessayez.');
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(`Dernière publiée : ${latest}`);
|
|
71
|
+
if (!isNewer(latest, current)) {
|
|
72
|
+
console.log('\nVous êtes à jour.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log('\nRécupération de la nouvelle version…');
|
|
76
|
+
// `npx -y pkg@version` bypasses whatever npx had cached for the bare name,
|
|
77
|
+
// and populates the cache with the new one in the same step.
|
|
78
|
+
const r = spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['-y', `@stratta/mcp@${latest}`, '--version'], { stdio: 'inherit' });
|
|
79
|
+
if (r.status === 0) {
|
|
80
|
+
console.log(`\n@stratta/mcp ${latest} est prêt.\n` +
|
|
81
|
+
'Redémarrez votre agent pour que le serveur reparte sur cette version.');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(`\nLa récupération a échoué. À lancer à la main :\n` +
|
|
85
|
+
` npx -y @stratta/mcp@${latest}`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratta/mcp",
|
|
3
3
|
"mcpName": "ch.stratta/mcp",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.6",
|
|
5
5
|
"description": "MCP server exposing the engineering norms your firm is licensed for (SIA / Eurocodes) to any MCP client, via Stratta TreeRAG.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"author": "SmartFlow <hello@stratta.ch>",
|