diff --git a/setup.py b/setup.py index 632ba23..094590d 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ setup( "urllib3==2.2.2", "networkx==3.3", "scipy==1.13.1", + "typer==0.15.1", "beautifulsoup4==4.12.3" ], keywords=['nostr', 'nip90', 'dvm', 'data vending machine'], diff --git a/tests/mcp/mcp_dvm_client.py b/tests/mcp/dvm-client/mcp_dvm_client.py similarity index 99% rename from tests/mcp/mcp_dvm_client.py rename to tests/mcp/dvm-client/mcp_dvm_client.py index a65a866..3d43fb9 100644 --- a/tests/mcp/mcp_dvm_client.py +++ b/tests/mcp/dvm-client/mcp_dvm_client.py @@ -127,7 +127,7 @@ async def nostr_client(): if __name__ == '__main__': - env_path = Path('../.env') + env_path = Path('../../.env') if env_path.is_file(): print(f'loading environment from {env_path.resolve()}') dotenv.load_dotenv(env_path, verbose=True, override=True) diff --git a/tests/mcp/mcp_server_config.json b/tests/mcp/dvm/mcp_server_config.json similarity index 50% rename from tests/mcp/mcp_server_config.json rename to tests/mcp/dvm/mcp_server_config.json index e540705..5fec8a2 100644 --- a/tests/mcp/mcp_server_config.json +++ b/tests/mcp/dvm/mcp_server_config.json @@ -9,6 +9,19 @@ "args": [ "../../mcp-crypto-price/build/index.js" ] + }, + "nostr-notes": { + "command": "uv", + "args": [ + "run", + "--with", + "mcp[cli]", + "--with", + "nostr_sdk==0.39.0", + "mcp", + "run", + "tests/mcp/mcp-servers/nostr-notes/server.py" + ] } } } diff --git a/tests/mcp/mcp_test.py b/tests/mcp/dvm/mcp_test.py similarity index 94% rename from tests/mcp/mcp_test.py rename to tests/mcp/dvm/mcp_test.py index 053bef9..6533364 100644 --- a/tests/mcp/mcp_test.py +++ b/tests/mcp/dvm/mcp_test.py @@ -5,7 +5,7 @@ from pathlib import Path import dotenv from nostr_dvm.framework import DVMFramework -from nostr_dvm.tasks.mcpbridge import MCPBridge +from tests.mcp.dvm.mcpbridge import MCPBridge from nostr_dvm.utils.admin_utils import AdminConfig from nostr_dvm.utils.dvmconfig import build_default_config from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag @@ -35,7 +35,7 @@ def playground(announce=False): # MCP CONFIG config_path = str(Path.absolute(Path(__file__).parent / "mcp_server_config.json")) - server_names = ["mcp-crypto-price"] + server_names = ["mcp-crypto-price", "nostr-notes"] tools = asyncio.run(get_tools(config_path, server_names)) @@ -95,9 +95,9 @@ def playground(announce=False): if __name__ == '__main__': - env_path = Path('../.env') + env_path = Path('../../.env') if not env_path.is_file(): - with open('../.env', 'w') as f: + with open('../../.env', 'w') as f: print("Writing new .env file") f.write('') if env_path.is_file(): diff --git a/nostr_dvm/tasks/mcpbridge.py b/tests/mcp/dvm/mcpbridge.py similarity index 100% rename from nostr_dvm/tasks/mcpbridge.py rename to tests/mcp/dvm/mcpbridge.py diff --git a/tests/mcp/mcp-servers/nostr-notes/__init__.py b/tests/mcp/mcp-servers/nostr-notes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/mcp/mcp-servers/nostr-notes/server.py b/tests/mcp/mcp-servers/nostr-notes/server.py new file mode 100644 index 0000000..c965f2e --- /dev/null +++ b/tests/mcp/mcp-servers/nostr-notes/server.py @@ -0,0 +1,41 @@ +import re +from datetime import timedelta + +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("Nostr", description="Get notes from Nostr for a given key", dependencies=["nostr_sdk==0.39.0"]) + +@mcp.tool() +async def get_nostr_notes(npub: str, limit: int) -> str: + from nostr_sdk import Client, Keys, NostrSigner, Filter, Kind, PublicKey + + keys = Keys.parse("e318cb3e6ac163814dd297c2c7d745faacfbc2a826eb4f6d6c81430426a83c2b") + client = Client(NostrSigner.keys(keys)) + + relay_list = ["wss://relay.damus.io", + "wss://nostr.oxtr.dev", + "wss://relay.primal.net", + ] + + for relay in relay_list: + await client.add_relay(relay) + + + await client.connect() + + f = Filter().kind(Kind(1)).author(PublicKey.parse(npub)).limit(limit) + events = await client.fetch_events(f, timedelta(5)) + + index = 1 + notes = "" + for event in events.to_vec(): + try: + pattern = r"[^a-zA-Z0-9\s.!?:,-/]" + cleaned_string = re.sub(pattern, "", event.content()) + notes = notes + str(index) + ". " + cleaned_string + "\n" + index += 1 + except Exception as e: + print(e) + + return notes +