@tonyweckstrom/mcp-doctor 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/LICENSE +21 -0
- package/README.md +181 -3
- package/dist/bin.js +6 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.js +185 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.js +82 -0
- package/dist/lint.js.map +1 -0
- package/dist/report.js +30 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.js +120 -0
- package/dist/runner.js.map +1 -0
- package/dist/smoke.js +44 -0
- package/dist/smoke.js.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/versio.js +5 -0
- package/dist/versio.js.map +1 -0
- package/package.json +53 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tony Weckström
|
|
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,3 +1,181 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# mcp-doctor
|
|
2
|
+
|
|
3
|
+
Terveystarkastus MCP-palvelimelle. Yksi komento kertoo, toimiiko palvelimesi: vastaako se `initialize`-kättelyyn, ovatko työkalujen kuvaukset ja skeemat kunnossa ja onnistuvatko oikeat työkalukutsut. Raportti on luettava ihmiselle ja koneelle (JSON), ja poistumiskoodi sopii CI:hin.
|
|
4
|
+
|
|
5
|
+
*English summary: a CLI and GitHub Action that checks an MCP server's health: handshake, tool-schema lint and smoke calls with explicit example arguments. Human-readable or JSON report, exit code for CI, no API keys, no network access besides the server you point it at.*
|
|
6
|
+
|
|
7
|
+
Miksi: MCP-asiakkaat (Claude Desktop, Cursor) antavat vähän tietoa, kun palvelin tai työkalun skeema on rikki. Työkalu vain puuttuu listalta tai malli käyttää sitä väärin, ja kehittäjä jää arvailemaan. mcp-doctor löytää ongelmat ennen asiakasta.
|
|
8
|
+
|
|
9
|
+
## Asennus
|
|
10
|
+
|
|
11
|
+
Ei asennusta: aja suoraan npm:stä (Node 22.12+ tai 24):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @tonyweckstrom/mcp-doctor -- node ./oma-palvelin/dist/index.js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Tai asenna globaalisti (komento on silloin `mcp-doctor`):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @tonyweckstrom/mcp-doctor
|
|
21
|
+
mcp-doctor --version
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Tai lähteistä:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/tonyweckstrom1987/mcp-doctor.git
|
|
28
|
+
cd mcp-doctor
|
|
29
|
+
npm ci # asentaa ja kääntää (prepare-skripti)
|
|
30
|
+
node dist/bin.js --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Käyttö
|
|
34
|
+
|
|
35
|
+
stdio-palvelin (komento annetaan `--`-merkin jälkeen):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @tonyweckstrom/mcp-doctor -- node ../oma-palvelin/dist/index.js
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
HTTP-palvelin (Streamable HTTP), tarvittaessa Bearer-tokenilla ympäristömuuttujasta:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export DOCTOR_TOKEN=... # kirjoita itse, älä liitä tokenia komentoriville
|
|
45
|
+
npx @tonyweckstrom/mcp-doctor --url http://127.0.0.1:3000/mcp --bearer-env DOCTOR_TOKEN
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Esimerkkituloste (mcp-server-starter, savuajolla):
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
mcp-doctor: node ../starter/dist/index.js
|
|
52
|
+
Palvelin: mcp-server-starter 0.1.0, 2 työkalua
|
|
53
|
+
Savuajo:
|
|
54
|
+
OK greet (4 ms)
|
|
55
|
+
OK add (1 ms)
|
|
56
|
+
Yhteenveto: 0 virhettä, 0 varoitusta, 0 huomiota -> OK
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Ja kun palvelimessa on vikoja (testipalvelin, jossa on päällekkäisiä ja vajaasti kuvattuja työkaluja), tulos on virheitä ja poistumiskoodi `1`:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
mcp-doctor: node bad-tools-server.mjs
|
|
63
|
+
Palvelin: huono-palvelin 0.0.1, 4 työkalua
|
|
64
|
+
VIRHE [TOOL_DUPLICATE] dup: työkalun nimi esiintyy 2 kertaa
|
|
65
|
+
VIRHE [TOOL_NAME_INVALID] no desc!: nimi on tyhjä, yli 128 merkkiä tai sisältää sallittujen merkkien (A-Z a-z 0-9 _ - .) ulkopuolisia merkkejä
|
|
66
|
+
VIRHE [SCHEMA_REQUIRED_UNKNOWN] loose: pakollinen kenttä 'z' ei ole properties-listassa
|
|
67
|
+
VAROITUS [TOOL_DESC_MISSING] no desc!: kuvaus puuttuu: malli ei tiedä milloin työkalua kuuluu käyttää
|
|
68
|
+
VAROITUS [SCHEMA_PROP_UNTYPED] loose: kentällä 'x' ei ole tyyppiä (type/enum/const/$ref/anyOf/oneOf/allOf): skeema on liian väljä
|
|
69
|
+
VAROITUS [SCHEMA_PROP_DESC_MISSING] loose: kentältä 'x' puuttuu kuvaus
|
|
70
|
+
VAROITUS [SCHEMA_PROP_DESC_MISSING] loose: kentältä 'y' puuttuu kuvaus
|
|
71
|
+
Yhteenveto: 3 virhettä, 4 varoitusta, 0 huomiota -> EPÄONNISTUI
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Valinta | Selitys |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `--smoke <tiedosto>` | savuajo: kutsuu vain tiedostossa nimetyt työkalut |
|
|
77
|
+
| `--json` | raportti JSONina |
|
|
78
|
+
| `--strict` | varoituksetkin kaatavat ajon |
|
|
79
|
+
| `--timeout-ms <luku>` | yksittäisen pyynnön aikakatkaisu, oletus 10000 |
|
|
80
|
+
| `--env <NIMI>` | välittää ympäristömuuttujan stdio-palvelimelle (toistettava) |
|
|
81
|
+
| `--bearer-env <NIMI>` | HTTP-palvelimen Bearer-token tästä muuttujasta |
|
|
82
|
+
| `--version` | tulostaa version |
|
|
83
|
+
|
|
84
|
+
Poistumiskoodit: `0` kunnossa, `1` löydöksiä (virhe, tai varoitus `--strict`-tilassa), `2` käyttö- tai tiedostovirhe.
|
|
85
|
+
|
|
86
|
+
## Mitä tarkistetaan
|
|
87
|
+
|
|
88
|
+
| Tunniste | Vakavuus | Mitä |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `CONNECT_FAILED` | virhe | yhteys tai `initialize` epäonnistui, myös tuntematon protokollaversio. Viestissä näkyy stdio-palvelimen viimeinen stderr-teksti |
|
|
91
|
+
| `SERVER_INFO_MISSING` | varoitus | palvelin ei kerro nimeään |
|
|
92
|
+
| `TOOLS_CAPABILITY_MISSING` | varoitus | palvelin ei ilmoita tools-kykyä |
|
|
93
|
+
| `TOOLS_LIST_FAILED` | virhe | `tools/list` epäonnistui |
|
|
94
|
+
| `TOOLS_EMPTY` | varoitus | tools-kyky ilmoitettu, mutta lista tyhjä |
|
|
95
|
+
| `TOOLS_PAGINATION_LIMIT` | varoitus | lista jatkui yli 50 sivua, luku keskeytettiin |
|
|
96
|
+
| `TOOL_DUPLICATE` | virhe | sama nimi useammin kuin kerran |
|
|
97
|
+
| `TOOL_NAME_INVALID` | virhe | tyhjä, yli 128 merkkiä tai muita merkkejä kuin `A-Z a-z 0-9 _ - .` |
|
|
98
|
+
| `TOOL_NAME_PORTABILITY` | varoitus | ei sovi muotoon `[A-Za-z0-9_-]{1,64}`, jota osa asiakkaista vaatii |
|
|
99
|
+
| `TOOL_DESC_MISSING` / `TOOL_DESC_SHORT` | varoitus / huomio | kuvaus puuttuu / alle 20 merkkiä |
|
|
100
|
+
| `SCHEMA_NOT_OBJECT` | virhe | syöteskeema puuttuu tai sen `type` ei ole `object` |
|
|
101
|
+
| `SCHEMA_REQUIRED_INVALID` / `SCHEMA_REQUIRED_UNKNOWN` | virhe | `required` ei ole merkkijonolista / viittaa kenttään, jota ei ole |
|
|
102
|
+
| `SCHEMA_PROP_DESC_MISSING` | varoitus | kentältä puuttuu kuvaus |
|
|
103
|
+
| `SCHEMA_PROP_UNTYPED` | varoitus | kentällä ei ole `type`/`enum`/`const`/`$ref`/`anyOf`/`oneOf`/`allOf` |
|
|
104
|
+
| `OUTPUT_SCHEMA_NOT_OBJECT` | virhe | `outputSchema` ei ole `object` |
|
|
105
|
+
| `SMOKE_TOOL_NOT_FOUND` | virhe | savuajotiedosto nimeää työkalun, jota palvelimella ei ole |
|
|
106
|
+
| `SMOKE_TOOL_ERROR` | virhe | työkalu palautti `isError`, vaikka virhettä ei odotettu |
|
|
107
|
+
| `SMOKE_EXPECTED_ERROR` | virhe | odotettu virhe jäi tulematta |
|
|
108
|
+
| `SMOKE_CALL_FAILED` | virhe | kutsu epäonnistui (aikakatkaisu, protokollavirhe, `outputSchema`-vaatimus rikottu) |
|
|
109
|
+
| `SMOKE_EMPTY_RESULT` | varoitus | tulos on tyhjä |
|
|
110
|
+
| `SMOKE_SLOW` | varoitus | vastaus kesti yli `maxMs` (oletus 5000) |
|
|
111
|
+
|
|
112
|
+
## Savuajo
|
|
113
|
+
|
|
114
|
+
Savuajo kutsuu **vain** tiedostossa nimettyjä työkaluja, annetuilla argumenteilla. Työkaluja ei kutsuta arvaamalla, koska työkalu voi muuttaa dataa. Jätä muokkaavat työkalut pois tai käytä niille testiympäristöä.
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"calls": [
|
|
119
|
+
{ "tool": "greet", "arguments": { "name": "Tony" } },
|
|
120
|
+
{ "tool": "poista_kaikki", "arguments": {}, "expectError": true },
|
|
121
|
+
{ "tool": "hidas_haku", "arguments": { "q": "x" }, "maxMs": 15000 }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Kentät: `tool` (pakollinen), `arguments` (oletus `{}`), `expectError` (oletus `false`), `maxMs` (oletus 5000). Pelkkä taulukko käy myös.
|
|
127
|
+
|
|
128
|
+
## GitHub Action
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
- uses: actions/checkout@v7
|
|
132
|
+
- run: npm ci && npm run build # oma palvelimesi
|
|
133
|
+
- uses: tonyweckstrom1987/mcp-doctor@v0.2.0
|
|
134
|
+
with:
|
|
135
|
+
command: node dist/index.js
|
|
136
|
+
smoke: smoke.json
|
|
137
|
+
strict: "true"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Lukitse Action versiotagiin (`@v0.2.0`) tai tiukemmin commit-SHA:han, älä `@main`-haaraan. Syötteet: `command` tai `url`, `smoke`, `strict`, `timeout-ms`, `env-names`, `bearer-env`. Stdio-palvelin näkee vain nimeämäsi ympäristömuuttujat: aseta arvot action-askeleen `env`-lohkossa ja listaa nimet syötteellä `env-names` (välilyönnein erotettuna). HTTP-palvelimen tokenin aseta samoin `env`-lohkossa secretistä ja anna muuttujan nimi syötteellä `bearer-env`. Syötteet välitetään kuoreen ympäristömuuttujina, ei komentoriville, joten ne eivät voi injektoida komentoja.
|
|
141
|
+
|
|
142
|
+
## Turvallisuus
|
|
143
|
+
|
|
144
|
+
- Työkalu käynnistää komennon, jonka itse annat, ja puhuu vain sille palvelimelle. Muita verkkoyhteyksiä tai API-avaimia ei ole.
|
|
145
|
+
- Stdio-palvelin saa vain `PATH`- ja perusmuuttujat. Muut ympäristömuuttujat välitetään vain nimeämällä ne `--env`-valinnalla.
|
|
146
|
+
- Bearer-token luetaan ympäristömuuttujasta, ei komentoriviltä, eikä sitä tulosteta. Tulostuva kohde-osoite jättää kyselyparametrit pois.
|
|
147
|
+
- Raportti ei sisällä työkalujen tuloksia, vain virheen ensimmäisen tekstirivin (enintään 300 merkkiä).
|
|
148
|
+
- Yhteysvirheen viestiin liitetään stdio-palvelimen viimeinen stderr-teksti. Jos palvelimesi kirjoittaa lokiin salaisuuksia, ne päätyvät CI-lokiin.
|
|
149
|
+
|
|
150
|
+
## Vaihtoehtoja ja miksi tämä
|
|
151
|
+
|
|
152
|
+
Tilanne 16.10.2026. MCP-palvelimen testaamiseen on jo muitakin työkaluja, ja jotkut niistä ovat laajempia kuin tämä:
|
|
153
|
+
|
|
154
|
+
- [`@mcpjam/cli`](https://www.npmjs.com/package/@mcpjam/cli) on laaja testaus- ja virheenetsintätyökalu: yhteystarkistukset, OAuth-vaatimustenmukaisuus, protokollatarkistukset, työkalujen vertailu ja pilvipalvelu. Jos tarvitset OAuth-testejä tai kattavaa protokollakonformanssia, käytä sitä. Se kerää oletuksena anonyymiä käyttötilastoa (poistettavissa valinnalla `--no-telemetry`).
|
|
155
|
+
- MCP Inspector -tyyppiset työkalut (esim. `@modelcontextprotocol/inspector`) on tehty palvelimen käsin tutkimiseen ja kokeiluun selaimessa tai komentorivillä, ei pelkkään pass/fail-tarkistukseen CI:ssä.
|
|
156
|
+
|
|
157
|
+
mcp-doctor on tarkoituksella pieni ja rajattu yhteen tehtävään: yksi komento CI:ssä, joka kaatuu, kun palvelin tai sen työkalukuvaukset ovat rikki. Erot:
|
|
158
|
+
|
|
159
|
+
- **Kuvausten laatu mallin kannalta:** tarkistaa, että työkalulla on kuvaus ja että skeeman kentät ovat tyypitetty ja kuvattu, koska malli valitsee ja täyttää työkalut näiden perusteella.
|
|
160
|
+
- **Savuajo vain nimeämillesi työkaluille:** työkaluja kutsutaan vain tiedostossa (`--smoke`) nimetyillä, itse määritellyillä argumenteilla. Mitään ei kutsuta arvaamalla.
|
|
161
|
+
- **Ei verkkoa muualle:** työkalu puhuu vain osoittamallesi palvelimelle. Ei API-avaimia, ei tilastoja, ei tiliä.
|
|
162
|
+
- **Eristetty Action:** stdio-palvelin näkee vain nimeämäsi ympäristömuuttujat, ja Actionin syötteet eivät pääse komentoriville.
|
|
163
|
+
- **Pieni ja testattu:** vähän koodia, 80 testiä oikeilla stdio- ja HTTP-testipalvelimilla.
|
|
164
|
+
|
|
165
|
+
## Rajaukset
|
|
166
|
+
|
|
167
|
+
Ei tue vanhaa SSE-siirtoa eikä OAuth-kirjautumista. Tarkistaa työkalut, ei `resources`- tai `prompts`-osioita. JSON Schema -validointi on kevyt (rakenne, `required`, tyypit, kuvaukset), ei täyttä skeemavalidointia.
|
|
168
|
+
|
|
169
|
+
## Kehitys
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npm ci
|
|
173
|
+
npm run typecheck
|
|
174
|
+
npm test # 80 testiä, käynnistää oikeita stdio- ja HTTP-testipalvelimia
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Testit ajetaan oikeita MCP-palvelimia vastaan (`test/fixtures/`): kunnossa oleva, huonosti määritelty, väärää protokollaversiota puhuva, heti kaatuva sekä sivuttava. Ennen julkaisua koodiin tehtiin tahallisia rikkomisia (mutaatiot), (22 kappaletta), ja jokainen niistä kaatoi testin.
|
|
178
|
+
|
|
179
|
+
## Lisenssi
|
|
180
|
+
|
|
181
|
+
MIT
|
package/dist/bin.js
ADDED
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAExC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IAClD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { formatJson, formatText } from "./report.js";
|
|
5
|
+
import { diagnose } from "./runner.js";
|
|
6
|
+
import { parseSmokeSpec } from "./smoke.js";
|
|
7
|
+
import { UsageError } from "./types.js";
|
|
8
|
+
import { VERSIO } from "./versio.js";
|
|
9
|
+
export const EXIT_OK = 0;
|
|
10
|
+
export const EXIT_FINDINGS = 1;
|
|
11
|
+
export const EXIT_USAGE = 2;
|
|
12
|
+
export const USAGE = `mcp-doctor - MCP-palvelimen terveystarkastus
|
|
13
|
+
|
|
14
|
+
Käyttö:
|
|
15
|
+
mcp-doctor [valinnat] -- <komento> [argumentit...] stdio-palvelin
|
|
16
|
+
mcp-doctor [valinnat] --url <http(s)-osoite> HTTP-palvelin (Streamable HTTP)
|
|
17
|
+
|
|
18
|
+
Valinnat:
|
|
19
|
+
--smoke <tiedosto> savuajo: kutsuu vain tiedostossa nimetyt työkalut
|
|
20
|
+
--json tulosta raportti JSONina
|
|
21
|
+
--strict varoituksetkin kaatavat ajon (exit 1)
|
|
22
|
+
--timeout-ms <luku> yksittäisen pyynnön aikakatkaisu, oletus 10000
|
|
23
|
+
--env <NIMI> välitä ympäristömuuttuja stdio-palvelimelle (toistettava)
|
|
24
|
+
--bearer-env <NIMI> lue HTTP-palvelimen Bearer-token tästä ympäristömuuttujasta
|
|
25
|
+
--version tulosta versio
|
|
26
|
+
--help tämä ohje
|
|
27
|
+
|
|
28
|
+
Poistumiskoodit: 0 kunnossa, 1 löydöksiä (virhe, tai varoitus --strict-tilassa), 2 käyttö- tai tiedostovirhe.
|
|
29
|
+
`;
|
|
30
|
+
const BOOL_FLAGS = new Set(["json", "strict", "help", "version"]);
|
|
31
|
+
const VALUE_FLAGS = new Set(["smoke", "timeout-ms", "env", "bearer-env", "url"]);
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const flags = new Map();
|
|
34
|
+
const bools = new Set();
|
|
35
|
+
let command = [];
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
if (a === "--") {
|
|
39
|
+
command = argv.slice(i + 1);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (!a.startsWith("--"))
|
|
43
|
+
throw new UsageError(`Odottamaton argumentti '${a}' (palvelimen komento tulee '--'-merkin jälkeen)`);
|
|
44
|
+
const name = a.slice(2);
|
|
45
|
+
if (BOOL_FLAGS.has(name))
|
|
46
|
+
bools.add(name);
|
|
47
|
+
else if (VALUE_FLAGS.has(name)) {
|
|
48
|
+
const v = argv[++i];
|
|
49
|
+
if (v === undefined || v.startsWith("--"))
|
|
50
|
+
throw new UsageError(`Valinta --${name} tarvitsee arvon`);
|
|
51
|
+
flags.set(name, [...(flags.get(name) ?? []), v]);
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
throw new UsageError(`Tuntematon valinta --${name}`);
|
|
55
|
+
}
|
|
56
|
+
return { command, flags, bools };
|
|
57
|
+
}
|
|
58
|
+
function one(args, name) {
|
|
59
|
+
const v = args.flags.get(name);
|
|
60
|
+
if (!v)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (v.length > 1)
|
|
63
|
+
throw new UsageError(`Valinta --${name} saa esiintyä vain kerran`);
|
|
64
|
+
return v[0];
|
|
65
|
+
}
|
|
66
|
+
/** Ajaa komentorivin ja palauttaa poistumiskoodin. Ei kutsu process.exit-funktiota. */
|
|
67
|
+
export async function main(argv, io) {
|
|
68
|
+
try {
|
|
69
|
+
// Vain '--'-merkkiä edeltävät argumentit ovat meidän: palvelimen oma '--help' ei saa laukaista ohjetta.
|
|
70
|
+
const ennenErotinta = argv.includes("--") ? argv.slice(0, argv.indexOf("--")) : argv;
|
|
71
|
+
if (ennenErotinta.includes("--version")) {
|
|
72
|
+
io.stdout(`${VERSIO}\n`);
|
|
73
|
+
return EXIT_OK;
|
|
74
|
+
}
|
|
75
|
+
if (ennenErotinta.includes("--help")) {
|
|
76
|
+
io.stdout(USAGE);
|
|
77
|
+
return EXIT_OK;
|
|
78
|
+
}
|
|
79
|
+
if (argv.length === 0) {
|
|
80
|
+
io.stderr(USAGE);
|
|
81
|
+
return EXIT_USAGE;
|
|
82
|
+
}
|
|
83
|
+
const args = parseArgs(argv);
|
|
84
|
+
const url = one(args, "url");
|
|
85
|
+
if (url !== undefined && args.command.length > 0)
|
|
86
|
+
throw new UsageError("Anna joko --url tai komento '--'-merkin jälkeen, ei molempia");
|
|
87
|
+
if (url === undefined && args.command.length === 0)
|
|
88
|
+
throw new UsageError("Anna palvelin: --url <osoite> tai komento '--'-merkin jälkeen");
|
|
89
|
+
const timeoutRaw = one(args, "timeout-ms");
|
|
90
|
+
const timeoutMs = timeoutRaw === undefined ? 10000 : Number(timeoutRaw);
|
|
91
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1)
|
|
92
|
+
throw new UsageError("--timeout-ms täytyy olla positiivinen kokonaisluku");
|
|
93
|
+
const smokePath = one(args, "smoke");
|
|
94
|
+
let smoke;
|
|
95
|
+
if (smokePath !== undefined) {
|
|
96
|
+
let text;
|
|
97
|
+
try {
|
|
98
|
+
text = io.readFile(smokePath);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new UsageError(`Savuajotiedostoa '${smokePath}' ei voi lukea`);
|
|
102
|
+
}
|
|
103
|
+
smoke = parseSmokeSpec(text);
|
|
104
|
+
}
|
|
105
|
+
let transport;
|
|
106
|
+
let target;
|
|
107
|
+
let stderrTail;
|
|
108
|
+
if (url !== undefined) {
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = new URL(url);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new UsageError(`Virheellinen osoite '${url}'`);
|
|
115
|
+
}
|
|
116
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
117
|
+
throw new UsageError("--url: vain http ja https sallitaan");
|
|
118
|
+
if (args.flags.has("env"))
|
|
119
|
+
throw new UsageError("--env koskee vain stdio-palvelinta");
|
|
120
|
+
const bearerEnv = one(args, "bearer-env");
|
|
121
|
+
let requestInit;
|
|
122
|
+
if (bearerEnv !== undefined) {
|
|
123
|
+
const token = io.env[bearerEnv];
|
|
124
|
+
if (!token)
|
|
125
|
+
throw new UsageError(`Ympäristömuuttuja ${bearerEnv} on tyhjä tai puuttuu`);
|
|
126
|
+
requestInit = { headers: { Authorization: `Bearer ${token}` } };
|
|
127
|
+
}
|
|
128
|
+
transport = new StreamableHTTPClientTransport(parsed, requestInit ? { requestInit } : undefined);
|
|
129
|
+
target = `${parsed.origin}${parsed.pathname}`; // ei kyselyparametreja eikä tunnuksia tulosteisiin
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (args.flags.has("bearer-env"))
|
|
133
|
+
throw new UsageError("--bearer-env koskee vain --url-palvelinta");
|
|
134
|
+
const env = {};
|
|
135
|
+
for (const name of args.flags.get("env") ?? []) {
|
|
136
|
+
const v = io.env[name];
|
|
137
|
+
if (v === undefined)
|
|
138
|
+
throw new UsageError(`Ympäristömuuttujaa ${name} ei ole asetettu`);
|
|
139
|
+
env[name] = v;
|
|
140
|
+
}
|
|
141
|
+
const [command, ...rest] = args.command;
|
|
142
|
+
const stdio = new StdioClientTransport({ command, args: rest, env: { ...getSafeEnv(io.env), ...env }, stderr: "pipe" });
|
|
143
|
+
let tail = "";
|
|
144
|
+
stdio.stderr?.on("data", (d) => {
|
|
145
|
+
tail = (tail + d.toString()).slice(-1000);
|
|
146
|
+
});
|
|
147
|
+
stderrTail = () => tail;
|
|
148
|
+
transport = stdio;
|
|
149
|
+
target = args.command.join(" ");
|
|
150
|
+
}
|
|
151
|
+
const report = await diagnose({ transport, target, smoke, timeoutMs, strict: args.bools.has("strict"), stderrTail });
|
|
152
|
+
io.stdout(args.bools.has("json") ? formatJson(report) : formatText(report));
|
|
153
|
+
return report.ok ? EXIT_OK : EXIT_FINDINGS;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (err instanceof UsageError) {
|
|
157
|
+
io.stderr(`Virhe: ${err.message}\n\n${USAGE}`);
|
|
158
|
+
return EXIT_USAGE;
|
|
159
|
+
}
|
|
160
|
+
io.stderr(`Odottamaton virhe: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
161
|
+
return EXIT_USAGE;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Vain PATH ja perusmuuttujat: tarkastettava palvelin ei näe muuta ympäristöä kuin --env:llä nimetyn. */
|
|
165
|
+
function getSafeEnv(env) {
|
|
166
|
+
const keep = process.platform === "win32"
|
|
167
|
+
? ["PATH", "PATHEXT", "SYSTEMROOT", "COMSPEC", "TEMP", "TMP", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "HOMEDRIVE", "HOMEPATH"]
|
|
168
|
+
: ["PATH", "HOME", "LANG", "TMPDIR", "SHELL", "USER"];
|
|
169
|
+
const out = {};
|
|
170
|
+
for (const k of keep) {
|
|
171
|
+
const v = env[k];
|
|
172
|
+
if (v !== undefined && !v.startsWith("()"))
|
|
173
|
+
out[k] = v;
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
export function realIo() {
|
|
178
|
+
return {
|
|
179
|
+
stdout: (s) => process.stdout.write(s),
|
|
180
|
+
stderr: (s) => process.stderr.write(s),
|
|
181
|
+
env: process.env,
|
|
182
|
+
readFile: (p) => readFileSync(p, "utf8"),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,cAAc,YAAY,CAAC"}
|
package/dist/lint.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const NAME_STRICT = /^[A-Za-z0-9_-]{1,64}$/;
|
|
2
|
+
const NAME_SPEC = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
3
|
+
const TYPE_KEYS = ["type", "enum", "const", "$ref", "anyOf", "oneOf", "allOf"];
|
|
4
|
+
const SHORT_DESCRIPTION = 20;
|
|
5
|
+
function isRecord(v) {
|
|
6
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7
|
+
}
|
|
8
|
+
function find(id, severity, tool, message) {
|
|
9
|
+
return { id, severity, tool, message };
|
|
10
|
+
}
|
|
11
|
+
function lintSchema(tool, schema, kind) {
|
|
12
|
+
const out = [];
|
|
13
|
+
const label = kind === "input" ? "syöteskeema" : "tulosskeema";
|
|
14
|
+
if (!isRecord(schema) || schema.type !== "object") {
|
|
15
|
+
out.push(find(kind === "input" ? "SCHEMA_NOT_OBJECT" : "OUTPUT_SCHEMA_NOT_OBJECT", "error", tool, `${label} puuttuu tai sen type ei ole "object"`));
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
const props = isRecord(schema.properties) ? schema.properties : undefined;
|
|
19
|
+
if (kind === "output")
|
|
20
|
+
return out;
|
|
21
|
+
const required = schema.required;
|
|
22
|
+
if (required !== undefined) {
|
|
23
|
+
if (!Array.isArray(required) || required.some((r) => typeof r !== "string")) {
|
|
24
|
+
out.push(find("SCHEMA_REQUIRED_INVALID", "error", tool, "required ei ole merkkijonolista"));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
for (const r of required) {
|
|
28
|
+
if (!props || !(r in props)) {
|
|
29
|
+
out.push(find("SCHEMA_REQUIRED_UNKNOWN", "error", tool, `pakollinen kenttä '${r}' ei ole properties-listassa`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (props) {
|
|
35
|
+
for (const [name, def] of Object.entries(props)) {
|
|
36
|
+
if (!isRecord(def)) {
|
|
37
|
+
out.push(find("SCHEMA_PROP_UNTYPED", "warning", tool, `kentän '${name}' skeema ei ole objekti`));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!TYPE_KEYS.some((k) => k in def)) {
|
|
41
|
+
out.push(find("SCHEMA_PROP_UNTYPED", "warning", tool, `kentällä '${name}' ei ole tyyppiä (type/enum/const/$ref/anyOf/oneOf/allOf): skeema on liian väljä`));
|
|
42
|
+
}
|
|
43
|
+
if (typeof def.description !== "string" || def.description.trim() === "") {
|
|
44
|
+
out.push(find("SCHEMA_PROP_DESC_MISSING", "warning", tool, `kentältä '${name}' puuttuu kuvaus`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Tarkistaa työkalulistan ja niiden skeemat. Puhdas funktio, ei verkkoa. */
|
|
51
|
+
export function lintTools(tools) {
|
|
52
|
+
const out = [];
|
|
53
|
+
const seen = new Map();
|
|
54
|
+
for (const t of tools)
|
|
55
|
+
seen.set(t.name, (seen.get(t.name) ?? 0) + 1);
|
|
56
|
+
for (const [name, n] of seen) {
|
|
57
|
+
if (n > 1)
|
|
58
|
+
out.push(find("TOOL_DUPLICATE", "error", name, `työkalun nimi esiintyy ${n} kertaa`));
|
|
59
|
+
}
|
|
60
|
+
const reported = new Set();
|
|
61
|
+
for (const t of tools) {
|
|
62
|
+
if (reported.has(t.name))
|
|
63
|
+
continue; // duplikaatin skeema tarkistetaan kerran
|
|
64
|
+
reported.add(t.name);
|
|
65
|
+
if (!NAME_SPEC.test(t.name)) {
|
|
66
|
+
out.push(find("TOOL_NAME_INVALID", "error", t.name, "nimi on tyhjä, yli 128 merkkiä tai sisältää sallittujen merkkien (A-Z a-z 0-9 _ - .) ulkopuolisia merkkejä"));
|
|
67
|
+
}
|
|
68
|
+
else if (!NAME_STRICT.test(t.name)) {
|
|
69
|
+
out.push(find("TOOL_NAME_PORTABILITY", "warning", t.name, "nimi ei sovi muotoon [A-Za-z0-9_-]{1,64}, jota osa asiakkaista (esim. Claude API) vaatii"));
|
|
70
|
+
}
|
|
71
|
+
const desc = typeof t.description === "string" ? t.description.trim() : "";
|
|
72
|
+
if (desc === "")
|
|
73
|
+
out.push(find("TOOL_DESC_MISSING", "warning", t.name, "kuvaus puuttuu: malli ei tiedä milloin työkalua kuuluu käyttää"));
|
|
74
|
+
else if (desc.length < SHORT_DESCRIPTION)
|
|
75
|
+
out.push(find("TOOL_DESC_SHORT", "info", t.name, `kuvaus on hyvin lyhyt (${desc.length} merkkiä)`));
|
|
76
|
+
out.push(...lintSchema(t.name, t.inputSchema, "input"));
|
|
77
|
+
if (t.outputSchema !== undefined)
|
|
78
|
+
out.push(...lintSchema(t.name, t.outputSchema, "output"));
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=lint.js.map
|
package/dist/lint.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAC5C,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC/E,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,IAAI,CAAC,EAAU,EAAE,QAA6B,EAAE,IAAY,EAAE,OAAe;IACpF,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,MAAe,EAAE,IAAwB;IACzE,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;IAC/D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,0BAA0B,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,uCAAuC,CAAC,CAAC,CAAC;QACpJ,OAAO,GAAG,CAAC;IACb,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IAElC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,OAAO,EAAE,IAAI,EAAE,iCAAiC,CAAC,CAAC,CAAC;QAC9F,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBAC5B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,OAAO,EAAE,IAAI,EAAE,sBAAsB,CAAC,8BAA8B,CAAC,CAAC,CAAC;gBAClH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACV,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,IAAI,yBAAyB,CAAC,CAAC,CAAC;gBACjG,SAAS;YACX,CAAC;YACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACrC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,IAAI,kFAAkF,CAAC,CAAC,CAAC;YAC9J,CAAC;YACD,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACzE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,IAAI,kBAAkB,CAAC,CAAC,CAAC;YACnG,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,KAAiB;IACzC,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,0BAA0B,CAAC,SAAS,CAAC,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,yCAAyC;QAC7E,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,4GAA4G,CAAC,CAAC,CAAC;QACrK,CAAC;aAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,0FAA0F,CAAC,CAAC,CAAC;QACzJ,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,IAAI,IAAI,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,gEAAgE,CAAC,CAAC,CAAC;aACrI,IAAI,IAAI,CAAC,MAAM,GAAG,iBAAiB;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,0BAA0B,IAAI,CAAC,MAAM,WAAW,CAAC,CAAC,CAAC;QAC9I,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function summarize(findings) {
|
|
2
|
+
return {
|
|
3
|
+
errors: findings.filter((f) => f.severity === "error").length,
|
|
4
|
+
warnings: findings.filter((f) => f.severity === "warning").length,
|
|
5
|
+
infos: findings.filter((f) => f.severity === "info").length,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const LABEL = { error: "VIRHE ", warning: "VAROITUS", info: "HUOMIO " };
|
|
9
|
+
const ORDER = { error: 0, warning: 1, info: 2 };
|
|
10
|
+
export function formatText(r) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
lines.push(`mcp-doctor: ${r.target}`);
|
|
13
|
+
lines.push(r.server ? `Palvelin: ${r.server.name} ${r.server.version}, ${r.toolCount} työkalua` : "Palvelin: ei tietoja (yhteys ei onnistunut)");
|
|
14
|
+
const sorted = [...r.findings].sort((a, b) => ORDER[a.severity] - ORDER[b.severity]);
|
|
15
|
+
for (const f of sorted) {
|
|
16
|
+
lines.push(`${LABEL[f.severity]} [${f.id}]${f.tool ? ` ${f.tool}:` : ""} ${f.message}`);
|
|
17
|
+
}
|
|
18
|
+
if (r.smoke.length > 0) {
|
|
19
|
+
lines.push("Savuajo:");
|
|
20
|
+
for (const s of r.smoke)
|
|
21
|
+
lines.push(` ${s.ok ? "OK " : "VIRHE "} ${s.tool} (${s.ms} ms)`);
|
|
22
|
+
}
|
|
23
|
+
const { errors, warnings, infos } = r.summary;
|
|
24
|
+
lines.push(`Yhteenveto: ${errors} virhettä, ${warnings} varoitusta, ${infos} huomiota -> ${r.ok ? "OK" : "EPÄONNISTUI"}`);
|
|
25
|
+
return lines.join("\n") + "\n";
|
|
26
|
+
}
|
|
27
|
+
export function formatJson(r) {
|
|
28
|
+
return JSON.stringify(r, null, 2) + "\n";
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,QAAmB;IAC3C,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;QAC7D,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QACjE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;KAC5D,CAAC;AACJ,CAAC;AAED,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAW,CAAC;AACpF,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AAEzD,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,6CAA6C,CAAC,CAAC;IACjJ,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrF,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,eAAe,MAAM,cAAc,QAAQ,gBAAgB,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1H,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAC3C,CAAC"}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { lintTools } from "./lint.js";
|
|
3
|
+
import { summarize } from "./report.js";
|
|
4
|
+
import { VERSIO } from "./versio.js";
|
|
5
|
+
const MAX_PAGES = 50;
|
|
6
|
+
const MAX_MESSAGE = 300;
|
|
7
|
+
function short(text) {
|
|
8
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
9
|
+
return one.length > MAX_MESSAGE ? one.slice(0, MAX_MESSAGE) + "…" : one;
|
|
10
|
+
}
|
|
11
|
+
function errText(e) {
|
|
12
|
+
return short(e instanceof Error ? e.message : String(e));
|
|
13
|
+
}
|
|
14
|
+
function firstText(content) {
|
|
15
|
+
if (!Array.isArray(content))
|
|
16
|
+
return "";
|
|
17
|
+
for (const c of content) {
|
|
18
|
+
if (c && typeof c === "object" && c.type === "text" && typeof c.text === "string") {
|
|
19
|
+
return short(c.text);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
function build(opts, server, toolCount, findings, smoke) {
|
|
25
|
+
const summary = summarize(findings);
|
|
26
|
+
const ok = summary.errors === 0 && (!opts.strict || summary.warnings === 0);
|
|
27
|
+
return { version: 1, target: opts.target, server, toolCount, findings, smoke, summary, ok };
|
|
28
|
+
}
|
|
29
|
+
/** Yhdistää palvelimeen, tarkistaa työkalut ja ajaa savuajon. Ei koskaan heitä: virheet ovat löydöksiä. */
|
|
30
|
+
export async function diagnose(opts) {
|
|
31
|
+
const findings = [];
|
|
32
|
+
const smokeResults = [];
|
|
33
|
+
// Huom: SDK:n asiakas tarkistaa itse outputSchema-vaatimuksen (puuttuva tai virheellinen structuredContent
|
|
34
|
+
// heittää virheen), ja se näkyy löydöksenä SMOKE_CALL_FAILED.
|
|
35
|
+
const client = new Client({ name: "mcp-doctor", version: VERSIO });
|
|
36
|
+
try {
|
|
37
|
+
try {
|
|
38
|
+
await client.connect(opts.transport, { timeout: opts.timeoutMs });
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
const tail = opts.stderrTail?.().trim();
|
|
42
|
+
const extra = tail ? ` | palvelimen stderr: ${short(tail)}` : "";
|
|
43
|
+
findings.push({ id: "CONNECT_FAILED", severity: "error", message: `yhteys tai initialize-kättely epäonnistui: ${errText(e)}${extra}` });
|
|
44
|
+
return build(opts, null, 0, findings, smokeResults);
|
|
45
|
+
}
|
|
46
|
+
const info = client.getServerVersion();
|
|
47
|
+
const server = info ? { name: String(info.name), version: String(info.version) } : null;
|
|
48
|
+
if (!server || !server.name) {
|
|
49
|
+
findings.push({ id: "SERVER_INFO_MISSING", severity: "warning", message: "palvelin ei kertonut nimeään ja versiotaan (serverInfo)" });
|
|
50
|
+
}
|
|
51
|
+
const caps = client.getServerCapabilities();
|
|
52
|
+
const tools = [];
|
|
53
|
+
if (!caps?.tools) {
|
|
54
|
+
findings.push({ id: "TOOLS_CAPABILITY_MISSING", severity: "warning", message: "palvelin ei ilmoita tools-kykyä, työkaluja ei listata" });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
let cursor;
|
|
58
|
+
let pages = 0;
|
|
59
|
+
try {
|
|
60
|
+
do {
|
|
61
|
+
const res = await client.listTools(cursor ? { cursor } : undefined, { timeout: opts.timeoutMs });
|
|
62
|
+
tools.push(...res.tools);
|
|
63
|
+
cursor = res.nextCursor;
|
|
64
|
+
pages++;
|
|
65
|
+
} while (cursor && pages < MAX_PAGES);
|
|
66
|
+
if (cursor)
|
|
67
|
+
findings.push({ id: "TOOLS_PAGINATION_LIMIT", severity: "warning", message: `työkalulistaus keskeytettiin ${MAX_PAGES} sivun jälkeen` });
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
findings.push({ id: "TOOLS_LIST_FAILED", severity: "error", message: `tools/list epäonnistui: ${errText(e)}` });
|
|
71
|
+
}
|
|
72
|
+
if (tools.length === 0 && !findings.some((f) => f.id === "TOOLS_LIST_FAILED")) {
|
|
73
|
+
findings.push({ id: "TOOLS_EMPTY", severity: "warning", message: "palvelin ilmoittaa tools-kyvyn mutta työkalulista on tyhjä" });
|
|
74
|
+
}
|
|
75
|
+
findings.push(...lintTools(tools));
|
|
76
|
+
}
|
|
77
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
78
|
+
for (const call of opts.smoke ?? []) {
|
|
79
|
+
const tool = byName.get(call.tool);
|
|
80
|
+
if (!tool) {
|
|
81
|
+
findings.push({ id: "SMOKE_TOOL_NOT_FOUND", severity: "error", tool: call.tool, message: "savuajotiedostossa nimetty työkalu ei ole palvelimen listalla" });
|
|
82
|
+
smokeResults.push({ tool: call.tool, ok: false, ms: 0 });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const start = Date.now();
|
|
86
|
+
let ok = true;
|
|
87
|
+
const fail = (id, severity, message) => {
|
|
88
|
+
findings.push({ id, severity, tool: call.tool, message });
|
|
89
|
+
if (severity === "error")
|
|
90
|
+
ok = false;
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
const res = await client.callTool({ name: call.tool, arguments: call.arguments }, undefined, { timeout: opts.timeoutMs });
|
|
94
|
+
const ms = Date.now() - start;
|
|
95
|
+
const isError = res.isError === true;
|
|
96
|
+
if (isError && !call.expectError)
|
|
97
|
+
fail("SMOKE_TOOL_ERROR", "error", `työkalu palautti virheen: ${firstText(res.content) || "(ei tekstiä)"}`);
|
|
98
|
+
else if (!isError && call.expectError)
|
|
99
|
+
fail("SMOKE_EXPECTED_ERROR", "error", "työkalun odotettiin palauttavan virheen, mutta kutsu onnistui");
|
|
100
|
+
if (!isError) {
|
|
101
|
+
const hasContent = Array.isArray(res.content) && res.content.length > 0;
|
|
102
|
+
if (!hasContent && res.structuredContent === undefined)
|
|
103
|
+
fail("SMOKE_EMPTY_RESULT", "warning", "työkalu palautti tyhjän tuloksen");
|
|
104
|
+
}
|
|
105
|
+
if (ms > call.maxMs)
|
|
106
|
+
fail("SMOKE_SLOW", "warning", `vastaus kesti ${ms} ms (raja ${call.maxMs} ms)`);
|
|
107
|
+
smokeResults.push({ tool: call.tool, ok, ms });
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
fail("SMOKE_CALL_FAILED", "error", `kutsu epäonnistui: ${errText(e)}`);
|
|
111
|
+
smokeResults.push({ tool: call.tool, ok: false, ms: Date.now() - start });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return build(opts, server, tools.length, findings, smokeResults);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await client.close().catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/smoke.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { UsageError } from "./types.js";
|
|
2
|
+
export const DEFAULT_MAX_MS = 5000;
|
|
3
|
+
function isRecord(v) {
|
|
4
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Savuajotiedosto: {"calls": [{"tool": "nimi", "arguments": {...}, "expectError": false, "maxMs": 5000}]}
|
|
8
|
+
* (tai pelkkä taulukko). Vain tiedostossa nimetyt työkalut kutsutaan, mitään ei arvata.
|
|
9
|
+
*/
|
|
10
|
+
export function parseSmokeSpec(text) {
|
|
11
|
+
let data;
|
|
12
|
+
try {
|
|
13
|
+
data = JSON.parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new UsageError("Savuajotiedosto ei ole kelvollista JSONia");
|
|
17
|
+
}
|
|
18
|
+
const calls = Array.isArray(data) ? data : isRecord(data) ? data.calls : undefined;
|
|
19
|
+
if (!Array.isArray(calls))
|
|
20
|
+
throw new UsageError('Savuajotiedoston pitää olla taulukko tai {"calls": [...]}');
|
|
21
|
+
const out = [];
|
|
22
|
+
calls.forEach((c, i) => {
|
|
23
|
+
const at = `calls[${i}]`;
|
|
24
|
+
if (!isRecord(c))
|
|
25
|
+
throw new UsageError(`${at}: pitää olla objekti`);
|
|
26
|
+
if (typeof c.tool !== "string" || c.tool === "")
|
|
27
|
+
throw new UsageError(`${at}: tool puuttuu`);
|
|
28
|
+
if (c.arguments !== undefined && !isRecord(c.arguments))
|
|
29
|
+
throw new UsageError(`${at}: arguments pitää olla objekti`);
|
|
30
|
+
if (c.expectError !== undefined && typeof c.expectError !== "boolean")
|
|
31
|
+
throw new UsageError(`${at}: expectError pitää olla true tai false`);
|
|
32
|
+
if (c.maxMs !== undefined && !(typeof c.maxMs === "number" && Number.isFinite(c.maxMs) && c.maxMs > 0)) {
|
|
33
|
+
throw new UsageError(`${at}: maxMs pitää olla positiivinen luku`);
|
|
34
|
+
}
|
|
35
|
+
out.push({
|
|
36
|
+
tool: c.tool,
|
|
37
|
+
arguments: c.arguments ?? {},
|
|
38
|
+
expectError: c.expectError === true,
|
|
39
|
+
maxMs: c.maxMs ?? DEFAULT_MAX_MS,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.js","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AAExD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAEnC,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,UAAU,CAAC,2CAA2C,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,2DAA2D,CAAC,CAAC;IAC7G,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrB,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;QACpE,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE;YAAE,MAAM,IAAI,UAAU,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAC7F,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,GAAG,EAAE,gCAAgC,CAAC,CAAC;QACrH,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,SAAS;YAAE,MAAM,IAAI,UAAU,CAAC,GAAG,EAAE,yCAAyC,CAAC,CAAC;QAC5I,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;YACvG,MAAM,IAAI,UAAU,CAAC,GAAG,EAAE,sCAAsC,CAAC,CAAC;QACpE,CAAC;QACD,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAG,CAAC,CAAC,SAAiD,IAAI,EAAE;YACrE,WAAW,EAAE,CAAC,CAAC,WAAW,KAAK,IAAI;YACnC,KAAK,EAAG,CAAC,CAAC,KAA4B,IAAI,cAAc;SACzD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA4CA,uFAAuF;AACvF,MAAM,OAAO,UAAW,SAAQ,KAAK;CAAG"}
|
package/dist/versio.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const vaatii = createRequire(import.meta.url);
|
|
3
|
+
/** Paketin versio luetaan package.json:sta, ei kovakoodata (sama polku src/- ja dist/-hakemistosta). */
|
|
4
|
+
export const VERSIO = vaatii("../package.json").version;
|
|
5
|
+
//# sourceMappingURL=versio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versio.js","sourceRoot":"","sources":["../src/versio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAE9C,wGAAwG;AACxG,MAAM,CAAC,MAAM,MAAM,GAAY,MAAM,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@tonyweckstrom/mcp-doctor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@tonyweckstrom/mcp-doctor",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Terveystarkastus MCP-palvelimelle: initialize-kättely, työkalujen skeemojen tarkistus ja savuajo, raportti ja exit-koodi CI:lle. / Health check for MCP servers: handshake, tool schema lint and smoke calls, with a CI-friendly exit code.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Tony Weckström",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/tonyweckstrom1987/mcp-doctor.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/tonyweckstrom1987/mcp-doctor#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/tonyweckstrom1987/mcp-doctor/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"health-check",
|
|
19
|
+
"lint",
|
|
20
|
+
"ci",
|
|
21
|
+
"github-action"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": "^22.12.0 || ^24.0.0"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"mcp-doctor": "dist/bin.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"prepare": "npm run build",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"prepublishOnly": "npm run typecheck && npm test"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.30.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^26.6.5",
|
|
45
|
+
"typescript": "^7.0.2",
|
|
46
|
+
"vite": "^8.3.4",
|
|
47
|
+
"vitest": "^5.0.4",
|
|
48
|
+
"zod": "^4.6.6"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|