diff --git a/lib/client/package.json b/lib/client/package.json index b8b97495..e2e20034 100644 --- a/lib/client/package.json +++ b/lib/client/package.json @@ -1,6 +1,6 @@ { "name": "@openctx/client", - "version": "0.0.32", + "version": "1.0.99", "description": "OpenCtx client library", "license": "Apache-2.0", "repository": { diff --git a/lib/client/src/index.ts b/lib/client/src/index.ts index df102a16..87eda90b 100644 --- a/lib/client/src/index.ts +++ b/lib/client/src/index.ts @@ -1,7 +1,7 @@ export type * from '@openctx/protocol' export type { Provider } from '@openctx/provider' export type * from '@openctx/schema' -export { observeItems, type Annotation, type EachWithProviderUri } from './api.js' +export { type Annotation, type EachWithProviderUri } from './api.js' export { createClient, type AuthInfo, diff --git a/package.json b/package.json index 008f3d46..b72d0895 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ "ts-node": "^10.9.2", "typescript": "^5.4.5", "vite": "^5.2.11", + "json-schema-to-zod": "^0.1.5", + "@apidevtools/json-schema-ref-parser": "^11.7.3", "vitest": "^1.6.0" }, "stylelint": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a874611..0044e9ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ importers: .: devDependencies: + '@apidevtools/json-schema-ref-parser': + specifier: ^11.7.3 + version: 11.7.3 '@biomejs/biome': specifier: 1.8.3 version: 1.8.3 @@ -49,6 +52,9 @@ importers: js-yaml: specifier: ^4.1.0 version: 4.1.0 + json-schema-to-zod: + specifier: ^0.1.5 + version: 0.1.5 semver: specifier: ^7.5.4 version: 7.5.4 @@ -649,6 +655,33 @@ importers: specifier: workspace:* version: link:../../lib/provider + provider/modelcontextprotocoltools: + dependencies: + '@apidevtools/json-schema-ref-parser': + specifier: ^11.7.3 + version: 11.7.3 + '@modelcontextprotocol/sdk': + specifier: 1.0.1 + version: 1.0.1 + '@openctx/provider': + specifier: workspace:* + version: link:../../lib/provider + ajv: + specifier: ^8.17.1 + version: 8.17.1 + express: + specifier: ^4.21.1 + version: 4.21.2 + json-schema-to-zod: + specifier: ^0.1.5 + version: 0.1.5 + zod: + specifier: ^3.24.1 + version: 3.24.1 + zod-to-json-schema: + specifier: ^3.24.1 + version: 3.24.1(zod@3.24.1) + provider/notion: dependencies: '@notionhq/client': @@ -901,6 +934,22 @@ packages: - encoding dev: false + /@apidevtools/json-schema-ref-parser@11.7.3: + resolution: {integrity: sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==} + engines: {node: '>= 16'} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + + /@apidevtools/json-schema-ref-parser@9.0.9: + resolution: {integrity: sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.14 + call-me-maybe: 1.0.2 + js-yaml: 4.1.0 + /@aw-web-design/x-default-browser@1.4.126: resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} hasBin: true @@ -3817,7 +3866,6 @@ packages: /@jsdevtools/ono@7.1.3: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - dev: true /@juggle/resize-observer@3.4.0: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} @@ -4152,6 +4200,14 @@ packages: zod: 3.23.8 dev: false + /@modelcontextprotocol/sdk@1.0.1: + resolution: {integrity: sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==} + dependencies: + content-type: 1.0.5 + raw-body: 3.0.0 + zod: 3.24.1 + dev: false + /@ndelangen/get-tarball@3.0.9: resolution: {integrity: sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==} dependencies: @@ -5302,7 +5358,7 @@ packages: '@types/promise.allsettled': 1.0.6 '@types/tsscmp': 1.0.2 axios: 1.7.2 - express: 4.18.2 + express: 4.21.2 path-to-regexp: 6.2.1 please-upgrade-node: 3.2.0 promise.allsettled: 1.0.7 @@ -5655,7 +5711,7 @@ packages: ejs: 3.1.9 esbuild: 0.18.20 esbuild-plugin-alias: 0.2.1 - express: 4.18.2 + express: 4.21.2 find-cache-dir: 3.3.2 fs-extra: 11.1.1 process: 0.11.10 @@ -5691,7 +5747,7 @@ packages: '@types/find-cache-dir': 3.2.1 browser-assert: 1.2.1 es-module-lexer: 0.9.3 - express: 4.18.2 + express: 4.21.2 find-cache-dir: 3.3.2 fs-extra: 11.1.1 magic-string: 0.30.1 @@ -5730,7 +5786,7 @@ packages: '@types/find-cache-dir': 3.2.1 browser-assert: 1.2.1 es-module-lexer: 1.5.2 - express: 4.18.2 + express: 4.21.2 find-cache-dir: 3.3.2 fs-extra: 11.1.1 magic-string: 0.30.7 @@ -5789,7 +5845,7 @@ packages: detect-indent: 6.1.0 envinfo: 7.10.0 execa: 5.1.1 - express: 4.18.2 + express: 4.21.2 find-up: 5.0.0 fs-extra: 11.1.1 get-npm-tarball-url: 2.0.3 @@ -5999,7 +6055,7 @@ packages: cli-table3: 0.6.3 compression: 1.7.4 detect-port: 1.5.1 - express: 4.18.2 + express: 4.21.2 fs-extra: 11.1.1 globby: 11.1.0 ip: 2.0.1 @@ -6804,7 +6860,9 @@ packages: /@types/json-schema@7.0.14: resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} - dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} /@types/jsonwebtoken@8.5.9: resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==} @@ -7343,6 +7401,15 @@ packages: uri-js: 4.4.1 dev: true + /ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.5 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + dev: false + /ansi-colors@4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} engines: {node: '>=6'} @@ -7411,7 +7478,6 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true /aria-hidden@1.2.3: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} @@ -7693,6 +7759,26 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color + dev: false + + /body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -7808,6 +7894,13 @@ packages: ylru: 1.3.2 dev: true + /call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + /call-bind@1.0.5: resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} dependencies: @@ -7825,9 +7918,15 @@ packages: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + /call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.7 + /call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -8253,7 +8352,6 @@ packages: /content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - dev: false /convert-source-map@1.7.0: resolution: {integrity: sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==} @@ -8271,6 +8369,11 @@ packages: /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + dev: false + + /cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} /cookies@0.8.0: resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} @@ -8772,6 +8875,14 @@ packages: engines: {node: '>=12'} dev: true + /dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + /duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} dependencies: @@ -8821,6 +8932,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + /encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: @@ -8909,6 +9024,10 @@ packages: dependencies: get-intrinsic: 1.2.4 + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + /es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -8948,7 +9067,6 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - dev: false /es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} @@ -9338,6 +9456,45 @@ packages: vary: 1.1.2 transitivePeerDependencies: - supports-color + dev: false + + /express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color /ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -9376,7 +9533,6 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true /fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -9396,6 +9552,10 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-uri@3.0.5: + resolution: {integrity: sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==} + dev: false + /fast-xml-parser@4.4.0: resolution: {integrity: sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==} hasBin: true @@ -9468,6 +9628,21 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color + dev: false + + /finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color /find-cache-dir@2.1.0: resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} @@ -9746,6 +9921,21 @@ packages: has-symbols: 1.0.3 hasown: 2.0.2 + /get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + /get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -9760,6 +9950,13 @@ packages: engines: {node: '>=8'} dev: true + /get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.0.0 + /get-stdin@8.0.0: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} engines: {node: '>=10'} @@ -9972,6 +10169,10 @@ packages: dependencies: get-intrinsic: 1.2.2 + /gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true @@ -10052,6 +10253,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + /has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} @@ -10965,7 +11170,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /jscodeshift@0.15.2(@babel/preset-env@7.23.9): resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} @@ -11032,6 +11236,13 @@ packages: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true + /json-schema-ref-parser@9.0.9: + resolution: {integrity: sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==} + engines: {node: '>=10'} + deprecated: Please switch to @apidevtools/json-schema-ref-parser + dependencies: + '@apidevtools/json-schema-ref-parser': 9.0.9 + /json-schema-to-typescript@13.1.2: resolution: {integrity: sha512-17G+mjx4nunvOpkPvcz7fdwUwYCEwyH8vR3Ym3rFiQ8uzAL3go+c1306Kk7iGRk8HuXBXqy+JJJmpYl0cvOllw==} engines: {node: '>=12.0.0'} @@ -11053,9 +11264,16 @@ packages: prettier: 2.8.1 dev: true + /json-schema-to-zod@0.1.5: + resolution: {integrity: sha512-jJNMBluhFj9eQJq+zLFKwgwsPVm7vbIf3lxNBFY+58gnkYXUJVfpvQ2ujiDZZQDxAfaR5c9TvmfTUOOb46mfAw==} + hasBin: true + dependencies: + '@types/json-schema': 7.0.14 + json-schema-ref-parser: 9.0.9 + prettier: 2.8.1 + /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true /json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -11558,6 +11776,10 @@ packages: hasBin: true dev: false + /math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + /mathml-tag-names@2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true @@ -11739,6 +11961,10 @@ packages: /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -12391,6 +12617,10 @@ packages: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} dev: false + /object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} + /object-is@1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} engines: {node: '>= 0.4'} @@ -12717,8 +12947,12 @@ packages: minipass: 7.0.4 dev: true + /path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} @@ -12990,7 +13224,6 @@ packages: resolution: {integrity: sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==} engines: {node: '>=10.13.0'} hasBin: true - dev: true /prettier@3.2.5: resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} @@ -13163,6 +13396,7 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 + dev: false /qs@6.11.2: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} @@ -13170,6 +13404,12 @@ packages: dependencies: side-channel: 1.0.4 + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.1.0 + /queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} dev: true @@ -13201,6 +13441,16 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 + dev: false + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 /raw-body@3.0.0: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} @@ -13658,7 +13908,6 @@ packages: /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - dev: true /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -13890,6 +14139,27 @@ packages: statuses: 2.0.1 transitivePeerDependencies: - supports-color + dev: false + + /send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color /serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} @@ -13907,6 +14177,18 @@ packages: send: 0.18.0 transitivePeerDependencies: - supports-color + dev: false + + /serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color /server-destroy@1.0.1: resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} @@ -13974,6 +14256,32 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true + /side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + + /side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + + /side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + side-channel-map: 1.0.1 + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -13981,6 +14289,16 @@ packages: get-intrinsic: 1.2.2 object-inspect: 1.12.3 + /side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true @@ -15765,10 +16083,22 @@ packages: engines: {node: '>=12.20'} dev: true + /zod-to-json-schema@3.24.1(zod@3.24.1): + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 3.24.1 + dev: false + /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false + /zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false diff --git a/provider/modelcontextprotocol/index.test.ts b/provider/modelcontextprotocol/index.test.ts new file mode 100644 index 00000000..f0e1eab8 --- /dev/null +++ b/provider/modelcontextprotocol/index.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, test, vi } from 'vitest' +import type { MetaParams, ProviderSettings } from '@openctx/provider' +import proxy from './index.js' + +vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({ + Client: vi.fn().mockImplementation(() => ({ + connect: vi.fn(), + getServerVersion: vi.fn().mockReturnValue({ name: 'Test MCP Server' }), + request: vi.fn().mockImplementation(async (req) => { + if (req.method === 'resources/list') { + return { resources: [ + { uri: 'test://resource', name: 'Test Resource', description: 'Test Description' } + ]} + } + if (req.method === 'resources/read') { + return { contents: [ + { uri: 'test://resource', text: 'Test Content', mimeType: 'text/plain' } + ]} + } + }), + setNotificationHandler: vi.fn(), + setRequestHandler: vi.fn(), + close: vi.fn() + })) +})) + +vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({ + StdioClientTransport: vi.fn() +})) + +describe('MCP Provider', () => { + const settings: ProviderSettings = { + 'mcp.provider.uri': 'file:///path/to/your/mcp/provider.js', + 'nodeCommand': 'node', + 'mcp.provider.args': ['--some-arg', 'value'] + } + + test('meta returns provider info', async () => { + const result = await proxy.meta({} as MetaParams, settings) + expect(result).toMatchObject({ + name: expect.any(String), + mentions: { + label: expect.any(String) + } + }) + }) + + test('mentions returns resources', async () => { + const result = await proxy.mentions?.({ + query: 'test' + }, settings) + + expect(result).toBeDefined() + expect(Array.isArray(result)).toBe(true) + }) + + test('items returns content', async () => { + const result = await proxy.items?.({ + mention: { + uri: 'test://resource', + title: 'Test Resource' + } + }, settings) + + expect(result).toBeDefined() + expect(Array.isArray(result)).toBe(true) + }) +}) \ No newline at end of file diff --git a/provider/modelcontextprotocol/index.ts b/provider/modelcontextprotocol/index.ts index 14817a30..45537735 100644 --- a/provider/modelcontextprotocol/index.ts +++ b/provider/modelcontextprotocol/index.ts @@ -132,6 +132,7 @@ class MCPProxy implements Provider { } async items?(params: ItemsParams, _settings: ProviderSettings): Promise { + console.log('items', params) if (!params.mention || !this.mcpClient) { return [] } diff --git a/provider/modelcontextprotocol/package.json b/provider/modelcontextprotocol/package.json index 1d77a908..f7aba88b 100644 --- a/provider/modelcontextprotocol/package.json +++ b/provider/modelcontextprotocol/package.json @@ -21,6 +21,7 @@ "bundle": "tsc --build && esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js index.ts", "prepublishOnly": "tsc --build --clean && npm run --silent bundle", "test": "vitest", + "test:unit": "vitest run", "watch": "tsc --build --watch & esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js --watch index.ts" }, "dependencies": { diff --git a/provider/modelcontextprotocol/vitest.config.t b/provider/modelcontextprotocol/vitest.config.t new file mode 100644 index 00000000..701d5ef6 --- /dev/null +++ b/provider/modelcontextprotocol/vitest.config.t @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) \ No newline at end of file diff --git a/provider/modelcontextprotocoltools/README.md b/provider/modelcontextprotocoltools/README.md new file mode 100644 index 00000000..c132c1f4 --- /dev/null +++ b/provider/modelcontextprotocoltools/README.md @@ -0,0 +1,55 @@ +# MCP proxy for OpenCtx + +This is a context provider for [OpenCtx](https://openctx.org) that fetches contents from a [MCP](https://modelcontextprotocol.io) provider for use as context. + +Currently, only MCP over stdio is supported (HTTP is not yet supported). + +## Development + +1. Clone the [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) repository. Follow the instructions there to build the example providers. This should generate output files of the form `build/${example_name}/index.js`. +1. Run `pnpm watch` in this directory. +1. Add the following to your VS Code settings: + ```json + "openctx.providers": { + // ...other providers... + "https://openctx.org/npm/@openctx/provider-modelcontextprotocol": { + "nodeCommand": "node", + "mcp.provider.uri": "file:///path/to/servers/root/build/everything/index.js", + } + } + ``` +1. Reload the VS Code window. You should see `servers/everything` in the `@`-mention dropdown. + +To hook up to the Postgres MCP provider, use: + +```json +"openctx.providers": { + // ...other providers... + "https://openctx.org/npm/@openctx/provider-modelcontextprotocol": { + "nodeCommand": "node", + "mcp.provider.uri": "file:///path/to/servers/root/build/postgres/index.js", + "mcp.provider.args": [ + "postgresql://sourcegraph:sourcegraph@localhost:5432/sourcegraph" + ] + } +} +``` + +## More MCP Servers + +The following MCP servers are available in the [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) repository: + +- [Brave Search](https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search) - Search the Brave search API +- [Postgres](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres) - Connect to your Postgres databases to query schema information and write optimized SQL +- [Filesystem](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) - Access files on your local machine +- [Everything](https://github.com/modelcontextprotocol/servers/tree/main/src/everything) - A demo server showing MCP capabilities +- [Google Drive](https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive) - Search and access your Google Drive documents +- [Google Maps](https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps) - Get directions and information about places +- [Memo](https://github.com/modelcontextprotocol/servers/tree/main/src/memo) - Access your Memo notes +- [Git](https://github.com/modelcontextprotocol/servers/tree/main/src/git) - Get git history and commit information +- [Puppeteer](https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer) - Control headless Chrome for web automation +- [SQLite](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite) - Query SQLite databases + +## Creating your own MCP server + +See the [MCP docs](https://modelcontextprotocol.io) for how to create your own MCP servers. \ No newline at end of file diff --git a/provider/modelcontextprotocoltools/index.test.ts b/provider/modelcontextprotocoltools/index.test.ts new file mode 100644 index 00000000..db7583f7 --- /dev/null +++ b/provider/modelcontextprotocoltools/index.test.ts @@ -0,0 +1,87 @@ +import { describe, test } from 'vitest' +import proxy from './index.js' +const Ajv = require("ajv"); +describe('Module exports', () => { + // test('exports expected type definitions', async () => { + // // We can't directly test types at runtime, but we can verify the exports exist + // console.log("testing") + // const client = await proxy.meta!( { 'mcp.provider.uri': 'file://dist/index.js' , 'mcp.provider.args': [] }) + // console.log(client) + // const mentions = await proxy.mentions!({ query: '' }, {}) + // console.log(mentions) + + // const inputSchemaString = `{ + // "inputSchema": { + // "type": "object", + // "properties": { + // "prompt": { + // "type": "string", + // "description": "The prompt to send to the LLM" + // }, + // "maxTokens": { + // "type": "number", + // "default": 100, + // "description": "Maximum number of tokens to generate" + // } + // }, + // "required": [ + // "prompt" + // ], + // "additionalProperties": false, + // "$schema": "http://json-schema.org/draft-07/schema#" + // } + // }` + // const ajv = new Ajv(); + // // Parse the schema string to JSON + // const inputSchema = JSON.parse(inputSchemaString).inputSchema; + + // // Example valid input + // const validInput = { + // prompt: "Hello, how are you?", + // maxTokens: 50 + // }; + + // // Example invalid input + // const invalidInput = { + // maxTokens: 50 + // // missing required 'prompt' + // }; + + // try { + // // Validate valid input + // const isValidInput = ajv.validate(inputSchema, validInput); + // console.log('Valid input:', isValidInput, validInput); + + // // Try to validate invalid input + // const isInvalidInput = ajv.validate(inputSchema, invalidInput); + // console.log('Invalid input result:', isInvalidInput, ajv.errors); + // } catch (error) { + // console.log('Validation error:', error); + // } + // // While we can't test types directly, we can verify the module has exports + // // expect(mentions.length).toBeGreaterThan(0) + // }) + + test('exports me type definitions', async () => { + // We can't directly test types at runtime, but we can verify the exports exist + const client = await proxy.meta!( {}, { 'mcp.provider.uri': 'file:///Users/arafatkhan/Desktop/servers/src/everything/dist/index.js' , 'mcp.provider.args': [] }) + console.log(client) + const inputs = await proxy.mentions!({ query: '' }, {}) + console.log("inputs response is :", inputs[0].data) + const inputSchema = inputs[0].data + console.log("inputSchema is :", inputSchema) + const ajv = new Ajv(); + const validInput = { + message: "hello" + }; + const isValidInput = ajv.validate(inputSchema, validInput); + console.log('Valid input:', isValidInput, validInput); + + const items = await proxy.items!({ mention: { uri: '', title: 'brownianModel', data: { message: 'hello to me' } } }, {}) + console.log(items) + + + // While we can't test types directly, we can verify the module has exports + // expect(mentions.length).toBeGreaterThan(0) + }) +}) \ No newline at end of file diff --git a/provider/modelcontextprotocoltools/index.ts b/provider/modelcontextprotocoltools/index.ts new file mode 100644 index 00000000..7c41db08 --- /dev/null +++ b/provider/modelcontextprotocoltools/index.ts @@ -0,0 +1,194 @@ +import { basename } from 'node:path' +import { Client } from '@modelcontextprotocol/sdk/client/index.js' +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' +import { + CallToolResultSchema, + +} from '@modelcontextprotocol/sdk/types.js' +import type { + Item, + ItemsParams, + ItemsResult, + Mention, + MentionsParams, + MentionsResult, + MetaParams, + MetaResult, + Provider, + ProviderSettings, +} from '@openctx/provider' +const Ajv = require('ajv') + +async function createClient( + nodeCommand: string, + mcpProviderFile: string, + mcpProviderArgs: string[], +): Promise { + const client = new Client( + { + name: 'mcp-tool', + version: '0.0.1', + }, + { + capabilities: { + experimental: {}, + sampling: {}, + roots: {}, + }, + }, + ) + const transport = new StdioClientTransport({ + command: nodeCommand, + args: [mcpProviderFile, ...mcpProviderArgs], + }) + await client.connect(transport) + return client +} + +class MCPToolsProxy implements Provider { + private mcpClient?: Promise + private toolSchemas: Map = new Map() + private ajv = new Ajv() + + // Gets the Metadata for the MCP Tools Provider + async meta(_params: MetaParams, settings: ProviderSettings): Promise { + const nodeCommand: string = (settings.nodeCommand as string) ?? 'node' + const mcpProviderUri = settings['mcp.provider.uri'] as string + if (!mcpProviderUri) { + this.mcpClient = undefined + return { + name: 'undefined MCP provider', + } + } + if (!mcpProviderUri.startsWith('file://')) { + throw new Error('mcp.provider.uri must be a file:// URI') + } + const mcpProviderFile = mcpProviderUri.slice('file://'.length) + const mcpProviderArgsRaw = settings['mcp.provider.args'] + const mcpProviderArgs = Array.isArray(mcpProviderArgsRaw) + ? mcpProviderArgsRaw.map(e => `${e}`) + : [] + this.mcpClient = createClient(nodeCommand, mcpProviderFile, mcpProviderArgs) + const mcpClient = await this.mcpClient + const serverInfo = mcpClient.getServerVersion() + const name = serverInfo?.name ?? basename(mcpProviderFile) + return { + name, + mentions: { + label: name, + }, + } + } + + // Gets Lists All the tools available in the MCP Provider along with their schemas + async mentions?(params: MentionsParams, _settings: ProviderSettings): Promise { + if (!this.mcpClient) { + return [] + } + const mcpClient = await this.mcpClient + const toolsResp = await mcpClient.listTools() + + const { tools } = toolsResp + const mentions: Mention[] = [] + for (const tool of tools) { + // Store the schema in the Map using tool name as key + this.toolSchemas.set(tool.name, JSON.stringify(tool.inputSchema)) + + const r = { + uri: tool.uri, + title: tool.name, + description: tool.description, + data: (tool.inputSchema), + } as Mention + mentions.push(r) + } + + const query = params.query?.trim().toLowerCase() + if (!query) { + return mentions + } + const prefixMatches: Mention[] = [] + const substringMatches: Mention[] = [] + + // Filters the tools based on the query + for (const mention of mentions) { + const title = mention.title.toLowerCase() + if (title.startsWith(query)) { + prefixMatches.push(mention) + } else if (title.includes(query)) { + substringMatches.push(mention) + } + } + + // Combines the prefix and substring matches + return [...prefixMatches, ...substringMatches] + } + + // Retrieves the schema for a tool from the Map using the tool name as key + getToolSchema(toolName: string): any { + return JSON.parse(this.toolSchemas.get(toolName) as string) + } + + // Calls the tool with the provided input and returns the result + async items?(params: ItemsParams, _settings: ProviderSettings): Promise { + if (!this.mcpClient) { + return [] + } + const mcpClient = await this.mcpClient + + const toolName = params.mention?.title + const toolInput = params.mention?.data + + // Validates the tool input against the stored schema + if (toolName && toolInput) { + const schema = this.getToolSchema(toolName) + if (schema) { + const isValid = this.ajv.validate(schema, toolInput) + if (!isValid) { + console.error('Invalid tool input:', this.ajv.errors) + throw new Error(`Invalid input for tool ${toolName}: ${JSON.stringify(this.ajv.errors)}`) + } + } + } + + // Calls the tool with the provided input + const response = await mcpClient.request( + { + method: 'tools/call' as const, + params: { + name: toolName, + arguments: toolInput + }, + }, + CallToolResultSchema, + ) + + const contents = response.content + const items: Item[] = [] + for (const content of contents) { + if (content.text) { + items.push({ + title: (toolName as string) ?? '', + ai: { + content: (content.text as string) ?? '', + }, + }) + } else { + console.log('No text field was present, mimeType was', content.mimeType) + } + } + return items + } + + dispose?(): void { + if (this.mcpClient) { + this.mcpClient.then(c => { + c.close() + }) + } + } +} + + +const proxy = new MCPToolsProxy() +export default proxy diff --git a/provider/modelcontextprotocoltools/package-lock.json b/provider/modelcontextprotocoltools/package-lock.json new file mode 100644 index 00000000..856bdcb9 --- /dev/null +++ b/provider/modelcontextprotocoltools/package-lock.json @@ -0,0 +1,298 @@ +{ + "name": "@openctx/provider-modelcontextprotocoltools", + "version": "0.0.13", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@openctx/provider-modelcontextprotocoltools", + "version": "0.0.13", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "1.0.1", + "@openctx/provider": "^0.0.13", + "express": "^4.21.1", + "json-schema-to-zod": "^0.1.1" + } + }, + "../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.0.1/node_modules/@modelcontextprotocol/sdk": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/content-type": "^1.1.8", + "@types/eslint__js": "^8.42.3", + "@types/eventsource": "^1.1.15", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/node": "^22.0.2", + "@types/ws": "^8.5.12", + "eslint": "^9.8.0", + "eventsource": "^2.0.2", + "express": "^4.19.2", + "jest": "^29.7.0", + "ts-jest": "^29.2.4", + "tsx": "^4.16.5", + "typescript": "^5.5.4", + "typescript-eslint": "^8.0.0", + "ws": "^8.18.0" + } + }, + "../../node_modules/.pnpm/express@4.21.2/node_modules/express": { + "version": "4.21.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "devDependencies": { + "after": "0.8.2", + "connect-redis": "3.4.2", + "cookie-parser": "1.4.6", + "cookie-session": "2.0.0", + "ejs": "3.1.9", + "eslint": "8.47.0", + "express-session": "1.17.2", + "hbs": "4.2.0", + "marked": "0.7.0", + "method-override": "3.0.0", + "mocha": "10.2.0", + "morgan": "1.10.0", + "nyc": "15.1.0", + "pbkdf2-password": "1.2.1", + "supertest": "6.3.0", + "vhost": "~3.0.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "../../node_modules/.pnpm/zod-to-json-schema@3.24.1_zod@3.23.8/node_modules/zod-to-json-schema": { + "version": "3.24.1", + "extraneous": true, + "license": "ISC", + "devDependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^20.9.0", + "ajv": "^8.6.3", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "fast-diff": "^1.3.0", + "local-ref-resolver": "^0.2.0", + "rimraf": "^3.0.2", + "tsx": "^4.19.0", + "typescript": "^5.1.3", + "zod": "^3.24.1" + }, + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "../../node_modules/.pnpm/zod@3.23.8/node_modules/zod": { + "version": "3.23.8", + "extraneous": true, + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.22.5", + "@babel/preset-env": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@jest/globals": "^29.4.3", + "@rollup/plugin-typescript": "^8.2.0", + "@swc/core": "^1.3.66", + "@swc/jest": "^0.2.26", + "@types/benchmark": "^2.1.0", + "@types/jest": "^29.2.2", + "@types/node": "14", + "@typescript-eslint/eslint-plugin": "^5.15.0", + "@typescript-eslint/parser": "^5.15.0", + "babel-jest": "^29.5.0", + "benchmark": "^2.1.4", + "dependency-cruiser": "^9.19.0", + "eslint": "^8.11.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-ban": "^1.6.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-simple-import-sort": "^7.0.0", + "eslint-plugin-unused-imports": "^2.0.0", + "husky": "^7.0.4", + "jest": "^29.3.1", + "lint-staged": "^12.3.7", + "nodemon": "^2.0.15", + "prettier": "^2.6.0", + "pretty-quick": "^3.1.3", + "rollup": "^2.70.1", + "ts-jest": "^29.1.0", + "ts-morph": "^14.0.0", + "ts-node": "^10.9.1", + "tslib": "^2.3.1", + "tsx": "^3.8.0", + "typescript": "~4.5.5", + "vitest": "^0.32.2" + }, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@modelcontextprotocol/sdk": { + "resolved": "../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.0.1/node_modules/@modelcontextprotocol/sdk", + "link": true + }, + "node_modules/@openctx/protocol": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@openctx/protocol/-/protocol-0.0.13.tgz", + "integrity": "sha512-n5TD9ZLzc53ggOZJGRMgcHgE++9vh/W48bgNxBWj4qib0SX3tLa8kLV0IryIL0y5MKIzrRImDmNrGuWT/cxA+Q==", + "dependencies": { + "@openctx/schema": "0.0.11" + } + }, + "node_modules/@openctx/provider": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@openctx/provider/-/provider-0.0.13.tgz", + "integrity": "sha512-w/c2QrybiEJo5bGRDjmikCVexuGZpJubNsRANAf0OIGuxzEMLZ321BtdGCEsvIzIm2guR0D59dJ6qUexo+Hdaw==", + "dependencies": { + "@openctx/protocol": "0.0.13", + "@openctx/schema": "0.0.11", + "picomatch": "^3.0.1" + } + }, + "node_modules/@openctx/schema": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@openctx/schema/-/schema-0.0.11.tgz", + "integrity": "sha512-qPJBCZNxXv3NDAmXWO0TSzDwwAbAh+LmLfjzvepj1sd4iwuLtxYZJlIEMsZzOd/7lyV1NFFMzM/C3X9EDeUz/g==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "node_modules/express": { + "resolved": "../../node_modules/.pnpm/express@4.21.2/node_modules/express", + "link": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/json-schema-to-zod": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-0.1.5.tgz", + "integrity": "sha512-jJNMBluhFj9eQJq+zLFKwgwsPVm7vbIf3lxNBFY+58gnkYXUJVfpvQ2ujiDZZQDxAfaR5c9TvmfTUOOb46mfAw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "json-schema-ref-parser": "^9.0.9", + "prettier": "^2.4.1" + }, + "bin": { + "json-schema-to-zod": "cli.js" + } + }, + "node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/provider/modelcontextprotocoltools/package.json b/provider/modelcontextprotocoltools/package.json new file mode 100644 index 00000000..839bf88e --- /dev/null +++ b/provider/modelcontextprotocoltools/package.json @@ -0,0 +1,44 @@ +{ + "name": "@openctx/provider-modelcontextprotocoltools", + "version": "0.0.13", + "description": "Use information from MCP providers", + "license": "Apache-2.0", + "homepage": "https://openctx.org/docs/providers/modelcontextprotocoltools", + "repository": { + "type": "git", + "url": "https://github.com/sourcegraph/openctx", + "directory": "provider/modelcontextprotocoltools" + }, + "type": "module", + "main": "dist/bundle.js", + "types": "dist/index.d.ts", + "files": [ + "dist/bundle.js", + "dist/index.d.ts" + ], + "sideEffects": false, + "scripts": { + "bundle": "tsc --build && esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js index.ts", + "prepublishOnly": "tsc --build --clean && npm run --silent bundle", + "test": "vitest", + "test:unit": "vitest run", + "watch": "tsc --build --watch & esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js --watch index.ts" + }, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.3", + "@modelcontextprotocol/sdk": "1.0.1", + "@openctx/provider": "workspace:*", + "ajv": "^8.17.1", + "express": "^4.21.1", + "json-schema-to-zod": "^0.1.5", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.1" + }, + "pnpm": { + "peerDependencyRules": { + "allowAny": [ + "@apidevtools/json-schema-ref-parser" + ] + } + } +} diff --git a/provider/modelcontextprotocoltools/tsconfig.json b/provider/modelcontextprotocoltools/tsconfig.json new file mode 100644 index 00000000..a1d94187 --- /dev/null +++ b/provider/modelcontextprotocoltools/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../.config/tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "lib": ["ESNext"] + }, + "include": ["*.ts"], + "exclude": ["dist", "vitest.config.ts"], + "references": [{ "path": "../../lib/provider" }] +} diff --git a/provider/modelcontextprotocoltools/vitest.config.t b/provider/modelcontextprotocoltools/vitest.config.t new file mode 100644 index 00000000..701d5ef6 --- /dev/null +++ b/provider/modelcontextprotocoltools/vitest.config.t @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) \ No newline at end of file diff --git a/provider/modelcontextprotocoltools/vitest.config.ts b/provider/modelcontextprotocoltools/vitest.config.ts new file mode 100644 index 00000000..abed6b21 --- /dev/null +++ b/provider/modelcontextprotocoltools/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({})