mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
Init
This commit is contained in:
13
.env.example
Normal file
13
.env.example
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Nostr
|
||||||
|
PRIVATE_KEY=your_private_key_here
|
||||||
|
RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band,wss://nos.lol
|
||||||
|
|
||||||
|
# MCP Service Info
|
||||||
|
MCP_SERVICE_NAME="DVM MCP Bridge"
|
||||||
|
MCP_SERVICE_ABOUT="MCP-enabled DVM providing AI and computational tools"
|
||||||
|
|
||||||
|
# MCP Client Connection
|
||||||
|
MCP_CLIENT_NAME="DVM MCP Bridge Client"
|
||||||
|
MCP_CLIENT_VERSION="1.0.0"
|
||||||
|
MCP_SERVER_COMMAND=bun # The command to start the MCP server
|
||||||
|
MCP_SERVER_ARGS=run,src/external-mcp-server.ts # Comma-separated args to pass to the server command
|
||||||
179
.gitignore
vendored
Normal file
179
.gitignore
vendored
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
#
|
||||||
|
codebase*
|
||||||
|
.aider*
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 80,
|
||||||
|
"bracketSpacing": true
|
||||||
|
}
|
||||||
|
|||||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# dvmcp
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
98
bun.lock
Normal file
98
bun.lock
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "dvmcp",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||||
|
"@noble/hashes": "^1.7.1",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"nostr-tools": "^2.10.4",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.4.1", "", { "dependencies": { "content-type": "^1.0.5", "eventsource": "^3.0.2", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-wS6YC4lkUZ9QpP+/7NBTlVNiEvsnyl0xF7rRusLF+RsG0xDPc/zWR7fEEyhKnnNutGsDAZh59l/AeoWGwIb1+g=="],
|
||||||
|
|
||||||
|
"@noble/ciphers": ["@noble/ciphers@0.5.3", "", {}, "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="],
|
||||||
|
|
||||||
|
"@noble/curves": ["@noble/curves@1.2.0", "", { "dependencies": { "@noble/hashes": "1.3.2" } }, "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw=="],
|
||||||
|
|
||||||
|
"@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="],
|
||||||
|
|
||||||
|
"@scure/base": ["@scure/base@1.1.1", "", {}, "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="],
|
||||||
|
|
||||||
|
"@scure/bip32": ["@scure/bip32@1.3.1", "", { "dependencies": { "@noble/curves": "~1.1.0", "@noble/hashes": "~1.3.1", "@scure/base": "~1.1.0" } }, "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A=="],
|
||||||
|
|
||||||
|
"@scure/bip39": ["@scure/bip39@1.2.1", "", { "dependencies": { "@noble/hashes": "~1.3.0", "@scure/base": "~1.1.0" } }, "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
||||||
|
|
||||||
|
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
|
||||||
|
|
||||||
|
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||||
|
|
||||||
|
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||||
|
|
||||||
|
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
|
"dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
|
||||||
|
|
||||||
|
"eventsource": ["eventsource@3.0.5", "", { "dependencies": { "eventsource-parser": "^3.0.0" } }, "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw=="],
|
||||||
|
|
||||||
|
"eventsource-parser": ["eventsource-parser@3.0.0", "", {}, "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA=="],
|
||||||
|
|
||||||
|
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||||
|
|
||||||
|
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||||
|
|
||||||
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
|
"nostr-tools": ["nostr-tools@2.10.4", "", { "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.1", "@scure/base": "1.1.1", "@scure/bip32": "1.3.1", "@scure/bip39": "1.2.1" }, "optionalDependencies": { "nostr-wasm": "0.1.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-biU7sk+jxHgVASfobg2T5ttxOGGSt69wEVBC51sHHOEaKAAdzHBLV/I2l9Rf61UzClhliZwNouYhqIso4a3HYg=="],
|
||||||
|
|
||||||
|
"nostr-wasm": ["nostr-wasm@0.1.0", "", {}, "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA=="],
|
||||||
|
|
||||||
|
"prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="],
|
||||||
|
|
||||||
|
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||||
|
|
||||||
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
|
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||||
|
|
||||||
|
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||||
|
|
||||||
|
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||||
|
|
||||||
|
"zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
|
||||||
|
|
||||||
|
"zod-to-json-schema": ["zod-to-json-schema@3.24.1", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w=="],
|
||||||
|
|
||||||
|
"@noble/curves/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="],
|
||||||
|
|
||||||
|
"@scure/bip32/@noble/curves": ["@noble/curves@1.1.0", "", { "dependencies": { "@noble/hashes": "1.3.1" } }, "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA=="],
|
||||||
|
|
||||||
|
"@scure/bip32/@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="],
|
||||||
|
|
||||||
|
"@scure/bip39/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="],
|
||||||
|
|
||||||
|
"nostr-tools/@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
25
package.json
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "dvmcp",
|
||||||
|
"module": "src/dvm-bridge.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
|
||||||
|
"dev": "bun --watch src/dvm-bridge.ts",
|
||||||
|
"start": "bun run src/dvm-bridge.ts",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "bun run typecheck && bun run format"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||||
|
"@noble/hashes": "^1.7.1",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"nostr-tools": "^2.10.4",
|
||||||
|
"prettier": "^3.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/config.ts
Normal file
21
src/config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { config } from 'dotenv';
|
||||||
|
config();
|
||||||
|
|
||||||
|
export const CONFIG = {
|
||||||
|
nostr: {
|
||||||
|
privateKey: process.env.PRIVATE_KEY!,
|
||||||
|
relayUrls: process.env.RELAY_URLS!.split(',').map((url) => url.trim()),
|
||||||
|
},
|
||||||
|
mcp: {
|
||||||
|
// Service info
|
||||||
|
name: process.env.MCP_SERVICE_NAME || 'DVM MCP Bridge',
|
||||||
|
about:
|
||||||
|
process.env.MCP_SERVICE_ABOUT ||
|
||||||
|
'MCP-enabled DVM providing AI and computational tools',
|
||||||
|
// Client connection info
|
||||||
|
clientName: process.env.MCP_CLIENT_NAME!,
|
||||||
|
clientVersion: process.env.MCP_CLIENT_VERSION!,
|
||||||
|
serverCommand: process.env.MCP_SERVER_COMMAND!,
|
||||||
|
serverArgs: process.env.MCP_SERVER_ARGS!.split(','),
|
||||||
|
},
|
||||||
|
};
|
||||||
108
src/dvm-bridge.ts
Normal file
108
src/dvm-bridge.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { MCPClientHandler } from './mcp-client';
|
||||||
|
import { NostrAnnouncer } from './nostr/announcer';
|
||||||
|
import { RelayHandler } from './nostr/relay';
|
||||||
|
import { keyManager } from './nostr/keys';
|
||||||
|
import { CONFIG } from './config';
|
||||||
|
import type { Event } from 'nostr-tools/pure';
|
||||||
|
|
||||||
|
export class DVMBridge {
|
||||||
|
private mcpClient: MCPClientHandler;
|
||||||
|
private nostrAnnouncer: NostrAnnouncer;
|
||||||
|
private relayHandler: RelayHandler;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.mcpClient = new MCPClientHandler();
|
||||||
|
this.nostrAnnouncer = new NostrAnnouncer();
|
||||||
|
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.mcpClient.connect();
|
||||||
|
|
||||||
|
const tools = await this.mcpClient.listTools();
|
||||||
|
console.log('Available MCP tools:', tools);
|
||||||
|
|
||||||
|
await this.nostrAnnouncer.announceService();
|
||||||
|
|
||||||
|
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleRequest(event: Event) {
|
||||||
|
try {
|
||||||
|
if (event.kind === 5000) {
|
||||||
|
const tools = await this.mcpClient.listTools();
|
||||||
|
|
||||||
|
const response = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(6000),
|
||||||
|
content: JSON.stringify({
|
||||||
|
schema_version: '1.0',
|
||||||
|
tools,
|
||||||
|
}),
|
||||||
|
tags: [
|
||||||
|
['e', event.id],
|
||||||
|
['p', event.pubkey],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.relayHandler.publishEvent(response);
|
||||||
|
} else if (event.kind === 5001) {
|
||||||
|
const { name, parameters } = JSON.parse(event.content);
|
||||||
|
|
||||||
|
const processingStatus = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(7000),
|
||||||
|
tags: [
|
||||||
|
['status', 'processing'],
|
||||||
|
['e', event.id],
|
||||||
|
['p', event.pubkey],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await this.relayHandler.publishEvent(processingStatus);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.mcpClient.callTool(name, parameters);
|
||||||
|
|
||||||
|
const successStatus = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(7000),
|
||||||
|
tags: [
|
||||||
|
['status', 'success'],
|
||||||
|
['e', event.id],
|
||||||
|
['p', event.pubkey],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await this.relayHandler.publishEvent(successStatus);
|
||||||
|
|
||||||
|
const response = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(6001),
|
||||||
|
content: JSON.stringify(result),
|
||||||
|
tags: [
|
||||||
|
['e', event.id],
|
||||||
|
['p', event.pubkey],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await this.relayHandler.publishEvent(response);
|
||||||
|
} catch (error) {
|
||||||
|
const errorStatus = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(7000),
|
||||||
|
tags: [
|
||||||
|
[
|
||||||
|
'status',
|
||||||
|
'error',
|
||||||
|
error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
],
|
||||||
|
['e', event.id],
|
||||||
|
['p', event.pubkey],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await this.relayHandler.publishEvent(errorStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling request:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
await this.mcpClient.disconnect();
|
||||||
|
this.relayHandler.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/mcp-client.ts
Normal file
49
src/mcp-client.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||||
|
import { CONFIG } from './config';
|
||||||
|
|
||||||
|
export class MCPClientHandler {
|
||||||
|
private client: Client;
|
||||||
|
private transport: StdioClientTransport;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.transport = new StdioClientTransport({
|
||||||
|
command: CONFIG.mcp.serverCommand,
|
||||||
|
args: CONFIG.mcp.serverArgs,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client = new Client(
|
||||||
|
{
|
||||||
|
name: CONFIG.mcp.clientName,
|
||||||
|
version: CONFIG.mcp.clientVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
prompts: {},
|
||||||
|
resources: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
await this.client.connect(this.transport);
|
||||||
|
console.log('Connected to MCP server');
|
||||||
|
}
|
||||||
|
|
||||||
|
async listTools() {
|
||||||
|
return await this.client.listTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
async callTool(name: string, args: Record<string, any>) {
|
||||||
|
return await this.client.callTool({
|
||||||
|
name,
|
||||||
|
arguments: args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
await this.transport.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/nostr/announcer.ts
Normal file
30
src/nostr/announcer.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { CONFIG } from '../config';
|
||||||
|
import { RelayHandler } from './relay';
|
||||||
|
import { keyManager } from './keys';
|
||||||
|
|
||||||
|
export class NostrAnnouncer {
|
||||||
|
private relayHandler: RelayHandler;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
async announceService() {
|
||||||
|
const event = keyManager.signEvent({
|
||||||
|
...keyManager.createEventTemplate(31990),
|
||||||
|
content: JSON.stringify({
|
||||||
|
name: CONFIG.mcp.name,
|
||||||
|
about: CONFIG.mcp.about,
|
||||||
|
}),
|
||||||
|
tags: [
|
||||||
|
['d', Math.random().toString(36).substring(7)],
|
||||||
|
['k', '5000'],
|
||||||
|
['k', '5001'],
|
||||||
|
['capabilities', 'mcp-1.0'],
|
||||||
|
['t', 'mcp'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.relayHandler.publishEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/nostr/keys.ts
Normal file
33
src/nostr/keys.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { hexToBytes } from '@noble/hashes/utils';
|
||||||
|
import { getPublicKey, finalizeEvent } from 'nostr-tools/pure';
|
||||||
|
import type { Event } from 'nostr-tools/pure';
|
||||||
|
import { CONFIG } from '../config';
|
||||||
|
|
||||||
|
export type UnsignedEvent = Omit<Event, 'sig' | 'id'>;
|
||||||
|
|
||||||
|
export const createKeyManager = (privateKeyHex: string) => {
|
||||||
|
const privateKeyBytes = hexToBytes(privateKeyHex);
|
||||||
|
const pubkey = getPublicKey(privateKeyBytes);
|
||||||
|
|
||||||
|
class Manager {
|
||||||
|
public readonly pubkey = pubkey;
|
||||||
|
|
||||||
|
signEvent(eventInitial: UnsignedEvent): Event {
|
||||||
|
return finalizeEvent(eventInitial, privateKeyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
createEventTemplate(kind: number): UnsignedEvent {
|
||||||
|
return {
|
||||||
|
kind,
|
||||||
|
pubkey: this.pubkey,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [],
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Manager();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const keyManager = createKeyManager(CONFIG.nostr.privateKey);
|
||||||
59
src/nostr/relay.ts
Normal file
59
src/nostr/relay.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { SimplePool } from 'nostr-tools/pool';
|
||||||
|
import type { Event } from 'nostr-tools/pure';
|
||||||
|
import type { SubCloser } from 'nostr-tools/pool';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
import { useWebSocketImplementation } from 'nostr-tools/pool';
|
||||||
|
import type { Filter } from 'nostr-tools';
|
||||||
|
|
||||||
|
useWebSocketImplementation(WebSocket);
|
||||||
|
|
||||||
|
export class RelayHandler {
|
||||||
|
private pool: SimplePool;
|
||||||
|
private relayUrls: string[];
|
||||||
|
private subscriptions: SubCloser[] = [];
|
||||||
|
|
||||||
|
constructor(relayUrls: string[]) {
|
||||||
|
this.pool = new SimplePool();
|
||||||
|
this.relayUrls = relayUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
async publishEvent(event: Event): Promise<void> {
|
||||||
|
try {
|
||||||
|
await Promise.any(this.pool.publish(this.relayUrls, event));
|
||||||
|
console.log('Event published:', event.id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to publish event:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToRequests(onRequest: (event: Event) => void): SubCloser {
|
||||||
|
const filters: Filter[] = [
|
||||||
|
{
|
||||||
|
kinds: [5000, 5001],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const sub = this.pool.subscribeMany(this.relayUrls, filters, {
|
||||||
|
onevent(event) {
|
||||||
|
onRequest(event);
|
||||||
|
},
|
||||||
|
oneose() {
|
||||||
|
console.log('Reached end of stored events');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.subscriptions.push(sub);
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryEvents(filter: Filter): Promise<Event[]> {
|
||||||
|
return await this.pool.querySync(this.relayUrls, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this.subscriptions.forEach((sub) => sub.close());
|
||||||
|
this.subscriptions = [];
|
||||||
|
this.pool.close(this.relayUrls);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user