mirror of
https://github.com/aljazceru/bakery.git
synced 2025-12-17 04:35:13 +01:00
replace all pubkey inputs to user inputs in mcp
This commit is contained in:
@@ -41,7 +41,6 @@
|
|||||||
"applesauce-loaders": "next",
|
"applesauce-loaders": "next",
|
||||||
"applesauce-relay": "next",
|
"applesauce-relay": "next",
|
||||||
"applesauce-signers": "next",
|
"applesauce-signers": "next",
|
||||||
"nostr-bakery-common": "^0.1.0",
|
|
||||||
"better-sqlite3": "^11.9.1",
|
"better-sqlite3": "^11.9.1",
|
||||||
"blossom-client-sdk": "^2.1.1",
|
"blossom-client-sdk": "^2.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@@ -63,6 +62,7 @@
|
|||||||
"mkdirp": "^3.0.1",
|
"mkdirp": "^3.0.1",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"node-graceful-shutdown": "^1.1.5",
|
"node-graceful-shutdown": "^1.1.5",
|
||||||
|
"nostr-bakery-common": "^0.1.0",
|
||||||
"nostr-tools": "^2.12.0",
|
"nostr-tools": "^2.12.0",
|
||||||
"pac-proxy-agent": "^7.2.0",
|
"pac-proxy-agent": "^7.2.0",
|
||||||
"process-streams": "^1.0.3",
|
"process-streams": "^1.0.3",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.28.1",
|
"@changesets/cli": "^2.28.1",
|
||||||
"@modelcontextprotocol/inspector": "^0.7.0",
|
"@modelcontextprotocol/inspector": "^0.8.2",
|
||||||
"@swc-node/register": "^1.10.10",
|
"@swc-node/register": "^1.10.10",
|
||||||
"@swc/core": "^1.11.18",
|
"@swc/core": "^1.11.18",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
|||||||
@@ -10,23 +10,17 @@ import cors from "cors";
|
|||||||
|
|
||||||
import { logger } from "../logger.js";
|
import { logger } from "../logger.js";
|
||||||
|
|
||||||
import { NIP_11_SOFTWARE_URL, SENSITIVE_KINDS } from "../const.js";
|
import { NIP_11_SOFTWARE_URL } from "../const.js";
|
||||||
import { OWNER_PUBKEY, BAKERY_PORT } from "../env.js";
|
import { OWNER_PUBKEY, BAKERY_PORT } from "../env.js";
|
||||||
|
|
||||||
import ControlApi from "../modules/control/control-api.js";
|
|
||||||
import DirectMessageManager from "../modules/direct-message-manager.js";
|
import DirectMessageManager from "../modules/direct-message-manager.js";
|
||||||
import DirectMessageActions from "../modules/control/dm-actions.js";
|
|
||||||
import AddressBook from "../modules/address-book.js";
|
import AddressBook from "../modules/address-book.js";
|
||||||
import NotificationsManager from "../modules/notifications/notifications-manager.js";
|
import NotificationsManager from "../modules/notifications/notifications-manager.js";
|
||||||
import NotificationActions from "../modules/control/notification-actions.js";
|
|
||||||
import ProfileBook from "../modules/profile-book.js";
|
import ProfileBook from "../modules/profile-book.js";
|
||||||
import ContactBook from "../modules/contact-book.js";
|
import ContactBook from "../modules/contact-book.js";
|
||||||
import CautiousPool from "../modules/cautious-pool.js";
|
import CautiousPool from "../modules/cautious-pool.js";
|
||||||
import RemoteAuthActions from "../modules/control/remote-auth-actions.js";
|
|
||||||
import LogStore from "../modules/log-store/log-store.js";
|
import LogStore from "../modules/log-store/log-store.js";
|
||||||
import DecryptionCache from "../modules/decryption-cache/decryption-cache.js";
|
import DecryptionCache from "../modules/decryption-cache/decryption-cache.js";
|
||||||
import DecryptionCacheActions from "../modules/control/decryption-cache.js";
|
|
||||||
import LogsActions from "../modules/control/logs-actions.js";
|
|
||||||
import ApplicationStateManager from "../modules/application-state/manager.js";
|
import ApplicationStateManager from "../modules/application-state/manager.js";
|
||||||
import InboundNetworkManager from "../modules/network/inbound/index.js";
|
import InboundNetworkManager from "../modules/network/inbound/index.js";
|
||||||
import OutboundNetworkManager from "../modules/network/outbound/index.js";
|
import OutboundNetworkManager from "../modules/network/outbound/index.js";
|
||||||
@@ -71,7 +65,6 @@ export default class App extends EventEmitter<EventMap> {
|
|||||||
eventStore: SQLiteEventStore;
|
eventStore: SQLiteEventStore;
|
||||||
logStore: LogStore;
|
logStore: LogStore;
|
||||||
relay: NostrRelay;
|
relay: NostrRelay;
|
||||||
control: ControlApi;
|
|
||||||
pool: CautiousPool;
|
pool: CautiousPool;
|
||||||
addressBook: AddressBook;
|
addressBook: AddressBook;
|
||||||
profileBook: ProfileBook;
|
profileBook: ProfileBook;
|
||||||
@@ -167,21 +160,6 @@ export default class App extends EventEmitter<EventMap> {
|
|||||||
if (config.owner) this.directMessageManager.watchInbox(config.owner);
|
if (config.owner) this.directMessageManager.watchInbox(config.owner);
|
||||||
});
|
});
|
||||||
|
|
||||||
// API for controlling the node
|
|
||||||
this.control = new ControlApi(this);
|
|
||||||
this.control.registerHandler(new DirectMessageActions(this));
|
|
||||||
this.control.registerHandler(new NotificationActions(this));
|
|
||||||
this.control.registerHandler(new RemoteAuthActions(this));
|
|
||||||
this.control.registerHandler(new DecryptionCacheActions(this));
|
|
||||||
|
|
||||||
this.control.registerHandler(new LogsActions(this));
|
|
||||||
|
|
||||||
// connect control api to websocket server
|
|
||||||
this.control.attachToServer(this.wss);
|
|
||||||
|
|
||||||
// if process has an RPC interface, attach control api to it
|
|
||||||
if (process.send) this.control.attachToProcess(process);
|
|
||||||
|
|
||||||
const connection = onConnection(this.wss);
|
const connection = onConnection(this.wss);
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
@@ -251,13 +229,6 @@ export default class App extends EventEmitter<EventMap> {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// when the owner NIP-42 authenticates with the relay pass it along to the control
|
|
||||||
this.relay.on("socket:auth", (ws, auth) => {
|
|
||||||
if (auth.pubkey === this.config.data.owner) {
|
|
||||||
this.control.authenticatedConnections.add(ws);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if socket is unauthenticated only allow owner's events and incoming DMs
|
// if socket is unauthenticated only allow owner's events and incoming DMs
|
||||||
this.relay.registerEventHandler((ctx, next) => {
|
this.relay.registerEventHandler((ctx, next) => {
|
||||||
const auth = ctx.relay.getSocketAuth(ctx.socket);
|
const auth = ctx.relay.getSocketAuth(ctx.socket);
|
||||||
@@ -287,22 +258,22 @@ export default class App extends EventEmitter<EventMap> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// block subscriptions for sensitive kinds unless NIP-42 auth or Auth Code
|
// block subscriptions for sensitive kinds unless NIP-42 auth or Auth Code
|
||||||
this.relay.registerSubscriptionFilter((ctx, next) => {
|
// this.relay.registerSubscriptionFilter((ctx, next) => {
|
||||||
// always allow if authenticated with auth code
|
// // always allow if authenticated with auth code
|
||||||
const isAuthenticatedWithAuthCode = this.control.authenticatedConnections.has(ctx.socket);
|
// const isAuthenticatedWithAuthCode = this.control.authenticatedConnections.has(ctx.socket);
|
||||||
if (isAuthenticatedWithAuthCode) return next();
|
// if (isAuthenticatedWithAuthCode) return next();
|
||||||
|
|
||||||
const hasSensitiveKinds = ctx.filters.some(
|
// const hasSensitiveKinds = ctx.filters.some(
|
||||||
(filter) => filter.kinds && SENSITIVE_KINDS.some((k) => filter.kinds?.includes(k)),
|
// (filter) => filter.kinds && SENSITIVE_KINDS.some((k) => filter.kinds?.includes(k)),
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (hasSensitiveKinds) {
|
// if (hasSensitiveKinds) {
|
||||||
const auth = ctx.relay.getSocketAuth(ctx.socket);
|
// const auth = ctx.relay.getSocketAuth(ctx.socket);
|
||||||
if (!auth) throw new Error(ctx.relay.makeAuthRequiredReason("Cant view sensitive events without auth"));
|
// if (!auth) throw new Error(ctx.relay.makeAuthRequiredReason("Cant view sensitive events without auth"));
|
||||||
}
|
// }
|
||||||
|
|
||||||
return next();
|
// return next();
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Handle possible additional actions when the event store receives a new message
|
// Handle possible additional actions when the event store receives a new message
|
||||||
this.eventStore.on("event:inserted", (event) => {
|
this.eventStore.on("event:inserted", (event) => {
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
import { WebSocket, WebSocketServer } from "ws";
|
|
||||||
import { type IncomingMessage } from "http";
|
|
||||||
import { ControlResponse } from "@satellite-earth/core/types/control-api/index.js";
|
|
||||||
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { logger } from "../../logger.js";
|
|
||||||
|
|
||||||
export type ControlMessage = ["CONTROL", string, string, ...any[]];
|
|
||||||
export interface ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name: string;
|
|
||||||
handleConnection?(ws: WebSocket | NodeJS.Process): void;
|
|
||||||
handleDisconnect?(socket: WebSocket): void;
|
|
||||||
handleMessage(sock: WebSocket | NodeJS.Process, message: ControlMessage): boolean | Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** handles web socket connections and 'CONTROL' messages */
|
|
||||||
export default class ControlApi {
|
|
||||||
app: App;
|
|
||||||
auth?: string;
|
|
||||||
log = logger.extend("ControlApi");
|
|
||||||
handlers = new Map<string, ControlMessageHandler>();
|
|
||||||
|
|
||||||
authenticatedConnections = new Set<WebSocket | NodeJS.Process>();
|
|
||||||
|
|
||||||
constructor(app: App, auth?: string) {
|
|
||||||
this.app = app;
|
|
||||||
this.auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerHandler(handler: ControlMessageHandler) {
|
|
||||||
this.handlers.set(handler.name, handler);
|
|
||||||
}
|
|
||||||
unregisterHandler(handler: ControlMessageHandler) {
|
|
||||||
this.handlers.delete(handler.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** start listening for incoming ws connections */
|
|
||||||
attachToServer(wss: WebSocketServer) {
|
|
||||||
wss.on("connection", this.handleConnection.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConnection(ws: WebSocket, req: IncomingMessage) {
|
|
||||||
ws.on("message", (data, isBinary) => {
|
|
||||||
this.handleRawMessage(ws, data as Buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [id, handler] of this.handlers) {
|
|
||||||
handler.handleConnection?.(ws);
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.once("close", () => this.handleDisconnect(ws));
|
|
||||||
}
|
|
||||||
handleDisconnect(ws: WebSocket) {
|
|
||||||
this.authenticatedConnections.delete(ws);
|
|
||||||
|
|
||||||
for (const [id, handler] of this.handlers) {
|
|
||||||
handler.handleDisconnect?.(ws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachToProcess(p: NodeJS.Process) {
|
|
||||||
p.on("message", (message) => {
|
|
||||||
if (
|
|
||||||
Array.isArray(message) &&
|
|
||||||
message[0] === "CONTROL" &&
|
|
||||||
typeof message[1] === "string" &&
|
|
||||||
typeof message[2] === "string"
|
|
||||||
) {
|
|
||||||
this.handleMessage(p, message as ControlMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [id, handler] of this.handlers) {
|
|
||||||
handler.handleConnection?.(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** handle a ws message */
|
|
||||||
async handleRawMessage(ws: WebSocket | NodeJS.Process, message: Buffer) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(message.toString()) as string[];
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data[0] === "CONTROL" &&
|
|
||||||
typeof data[1] === "string" &&
|
|
||||||
typeof data[2] === "string"
|
|
||||||
) {
|
|
||||||
if (this.authenticatedConnections.has(ws) || data[1] === "AUTH") {
|
|
||||||
await this.handleMessage(ws, data as ControlMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.log("Failed to handle Control message", message.toString("utf-8"));
|
|
||||||
this.log(err);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// failed to parse JSON, do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** handle a ['CONTROL', ...] message */
|
|
||||||
async handleMessage(sock: WebSocket | NodeJS.Process, message: ControlMessage) {
|
|
||||||
// handle ['CONTROL', 'AUTH', <code>] messages
|
|
||||||
if (message[1] === "AUTH" && message[2] === "CODE") {
|
|
||||||
const code = message[3];
|
|
||||||
if (code === this.auth) {
|
|
||||||
this.authenticatedConnections.add(sock);
|
|
||||||
this.send(sock, ["CONTROL", "AUTH", "SUCCESS"]);
|
|
||||||
} else {
|
|
||||||
this.send(sock, ["CONTROL", "AUTH", "INVALID", "Invalid Auth Code"]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handler = this.handlers.get(message[1]);
|
|
||||||
if (handler) {
|
|
||||||
return await handler.handleMessage(sock, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log("Failed to handle Control message", message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
send(sock: WebSocket | NodeJS.Process, response: ControlResponse) {
|
|
||||||
sock.send?.(JSON.stringify(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { WebSocket } from "ws";
|
|
||||||
import {
|
|
||||||
DecryptionCacheMessage,
|
|
||||||
DecryptionCacheResponse,
|
|
||||||
} from "@satellite-earth/core/types/control-api/decryption-cache.js";
|
|
||||||
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { type ControlMessageHandler } from "./control-api.js";
|
|
||||||
|
|
||||||
/** handles ['CONTROL', 'DECRYPTION-CACHE', ...] messages */
|
|
||||||
export default class DecryptionCacheActions implements ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name = "DECRYPTION-CACHE";
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessage(sock: WebSocket | NodeJS.Process, message: DecryptionCacheMessage) {
|
|
||||||
const method = message[2];
|
|
||||||
switch (method) {
|
|
||||||
case "ADD-CONTENT":
|
|
||||||
this.app.decryptionCache.addEventContent(message[3], message[4]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "CLEAR":
|
|
||||||
this.app.decryptionCache.clearAll();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "REQUEST":
|
|
||||||
const contents = this.app.decryptionCache.getEventsContent(message[3]);
|
|
||||||
for (const { event, content } of contents)
|
|
||||||
this.send(sock, ["CONTROL", "DECRYPTION-CACHE", "CONTENT", event, content]);
|
|
||||||
this.send(sock, ["CONTROL", "DECRYPTION-CACHE", "END"]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send(sock: WebSocket | NodeJS.Process, response: DecryptionCacheResponse) {
|
|
||||||
sock.send?.(JSON.stringify(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { WebSocket } from "ws";
|
|
||||||
import { DirectMessageMessage } from "@satellite-earth/core/types/control-api/direct-messages.js";
|
|
||||||
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { type ControlMessageHandler } from "./control-api.js";
|
|
||||||
|
|
||||||
/** handles ['CONTROL', 'DM', ...] messages */
|
|
||||||
export default class DirectMessageActions implements ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name = "DM";
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessage(sock: WebSocket | NodeJS.Process, message: DirectMessageMessage) {
|
|
||||||
const method = message[2];
|
|
||||||
switch (method) {
|
|
||||||
case "OPEN":
|
|
||||||
this.app.directMessageManager.openConversation(message[3], message[4]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "CLOSE":
|
|
||||||
this.app.directMessageManager.closeConversation(message[3], message[4]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { WebSocket } from "ws";
|
|
||||||
import { LogsMessage } from "@satellite-earth/core/types/control-api/logs.js";
|
|
||||||
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { type ControlMessageHandler } from "./control-api.js";
|
|
||||||
|
|
||||||
/** handles ['CONTROL', 'DM', ...] messages */
|
|
||||||
export default class LogsActions implements ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name = "LOGS";
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessage(sock: WebSocket | NodeJS.Process, message: LogsMessage) {
|
|
||||||
const method = message[2];
|
|
||||||
switch (method) {
|
|
||||||
case "CLEAR":
|
|
||||||
this.app.logStore.clearLogs(message[3] ? { service: message[3] } : undefined);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { WebSocket } from "ws";
|
|
||||||
import { NotificationsMessage, NotificationsResponse } from "@satellite-earth/core/types/control-api/notifications.js";
|
|
||||||
|
|
||||||
import { ControlMessageHandler } from "./control-api.js";
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { NostrEvent } from "nostr-tools";
|
|
||||||
|
|
||||||
export default class NotificationActions implements ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name = "NOTIFICATIONS";
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessage(sock: WebSocket | NodeJS.Process, message: NotificationsMessage): boolean {
|
|
||||||
const action = message[2];
|
|
||||||
switch (action) {
|
|
||||||
case "GET-VAPID-KEY":
|
|
||||||
this.send(sock, ["CONTROL", "NOTIFICATIONS", "VAPID-KEY", this.app.notifications.webPushKeys.publicKey]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "REGISTER":
|
|
||||||
this.app.notifications.addOrUpdateChannel(message[3]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "NOTIFY":
|
|
||||||
const event: NostrEvent | undefined = this.app.eventStore.getEventsForFilters([{ ids: [message[3]] }])?.[0];
|
|
||||||
if (event) this.app.notifications.notify(event);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "UNREGISTER":
|
|
||||||
this.app.notifications.removeChannel(message[3]);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
send(sock: WebSocket | NodeJS.Process, response: NotificationsResponse) {
|
|
||||||
sock.send?.(JSON.stringify(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { WebSocket } from "ws";
|
|
||||||
import { verifyEvent } from "nostr-tools";
|
|
||||||
import { RemoteAuthMessage, RemoteAuthResponse } from "@satellite-earth/core/types/control-api/remote-auth.js";
|
|
||||||
|
|
||||||
import type App from "../../app/index.js";
|
|
||||||
import { type ControlMessageHandler } from "./control-api.js";
|
|
||||||
|
|
||||||
/** handles ['CONTROL', 'REMOTE-AUTH', ...] messages */
|
|
||||||
export default class RemoteAuthActions implements ControlMessageHandler {
|
|
||||||
app: App;
|
|
||||||
name = "REMOTE-AUTH";
|
|
||||||
|
|
||||||
private subscribed = new Set<WebSocket | NodeJS.Process>();
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
|
|
||||||
// when config changes send it to the subscribed sockets
|
|
||||||
this.app.pool.emitter.on("challenge", (relay, challenge) => {
|
|
||||||
for (const sock of this.subscribed) {
|
|
||||||
this.send(sock, [
|
|
||||||
"CONTROL",
|
|
||||||
"REMOTE-AUTH",
|
|
||||||
"STATUS",
|
|
||||||
relay.url,
|
|
||||||
challenge,
|
|
||||||
!!this.app.pool.authenticated.get(relay.url),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAllStatuses(sock: WebSocket | NodeJS.Process) {
|
|
||||||
for (const [url, relay] of this.app.pool) {
|
|
||||||
const challenge = this.app.pool.challenges.get(url);
|
|
||||||
const authenticated = this.app.pool.isAuthenticated(url);
|
|
||||||
|
|
||||||
if (challenge) {
|
|
||||||
this.send(sock, ["CONTROL", "REMOTE-AUTH", "STATUS", url, challenge, authenticated]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleMessage(sock: WebSocket | NodeJS.Process, message: RemoteAuthMessage) {
|
|
||||||
const method = message[2];
|
|
||||||
switch (method) {
|
|
||||||
case "SUBSCRIBE":
|
|
||||||
this.subscribed.add(sock);
|
|
||||||
sock.once("close", () => this.subscribed.delete(sock));
|
|
||||||
this.sendAllStatuses(sock);
|
|
||||||
return true;
|
|
||||||
case "UNSUBSCRIBE":
|
|
||||||
this.subscribed.delete(sock);
|
|
||||||
return true;
|
|
||||||
case "AUTHENTICATE":
|
|
||||||
const event = message[3];
|
|
||||||
if (verifyEvent(event)) {
|
|
||||||
const relay = event.tags.find((t) => (t[0] = "relay"))?.[1];
|
|
||||||
if (relay) await this.app.pool.authenticate(relay, event);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send(sock: WebSocket | NodeJS.Process, response: RemoteAuthResponse) {
|
|
||||||
sock.send?.(JSON.stringify(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { getProfileContent } from "applesauce-core/helpers";
|
import { getProfileContent } from "applesauce-core/helpers";
|
||||||
import { kinds } from "nostr-tools";
|
import { Filter, kinds } from "nostr-tools";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import mcpServer from "../server.js";
|
|
||||||
import { ownerFactory, ownerPublish } from "../../owner-signer.js";
|
|
||||||
import bakeryConfig from "../../bakery-config.js";
|
import bakeryConfig from "../../bakery-config.js";
|
||||||
import eventCache from "../../event-cache.js";
|
import eventCache from "../../event-cache.js";
|
||||||
import { normalizeToHexPubkey } from "../../../helpers/nip19.js";
|
|
||||||
import { asyncLoader } from "../../loaders.js";
|
import { asyncLoader } from "../../loaders.js";
|
||||||
|
import { ownerFactory, ownerPublish } from "../../owner-signer.js";
|
||||||
|
import { eventInput, userInput } from "../inputs.js";
|
||||||
|
import mcpServer from "../server.js";
|
||||||
|
|
||||||
mcpServer.tool(
|
mcpServer.tool(
|
||||||
"sign_draft_event",
|
"sign_draft_event",
|
||||||
@@ -70,9 +70,17 @@ mcpServer.tool(
|
|||||||
mcpServer.tool(
|
mcpServer.tool(
|
||||||
"search_events",
|
"search_events",
|
||||||
"Search for events using a sqlite FTS5 search query",
|
"Search for events using a sqlite FTS5 search query",
|
||||||
{ query: z.string(), kind: z.number().default(1), limit: z.number().default(50) },
|
{
|
||||||
async ({ query, kind, limit }) => {
|
query: z.string().describe("The sqlite FTS5 search query"),
|
||||||
const events = await eventCache.getEventsForFilters([{ kinds: [kind], limit, search: query }]);
|
kind: z.number().default(1).describe("The kind of events to search for"),
|
||||||
|
limit: z.number().default(50).describe("The number of events to return"),
|
||||||
|
author: userInput.optional().describe("The author of the events to search for"),
|
||||||
|
},
|
||||||
|
async ({ query, kind, limit, author }) => {
|
||||||
|
const filter: Filter = { kinds: [kind], limit, search: query };
|
||||||
|
if (author) filter.authors = [author];
|
||||||
|
|
||||||
|
const events = await eventCache.getEventsForFilters([filter]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
||||||
@@ -81,9 +89,7 @@ mcpServer.tool(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: this needs to accept naddr, and nevent
|
// TODO: this needs to accept naddr, and nevent
|
||||||
mcpServer.tool("get_event", "Get an event by id", { id: z.string().length(64) }, async ({ id }) => {
|
mcpServer.tool("get_event_json", "Gets the full event as json", { event: eventInput }, async ({ event }) => {
|
||||||
const event = await eventCache.getEventsForFilters([{ ids: [id] }]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(event) }],
|
content: [{ type: "text", text: JSON.stringify(event) }],
|
||||||
};
|
};
|
||||||
@@ -92,7 +98,10 @@ mcpServer.tool("get_event", "Get an event by id", { id: z.string().length(64) },
|
|||||||
mcpServer.tool(
|
mcpServer.tool(
|
||||||
"search_users",
|
"search_users",
|
||||||
"Search for users using a sqlite FTS5 search query",
|
"Search for users using a sqlite FTS5 search query",
|
||||||
{ query: z.string(), limit: z.number().default(20) },
|
{
|
||||||
|
query: z.string().describe("The sqlite FTS5 search query"),
|
||||||
|
limit: z.number().default(20).describe("The number of users to return"),
|
||||||
|
},
|
||||||
async ({ query, limit }) => {
|
async ({ query, limit }) => {
|
||||||
const profiles = await eventCache.getEventsForFilters([{ search: query, kinds: [kinds.Metadata], limit }]);
|
const profiles = await eventCache.getEventsForFilters([{ search: query, kinds: [kinds.Metadata], limit }]);
|
||||||
|
|
||||||
@@ -123,17 +132,14 @@ mcpServer.tool(
|
|||||||
"get_users_recent_events",
|
"get_users_recent_events",
|
||||||
"Gets a list of recent events created by a pubkey",
|
"Gets a list of recent events created by a pubkey",
|
||||||
{
|
{
|
||||||
pubkey: z
|
user: userInput.describe("The user to get events for"),
|
||||||
.string()
|
|
||||||
.transform((hex) => normalizeToHexPubkey(hex, true))
|
|
||||||
.describe("The pubkey of the user to get events for"),
|
|
||||||
limit: z.number().default(10).describe("The number of events to return"),
|
limit: z.number().default(10).describe("The number of events to return"),
|
||||||
kinds: z.array(z.number()).default([kinds.ShortTextNote]).describe("The kind number of events to return"),
|
kinds: z.array(z.number()).default([kinds.ShortTextNote]).describe("The kind number of events to return"),
|
||||||
since: z.number().optional().describe("The unix timestamp to start the search from"),
|
since: z.number().optional().describe("The unix timestamp to start the search from"),
|
||||||
until: z.number().optional().describe("The unix timestamp to end the search at"),
|
until: z.number().optional().describe("The unix timestamp to end the search at"),
|
||||||
},
|
},
|
||||||
async ({ pubkey, limit, kinds, since, until }) => {
|
async ({ user, limit, kinds, since, until }) => {
|
||||||
const events = await eventCache.getEventsForFilters([{ authors: [pubkey], limit, kinds, since, until }]);
|
const events = await eventCache.getEventsForFilters([{ authors: [user], limit, kinds, since, until }]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
||||||
@@ -142,20 +148,17 @@ mcpServer.tool(
|
|||||||
);
|
);
|
||||||
|
|
||||||
mcpServer.tool(
|
mcpServer.tool(
|
||||||
"get_events_pubkey_mentioned",
|
"get_events_user_mentioned",
|
||||||
"Gets a list of recent events that the pubkey was mentioned in",
|
"Gets a list of recent events that the user is mentioned in",
|
||||||
{
|
{
|
||||||
pubkey: z
|
user: userInput.describe("The user who is mentioned in the events"),
|
||||||
.string()
|
|
||||||
.transform((hex) => normalizeToHexPubkey(hex, true))
|
|
||||||
.describe("The pubkey of the user to get events for"),
|
|
||||||
limit: z.number().default(10).describe("The number of events to return"),
|
limit: z.number().default(10).describe("The number of events to return"),
|
||||||
kinds: z.array(z.number()).default([kinds.ShortTextNote]).describe("The kind number of events to return"),
|
kinds: z.array(z.number()).default([kinds.ShortTextNote]).describe("The kind number of events to return"),
|
||||||
since: z.number().optional().describe("The unix timestamp to start the search from"),
|
since: z.number().optional().describe("The unix timestamp to start the search from"),
|
||||||
until: z.number().optional().describe("The unix timestamp to end the search at"),
|
until: z.number().optional().describe("The unix timestamp to end the search at"),
|
||||||
},
|
},
|
||||||
async ({ pubkey, limit, kinds, since, until }) => {
|
async ({ user, limit, kinds, since, until }) => {
|
||||||
const events = await eventCache.getEventsForFilters([{ "#p": [pubkey], limit, kinds, since, until }]);
|
const events = await eventCache.getEventsForFilters([{ "#p": [user], limit, kinds, since, until }]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
content: events.map((event) => ({ type: "text", text: JSON.stringify(event) })),
|
||||||
|
|||||||
787
pnpm-lock.yaml
generated
787
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user