From 23ecad1e648fe8aed0a371200e4d74a69093d512 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 1/6] Patch vulnerable runtime dependency versions --- package-lock.json | 389 +++++++++++++++++++++++++++++++--------------- 1 file changed, 263 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0621cdb..71f912d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,9 +52,9 @@ "license": "MIT" }, "node_modules/@fastify/ajv-compiler": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", - "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.6.tgz", + "integrity": "sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==", "funding": [ { "type": "github", @@ -69,9 +69,25 @@ "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" + "fast-uri": "^4.0.0" } }, + "node_modules/@fastify/ajv-compiler/node_modules/fast-uri": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.4.tgz", + "integrity": "sha512-dODXrIxlS9JSdgAnhIUKOosKV1oMtU2VtVw87QRaHzyl5jxO290Ii5tEZfCfzfWNHi3jKWwBSdQj0qIyshdZdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/@fastify/cors": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.1.0.tgz", @@ -186,9 +202,9 @@ } }, "node_modules/@fastify/static": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.2.0.tgz", - "integrity": "sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", + "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", "funding": [ { "type": "github", @@ -209,27 +225,6 @@ "glob": "^11.0.0" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -284,6 +279,12 @@ "node": ">=8" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -368,10 +369,22 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -472,14 +485,15 @@ } }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -503,9 +517,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -650,6 +664,23 @@ "node": ">= 8" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -678,9 +709,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -754,9 +785,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -831,19 +862,10 @@ "fast-decode-uri-component": "^1.0.1" } }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "funding": [ { "type": "github", @@ -857,9 +879,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastify": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.4.0.tgz", - "integrity": "sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==", + "version": "5.12.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.4.tgz", + "integrity": "sha512-zATD676fP6tBMsn+jQ7Q5/cO/Qo+149TXH0dUqUKel+1/hcZxwIbnflqtPa9EjlrvpY8eCZcL3M1UYrMc31Y7g==", "funding": [ { "type": "github", @@ -872,17 +894,17 @@ ], "license": "MIT", "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", + "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", "light-my-request": "^6.0.0", - "pino": "^9.0.0", - "process-warning": "^5.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.1.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", @@ -895,6 +917,65 @@ "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", "license": "MIT" }, + "node_modules/fastify/node_modules/fast-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fastify/node_modules/fast-uri": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.4.tgz", + "integrity": "sha512-dODXrIxlS9JSdgAnhIUKOosKV1oMtU2VtVw87QRaHzyl5jxO290Ii5tEZfCfzfWNHi3jKWwBSdQj0qIyshdZdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify/node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -918,9 +999,9 @@ } }, "node_modules/find-my-way": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", - "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.9.0.tgz", + "integrity": "sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -932,9 +1013,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -968,16 +1049,16 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -1052,14 +1133,15 @@ } }, "node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "license": "ISC", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -1127,9 +1209,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1154,6 +1236,19 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1394,20 +1489,41 @@ } }, "node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "license": "ISC", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -1440,6 +1556,12 @@ "node": ">=10" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1518,9 +1640,9 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -1531,40 +1653,40 @@ } }, "node_modules/pino": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", - "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", "license": "MIT", "dependencies": { + "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", + "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" + "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", "license": "MIT", "dependencies": { "split2": "^4.0.0" } }, "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, "node_modules/prettier": { @@ -1584,9 +1706,9 @@ } }, "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", "funding": [ { "type": "github", @@ -1600,10 +1722,13 @@ "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/quick-format-unescaped": { "version": "4.0.4", @@ -1725,9 +1850,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1758,9 +1883,9 @@ "license": "MIT" }, "node_modules/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", "funding": [ { "type": "github", @@ -1774,6 +1899,9 @@ "license": "MIT", "dependencies": { "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" } }, "node_modules/safe-stable-stringify": { @@ -1859,9 +1987,9 @@ } }, "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -2036,14 +2164,23 @@ } }, "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", "license": "MIT", "dependencies": { - "real-require": "^0.2.0" + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" } }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", From 3fd6c479304bf659d4d0d131d31fe126e680e282 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 2/6] Restrict token-bearing Strava requests to the documented service --- src/app.ts | 35 +++++++++++ src/routes/contextRoutes.ts | 61 +++++++++++-------- src/server.ts | 39 +++++-------- tests/routes.test.cjs | 113 ++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 50 deletions(-) create mode 100644 src/app.ts create mode 100644 tests/routes.test.cjs diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..2a9ed2f --- /dev/null +++ b/src/app.ts @@ -0,0 +1,35 @@ +import Fastify from 'fastify'; +import { registerContextRoutes } from './routes/contextRoutes'; + +export async function createServer() { + const server = Fastify({ + logger: { + serializers: { + req(request) { + return { method: request.method, url: request.url?.split('?')[0] }; + }, + }, + redact: ['req.headers.authorization', 'req.body.accessToken'], + }, + bodyLimit: 16 * 1024, + ajv: { customOptions: { removeAdditional: false, coerceTypes: false } }, + }); + server.setErrorHandler((error, request, reply) => { + const code = (error as { statusCode?: number }).statusCode; + const status = code && [400, 413, 415, 429].includes(code) ? code : 500; + request.log.warn({ status }, 'Request rejected'); + const message = + status === 400 + ? 'Invalid request' + : status === 413 + ? 'Request body is too large' + : status === 415 + ? 'Unsupported media type' + : status === 429 + ? 'Too many requests' + : 'Request failed'; + reply.code(status).send({ error: message }); + }); + await registerContextRoutes(server); + return server; +} diff --git a/src/routes/contextRoutes.ts b/src/routes/contextRoutes.ts index 8540e46..9ffce44 100644 --- a/src/routes/contextRoutes.ts +++ b/src/routes/contextRoutes.ts @@ -16,14 +16,6 @@ const mcpMetadata = { export async function registerContextRoutes(server: FastifyInstance) { await server.register(cors, { origin: true }); - // Logging middleware - server.addHook("onRequest", async (request) => { - console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`); - }); - server.addHook("onError", async (request, reply, error) => { - console.error(`[${new Date().toISOString()}] ERROR: ${error.message}`); - }); - /** * MCP Discovery Endpoint * Returns metadata about the MCP server including name, description, version, tags, contact, and content endpoint. @@ -119,21 +111,42 @@ export async function registerContextRoutes(server: FastifyInstance) { return { error: "Model not found" }; }); - /** - * Strava Activities Integration Endpoint - * Fetches Strava athlete activities using provided access token and optional pagination parameters. - */ - server.post("/v1/strava/activities", async (request, reply) => { - const { accessToken, endpoint = "https://www.strava.com/api/v3/athlete/activities", page = 1, per_page = 30 } = request.body as any; - try { - const response = await axios.get(endpoint, { - headers: { Authorization: `Bearer ${accessToken}` }, - params: { page, per_page } - }); - return response.data; - } catch (error: any) { - console.error(`[${new Date().toISOString()}] Strava API ERROR:`, error.message); - reply.code(500).send({ error: error.message }); + // Token-bearing requests can reach only Strava's documented activities endpoint. + server.post<{ Body: { accessToken: string; page?: number; per_page?: number } }>( + '/v1/strava/activities', + { + onRequest: async (_request, reply) => { + reply.header('Cache-Control', 'no-store'); + }, + schema: { + body: { + type: 'object', + additionalProperties: false, + required: ['accessToken'], + properties: { + accessToken: { type: 'string', minLength: 1, maxLength: 4096, pattern: '^\\S+$' }, + page: { type: 'integer', minimum: 1, maximum: 10000, default: 1 }, + per_page: { type: 'integer', minimum: 1, maximum: 200, default: 30 }, + }, + }, + }, + }, + async (request, reply) => { + const { accessToken, page = 1, per_page = 30 } = request.body; + try { + const response = await axios.get('https://www.strava.com/api/v3/athlete/activities', { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { page, per_page }, + timeout: 10000, + maxRedirects: 0, + maxContentLength: 2 * 1024 * 1024, + }); + return response.data; + } catch (error: unknown) { + const status = axios.isAxiosError(error) ? error.response?.status : undefined; + request.log.error({ status }, 'Strava request failed'); + return reply.code(502).send({ error: 'Unable to retrieve Strava activities' }); + } } - }); + ); } diff --git a/src/server.ts b/src/server.ts index 733fcf4..e6e114d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,29 +1,16 @@ -import Fastify from "fastify"; -import dotenv from "dotenv"; -import { registerContextRoutes } from "./routes/contextRoutes"; -import fastifyStatic from "@fastify/static"; -import path from "path"; +import 'dotenv/config'; +import { createServer } from './app'; -// Load environment variables -dotenv.config(); - -const PORT = Number(process.env.APP_PORT) || 3000; -const server = Fastify({ logger: true }); -const appVersion = process.env.APP_VERSION || "1.0.0"; - -// Register context routes -registerContextRoutes(server); - -server.register(fastifyStatic, { - root: path.join(__dirname, "../public"), - prefix: "/", // optional: serve from root -}); - -// Start server -server.listen({ port: PORT }, (err, address) => { - if (err) { - server.log.error(err); - process.exit(1); +async function start() { + const port = Number(process.env.APP_PORT || 3000); + if (!Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error('APP_PORT must be an integer between 1 and 65535'); } - console.log(`🚀 MCP Server (v${appVersion}) ready at ${address}`); + const server = await createServer(); + await server.listen({ port, host: process.env.APP_HOST || '127.0.0.1' }); +} + +start().catch(() => { + console.error('Unable to start metadata API; check configuration and port availability.'); + process.exitCode = 1; }); diff --git a/tests/routes.test.cjs b/tests/routes.test.cjs new file mode 100644 index 0000000..a99b0cf --- /dev/null +++ b/tests/routes.test.cjs @@ -0,0 +1,113 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const Fastify = require('fastify'); +const axios = require('axios'); +const { registerContextRoutes } = require('../dist/routes/contextRoutes.js'); + +async function app(t) { + const server = Fastify({ + logger: false, + ajv: { customOptions: { removeAdditional: false, coerceTypes: false } }, + }); + t.after(() => server.close()); + await registerContextRoutes(server); + return server; +} + +test('arbitrary destinations and malformed bodies never make outbound requests', async (t) => { + const server = await app(t); + let calls = 0; + t.mock.method(axios, 'get', async () => { + calls++; + return { data: [] }; + }); + for (const body of [ + {}, + { accessToken: '' }, + { accessToken: 42 }, + { accessToken: 'bad\r\ntoken' }, + { accessToken: 'fixture', endpoint: 'http://127.0.0.1/internal' }, + { accessToken: 'fixture', page: 0 }, + { accessToken: 'fixture', page: 1.5 }, + { accessToken: 'fixture', page: '2' }, + { accessToken: 'fixture', per_page: 201 }, + { accessToken: 'fixture', per_page: -1 }, + ]) { + const response = await server.inject({ + method: 'POST', + url: '/v1/strava/activities', + payload: body, + }); + assert.equal(response.statusCode, 400, JSON.stringify(body)); + } + assert.equal(calls, 0); +}); + +test('valid Strava requests use only its fixed endpoint with bounded options', async (t) => { + const server = await app(t); + const received = []; + t.mock.method(axios, 'get', async (url, options) => { + received.push({ url, options }); + return { data: [{ id: 7, name: 'Synthetic activity' }] }; + }); + for (const pagination of [{}, { page: 2, per_page: 200 }]) { + const response = await server.inject({ + method: 'POST', + url: '/v1/strava/activities', + payload: { accessToken: 'fixture-token', ...pagination }, + }); + assert.equal(response.statusCode, 200); + assert.equal(response.headers['cache-control'], 'no-store'); + assert.equal(response.json()[0].id, 7); + } + for (const { url, options } of received) { + assert.equal(url, 'https://www.strava.com/api/v3/athlete/activities'); + assert.equal(options.headers.Authorization, 'Bearer fixture-token'); + assert.equal(options.maxRedirects, 0); + assert.equal(options.timeout, 10000); + assert.ok(options.maxContentLength > 0); + } + assert.deepEqual(received[0].options.params, { page: 1, per_page: 30 }); + assert.deepEqual(received[1].options.params, { page: 2, per_page: 200 }); +}); + +test('upstream failures return generic errors without private details', async (t) => { + const server = await app(t); + t.mock.method(axios, 'get', async () => { + throw new Error('private-token-marker and private-activity-marker'); + }); + const response = await server.inject({ + method: 'POST', + url: '/v1/strava/activities', + payload: { accessToken: 'fixture' }, + }); + assert.equal(response.statusCode, 502); + assert.deepEqual(response.json(), { error: 'Unable to retrieve Strava activities' }); + assert.equal(response.headers['cache-control'], 'no-store'); +}); + +test('the configured server rejects malformed JSON and oversized bodies', async (t) => { + const { createServer } = require('../dist/app.js'); + const server = await createServer(); + t.after(() => server.close()); + for (const [payload, statusCode] of [ + ['{"accessToken":"private-token-marker",', 400], + [JSON.stringify({ accessToken: 'a'.repeat(17000) }), 413], + ]) { + const response = await server.inject({ + method: 'POST', + url: '/v1/strava/activities', + headers: { 'content-type': 'application/json' }, + payload, + }); + assert.equal(response.statusCode, statusCode); + assert.ok(!response.body.includes('private-token-marker')); + assert.ok(!response.body.includes('stack')); + } + const response = await server.inject({ + method: 'POST', + url: '/v1/strava/activities', + payload: { accessToken: 'fixture', endpoint: 'https://example.invalid/' }, + }); + assert.equal(response.statusCode, 400); +}); From 4b0bec913fb293134825266155a9221b712d4c71 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 3/6] Keep metadata discovery consistent with configured examples --- .env.dist | 7 +- public/.well-known/v1.json | 14 ---- src/routes/contextRoutes.ts | 134 +++++++++++++++++++++--------------- tests/routes.test.cjs | 53 ++++++++++++++ 4 files changed, 135 insertions(+), 73 deletions(-) delete mode 100644 public/.well-known/v1.json diff --git a/.env.dist b/.env.dist index eb805fd..5244b97 100644 --- a/.env.dist +++ b/.env.dist @@ -1,13 +1,12 @@ # Port the server should listen on (default is 3000 if not specified) APP_PORT=3000 +# Loopback by default; use a trusted gateway before exposing this prototype. +APP_HOST=127.0.0.1 # Optional metadata values -APP_VERSION=1.0.0 +APP_VERSION=1.1.0 MCP_NAME="Data Science Services by Pierre-Henry Soria" MCP_DESCRIPTION="Freelance data scientist providing machine learning models, statistical analysis, and data-driven services." MCP_TAGS="data-science, machine-learning, statistical-analysis, data-visualization, freelance, MCP, AI, model-development, statistical-models" MCP_CONTACT_EMAIL="hi@ph7.me" MCP_CONTACT_WEBSITE="https://ph7.me" - -# Base URL used to construct full links for metadata (e.g., https://api.retainr.io) -BASE_URL=http://localhost:3000 diff --git a/public/.well-known/v1.json b/public/.well-known/v1.json deleted file mode 100644 index 4f3ce42..0000000 --- a/public/.well-known/v1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@context": { - "name": "https://schema.org/name", - "description": "https://schema.org/description", - "version": "https://schema.org/version", - "tags": "https://schema.org/keywords", - "contact": { - "email": "https://schema.org/email", - "website": "https://schema.org/url" - }, - "content_endpoint": "https://schema.org/url", - "metadata": "https://schema.org/additionalProperty" - } -} diff --git a/src/routes/contextRoutes.ts b/src/routes/contextRoutes.ts index 9ffce44..c3db61f 100644 --- a/src/routes/contextRoutes.ts +++ b/src/routes/contextRoutes.ts @@ -1,51 +1,68 @@ -import { FastifyInstance } from "fastify"; -import cors from "@fastify/cors"; -import axios from "axios"; - -const mcpMetadata = { - name: process.env.MCP_NAME || "MCP Service", - description: process.env.MCP_DESCRIPTION || "MCP Server providing model context.", - tags: (process.env.MCP_TAGS || "MCP").split(","), - contact: { - email: process.env.MCP_CONTACT_EMAIL || "hi@ph7.me", - website: process.env.MCP_CONTACT_WEBSITE || "https://ph7.me", - }, - version: process.env.APP_VERSION || "0.1.0", -}; +import { FastifyInstance } from 'fastify'; +import cors from '@fastify/cors'; +import axios from 'axios'; export async function registerContextRoutes(server: FastifyInstance) { + const mcpMetadata = { + name: process.env.MCP_NAME || 'ML Metadata Service', + description: + process.env.MCP_DESCRIPTION || 'REST prototype exposing sample ML service metadata.', + tags: [ + ...new Set( + (process.env.MCP_TAGS || 'ML') + .split(',') + .map((tag) => tag.trim()) + .filter(Boolean) + ), + ], + contact: { + email: process.env.MCP_CONTACT_EMAIL || 'hi@ph7.me', + website: process.env.MCP_CONTACT_WEBSITE || 'https://ph7.me', + }, + version: process.env.APP_VERSION || '1.1.0', + }; + await server.register(cors, { origin: true }); /** - * MCP Discovery Endpoint - * Returns metadata about the MCP server including name, description, version, tags, contact, and content endpoint. + * REST discovery metadata (not a Model Context Protocol transport). + * Returns metadata about this service including name, description, version, tags, contact, and content endpoint. * The @context points to the public .well-known/v1.json JSON-LD context served by this server. */ - server.get("/.well-known/model-context", async () => { + const discovery = async () => { return { - "@context": "/.well-known/v1.json", // Public JSON-LD context reference served by this server + '@context': '/.well-known/v1.json', // Public JSON-LD context reference served by this server name: mcpMetadata.name, description: mcpMetadata.description, version: mcpMetadata.version, tags: mcpMetadata.tags, contact: mcpMetadata.contact, - content_endpoint: "/v1/content", + content_endpoint: '/v1/content', }; - }); + }; + server.get('/.well-known/model-context', discovery); + server.get('/v1/discovery', discovery); /** * JSON-LD Context Endpoint * Serves the JSON-LD context document defining terms used in the MCP metadata, * mapping them to schema.org and other vocabularies. */ - server.get("/.well-known/v1.json", async () => { + server.get('/.well-known/v1.json', async () => { return { - "@context": { - name: "https://schema.org/name", - description: "https://schema.org/description", - version: "https://schema.org/version", - content_endpoint: "https://schema.org/url", - metadata: "https://schema.org/CreativeWork", + '@context': { + name: 'https://schema.org/name', + description: 'https://schema.org/description', + version: 'https://schema.org/version', + content_endpoint: 'https://schema.org/url', + tags: 'https://schema.org/keywords', + contact: { + '@id': 'https://schema.org/contactPoint', + '@context': { + email: 'https://schema.org/email', + website: { '@id': 'https://schema.org/url', '@type': '@id' }, + }, + }, }, }; }); @@ -54,31 +71,30 @@ export async function registerContextRoutes(server: FastifyInstance) { * Available Models Content Endpoint * Returns a list of available model contents with titles, descriptions, and tags. */ - server.get("/v1/content", async () => { + server.get('/v1/content', async () => { return [ { - title: "Customer Churn Prediction", + title: 'Customer Churn Prediction', content: - "Predictive model using logistic regression and XGBoost to identify potential customer churn.", - tags: ["ML", "churn", "classification", "xgboost"], + 'Predictive model using logistic regression and XGBoost to identify potential customer churn.', + tags: ['ML', 'churn', 'classification', 'xgboost'], }, { - title: "Exploratory Data Analysis Report", + title: 'Exploratory Data Analysis Report', content: - "Notebook-driven visualisation and summary statistics to uncover key trends and anomalies.", - tags: ["EDA", "data analysis", "notebooks"], + 'Notebook-driven visualisation and summary statistics to uncover key trends and anomalies.', + tags: ['EDA', 'data analysis', 'notebooks'], }, { - title: "Statistical Hypothesis Testing", + title: 'Statistical Hypothesis Testing', content: - "Support for t-tests, chi-square, ANOVA, and p-value interpretation for decision making.", - tags: ["statistics", "hypothesis testing", "inference"], + 'Support for t-tests, chi-square, ANOVA, and p-value interpretation for decision making.', + tags: ['statistics', 'hypothesis testing', 'inference'], }, { - title: "Time Series Forecasting", - content: - "Models using ARIMA and Prophet to forecast trends in business KPIs.", - tags: ["forecasting", "time series", "prophet"], + title: 'Time Series Forecasting', + content: 'Models using ARIMA and Prophet to forecast trends in business KPIs.', + tags: ['forecasting', 'time series', 'prophet'], }, ]; }); @@ -87,28 +103,36 @@ export async function registerContextRoutes(server: FastifyInstance) { * Model Metadata Endpoint * Returns metadata for a specific model identified by modelId. */ - server.get("/v1/model/:modelId", async (request) => { + server.get('/v1/model/:modelId', async (request, reply) => { const { modelId } = request.params as { modelId: string }; - if (modelId === "churn") { + if (modelId === 'churn') { return { - id: "churn", - title: "Customer Churn Prediction", - input: { type: "json", example: { customerId: "123", features: [/* ... */] } }, - output: { type: "json", example: { churnProbability: 0.87 } }, - usage: "POST /v1/predict/churn", + id: 'churn', + title: 'Customer Churn Prediction', + input: { + type: 'json', + example: { + customerId: '123', + features: [ + /* ... */ + ], + }, + }, + output: { type: 'json', example: { churnProbability: 0.87 } }, + example: true, }; } - if (modelId === "eda") { + if (modelId === 'eda') { return { - id: "eda", - title: "Exploratory Data Analysis Report", - input: { type: "csv", example: "data.csv" }, - output: { type: "json", example: { summary: "..." } }, - usage: "GET /v1/content/eda", + id: 'eda', + title: 'Exploratory Data Analysis Report', + input: { type: 'csv', example: 'data.csv' }, + output: { type: 'json', example: { summary: '...' } }, + example: true, }; } // ...other models... - return { error: "Model not found" }; + return reply.code(404).send({ error: 'Model not found' }); }); // Token-bearing requests can reach only Strava's documented activities endpoint. diff --git a/tests/routes.test.cjs b/tests/routes.test.cjs index a99b0cf..7dce0a1 100644 --- a/tests/routes.test.cjs +++ b/tests/routes.test.cjs @@ -14,6 +14,59 @@ async function app(t) { return server; } +test('metadata reads configured values at registration time', async (t) => { + const before = { + name: process.env.MCP_NAME, + tags: process.env.MCP_TAGS, + version: process.env.APP_VERSION, + }; + t.after(() => { + for (const [key, value] of Object.entries({ + MCP_NAME: before.name, + MCP_TAGS: before.tags, + APP_VERSION: before.version, + })) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + }); + Object.assign(process.env, { + MCP_NAME: 'Fixture service', + MCP_TAGS: ' ML, statistics, , ML ', + APP_VERSION: '9.8.7', + }); + const server = await app(t); + const response = await server.inject('/.well-known/model-context'); + assert.equal(response.statusCode, 200); + assert.equal(response.json().name, 'Fixture service'); + assert.equal(response.json().version, '9.8.7'); + assert.deepEqual(response.json().tags, ['ML', 'statistics']); +}); + +test('documented discovery resolves the same metadata and context', async (t) => { + const server = await app(t); + const metadata = await server.inject('/.well-known/model-context'); + const discovery = await server.inject('/v1/discovery'); + assert.equal(discovery.statusCode, 200); + assert.deepEqual(discovery.json(), metadata.json()); + const context = (await server.inject(metadata.json()['@context'])).json()['@context']; + for (const field of ['name', 'description', 'version', 'tags', 'contact', 'content_endpoint']) + assert.ok(context[field], field); +}); + +test('models identify examples and return 404 for unknown IDs', async (t) => { + const server = await app(t); + const content = await server.inject('/v1/content'); + assert.equal(content.json().length, 4); + for (const id of ['churn', 'eda']) { + const response = await server.inject(`/v1/model/${id}`); + assert.equal(response.statusCode, 200); + assert.equal(response.json().example, true); + assert.equal(response.json().usage, undefined); + } + assert.equal((await server.inject('/v1/model/missing')).statusCode, 404); +}); + test('arbitrary destinations and malformed bodies never make outbound requests', async (t) => { const server = await app(t); let calls = 0; From 6ce615c74095f432277e4da85dccdf9359c34966 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 4/6] Define a reproducible Node 22 validation workflow --- .prettierignore | 5 + package-lock.json | 665 +--------------------------------------------- package.json | 17 +- 3 files changed, 26 insertions(+), 661 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1cd516f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +# Preserve the verbatim license and historical social draft. +LICENSE.md +threads.txt diff --git a/package-lock.json b/package-lock.json index 71f912d..e5547a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@fastify/cors": "^11.1.0", - "@fastify/static": "^8.2.0", "axios": "^1.11.0", "dotenv": "^17.2.1", "fastify": "^5.4.0" @@ -20,6 +19,9 @@ "prettier": "^3.6.2", "ts-node-dev": "^2.0.0", "typescript": "^5.1.6" + }, + "engines": { + "node": ">=22" } }, "node_modules/@cspotcode/source-map-support": { @@ -35,22 +37,6 @@ "node": ">=12" } }, - "node_modules/@fastify/accept-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", - "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/@fastify/ajv-compiler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.6.tgz", @@ -178,70 +164,6 @@ "ipaddr.js": "^2.1.0" } }, - "node_modules/@fastify/send": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", - "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@lukeed/ms": "^2.0.2", - "escape-html": "~1.0.3", - "fast-decode-uri-component": "^1.0.1", - "http-errors": "^2.0.0", - "mime": "^3" - } - }, - "node_modules/@fastify/static": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", - "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "@fastify/send": "^4.0.0", - "content-disposition": "^0.5.4", - "fastify-plugin": "^5.0.0", - "fastq": "^1.17.1", - "glob": "^11.0.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -270,15 +192,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -414,30 +327,6 @@ } } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -585,24 +474,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -622,18 +493,6 @@ "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -650,20 +509,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -690,15 +535,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -754,18 +590,6 @@ "xtend": "^4.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -811,12 +635,6 @@ "node": ">= 0.4" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -1032,22 +850,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -1132,30 +934,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1220,22 +998,6 @@ "node": ">= 0.4" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1265,6 +1027,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ipaddr.js": { @@ -1315,15 +1078,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1347,27 +1101,6 @@ "node": ">=0.12.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/json-schema-ref-resolver": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", @@ -1430,15 +1163,6 @@ ], "license": "MIT" }, - "node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1455,18 +1179,6 @@ "node": ">= 0.4" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1489,39 +1201,16 @@ } }, "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "license": "MIT", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "balanced-match": "^4.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "20 || >=22" + "node": "*" } }, "node_modules/minimist": { @@ -1534,15 +1223,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1591,12 +1271,6 @@ "wrappy": "1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1607,15 +1281,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1623,22 +1288,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", @@ -1849,39 +1498,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-regex2": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", @@ -1947,45 +1563,6 @@ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -2025,111 +1602,6 @@ "node": ">= 10.x" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -2203,15 +1675,6 @@ "node": ">=12" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -2342,112 +1805,6 @@ "dev": true, "license": "MIT" }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 501190c..064afc4 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,27 @@ { "name": "mcp-server", "version": "1.1.0", - "description": "Model Context Provider (MCP) server for data science services", - "main": "src/server.ts", + "description": "REST prototype serving sample ML service metadata and a bounded Strava integration", + "main": "dist/server.js", "scripts": { "dev": "ts-node-dev --respawn src/server.ts", "build": "tsc", - "start": "npm run build && node dist/server.js", + "start": "node dist/server.js", "prod": "npm run build && node dist/server.js", "prettier:check": "prettier --check .", - "prettier:fix": "prettier --write ." + "prettier:fix": "prettier --write .", + "test": "npm run build && node --test tests/*.cjs" }, "author": { "name": "Pierre-Henry Soria", "email": "contact@ph7.me", "url": "https://x.com/phenrysay" }, - "homepage": "https://github.com/OpenData4Sciece/MCP-Server-API#readme", + "homepage": "https://github.com/OpenData4Science/MCP-Server-API#readme", "license": "MIT", "repository": { "type": "git", - "url": "git@github.com:OpenData4Sciece/MCP-Server-API.git" + "url": "git@github.com:OpenData4Science/MCP-Server-API.git" }, "keywords": [ "MCP", @@ -32,7 +33,6 @@ ], "dependencies": { "@fastify/cors": "^11.1.0", - "@fastify/static": "^8.2.0", "axios": "^1.11.0", "dotenv": "^17.2.1", "fastify": "^5.4.0" @@ -42,5 +42,8 @@ "prettier": "^3.6.2", "ts-node-dev": "^2.0.0", "typescript": "^5.1.6" + }, + "engines": { + "node": ">=22" } } From 1840d9dfee1aaeee338aa5f932a53e7fffb7bcde Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 5/6] Ignore environment variants to prevent credential commits --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d891793..2aea670 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ node_modules/ dist/ .env +.env.* +!.env.dist *.log # TypeScript From b0bb251bc90fb8eb9991e377f7a906cd3be6ec08 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Sun, 13 Sep 2026 00:25:30 +1000 Subject: [PATCH 6/6] Describe the REST prototype without unsupported MCP claims --- CITATION.cff | 12 +++--- README.md | 109 ++++++++++++++++++++++++--------------------------- threads.txt | 4 ++ 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index bf7f7ee..efb9bb8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,11 +1,11 @@ cff-version: 1.2.0 -message: "If you use this project, please cite it as below." -title: "MCP Server" +message: 'If you use this project, please cite it as below.' +title: 'MCP Server' authors: - family-names: Soria given-names: Pierre-Henry - orcid: "https://orcid.org/0000-0002-5641-1355" + orcid: 'https://orcid.org/0000-0002-5641-1355' date-released: 2025-07-26 -url: "https://github.com/pH-7/mcp-server" -repository-code: "https://github.com/pH-7/mcp-server" -license: MIT \ No newline at end of file +url: 'https://github.com/OpenData4Science/MCP-Server-API' +repository-code: 'https://github.com/OpenData4Science/MCP-Server-API' +license: MIT diff --git a/README.md b/README.md index 25c44b3..085a249 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,86 @@ -# My Personal Machine-Consumable Protocol (MCP) Compliant API for ML Services +# ML Service Metadata API -MCP Server is a structured, standards-compliant API for serving metadata, service discovery endpoints, and machine learning content. It's designed to help AI and data science applications discover model context, training artefacts, research notebooks, and statistical methods efficiently. +A small Fastify and TypeScript **REST prototype** for exploring machine-readable ML service descriptions. The repository keeps its historical `MCP-Server-API` name. -This server follows the [MCP specification](https://www.modelcontext.org/spec) and is production-ready, built with clean TypeScript code and a modular design. +**This is not a Model Context Protocol implementation.** The [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic) requires JSON-RPC messages and lifecycle negotiation; this project exposes ordinary HTTP routes with an experimental JSON-LD context. It does not support MCP client connections, inference, model training or automatic discovery by AI services. -[![Build Your Own MCP Server. Walkthrough, Step-by-Step Guide!](https://i1.ytimg.com/vi/DEaSz8kVPH8/sddefault.jpg)](https://youtu.be/DEaSz8kVPH8 "How to Use MCP Servers (Full Workflow)") +## Run locally -📺 [Click here to watch on YouTube](https://youtu.be/DEaSz8kVPH8) - -## Project Features 🚀 - -* `.well-known/model-context` for structured model-level context resolution -* `.well-known/v1.json` to define a public semantic schema using JSON-LD – this is your `@context` document, allowing linked data vocab reuse -* Service discovery endpoint at `/v1/discovery` -* `/v1/content` endpoint to list available ML resources and data science artefacts -* Clean Fastify setup using plugin architecture and async route loading -* `.env` support for flexible configuration, including versioning and metadata - - -## Get Started - -1. `npm install` to install all dependencies. -2. `npm run dev` to start the server in development mode with auto-reload (`ts-node-dev`). -3. Copy `.env.dist` to `.env` and configure the variables to fit your environment. +Requires Node.js 22 or newer. +```sh +npm ci +cp .env.dist .env +npm run build +npm start +``` -### Other Commands +The server binds to `127.0.0.1:3000` by default. `npm run dev` reloads TypeScript during development; `npm run prod` builds and starts the same server. `npm start` uses the existing compiled output. -* `npm run start` – start the compiled server in normal mode (no hot reload) -* `npm run prod` – full production build and run (builds and launches) -* `npm run build` – compile TypeScript source code into `dist/` JavaScript +```sh +curl http://127.0.0.1:3000/v1/discovery +curl http://127.0.0.1:3000/v1/content +``` +## Implemented routes -### Utility +| Route | Behaviour | +| -------------------------------- | ------------------------------------------------------------------------ | +| `GET /.well-known/model-context` | Configured service name, description, version, tags and contact | +| `GET /v1/discovery` | Alias for the same metadata | +| `GET /.well-known/v1.json` | The single experimental JSON-LD context definition | +| `GET /v1/content` | Four static example service descriptions; no datasets or model artefacts | +| `GET /v1/model/churn` or `/eda` | Illustrative input/output descriptions with `example: true` | +| `GET /v1/model/:unknown` | 404 | +| `POST /v1/strava/activities` | Retrieves the caller's Strava activities using the supplied access token | -* `npm run prettier:check` – check formatting issues using Prettier -* `npm run prettier:fix` – auto-format source files +The model examples do not link to nonexistent prediction or notebook endpoints. Replace examples with verified resources before presenting them as delivered services. +## Strava request contract -## Environment Variables +The body is JSON: `accessToken` is a nonempty string without whitespace; optional integer `page` is 1–10,000 (default 1), and `per_page` is 1–200 (default 30). Unknown fields, custom destinations and malformed values return 400. The whole request body is limited to 16 KB; tokens are limited to 4,096 characters. -The `.env` file supports the following values: +The destination is fixed to [Strava's athlete activities endpoint](https://developers.strava.com/docs/reference/#api-Activities-getLoggedInAthleteActivities). Requests have a 10-second timeout, a 2 MB response limit and no redirects. Success returns the upstream activity JSON; upstream failures return a generic 502. Responses use `Cache-Control: no-store`. -```env -APP_PORT=3000 -APP_VERSION=1.0.0 -MCP_VERSION=1.0.0 -MCP_NAME="Data Science Services by Pierre" -MCP_DESCRIPTION="Freelance data scientist providing machine learning models, statistical analysis, and data-driven consulting." -MCP_TAGS="data science,machine learning,statistics,freelance,MCP" -MCP_CONTACT_EMAIL=hi@ph7.me -MCP_CONTACT_WEBSITE=https://ph7.me -``` +Tokens and activity data are sensitive. Use only your own authorised token over localhost or a trusted HTTPS deployment. The proxy forwards it only to Strava; it does not obtain, refresh, store or grant scopes to tokens. Tests mock every upstream call and do not access a real athlete's account. -## Follow My MCP Journey +## Configuration -I regularly share updates, deep dives, and tutorials on **building MCP servers** and related AI infrastructure on social media: +Copy `.env.dist` before starting. Metadata is read after environment loading, when routes are registered. -* Bluesky: [https://bsky.app/profile/ph7s.bsky.social](https://bsky.app/profile/ph7s.bsky.social) -* X: [https://x.com/phenrysay](https://x.com/phenrysay) +- `APP_PORT`: integer 1–65,535; defaults to 3000. +- `APP_HOST`: defaults to `127.0.0.1`. Public exposure requires separate deployment controls. +- `APP_VERSION`: metadata version; defaults to 1.1.0. +- `MCP_NAME`, `MCP_DESCRIPTION`, `MCP_TAGS`: display metadata; tags are trimmed and deduplicated. +- `MCP_CONTACT_EMAIL`, `MCP_CONTACT_WEBSITE`: optional public contact values. -Feel free to follow along, ask questions, and engage! +The `MCP_*` names are retained for compatibility and do not imply protocol support. Previously documented `MCP_VERSION` and `BASE_URL` were unused and are not supported. -## What is an MCP Server? +## Validation and boundaries -A **Model Context Protocol (MCP) server** is not just another REST API. Rather than simply exposing data, it provides **machine-readable context and metadata** about your ML services or models—using linked data formats like JSON-LD. +```sh +npm test +npm run prettier:check +npm audit +``` -Its core purpose is to expose structured discovery endpoints—typically under `.well-known/`—which allow AI agents and tooling ecosystems to understand your capabilities without needing human interpretation. +Tests use Fastify injection with synthetic data for metadata, discovery, model examples, input validation and outbound-request restrictions. The build checks TypeScript; no AI model, database or external account is required. -## How is MCP different from a normal API? +This is not a production service: it has no per-user authentication, application-level rate limit, OAuth flow, persistent storage or deployment automation. CORS is permissive for local experimentation. Before public hosting, define the audience, add access and abuse controls, and verify real Strava scopes and quotas. Restricting the upstream URL is not a substitute for those controls. -* **Purpose:** Normal APIs deliver functional data. MCP servers deliver semantic metadata. -* **Endpoints:** MCP uses `.well-known/model-context` and `v1.json` for semantic discovery. -* **Format:** All responses are structured as JSON-LD (not plain JSON), enabling interoperability and reuse across systems. -* **Consumers:** Designed for machines, not people—MCP endpoints target **AI agents, search engines, or distributed tools**. +## Historical material -![Pierre-Henry Soria, author of the MCP Server project](assets/images/machine-consumable-protocol-mcp-compliant-api-for-ml-services.png) +[Original walkthrough](https://youtu.be/DEaSz8kVPH8) and [original illustration](assets/images/machine-consumable-protocol-mcp-compliant-api-for-ml-services.png) are preserved as project history. Their protocol/compliance claims are superseded by the implementation description above. `threads.txt` is a historical draft, not evidence of AI-platform indexing, deployment or delivered ML models. +A future real MCP implementation should be a separately specified change using the official protocol and SDK, with lifecycle and client interoperability tests. ## The Baker -[![Pierre-Henry Soria](https://avatars0.githubusercontent.com/u/1325411?s=200)](https://ph7.me "Pierre-Henry Soria, Software Developer") +[![Pierre-Henry Soria](https://avatars0.githubusercontent.com/u/1325411?s=200)](https://ph7.me 'Pierre-Henry Soria, Software Developer') Made with ❤️ by **[Pierre-Henry Soria](https://pierrehenry.be)**. A super passionate & enthusiastic Problem-Solver / Senior Software Engineer. Also a true cheese 🧀, ristretto ☕️, and dark chocolate lover! 😋 -[![@phenrysay](https://img.shields.io/badge/x-000000?style=for-the-badge&logo=x)](https://x.com/phenrysay "Follow Me on X") [![pH-7](https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/pH-7 "My GitHub") [![YouTube Video](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://youtube.com/@pH7Programming "YouTube SucceedAI Video") [![BlueSky](https://img.shields.io/badge/BlueSky-00A8E8?style=for-the-badge&logo=bluesky&logoColor=white)](https://bsky.app/profile/ph7s.bsky.social "Follow Me on BlueSky") - +[![@phenrysay](https://img.shields.io/badge/x-000000?style=for-the-badge&logo=x)](https://x.com/phenrysay 'Follow Me on X') [![pH-7](https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/pH-7 'My GitHub') [![YouTube Video](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://youtube.com/@pH7Programming 'Pierre-Henry Soria programming channel') [![BlueSky](https://img.shields.io/badge/BlueSky-00A8E8?style=for-the-badge&logo=bluesky&logoColor=white)](https://bsky.app/profile/ph7s.bsky.social 'Follow Me on BlueSky') ## License diff --git a/threads.txt b/threads.txt index 9fc97e2..5ae6405 100644 --- a/threads.txt +++ b/threads.txt @@ -1,3 +1,7 @@ +HISTORICAL DRAFT — NOT A CURRENT PRODUCT CLAIM + +The REST prototype does not implement Model Context Protocol or establish indexing by AI platforms. The original draft below is preserved for provenance and must be fact-checked before publication. + 📊 Just launched a public MCP server for my freelance data science work using Fastify + TypeScript. Now AI agents and clients can auto-discover my services. Here's why it matters 🧵