chore: remove dead v1 MCP code and legacy schema dump

- src/mcp.ts: unused v1 JSON-RPC MCP implementation (claimed Basic Auth,
  old tool names); the live server is src/mcp/server.ts
- test/mcp.test.ts: only tested the dead file
- db/schema.sql: superseded by migrations/
This commit is contained in:
TooonyChen committed 2026-07-08 00:48:13 +12:00
1 parent b71375bfa7
commit 5b6e8b0efc
4 files changed
+1 -571

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@
- `src/email/handler.ts` — incoming email pipeline; `src/email/rpcEmail.ts` — RPC-compatible `ForwardableEmailMessage` wrapper.
- `src/mcp/server.ts` — remote MCP server at `/mcp` (`@hono/mcp` Streamable HTTP): `list_addresses`, `list_codes`, `get_latest_code`, `wait_for_code`.
- `web/` — React 18 + Vite + Tailwind + shadcn/ui frontend. Built to `web/dist`, served via Cloudflare `ASSETS` binding. Pages: Login (doubles as first-admin setup), Inbox, API Keys, Users & Access (admin).
- `migrations/` — D1 schema, applied with `wrangler d1 migrations apply`. Tables: `raw_mails`, `code_mails` (+ `category`), `users`, `api_keys`, `grants`. (`db/schema.sql` is the pre-migration legacy dump — do not use it.)
- `migrations/` — D1 schema, applied with `wrangler d1 migrations apply`. Tables: `raw_mails`, `code_mails` (+ `category`), `users`, `api_keys`, `grants`.
## Commands
-26
View File
@@ -1,26 +0,0 @@
CREATE TABLE IF NOT EXISTS raw_mails (
id INTEGER PRIMARY KEY,
message_id TEXT UNIQUE,
from_addr TEXT,
to_addr TEXT,
subject TEXT,
raw TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
processed INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS code_mails (
id INTEGER PRIMARY KEY,
message_id TEXT UNIQUE,
from_addr TEXT,
from_org TEXT,
to_addr TEXT,
topic TEXT,
code TEXT,
expires_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (message_id) REFERENCES raw_mails(message_id)
);
CREATE INDEX idx_raw_message_id ON raw_mails (message_id);
CREATE INDEX idx_code_message_id ON code_mails (message_id);
-386
View File
@@ -1,386 +0,0 @@
const MCP_PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'] as const;
const DEFAULT_HTTP_MCP_PROTOCOL_VERSION = '2025-03-26';
const MAX_MCP_MAIL_LIMIT = 50;
const MCP_TOOLS = [
{
name: 'get_latest_mail',
title: 'Get Latest Mail',
description: 'Return the newest extracted mail with parsed details and decoded message bodies.',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
},
{
name: 'get_multiple_mail',
title: 'Get Multiple Mail',
description: `Return the newest extracted mails sorted by createdAt descending. Defaults to 5 and caps at ${MAX_MCP_MAIL_LIMIT}.`,
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'integer',
minimum: 1,
maximum: MAX_MCP_MAIL_LIMIT,
default: 5,
description: `How many latest mails to return. Defaults to 5, maximum ${MAX_MCP_MAIL_LIMIT}.`,
},
},
additionalProperties: false,
},
},
] as const;
type SupportedMcpProtocolVersion = (typeof MCP_PROTOCOL_VERSIONS)[number];
type JsonRpcId = string | number | null;
export interface McpMailDetail {
id: number;
messageId: string | null;
fromOrg: string | null;
fromAddr: string | null;
toAddr: string | null;
subject: string | null;
topic: string | null;
code: string | null;
createdAt: string | null;
textBody: string | null;
htmlBody: string | null;
}
interface JsonRpcRequest {
jsonrpc: '2.0';
id?: JsonRpcId;
method?: string;
params?: unknown;
result?: unknown;
error?: unknown;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isSupportedMcpProtocolVersion(value: string): value is SupportedMcpProtocolVersion {
return (MCP_PROTOCOL_VERSIONS as readonly string[]).includes(value);
}
function getEffectiveMcpProtocolVersion(request: Request): SupportedMcpProtocolVersion {
const headerValue = request.headers.get('MCP-Protocol-Version');
if (headerValue && isSupportedMcpProtocolVersion(headerValue)) {
return headerValue;
}
return DEFAULT_HTTP_MCP_PROTOCOL_VERSION;
}
function buildMcpResponseHeaders(protocolVersion?: SupportedMcpProtocolVersion): Record<string, string> {
const headers: Record<string, string> = {
'Cache-Control': 'no-store',
};
if (protocolVersion) {
headers['MCP-Protocol-Version'] = protocolVersion;
}
return headers;
}
function methodNotAllowedResponse(allowed: string): Response {
return new Response('Method Not Allowed', {
status: 405,
headers: {
Allow: allowed,
'Cache-Control': 'no-store',
},
});
}
function acceptedMcpNotificationResponse(protocolVersion?: SupportedMcpProtocolVersion): Response {
return new Response(null, {
status: 202,
headers: buildMcpResponseHeaders(protocolVersion),
});
}
function makeJsonRpcSuccessResponse(id: JsonRpcId, result: unknown, protocolVersion: SupportedMcpProtocolVersion): Response {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id,
result,
}),
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
...buildMcpResponseHeaders(protocolVersion),
},
}
);
}
function makeJsonRpcErrorResponse(
id: JsonRpcId,
code: number,
message: string,
protocolVersion: SupportedMcpProtocolVersion,
data?: unknown
): Response {
const errorBody: {
jsonrpc: '2.0';
id: JsonRpcId;
error: {
code: number;
message: string;
data?: unknown;
};
} = {
jsonrpc: '2.0',
id,
error: {
code,
message,
},
};
if (data !== undefined) {
errorBody.error.data = data;
}
return new Response(JSON.stringify(errorBody), {
status: code === -32700 || code === -32600 ? 400 : 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
...buildMcpResponseHeaders(protocolVersion),
},
});
}
function makeMcpToolResult(payload: Record<string, unknown>, isError = false) {
return {
content: [
{
type: 'text',
text: JSON.stringify(payload, null, 2),
},
],
structuredContent: payload,
...(isError ? { isError: true } : {}),
};
}
function isAllowedMcpOrigin(request: Request): boolean {
const origin = request.headers.get('Origin');
if (!origin || origin === 'null') {
return true;
}
return origin === new URL(request.url).origin;
}
function parsePositiveInteger(value: unknown): number | null {
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
return value;
}
if (typeof value === 'string' && /^\d+$/.test(value)) {
const parsedValue = Number.parseInt(value, 10);
return parsedValue > 0 ? parsedValue : null;
}
return null;
}
async function executeMcpTool(
name: string,
args: Record<string, unknown> | undefined,
readLatestMailDetails: (limit: number) => Promise<McpMailDetail[]>
): Promise<Record<string, unknown>> {
if (name === 'get_latest_mail') {
const [mail] = await readLatestMailDetails(1);
return {
mail: mail ?? null,
};
}
if (name === 'get_multiple_mail') {
let limit = 5;
if (args?.limit !== undefined) {
const parsedLimit = parsePositiveInteger(args.limit);
if (parsedLimit === null) {
throw new Error('limit must be a positive integer.');
}
limit = Math.min(MAX_MCP_MAIL_LIMIT, parsedLimit);
}
const mails = await readLatestMailDetails(limit);
return {
limit,
count: mails.length,
mails,
};
}
throw new Error(`Tool not found: ${name}`);
}
export function buildMcpBasicAuthConfigSnippet(mcpUrl: string): string {
return JSON.stringify(
{
mcpServers: {
authInbox: {
url: mcpUrl,
headers: {
Authorization: 'Basic <base64(username:password)>',
},
},
},
},
null,
2
);
}
export async function handleMcpRequest(
request: Request,
readLatestMailDetails: (limit: number) => Promise<McpMailDetail[]>
): Promise<Response> {
if (!isAllowedMcpOrigin(request)) {
return new Response(JSON.stringify({ error: 'Forbidden origin for MCP endpoint.' }), {
status: 403,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store',
},
});
}
if (request.method !== 'POST') {
return methodNotAllowedResponse('POST');
}
const headerProtocolVersion = request.headers.get('MCP-Protocol-Version');
if (headerProtocolVersion && !isSupportedMcpProtocolVersion(headerProtocolVersion)) {
return new Response(
JSON.stringify({
error: 'Unsupported MCP-Protocol-Version header.',
supported: [...MCP_PROTOCOL_VERSIONS],
}),
{
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store',
},
}
);
}
const protocolVersion = getEffectiveMcpProtocolVersion(request);
let payload: unknown;
try {
payload = await request.json();
} catch {
return makeJsonRpcErrorResponse(null, -32700, 'Parse error', protocolVersion);
}
if (Array.isArray(payload) || !isRecord(payload) || payload.jsonrpc !== '2.0') {
return makeJsonRpcErrorResponse(null, -32600, 'Invalid Request', protocolVersion);
}
const message = payload as unknown as JsonRpcRequest;
if (typeof message.method !== 'string') {
return acceptedMcpNotificationResponse(protocolVersion);
}
if (message.method === 'notifications/initialized') {
return acceptedMcpNotificationResponse(protocolVersion);
}
if (message.method === 'ping') {
if (message.id === undefined) {
return acceptedMcpNotificationResponse(protocolVersion);
}
return makeJsonRpcSuccessResponse(message.id, {}, protocolVersion);
}
if (message.method === 'initialize') {
if (!isRecord(message.params) || typeof message.params.protocolVersion !== 'string') {
return makeJsonRpcErrorResponse(message.id ?? null, -32602, 'initialize requires a protocolVersion.', protocolVersion);
}
const requestedVersion = message.params.protocolVersion;
if (!isSupportedMcpProtocolVersion(requestedVersion)) {
return makeJsonRpcErrorResponse(
message.id ?? null,
-32602,
'Unsupported protocol version',
protocolVersion,
{
supported: [...MCP_PROTOCOL_VERSIONS],
requested: requestedVersion,
}
);
}
return makeJsonRpcSuccessResponse(
message.id ?? null,
{
protocolVersion: requestedVersion,
capabilities: {
tools: {},
},
serverInfo: {
name: 'auth-inbox',
title: 'Auth Inbox MCP',
version: '1.0.0',
},
instructions: 'This endpoint is protected by HTTP Basic Auth. Keep the Authorization header on every MCP request.',
},
requestedVersion
);
}
if (message.id === undefined) {
return acceptedMcpNotificationResponse(protocolVersion);
}
if (message.method === 'tools/list') {
return makeJsonRpcSuccessResponse(message.id, { tools: MCP_TOOLS }, protocolVersion);
}
if (message.method === 'tools/call') {
if (!isRecord(message.params) || typeof message.params.name !== 'string') {
return makeJsonRpcErrorResponse(message.id, -32602, 'tools/call requires a valid tool name.', protocolVersion);
}
const args = isRecord(message.params.arguments) ? message.params.arguments : undefined;
try {
const toolResult = await executeMcpTool(message.params.name, args, readLatestMailDetails);
return makeJsonRpcSuccessResponse(message.id, makeMcpToolResult(toolResult), protocolVersion);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Tool execution failed.';
const errorCode = errorMessage.startsWith('Tool not found:') ? -32602 : -32000;
if (errorCode === -32602) {
return makeJsonRpcErrorResponse(message.id, errorCode, errorMessage, protocolVersion);
}
return makeJsonRpcSuccessResponse(
message.id,
makeMcpToolResult(
{
error: errorMessage,
},
true
),
protocolVersion
);
}
}
return makeJsonRpcErrorResponse(message.id, -32601, `Method not found: ${message.method}`, protocolVersion);
}
-158
View File
@@ -1,158 +0,0 @@
import { describe, expect, it } from 'vitest';
import { buildMcpBasicAuthConfigSnippet, handleMcpRequest, type McpMailDetail } from '../src/mcp';
const sampleMails: McpMailDetail[] = [
{
id: 11,
messageId: 'msg-11',
fromOrg: 'Example App',
fromAddr: 'no-reply@example.com',
toAddr: 'user@example.com',
subject: 'Your verification code',
topic: 'login verification',
code: '123456',
createdAt: '2026-03-17T10:00:00.000Z',
textBody: 'Your code is 123456.',
htmlBody: '<p>Your code is <strong>123456</strong>.</p>',
},
{
id: 10,
messageId: 'msg-10',
fromOrg: 'Second App',
fromAddr: 'hello@second.com',
toAddr: 'user@example.com',
subject: 'Magic link',
topic: 'signin link',
code: 'https://second.com/login',
createdAt: '2026-03-17T09:00:00.000Z',
textBody: 'Open https://second.com/login',
htmlBody: '<a href="https://second.com/login">Sign in</a>',
},
];
async function readLatestMailDetails(limit: number): Promise<McpMailDetail[]> {
return sampleMails.slice(0, limit);
}
describe('mcp transport', () => {
it('returns initialize metadata', async () => {
const request = new Request('https://authinbox.test/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-06-18',
capabilities: {},
clientInfo: {
name: 'vitest',
version: '1.0.0',
},
},
}),
});
const response = await handleMcpRequest(request, readLatestMailDetails);
const payload = await response.json<{
result: {
protocolVersion: string;
serverInfo: { name: string };
};
}>();
expect(response.status).toBe(200);
expect(payload.result.protocolVersion).toBe('2025-06-18');
expect(payload.result.serverInfo.name).toBe('auth-inbox');
});
it('returns the latest mail via get_latest_mail', async () => {
const request = new Request('https://authinbox.test/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'MCP-Protocol-Version': '2025-06-18',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 2,
method: 'tools/call',
params: {
name: 'get_latest_mail',
arguments: {},
},
}),
});
const response = await handleMcpRequest(request, readLatestMailDetails);
const payload = await response.json<{
result: {
structuredContent: {
mail: McpMailDetail;
};
};
}>();
expect(response.status).toBe(200);
expect(payload.result.structuredContent.mail.id).toBe(11);
expect(payload.result.structuredContent.mail.code).toBe('123456');
});
it('returns multiple mails with the requested limit', async () => {
const request = new Request('https://authinbox.test/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'MCP-Protocol-Version': '2025-06-18',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 3,
method: 'tools/call',
params: {
name: 'get_multiple_mail',
arguments: {
limit: 2,
},
},
}),
});
const response = await handleMcpRequest(request, readLatestMailDetails);
const payload = await response.json<{
result: {
structuredContent: {
limit: number;
count: number;
mails: McpMailDetail[];
};
};
}>();
expect(response.status).toBe(200);
expect(payload.result.structuredContent.limit).toBe(2);
expect(payload.result.structuredContent.count).toBe(2);
expect(payload.result.structuredContent.mails).toHaveLength(2);
});
it('builds a config snippet with Authorization header', () => {
const config = JSON.parse(
buildMcpBasicAuthConfigSnippet('https://authinbox.test/mcp')
) as {
mcpServers: {
authInbox: {
url: string;
headers: {
Authorization: string;
};
};
};
};
expect(config.mcpServers.authInbox.url).toBe('https://authinbox.test/mcp');
expect(config.mcpServers.authInbox.headers.Authorization).toBe('Basic <base64(username:password)>');
});
});