Sign In

@aipost/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@aipost/mcp-server - npm Package Compare versions

Comparing version
1.1.5
to
1.1.6
+49
-13
dist/filter.d.ts

@@ -10,13 +10,23 @@ /**

*
* Every blacklist / whitelist entry and every sender address normalises to
* { keyname, alias }. Four input formats are accepted:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
* Supported address formats:
*
* AIPost addresses:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
*
* Standard email addresses (for external senders / domain filtering):
* 5. user@domain.tld (full email — exact match)
* 6. @domain.tld (domain-only — matches any user@domain.tld)
*
* Matching rules:
* - alias must always match (case-insensitive).
* - If the filter entry specifies a keyname, the sender's keyname must also
* match. If the filter entry omits keyname, any keyname is accepted.
* - For AIPost addresses: alias must always match (case-insensitive).
* If the filter entry specifies a keyname, the sender's keyname must also
* match. If the filter entry omits keyname, any keyname is accepted.
* - For standard emails: full case-insensitive exact match on the email.
* - For domain-only patterns (@domain.tld): matches any sender whose address
* ends with @domain.tld.
* - For EXTERNAL_EMAIL messages (IMAP imports): the filter checks both
* `sender` (display name) and `payload.from` (actual email address).
*/

@@ -30,4 +40,15 @@ export interface ParsedAddress {

/**
* Parse an AIPost email address in any of the 4 supported formats.
* Returns null when the address doesn't look like an AIPost address.
* Parse an address in any supported format.
*
* AIPost formats:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
*
* Standard email formats (for external senders / domain-level filtering):
* 5. user@domain.tld (full email)
* 6. @domain.tld (domain-only — used in filter entries)
*
* Returns null when the address is unparseable (e.g. a bare display name).
*/

@@ -47,3 +68,14 @@ export declare function parseAddress(raw: string): ParsedAddress | null;

/**
* Filter an array of objects that have a `sender` field.
* Check whether a mail item is allowed, considering both `sender` and
* `payload.from` (external emails store the real address in payload.from).
*/
isItemAllowed(item: {
sender?: string;
payload?: {
from?: string;
};
}): boolean;
/**
* Filter an array of objects that have a `sender` field (and optionally
* `payload.from` for external emails).
* Returns a new array with blocked senders removed.

@@ -53,2 +85,5 @@ */

sender?: string;
payload?: {
from?: string;
};
}>(items: T[]): T[];

@@ -70,3 +105,4 @@ /**

/**
* Filter SSE events. Each event's `data` may contain a `sender` field.
* Filter SSE events. Each event's `data` may contain a `sender` field
* and/or `payload.from` (external emails).
*/

@@ -73,0 +109,0 @@ filterEvents<T extends {

@@ -10,13 +10,23 @@ /**

*
* Every blacklist / whitelist entry and every sender address normalises to
* { keyname, alias }. Four input formats are accepted:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
* Supported address formats:
*
* AIPost addresses:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
*
* Standard email addresses (for external senders / domain filtering):
* 5. user@domain.tld (full email — exact match)
* 6. @domain.tld (domain-only — matches any user@domain.tld)
*
* Matching rules:
* - alias must always match (case-insensitive).
* - If the filter entry specifies a keyname, the sender's keyname must also
* match. If the filter entry omits keyname, any keyname is accepted.
* - For AIPost addresses: alias must always match (case-insensitive).
* If the filter entry specifies a keyname, the sender's keyname must also
* match. If the filter entry omits keyname, any keyname is accepted.
* - For standard emails: full case-insensitive exact match on the email.
* - For domain-only patterns (@domain.tld): matches any sender whose address
* ends with @domain.tld.
* - For EXTERNAL_EMAIL messages (IMAP imports): the filter checks both
* `sender` (display name) and `payload.from` (actual email address).
*/

@@ -26,4 +36,15 @@ const WHITELIST_ENV = "AIPOST_SENDER_WHITELIST";

/**
* Parse an AIPost email address in any of the 4 supported formats.
* Returns null when the address doesn't look like an AIPost address.
* Parse an address in any supported format.
*
* AIPost formats:
* 1. alias.aipost.email (short dot)
* 2. keyname.alias.aipost.email (full dot)
* 3. alias@aipost.email (short at)
* 4. keyname.alias@aipost.email (full at)
*
* Standard email formats (for external senders / domain-level filtering):
* 5. user@domain.tld (full email)
* 6. @domain.tld (domain-only — used in filter entries)
*
* Returns null when the address is unparseable (e.g. a bare display name).
*/

@@ -34,3 +55,3 @@ export function parseAddress(raw) {

return null;
// At-format: alias@aipost.email or keyname.alias@aipost.email
// At-format
const atIdx = s.indexOf("@");

@@ -40,5 +61,13 @@ if (atIdx !== -1) {

const domain = s.slice(atIdx + 1);
if (domain !== "aipost.email")
return null;
return parseLocal(local);
// AIPost address: alias@aipost.email or keyname.alias@aipost.email
if (domain === "aipost.email") {
return parseLocal(local);
}
// Standard email: user@domain.tld or domain-only pattern: @domain.tld
if (domain.includes(".")) {
// local is empty for @domain.tld patterns; non-empty for user@domain.tld
return { alias: s, keyname: null };
}
// Bare domain without TLD (e.g. "user@localhost") — not useful for filtering
return null;
}

@@ -125,3 +154,24 @@ // Dot-format: alias.aipost.email or keyname.alias.aipost.email

/**
* Filter an array of objects that have a `sender` field.
* Check whether a mail item is allowed, considering both `sender` and
* `payload.from` (external emails store the real address in payload.from).
*/
isItemAllowed(item) {
if (!this.active)
return true;
// Check sender field first
if (item.sender && this.isAllowed(item.sender))
return true;
// Fallback: for EXTERNAL_EMAIL, the sender field may be a display name;
// the actual email address is in payload.from.
if (item.payload?.from && this.isAllowed(item.payload.from))
return true;
// If we have any sender info but neither check passed, block
if (item.sender || item.payload?.from)
return false;
// No sender info at all — allow through
return true;
}
/**
* Filter an array of objects that have a `sender` field (and optionally
* `payload.from` for external emails).
* Returns a new array with blocked senders removed.

@@ -132,7 +182,3 @@ */

return items;
return items.filter((item) => {
if (!item.sender)
return true; // no sender field — allow through
return this.isAllowed(item.sender);
});
return items.filter((item) => this.isItemAllowed(item));
}

@@ -166,3 +212,4 @@ /**

/**
* Filter SSE events. Each event's `data` may contain a `sender` field.
* Filter SSE events. Each event's `data` may contain a `sender` field
* and/or `payload.from` (external emails).
*/

@@ -175,6 +222,9 @@ filterEvents(events) {

return true;
const sender = event.data.sender;
if (typeof sender !== "string")
return true;
return this.isAllowed(sender);
const d = event.data;
const sender = typeof d.sender === "string" ? d.sender : undefined;
const payload = d.payload && typeof d.payload === "object"
? d.payload
: undefined;
const from = payload && typeof payload.from === "string" ? payload.from : undefined;
return this.isItemAllowed({ sender, payload: from ? { from } : undefined });
});

@@ -207,2 +257,11 @@ }

function entryMatches(entry, sender) {
// ── Domain-only pattern: @domain.tld matches any user@domain.tld ──────
if (entry.alias.startsWith("@")) {
return sender.alias.endsWith(entry.alias);
}
// ── Standard email matching (both contain @, non-AIPost) ──────────────
if (entry.alias.includes("@") && sender.alias.includes("@")) {
return entry.alias === sender.alias;
}
// ── AIPost address matching ────────────────────────────────────────────
// alias must always match (case-insensitive — already lowercased)

@@ -209,0 +268,0 @@ if (entry.alias !== sender.alias)

@@ -234,5 +234,5 @@ import { createRequire } from "module";

result = await client.getMessage(args.messageId);
// Block if sender is filtered
if (senderFilter.active && result?.sender) {
if (!senderFilter.isAllowed(result.sender)) {
// Block if sender is filtered (checks both sender and payload.from)
if (senderFilter.active) {
if (!senderFilter.isItemAllowed(result)) {
throw new Error(`Message ${args.messageId} not found`);

@@ -290,5 +290,5 @@ }

result = await client.getThread(args.threadId);
// Filter messages in thread by sender
// Filter messages in thread by sender (checks both sender and payload.from)
if (senderFilter.active && Array.isArray(result)) {
result = senderFilter.filterBySender(result.map((m) => ({ ...m, sender: m.sender })));
result = senderFilter.filterBySender(result);
}

@@ -299,5 +299,5 @@ break;

result = await client.deleteMessage(args.messageId);
// Filter response sender
if (senderFilter.active && result?.sender) {
if (!senderFilter.isAllowed(result.sender)) {
// Filter response sender (checks both sender and payload.from)
if (senderFilter.active) {
if (!senderFilter.isItemAllowed(result)) {
throw new Error(`Message ${args.messageId} not found`);

@@ -304,0 +304,0 @@ }

{
"name": "@aipost/mcp-server",
"version": "1.1.5",
"version": "1.1.6",
"mcpName": "io.github.AIPOST-EMAIL/mcp-server",

@@ -5,0 +5,0 @@ "description": "MCP Server for AIPost.email — typed, structured messaging for AI agents with Ed25519 identities",