@koderlabs/inbox-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/chunk-T7SFANFP.js +969 -0
- package/dist/chunk-T7SFANFP.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +255 -0
- package/dist/index.js.map +1 -0
- package/dist/stdio.d.ts +1 -0
- package/dist/stdio.js +45 -0
- package/dist/stdio.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/shared/src/scopes.ts","../../../packages/shared/src/types/organization.ts","../../../packages/shared/src/constants.ts","../../../packages/shared/src/schemas/auth.schema.ts","../../../packages/shared/src/schemas/organization.schema.ts","../../../packages/shared/src/schemas/inbox.schema.ts","../src/server.ts","../src/tools/discover/whoami.ts","../src/tools/define.ts","../src/tools/inboxes/list-inboxes.ts","../src/errors.ts","../src/auth/scopes.ts","../src/formatting/inbox.ts","../src/tools/inboxes/create-inbox.ts","../src/tools/inboxes/get-inbox.ts","../src/tools/inboxes/delete-inbox.ts","../src/tools/emails/list-emails.ts","../src/formatting/email.ts","../src/tools/emails/get-email.ts","../src/tools/emails/wait-for-email.ts","../src/tools/emails/delete-email.ts","../src/tools/emails/mark-email-read.ts","../src/tools/index.ts","../src/api-client.ts","../src/auth/secret-key.ts"],"sourcesContent":["/**\n * MCP scope vocabulary for InstantInbox.\n *\n * Shared between the API (when issuing keys/tokens) and the MCP server\n * (when filtering tool availability).\n */\nexport const SCOPE = {\n INBOX_READ: 'inbox:read',\n INBOX_WRITE: 'inbox:write',\n EMAIL_READ: 'email:read',\n EMAIL_WRITE: 'email:write',\n} as const;\n\nexport type Scope = typeof SCOPE[keyof typeof SCOPE];\n\nexport const ALL_SCOPES: Scope[] = Object.values(SCOPE);\n","import { Plan } from './billing';\n\nexport enum OrgRole {\n OWNER = 'OWNER',\n ADMIN = 'ADMIN',\n MEMBER = 'MEMBER',\n}\n\nexport enum OrgStatus {\n ACTIVE = 'active',\n SUSPENDED = 'suspended',\n}\n\nexport interface Organization {\n id: string;\n name: string;\n slug: string;\n plan: Plan;\n status: OrgStatus;\n isBillingExempt: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OrgMembership {\n id: string;\n userId: string;\n organizationId: string;\n role: OrgRole;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OrgInvitation {\n id: string;\n organizationId: string;\n email: string;\n role: OrgRole;\n expiresAt: string;\n acceptedAt: string | null;\n createdAt: string;\n}\n","export const DEFAULT_MAIL_DOMAIN = 'inbox.koderlabs.net';\nexport const ADDRESS_LENGTH = 8;\nexport const ADDRESS_CHARSET = 'abcdefghijklmnopqrstuvwxyz0123456789';\nexport const MAX_EMAIL_SIZE_BYTES = 10 * 1024 * 1024; // 10MB\n\nexport const PORTS = { DB: 10400, API: 10402, WEB: 10403 } as const;\n\nexport const PASSWORD_MIN_LENGTH = 8;\nexport const PASSWORD_MAX_LENGTH = 128;\nexport const NAME_MAX_LENGTH = 100;\nexport const ORG_NAME_MIN_LENGTH = 2;\nexport const ORG_NAME_MAX_LENGTH = 255;\nexport const LABEL_MAX_LENGTH = 255;\nexport const MAX_ADDRESS_LENGTH = 64;\n\nexport const ADDRESS_REGEX = /^[a-zA-Z0-9._-]+$/;\nexport const SLUG_REGEX = /[^a-z0-9]+/g;\n\nexport enum PlatformSettingKey {\n REGISTRATION_MODE = 'registration_mode',\n}\n\nexport enum FeatureFlag {\n ADVANCED_FILTERS = 'advancedFilters',\n CUSTOM_DOMAIN = 'customDomain',\n WEBHOOKS = 'webhooks',\n API_ACCESS = 'apiAccess',\n PRIORITY_SUPPORT = 'prioritySupport',\n}\n","import { z } from 'zod';\nimport {\n NAME_MAX_LENGTH,\n PASSWORD_MIN_LENGTH,\n PASSWORD_MAX_LENGTH,\n ORG_NAME_MIN_LENGTH,\n ORG_NAME_MAX_LENGTH,\n} from '../constants';\n\nconst passwordField = z\n .string()\n .min(PASSWORD_MIN_LENGTH, `Password must be at least ${PASSWORD_MIN_LENGTH} characters.`)\n .max(PASSWORD_MAX_LENGTH, `Password must be at most ${PASSWORD_MAX_LENGTH} characters.`);\n\nconst emailField = z.string().email('Enter a valid email address.');\n\nconst nameField = z\n .string()\n .trim()\n .max(NAME_MAX_LENGTH, `Must be at most ${NAME_MAX_LENGTH} characters.`)\n .optional()\n .or(z.literal(''));\n\nexport const loginSchema = z.object({\n email: emailField,\n password: z.string().min(1, 'Password is required.'),\n});\n\nexport const registerSchema = z\n .object({\n firstName: nameField,\n lastName: nameField,\n email: emailField,\n password: passwordField,\n confirmPassword: z.string().min(1, 'Confirm your password.'),\n organizationName: z\n .string()\n .trim()\n .min(ORG_NAME_MIN_LENGTH, `Organization name must be at least ${ORG_NAME_MIN_LENGTH} characters.`)\n .max(ORG_NAME_MAX_LENGTH, `Organization name must be at most ${ORG_NAME_MAX_LENGTH} characters.`)\n .optional()\n .or(z.literal('')),\n })\n .refine((d) => d.password === d.confirmPassword, {\n path: ['confirmPassword'],\n message: 'Passwords do not match.',\n });\n\nexport const changePasswordSchema = z\n .object({\n currentPassword: z.string().min(1, 'Current password is required.'),\n newPassword: passwordField,\n confirmNewPassword: z.string().min(1, 'Confirm your new password.'),\n })\n .refine((d) => d.newPassword === d.confirmNewPassword, {\n path: ['confirmNewPassword'],\n message: 'Passwords do not match.',\n });\n\nexport type LoginInput = z.infer<typeof loginSchema>;\nexport type RegisterInput = z.infer<typeof registerSchema>;\nexport type ChangePasswordInput = z.infer<typeof changePasswordSchema>;\n","import { z } from 'zod';\nimport { ORG_NAME_MIN_LENGTH, ORG_NAME_MAX_LENGTH } from '../constants';\nimport { OrgRole } from '../types/organization';\n\nconst orgNameField = z\n .string()\n .trim()\n .min(ORG_NAME_MIN_LENGTH, `Organization name must be at least ${ORG_NAME_MIN_LENGTH} characters.`)\n .max(ORG_NAME_MAX_LENGTH, `Organization name must be at most ${ORG_NAME_MAX_LENGTH} characters.`);\n\nexport const createOrgSchema = z.object({\n name: orgNameField,\n});\n\nexport const updateOrgSchema = z.object({\n name: orgNameField,\n});\n\nexport const inviteMemberSchema = z.object({\n email: z.string().email('Enter a valid email address.'),\n role: z.enum([OrgRole.ADMIN, OrgRole.MEMBER]),\n});\n\nexport const updateMemberRoleSchema = z.object({\n role: z.nativeEnum(OrgRole),\n});\n\nexport type CreateOrgInput = z.infer<typeof createOrgSchema>;\nexport type UpdateOrgInput = z.infer<typeof updateOrgSchema>;\nexport type InviteMemberInput = z.infer<typeof inviteMemberSchema>;\nexport type UpdateMemberRoleInput = z.infer<typeof updateMemberRoleSchema>;\n","import { z } from 'zod';\nimport { ADDRESS_REGEX, MAX_ADDRESS_LENGTH, LABEL_MAX_LENGTH } from '../constants';\n\nexport const createInboxSchema = z.object({\n address: z\n .string()\n .trim()\n .max(MAX_ADDRESS_LENGTH, `Address must be at most ${MAX_ADDRESS_LENGTH} characters.`)\n .regex(ADDRESS_REGEX, 'Address may only contain letters, numbers, dots, dashes, and underscores.')\n .optional()\n .or(z.literal('')),\n label: z\n .string()\n .trim()\n .max(LABEL_MAX_LENGTH, `Label must be at most ${LABEL_MAX_LENGTH} characters.`)\n .optional()\n .or(z.literal('')),\n});\n\nexport type CreateInboxInput = z.infer<typeof createInboxSchema>;\n","/**\n * MCP server construction — shared by HTTP and stdio transports.\n *\n * Tools are filtered by scope at list_tools time; on call_tools the server\n * re-asserts the required scope and runs zod validation before invoking\n * the handler.\n */\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ErrorCode,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { ALL_TOOLS, toolsForScopes } from './tools/index.js';\nimport { zodToJsonSchema } from './tools/define.js';\nimport { ApiClient } from './api-client.js';\nimport { ScopeSet } from './auth/scopes.js';\n\nexport const SERVER_NAME = 'instantinbox-mcp';\nexport const SERVER_VERSION = '0.1.0';\n\nexport interface ServerContext {\n apiClient: ApiClient;\n scopes: ScopeSet;\n}\n\nexport function createMcpServer(getContext: () => ServerContext | Promise<ServerContext>): Server {\n const server = new Server(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: { listChanged: false } } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const ctx = await getContext();\n const available = toolsForScopes(ctx.scopes.toArray());\n return {\n tools: available.map((tool) => ({\n name: tool.name,\n title: tool.title,\n description: tool.description,\n inputSchema: zodToJsonSchema(tool.inputSchema),\n ...(tool.annotations && { annotations: tool.annotations }),\n ...(tool.meta && { _meta: tool.meta }),\n })),\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: rawArgs } = request.params;\n\n const tool = ALL_TOOLS.find((t) => t.name === name);\n if (!tool) {\n throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);\n }\n\n const ctx = await getContext();\n\n for (const scope of tool.requiredScopes) {\n if (!ctx.scopes.has(scope)) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Token missing required scope '${scope}' for tool '${name}'`,\n );\n }\n }\n\n const parseResult = tool.inputSchema.safeParse(rawArgs ?? {});\n if (!parseResult.success) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Invalid parameters for tool '${name}': ${parseResult.error.message}`,\n );\n }\n\n const result = await tool.handler(parseResult.data as Record<string, unknown>, {\n apiClient: ctx.apiClient,\n scopes: ctx.scopes,\n });\n\n return {\n content: result.content,\n isError: result.isError ?? false,\n ...(result._meta && { _meta: result._meta }),\n };\n });\n\n return server;\n}\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You need to confirm the InstantInbox token is valid before running any\n other tool.\n- The user asks \"who am I\" or \"am I logged in to InstantInbox\".\n- As a pre-flight before a long agentic workflow that creates inboxes\n and waits for email.\n\nDO NOT USE:\n- More than once per session unless the token changes.\n- To list inboxes — use list_inboxes for that.\n\nTRIGGER PATTERNS:\n- \"check my token\"\n- \"what inbox access do I have\"\n- \"verify InstantInbox auth\"\n\n<examples>\n<example>\n<user>Am I logged in?</user>\n<tool>whoami</tool>\n<result>Authenticated as alice@example.com (user id: u_123). Granted scopes: inbox:read, inbox:write, email:read, email:write.</result>\n</example>\n</examples>\n`.trim();\n\nexport const whoamiTool = defineTool({\n name: 'whoami',\n title: 'Identify Current User',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['discover'],\n requiredScopes: [],\n description: DESCRIPTION,\n inputSchema: z.object({}),\n async handler(_params, ctx): Promise<ToolResult> {\n const me = await ctx.apiClient.whoami();\n const scopes = ctx.scopes.toArray().join(', ') || '(none)';\n if (!me) {\n return {\n content: [{ type: 'text', text: `Token valid but /auth/me returned no profile. Scopes: ${scopes}` }],\n };\n }\n const lines = [\n `Authenticated as ${me.email ?? '(unknown email)'} (user id: ${me.id})`,\n `Scopes: ${scopes}`,\n ];\n if (me.isEmailVerified === false) {\n lines.push('Warning: email is NOT verified — some write operations may be rejected.');\n }\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n});\n","/**\n * defineTool() — single registration point for every MCP tool.\n *\n * Tool descriptions are long-form LLM prompts (USE WHEN / DO NOT USE /\n * EXAMPLES). The description IS the prompt injected into the model's\n * context when the tool is exposed, so it's worth spending words on.\n */\nimport { type ZodSchema } from 'zod';\nimport { zodToJsonSchema as convertZodToJsonSchema } from 'zod-to-json-schema';\nimport { type ApiClient } from '../api-client.js';\nimport { type ScopeSet } from '../auth/scopes.js';\nimport type { Scope } from '@koderlabs/inbox-shared';\n\nexport interface ToolContext {\n apiClient: ApiClient;\n scopes: ScopeSet;\n}\n\nexport type ToolHandler<TInput extends Record<string, unknown>> = (\n params: TInput,\n ctx: ToolContext,\n) => Promise<ToolResult>;\n\nexport type ToolContent =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string };\n\nexport interface ToolResult {\n content: ToolContent[];\n isError?: boolean;\n _meta?: Record<string, unknown>;\n}\n\n/**\n * MCP 2025-06-18 tool-behaviour hints. All advisory — the server still\n * enforces scopes + zod validation.\n */\nexport interface ToolAnnotations {\n title?: string;\n readOnlyHint?: boolean;\n destructiveHint?: boolean;\n idempotentHint?: boolean;\n openWorldHint?: boolean;\n}\n\nexport interface ToolDefinition<TInput extends Record<string, unknown> = Record<string, unknown>> {\n name: string;\n title: string;\n skills: string[];\n requiredScopes: Scope[];\n description: string;\n inputSchema: ZodSchema<TInput>;\n annotations?: ToolAnnotations;\n meta?: Record<string, unknown>;\n handler: ToolHandler<TInput>;\n}\n\nexport function defineTool<TInput extends Record<string, unknown>>(\n definition: ToolDefinition<TInput>,\n): ToolDefinition<TInput> {\n return definition;\n}\n\n/** Convert a Zod schema to the JSON-Schema shape MCP expects. */\nexport function zodToJsonSchema(schema: ZodSchema): object {\n const out = convertZodToJsonSchema(schema, {\n target: 'openApi3',\n $refStrategy: 'none',\n }) as Record<string, unknown>;\n delete out.$schema;\n delete out.definitions;\n return out;\n}\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { formatInbox } from '../../formatting/inbox.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You need to find an existing disposable inbox the caller already owns.\n- The user asks \"what inboxes do I have\" or \"show my test inboxes\".\n- You want to confirm an inbox exists before sending email to it.\n\nDO NOT USE:\n- To create a new inbox — use create_inbox.\n- To read messages — use list_emails or wait_for_email.\n\nREQUIRED SCOPE: inbox:read\n\n<examples>\n<example>\n<user>List my inboxes</user>\n<tool>list_inboxes</tool>\n<result>Inbox: ab12cd@inbox.koderlabs.net (emails: 3, last: \"Verify your email\" ...)</result>\n</example>\n</examples>\n`.trim();\n\nexport const listInboxesTool = defineTool({\n name: 'list_inboxes',\n title: 'List Inboxes',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['inboxes'],\n requiredScopes: [SCOPE.INBOX_READ],\n description: DESCRIPTION,\n inputSchema: z.object({\n organizationId: z.string().optional().describe('Filter to inboxes in this organization'),\n search: z.string().optional().describe('Substring match on address or label'),\n limit: z.number().int().min(1).max(100).optional().describe('Default 20, max 100'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.INBOX_READ);\n const { items, meta } = await ctx.apiClient.listInboxes({\n organizationId: params.organizationId,\n search: params.search,\n limit: params.limit ?? 20,\n });\n if (items.length === 0) {\n return { content: [{ type: 'text', text: 'No inboxes found. Use create_inbox to make one.' }] };\n }\n const text = [\n `Found ${meta.total} inbox(es), showing ${items.length}:`,\n '',\n ...items.map(formatInbox),\n ].join('\\n');\n return { content: [{ type: 'text', text }] };\n },\n});\n","/**\n * MCP error types mapped to MCP protocol error codes.\n *\n * Tool handlers throw these instead of raw Error so the MCP SDK\n * serializes a useful error to the client.\n */\nimport { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';\n\n/** Caller passed invalid or missing input parameters. */\nexport class UserInputError extends McpError {\n constructor(message: string) {\n super(ErrorCode.InvalidParams, message);\n this.name = 'UserInputError';\n }\n}\n\n/** API returned 404 for a resource. */\nexport class ApiNotFoundError extends McpError {\n constructor(resource: string, id: string) {\n super(ErrorCode.InvalidParams, `${resource} not found: ${id}`);\n this.name = 'ApiNotFoundError';\n }\n}\n\n/** Token lacks a required scope. */\nexport class ScopeError extends McpError {\n constructor(requiredScope: string) {\n super(ErrorCode.InvalidRequest, `Token missing required scope: ${requiredScope}`);\n this.name = 'ScopeError';\n }\n}\n\n/** Unexpected error from the upstream API. */\nexport class ApiError extends McpError {\n constructor(message: string, public readonly statusCode?: number) {\n super(ErrorCode.InternalError, `InstantInbox API error: ${message}`);\n this.name = 'ApiError';\n }\n}\n\n/** Bearer token could not be validated. */\nexport class AuthError extends McpError {\n constructor(\n public readonly reason: 'invalid_token' | 'expired_token' | 'missing_token' | 'config_error',\n detail?: string,\n ) {\n super(ErrorCode.InvalidRequest, detail ? `${reason}: ${detail}` : reason);\n this.name = 'AuthError';\n }\n}\n","/**\n * ScopeSet + assertions for MCP token validation.\n *\n * The vocabulary lives in @koderlabs/inbox-shared so it can be shared with\n * the API when issuing keys / OAuth tokens.\n */\nimport { type Scope } from '@koderlabs/inbox-shared';\nimport { ScopeError } from '../errors.js';\n\nexport type { Scope };\n\n/** Immutable set of scopes attached to the current request's token. */\nexport class ScopeSet {\n private readonly set: ReadonlySet<string>;\n\n constructor(scopes: string[]) {\n this.set = new Set(scopes);\n }\n\n has(scope: Scope): boolean {\n return this.set.has(scope);\n }\n\n hasAll(scopes: Scope[]): boolean {\n return scopes.every((s) => this.set.has(s));\n }\n\n toArray(): string[] {\n return [...this.set];\n }\n}\n\n/** Throw ScopeError if the set lacks the required scope. */\nexport function assertScope(scopes: ScopeSet, required: Scope): void {\n if (!scopes.has(required)) {\n throw new ScopeError(required);\n }\n}\n\nexport function assertScopes(scopes: ScopeSet, required: Scope[]): void {\n for (const s of required) assertScope(scopes, s);\n}\n","import type { Inbox, InboxListItem } from '../api-client.js';\n\nexport function formatInbox(inbox: Inbox | InboxListItem): string {\n const meta = inbox as InboxListItem;\n const lines = [\n `Inbox: ${inbox.address}`,\n ` id: ${inbox.id}`,\n ];\n if (inbox.label) lines.push(` label: ${inbox.label}`);\n if (typeof meta.emailCount === 'number') lines.push(` emails: ${meta.emailCount}`);\n if (meta.lastReceivedAt) {\n lines.push(` last: ${meta.lastSubject ?? '(no subject)'} <${meta.lastFrom ?? '?'}> @ ${meta.lastReceivedAt}`);\n }\n lines.push(` created: ${inbox.createdAt}`);\n return lines.join('\\n');\n}\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { formatInbox } from '../../formatting/inbox.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You are about to test an email-driven flow (signup verification, password\n reset, magic link, etc.) and need a fresh disposable inbox to plug into\n the app under test.\n- The user asks to \"create a test inbox\" or \"give me a throwaway email\".\n\nWHAT YOU GET BACK:\n- The full email address (e.g. ab12cd@inbox.koderlabs.net) — this is the\n string you plug into the signup form, profile, etc.\n- The inbox id (UUID) — use this with list_emails / wait_for_email.\n\nNEXT STEP after calling this tool:\n- Trigger the flow that should send mail to this address.\n- Then call wait_for_email with this inbox's id.\n\nREQUIRED SCOPE: inbox:write\n\n<examples>\n<example>\n<user>Create a disposable inbox for testing the signup flow.</user>\n<tool>create_inbox {}</tool>\n<result>Created inbox: ab12cd@inbox.koderlabs.net (id u_abc). Use this address in the app, then call wait_for_email.</result>\n</example>\n</examples>\n`.trim();\n\nexport const createInboxTool = defineTool({\n name: 'create_inbox',\n title: 'Create Disposable Inbox',\n annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },\n skills: ['inboxes'],\n requiredScopes: [SCOPE.INBOX_WRITE],\n description: DESCRIPTION,\n inputSchema: z.object({\n address: z.string().optional().describe('Local-part only (e.g. \"alice\"). Omit to auto-generate a random address.'),\n label: z.string().optional().describe('Human-readable label for this inbox'),\n organizationId: z.string().optional().describe('Scope to an organization; omit for personal inbox'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.INBOX_WRITE);\n const inbox = await ctx.apiClient.createInbox({\n address: params.address,\n label: params.label,\n organizationId: params.organizationId,\n });\n const text = [\n `Inbox created: ${inbox.address}`,\n ` id: ${inbox.id}`,\n params.label ? ` label: ${params.label}` : null,\n '',\n `Next step: trigger the email flow with this address, then call wait_for_email with inboxId=${inbox.id}.`,\n ].filter(Boolean).join('\\n');\n return {\n content: [{ type: 'text', text }],\n _meta: {\n 'instantinbox/inboxId': inbox.id,\n 'instantinbox/address': inbox.address,\n },\n };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { UserInputError, ApiNotFoundError } from '../../errors.js';\nimport { formatInbox } from '../../formatting/inbox.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You have an inbox id OR address and want its current state (label, counts,\n last received email).\n- You need to translate an address back into an inbox id before calling\n list_emails / wait_for_email.\n\nDO NOT USE to list multiple inboxes (use list_inboxes) or to fetch emails\n(use list_emails or get_email).\n\nREQUIRED SCOPE: inbox:read\n`.trim();\n\nexport const getInboxTool = defineTool({\n name: 'get_inbox',\n title: 'Get Inbox Details',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['inboxes'],\n requiredScopes: [SCOPE.INBOX_READ],\n description: DESCRIPTION,\n inputSchema: z.object({\n inboxId: z.string().optional().describe('Inbox UUID. One of inboxId or address required.'),\n address: z.string().optional().describe('Full email address. One of inboxId or address required.'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.INBOX_READ);\n if (!params.inboxId && !params.address) {\n throw new UserInputError('Provide either inboxId or address.');\n }\n const inbox = params.inboxId\n ? await ctx.apiClient.getInboxById(params.inboxId)\n : await ctx.apiClient.getInboxByAddress(params.address!);\n if (!inbox) throw new ApiNotFoundError('Inbox', params.inboxId ?? params.address!);\n return {\n content: [{ type: 'text', text: formatInbox(inbox) }],\n _meta: { 'instantinbox/inboxId': inbox.id, 'instantinbox/address': inbox.address },\n };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- A test run is complete and the disposable inbox is no longer needed.\n- The user asks to \"delete this inbox\" or \"clean up the test inbox\".\n\nDESTRUCTIVE: This permanently deletes the inbox AND every email it ever\nreceived. There is no recovery. Idempotent — re-calling with the same id\nafter deletion returns 404 from the API which this tool reports cleanly.\n\nREQUIRED SCOPE: inbox:write\n`.trim();\n\nexport const deleteInboxTool = defineTool({\n name: 'delete_inbox',\n title: 'Delete Inbox',\n annotations: { destructiveHint: true, idempotentHint: true, openWorldHint: true },\n skills: ['inboxes'],\n requiredScopes: [SCOPE.INBOX_WRITE],\n description: DESCRIPTION,\n inputSchema: z.object({\n inboxId: z.string().describe('Inbox UUID'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.INBOX_WRITE);\n await ctx.apiClient.deleteInbox(params.inboxId);\n return {\n content: [{ type: 'text', text: `Inbox ${params.inboxId} deleted (and all its emails).` }],\n };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { UserInputError, ApiNotFoundError } from '../../errors.js';\nimport { formatEmailHeader } from '../../formatting/email.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You want to see what mail has already arrived in an inbox.\n- The user asks \"what emails did I receive\" or \"show me the inbox\".\n\nDO NOT USE:\n- To poll for a NOT-YET-ARRIVED email — use wait_for_email which long-polls\n efficiently. Repeatedly calling list_emails in a tight loop wastes tokens\n and load on the API.\n- To get the full body — use get_email; this tool only returns headers\n (subject / from / date).\n\nREQUIRED SCOPE: email:read\n`.trim();\n\nexport const listEmailsTool = defineTool({\n name: 'list_emails',\n title: 'List Emails in Inbox',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['emails'],\n requiredScopes: [SCOPE.EMAIL_READ, SCOPE.INBOX_READ],\n description: DESCRIPTION,\n inputSchema: z.object({\n inboxId: z.string().optional().describe('Inbox UUID. One of inboxId or address required.'),\n address: z.string().optional().describe('Full inbox email address. One of inboxId or address required.'),\n limit: z.number().int().min(1).max(100).optional().describe('Default 20, max 100'),\n unreadOnly: z.boolean().optional().describe('Filter to unread emails only'),\n since: z.string().optional().describe('ISO timestamp — only emails received after this time'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.EMAIL_READ);\n if (!params.inboxId && !params.address) {\n throw new UserInputError('Provide either inboxId or address.');\n }\n\n let inboxId = params.inboxId;\n if (!inboxId) {\n const inbox = await ctx.apiClient.getInboxByAddress(params.address!);\n if (!inbox) throw new ApiNotFoundError('Inbox', params.address!);\n inboxId = inbox.id;\n }\n\n const { items, meta } = await ctx.apiClient.listEmails(inboxId, {\n limit: params.limit ?? 20,\n unreadOnly: params.unreadOnly,\n since: params.since,\n });\n\n if (items.length === 0) {\n return {\n content: [{\n type: 'text',\n text: 'No emails in this inbox yet. If you are waiting for one, use wait_for_email to long-poll efficiently.',\n }],\n };\n }\n\n const text = [\n `Found ${meta.total} email(s), showing ${items.length} (\\`*\\` = unread):`,\n ...items.map(formatEmailHeader),\n ].join('\\n');\n return { content: [{ type: 'text', text }] };\n },\n});\n","import type { Email } from '../api-client.js';\n\nfunction snippet(text: string | null | undefined, max = 240): string {\n if (!text) return '';\n const t = text.replace(/\\s+/g, ' ').trim();\n return t.length > max ? `${t.slice(0, max)}…` : t;\n}\n\n/** Compact one-liner — used in list views. */\nexport function formatEmailHeader(email: Email): string {\n const read = email.readAt ? ' ' : '*';\n return `${read} [${email.receivedAt}] ${email.fromAddress} → ${email.subject || '(no subject)'} (${email.id})`;\n}\n\n/** Full text view — used in get_email and wait_for_email returns. */\nexport function formatEmail(email: Email): string {\n const lines = [\n `Email ${email.id}`,\n `From: ${email.fromAddress}`,\n `To: ${email.toAddress}`,\n `Subject: ${email.subject || '(no subject)'}`,\n `Date: ${email.receivedAt}`,\n `Read: ${email.readAt ?? 'no'}`,\n `Attachments: ${email.attachmentsCount}`,\n '',\n ];\n if (email.textBody) {\n lines.push('--- text body ---', email.textBody);\n } else if (email.htmlBody) {\n lines.push('--- html body (text fallback unavailable) ---', snippet(email.htmlBody, 4000));\n } else {\n lines.push('(no body)');\n }\n return lines.join('\\n');\n}\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { ApiNotFoundError } from '../../errors.js';\nimport { formatEmail } from '../../formatting/email.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You have an email id (from list_emails or wait_for_email) and need the\n full body to extract a verification link, OTP code, or password reset\n token.\n\nDO NOT USE to scan the inbox (use list_emails).\n\nREQUIRED SCOPE: email:read\n`.trim();\n\nexport const getEmailTool = defineTool({\n name: 'get_email',\n title: 'Get Email',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['emails'],\n requiredScopes: [SCOPE.EMAIL_READ],\n description: DESCRIPTION,\n inputSchema: z.object({\n emailId: z.string().describe('Email UUID'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.EMAIL_READ);\n const email = await ctx.apiClient.getEmail(params.emailId);\n if (!email) throw new ApiNotFoundError('Email', params.emailId);\n return {\n content: [{ type: 'text', text: formatEmail(email) }],\n _meta: { 'instantinbox/emailId': email.id, 'instantinbox/inboxId': email.inboxId },\n };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\nimport { UserInputError, ApiNotFoundError } from '../../errors.js';\nimport { formatEmail } from '../../formatting/email.js';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You have just triggered an action that should send mail (signup, password\n reset, magic link, OTP) and you need to wait for it to arrive.\n- This is the canonical \"did the email arrive?\" tool — long-polls the API\n every second up to \\`timeoutSec\\` (default 30s, max 120s) so the agent\n blocks exactly as long as needed and no longer.\n\nARGS:\n- inboxId OR address — required.\n- timeoutSec — how long to wait (1–120, default 30).\n- subjectContains / fromContains — optional case-insensitive filters; the\n first email matching both is returned.\n- sinceMs — only consider emails received after Date.now() - sinceMs.\n Defaults to (timeoutSec + 5) seconds. Use this to avoid matching emails\n that arrived before the trigger.\n\nRETURNS:\n- The full email (subject, from, body, headers) when one arrives.\n- \\`{ timedOut: true, matched: 0 }\\` if nothing arrives in time.\n\nDO NOT USE for general inbox browsing (use list_emails) — wait_for_email\nis meant for the \"trigger → wait → extract\" loop in E2E tests.\n\nREQUIRED SCOPE: email:read\n\n<examples>\n<example>\n<user>I signed up. Wait for the verification email.</user>\n<tool>wait_for_email { inboxId, subjectContains: \"verify\", timeoutSec: 60 }</tool>\n<result>Email arrived from no-reply@example.com — \"Verify your email\". Body contains link https://...</result>\n</example>\n</examples>\n`.trim();\n\nexport const waitForEmailTool = defineTool({\n name: 'wait_for_email',\n title: 'Wait For Email',\n annotations: { readOnlyHint: true, openWorldHint: true },\n skills: ['emails'],\n requiredScopes: [SCOPE.EMAIL_READ, SCOPE.INBOX_READ],\n description: DESCRIPTION,\n inputSchema: z.object({\n inboxId: z.string().optional().describe('Inbox UUID. One of inboxId or address required.'),\n address: z.string().optional().describe('Full inbox email address. One of inboxId or address required.'),\n timeoutSec: z.number().int().min(1).max(120).optional().describe('Max wait, default 30s, hard cap 120s'),\n subjectContains: z.string().optional().describe('Case-insensitive substring match on subject'),\n fromContains: z.string().optional().describe('Case-insensitive substring match on from address'),\n sinceMs: z.number().int().min(0).optional().describe('Only match emails received within the last N ms (default: timeoutSec + 5s)'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.EMAIL_READ);\n if (!params.inboxId && !params.address) {\n throw new UserInputError('Provide either inboxId or address.');\n }\n\n let inboxId = params.inboxId;\n if (!inboxId) {\n const inbox = await ctx.apiClient.getInboxByAddress(params.address!);\n if (!inbox) throw new ApiNotFoundError('Inbox', params.address!);\n inboxId = inbox.id;\n }\n\n const timeoutSec = Math.min(params.timeoutSec ?? 30, 120);\n const sinceMs = params.sinceMs ?? (timeoutSec + 5) * 1000;\n const startedAt = Date.now();\n const sinceIso = new Date(startedAt - sinceMs).toISOString();\n const deadline = startedAt + timeoutSec * 1000;\n\n const subjectMatch = params.subjectContains?.toLowerCase();\n const fromMatch = params.fromContains?.toLowerCase();\n\n // 1s poll cadence — generous enough that 30s wait = 30 polls, cheap\n // for the API and keeps the agent's stream readable.\n const pollIntervalMs = 1000;\n\n // AbortController bounds the final iteration's API call so we don't\n // hang past `deadline` waiting on the server.\n while (Date.now() < deadline) {\n const { items } = await ctx.apiClient.listEmails(inboxId, {\n limit: 50,\n since: sinceIso,\n });\n const match = items.find((e) => {\n if (subjectMatch && !(e.subject ?? '').toLowerCase().includes(subjectMatch)) return false;\n if (fromMatch && !(e.fromAddress ?? '').toLowerCase().includes(fromMatch)) return false;\n return true;\n });\n if (match) {\n // Fetch the full email — list only returns headers + snippet.\n const full = (await ctx.apiClient.getEmail(match.id)) ?? match;\n const elapsedSec = ((Date.now() - startedAt) / 1000).toFixed(1);\n return {\n content: [{\n type: 'text',\n text: `Matched after ${elapsedSec}s.\\n\\n${formatEmail(full)}`,\n }],\n _meta: {\n 'instantinbox/emailId': full.id,\n 'instantinbox/inboxId': inboxId,\n 'instantinbox/elapsedSec': Number(elapsedSec),\n },\n };\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n await new Promise((r) => setTimeout(r, Math.min(pollIntervalMs, remaining)));\n }\n\n return {\n content: [{\n type: 'text',\n text: `Timed out after ${timeoutSec}s. No matching email arrived. Inbox: ${inboxId}.`,\n }],\n _meta: { 'instantinbox/timedOut': true, 'instantinbox/matched': 0 },\n };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- A test run is complete and you want to clear specific emails from the\n inbox before the next iteration.\n\nDESTRUCTIVE: deletes a single email permanently. Idempotent — re-calling\nwith the same id after deletion is safe.\n\nDO NOT USE to delete an entire inbox (use delete_inbox).\n\nREQUIRED SCOPE: email:write\n`.trim();\n\nexport const deleteEmailTool = defineTool({\n name: 'delete_email',\n title: 'Delete Email',\n annotations: { destructiveHint: true, idempotentHint: true, openWorldHint: true },\n skills: ['emails'],\n requiredScopes: [SCOPE.EMAIL_WRITE],\n description: DESCRIPTION,\n inputSchema: z.object({\n emailId: z.string().describe('Email UUID'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.EMAIL_WRITE);\n await ctx.apiClient.deleteEmail(params.emailId);\n return { content: [{ type: 'text', text: `Email ${params.emailId} deleted.` }] };\n },\n});\n","import { z } from 'zod';\nimport { defineTool, type ToolResult } from '../define.js';\nimport { assertScope } from '../../auth/scopes.js';\nimport { SCOPE } from '@koderlabs/inbox-shared';\n\nconst DESCRIPTION = `\nUSE WHEN:\n- You want to mark an email as read or unread (e.g. so list_emails with\n unreadOnly returns the expected set after you've processed one).\n\nIdempotent. Not destructive.\n\nREQUIRED SCOPE: email:write\n`.trim();\n\nexport const markEmailReadTool = defineTool({\n name: 'mark_email_read',\n title: 'Mark Email Read/Unread',\n annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: true },\n skills: ['emails'],\n requiredScopes: [SCOPE.EMAIL_WRITE],\n description: DESCRIPTION,\n inputSchema: z.object({\n emailId: z.string().describe('Email UUID'),\n read: z.boolean().describe('true → mark read; false → mark unread'),\n }),\n async handler(params, ctx): Promise<ToolResult> {\n assertScope(ctx.scopes, SCOPE.EMAIL_WRITE);\n const updated = params.read\n ? await ctx.apiClient.markRead(params.emailId)\n : await ctx.apiClient.markUnread(params.emailId);\n return {\n content: [{\n type: 'text',\n text: `Email ${params.emailId} marked ${params.read ? 'read' : 'unread'} (readAt=${updated.readAt ?? 'null'}).`,\n }],\n };\n },\n});\n","/**\n * Tool registry — 10 tools across 3 skill groups.\n *\n * discover (1): whoami\n * inboxes (4): list_inboxes, create_inbox, get_inbox, delete_inbox\n * emails (5): list_emails, get_email, wait_for_email, delete_email, mark_email_read\n */\nimport { whoamiTool } from './discover/whoami.js';\n\nimport { listInboxesTool } from './inboxes/list-inboxes.js';\nimport { createInboxTool } from './inboxes/create-inbox.js';\nimport { getInboxTool } from './inboxes/get-inbox.js';\nimport { deleteInboxTool } from './inboxes/delete-inbox.js';\n\nimport { listEmailsTool } from './emails/list-emails.js';\nimport { getEmailTool } from './emails/get-email.js';\nimport { waitForEmailTool } from './emails/wait-for-email.js';\nimport { deleteEmailTool } from './emails/delete-email.js';\nimport { markEmailReadTool } from './emails/mark-email-read.js';\n\nimport type { ToolDefinition } from './define.js';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const ALL_TOOLS: ToolDefinition<any>[] = [\n whoamiTool,\n listInboxesTool,\n createInboxTool,\n getInboxTool,\n deleteInboxTool,\n listEmailsTool,\n getEmailTool,\n waitForEmailTool,\n deleteEmailTool,\n markEmailReadTool,\n];\n\n/** Filter the registry to tools whose required scopes are all in the set. */\nexport function toolsForScopes(scopeList: string[]): typeof ALL_TOOLS {\n const set = new Set(scopeList);\n return ALL_TOOLS.filter((t) => t.requiredScopes.every((s) => set.has(s)));\n}\n\n/** Filter the registry to tools whose skill matches at least one given. */\nexport function toolsForSkills(skills: string[]): typeof ALL_TOOLS {\n if (!skills.length) return ALL_TOOLS;\n return ALL_TOOLS.filter((t) => t.skills.some((s) => skills.includes(s)));\n}\n","/**\n * Typed thin wrapper around the InstantInbox REST API.\n *\n * The API uses the envelope `{ success, data, meta? }`. We unwrap it here so\n * tool handlers get plain entities back. 404s become `null` so tools can\n * render \"not found\" without exception plumbing.\n */\nimport type { Inbox, Email } from '@koderlabs/inbox-shared';\nimport { ApiError } from './errors.js';\n\nexport type { Inbox, Email };\n\nexport interface InboxListItem extends Inbox {\n emailCount?: number;\n lastFrom?: string | null;\n lastSubject?: string | null;\n lastReceivedAt?: string | null;\n}\n\nexport interface PaginationMeta {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface ApiClientOptions {\n baseUrl: string;\n token: string;\n}\n\nexport interface UserProfile {\n id: string;\n email: string;\n fullName?: string;\n isEmailVerified?: boolean;\n}\n\nexport class ApiClient {\n private readonly baseUrl: string;\n private readonly headers: Record<string, string>;\n\n constructor(opts: ApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.headers = {\n Authorization: `Bearer ${opts.token}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n\n /** Unwrap `{ success, data, meta }` → either `data` or `{ items, meta }`. */\n private unwrap<T>(json: unknown): T {\n if (\n json && typeof json === 'object' &&\n 'success' in (json as Record<string, unknown>) &&\n 'data' in (json as Record<string, unknown>)\n ) {\n const data = (json as { data: unknown }).data;\n if (Array.isArray(data)) {\n return { items: data, meta: (json as { meta?: unknown }).meta } as unknown as T;\n }\n return data as T;\n }\n return json as T;\n }\n\n private async request<T>(method: string, path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>): Promise<T | null> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n let res: Response;\n try {\n res = await fetch(url.toString(), {\n method,\n headers: this.headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n throw new ApiError(`Network error: ${(err as Error).message}`);\n }\n\n if (method === 'GET' && res.status === 404) return null;\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new ApiError(`${res.status} ${res.statusText}: ${text}`, res.status);\n }\n\n // Some DELETEs may return 204.\n if (res.status === 204) return null;\n return this.unwrap<T>(await res.json());\n }\n\n // ── Auth / identity ─────────────────────────────────────────────────────\n\n async whoami(): Promise<UserProfile | null> {\n return this.request<UserProfile>('GET', '/auth/me');\n }\n\n // ── Inboxes ─────────────────────────────────────────────────────────────\n\n async listInboxes(query: { organizationId?: string; search?: string; limit?: number; page?: number } = {}): Promise<{ items: InboxListItem[]; meta: PaginationMeta }> {\n const result = await this.request<{ items: InboxListItem[]; meta: PaginationMeta }>(\n 'GET',\n '/inboxes',\n undefined,\n query,\n );\n return result ?? { items: [], meta: { total: 0, page: 1, limit: 20, totalPages: 0 } };\n }\n\n async createInbox(body: { address?: string; label?: string; organizationId?: string }): Promise<Inbox> {\n const result = await this.request<Inbox>('POST', '/inboxes', body);\n if (!result) throw new ApiError('createInbox returned no body');\n return result;\n }\n\n async getInboxById(id: string): Promise<Inbox | null> {\n return this.request<Inbox>('GET', `/inboxes/${id}`);\n }\n\n /**\n * The API has no GET /inboxes?address=... filter; emulate by listing and\n * matching client-side. Good enough at our volumes.\n */\n async getInboxByAddress(address: string): Promise<InboxListItem | null> {\n const target = address.toLowerCase();\n // Page through up to 500 inboxes — anything beyond that and an agent\n // should be using the id-form lookup anyway.\n const { items } = await this.listInboxes({ limit: 100 });\n return items.find((i) => i.address.toLowerCase() === target) ?? null;\n }\n\n async deleteInbox(id: string): Promise<void> {\n await this.request<void>('DELETE', `/inboxes/${id}`);\n }\n\n // ── Emails ──────────────────────────────────────────────────────────────\n\n async listEmails(\n inboxId: string,\n query: { limit?: number; page?: number; unreadOnly?: boolean; since?: string; search?: string } = {},\n ): Promise<{ items: Email[]; meta: PaginationMeta }> {\n const result = await this.request<{ items: Email[]; meta: PaginationMeta }>(\n 'GET',\n `/inboxes/${inboxId}/emails`,\n undefined,\n query,\n );\n return result ?? { items: [], meta: { total: 0, page: 1, limit: 20, totalPages: 0 } };\n }\n\n async getEmail(id: string): Promise<Email | null> {\n return this.request<Email>('GET', `/emails/${id}`);\n }\n\n async deleteEmail(id: string): Promise<void> {\n await this.request<void>('DELETE', `/emails/${id}`);\n }\n\n async markRead(id: string): Promise<Email> {\n const r = await this.request<Email>('PATCH', `/emails/${id}/read`, {});\n if (!r) throw new ApiError('markRead returned no body');\n return r;\n }\n\n async markUnread(id: string): Promise<Email> {\n const r = await this.request<Email>('PATCH', `/emails/${id}/unread`, {});\n if (!r) throw new ApiError('markUnread returned no body');\n return r;\n }\n}\n","/**\n * Secret-key (API-key) validator for server-to-server MCP access.\n *\n * InstantInbox does not yet have a dedicated key-introspection endpoint, so\n * we validate any presented Bearer token by calling `GET /auth/me`. Two\n * formats are accepted:\n * - `sk_live_*` / `sk_test_*` — reserved for future API-key support; today\n * these are passed through to /auth/me and will fail unless the API\n * accepts them.\n * - Any other string — treated as a user JWT and validated via /auth/me.\n *\n * On success, ALL_SCOPES is granted (the inbox API has no per-token scoping\n * yet — every authenticated user can read/write their own inboxes + emails).\n * When the API grows scoped keys, swap this for a real introspection call\n * and stop granting blanket scopes.\n */\nimport { ALL_SCOPES, SCOPE } from '@koderlabs/inbox-shared';\nimport { ApiError, AuthError } from '../errors.js';\nimport { ScopeSet } from './scopes.js';\n\nexport interface KeyIdentity {\n userId: string;\n email?: string;\n scopes: ScopeSet;\n kind: 'jwt' | 'secret-key';\n}\n\n/** Pull the raw token from \"Bearer <token>\". */\nexport function extractBearerToken(authHeader: string | undefined): string | null {\n if (!authHeader?.startsWith('Bearer ')) return null;\n const token = authHeader.slice(7).trim();\n return token || null;\n}\n\n/**\n * Validate a Bearer token by calling GET /auth/me on the InstantInbox API.\n * Any non-2xx response is fatal — we never grant scopes to an unverified token.\n */\nexport async function validateSecretKey(\n token: string,\n apiBaseUrl: string,\n): Promise<KeyIdentity> {\n const url = `${apiBaseUrl}/auth/me`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(5000),\n });\n } catch (err) {\n throw new ApiError(`Cannot reach InstantInbox API: ${(err as Error).message}`);\n }\n\n if (!res.ok) {\n throw new AuthError('invalid_token', `/auth/me returned ${res.status}`);\n }\n\n let body: { success?: boolean; data?: { id?: string; email?: string } };\n try {\n body = (await res.json()) as typeof body;\n } catch (err) {\n throw new ApiError(`Malformed /auth/me response: ${(err as Error).message}`);\n }\n\n const data = body.data ?? (body as unknown as { id?: string; email?: string });\n if (!data?.id) {\n throw new AuthError('invalid_token', '/auth/me response missing user id');\n }\n\n // No per-token scoping in the inbox API today — grant all four scopes.\n // Future work: introspect the key and intersect with its grants.\n void SCOPE; // keep import warm for downstream callers\n return {\n userId: data.id,\n email: data.email,\n scopes: new ScopeSet([...ALL_SCOPES]),\n kind: token.startsWith('sk_') ? 'secret-key' : 'jwt',\n };\n}\n"],"mappings":"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","names":["OrgRole","z","z","z","z","McpError","ErrorCode","z","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","z","DESCRIPTION","z","McpError","ErrorCode"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// @koderlabs/inbox-mcp — generated build, do not edit
|
|
2
|
+
import {
|
|
3
|
+
ALL_SCOPES,
|
|
4
|
+
ApiClient,
|
|
5
|
+
ApiError,
|
|
6
|
+
AuthError,
|
|
7
|
+
ScopeSet,
|
|
8
|
+
createMcpServer,
|
|
9
|
+
extractBearerToken,
|
|
10
|
+
validateSecretKey
|
|
11
|
+
} from "./chunk-T7SFANFP.js";
|
|
12
|
+
|
|
13
|
+
// src/index.ts
|
|
14
|
+
import { serve } from "@hono/node-server";
|
|
15
|
+
import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
|
|
16
|
+
import { Hono } from "hono";
|
|
17
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
18
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
19
|
+
|
|
20
|
+
// src/auth/oauth.ts
|
|
21
|
+
function assertIntrospectKeyConfigured(env = process.env) {
|
|
22
|
+
return env.MCP_INTROSPECT_KEY?.trim() ?? "";
|
|
23
|
+
}
|
|
24
|
+
async function validateOAuthToken(_token, apiBaseUrl, introspectKey, _expectedResource, _allowUnboundResource = false) {
|
|
25
|
+
if (!introspectKey) {
|
|
26
|
+
throw new AuthError(
|
|
27
|
+
"config_error",
|
|
28
|
+
"OAuth introspection is not yet configured on this InstantInbox MCP. Use a JWT or sk_* key instead."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const url = `${apiBaseUrl.replace(/\/$/, "")}/oauth/introspect`;
|
|
32
|
+
let res;
|
|
33
|
+
try {
|
|
34
|
+
res = await fetch(url, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
Authorization: `Bearer ${introspectKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ token: _token }),
|
|
41
|
+
signal: AbortSignal.timeout(5e3)
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw new ApiError(`Cannot reach InstantInbox API: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
if (res.status === 404) {
|
|
47
|
+
throw new AuthError(
|
|
48
|
+
"config_error",
|
|
49
|
+
"OAuth introspection endpoint not yet implemented on InstantInbox API"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
throw new AuthError("invalid_token", `introspect returned ${res.status}`);
|
|
54
|
+
}
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
body = await res.json();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new ApiError(`Malformed introspect response: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
if (!body.active || !body.userId || !Array.isArray(body.scopes)) {
|
|
62
|
+
throw new AuthError("invalid_token", "token inactive or malformed introspect payload");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
userId: body.userId,
|
|
66
|
+
scopes: new ScopeSet(body.scopes),
|
|
67
|
+
kind: "oauth",
|
|
68
|
+
expiresAt: body.expiresAt ? new Date(body.expiresAt) : new Date(Date.now() + 36e5),
|
|
69
|
+
resource: body.resource ?? []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/index.ts
|
|
74
|
+
var PORT = parseInt(process.env.PORT ?? "10405", 10);
|
|
75
|
+
var API_BASE_URL = (process.env.API_BASE_URL ?? "http://localhost:10402/api/v1").replace(/\/$/, "");
|
|
76
|
+
var MAX_BODY_BYTES = parseInt(process.env.MCP_MAX_BODY_BYTES ?? `${1024 * 1024}`, 10);
|
|
77
|
+
var ALLOWED_ORIGINS = new Set(
|
|
78
|
+
(process.env.MCP_ALLOWED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
79
|
+
);
|
|
80
|
+
var RATE_LIMIT_RPM = parseInt(process.env.MCP_RATE_LIMIT_RPM ?? "120", 10);
|
|
81
|
+
var RATE_WINDOW_MS = 6e4;
|
|
82
|
+
var rateBuckets = /* @__PURE__ */ new Map();
|
|
83
|
+
function rateLimitHit(tokenKey) {
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const bucket = rateBuckets.get(tokenKey);
|
|
86
|
+
if (!bucket || bucket.resetAt <= now) {
|
|
87
|
+
rateBuckets.set(tokenKey, { count: 1, resetAt: now + RATE_WINDOW_MS });
|
|
88
|
+
return { ok: true, retryAfterSec: 0 };
|
|
89
|
+
}
|
|
90
|
+
bucket.count += 1;
|
|
91
|
+
if (bucket.count > RATE_LIMIT_RPM) {
|
|
92
|
+
return { ok: false, retryAfterSec: Math.ceil((bucket.resetAt - now) / 1e3) };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, retryAfterSec: 0 };
|
|
95
|
+
}
|
|
96
|
+
setInterval(() => {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
for (const [k, b] of rateBuckets) if (b.resetAt <= now) rateBuckets.delete(k);
|
|
99
|
+
}, RATE_WINDOW_MS).unref();
|
|
100
|
+
async function hashToken(token) {
|
|
101
|
+
const data = new TextEncoder().encode(token);
|
|
102
|
+
const digest = await crypto.subtle.digest("SHA-1", data);
|
|
103
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
104
|
+
}
|
|
105
|
+
var PUBLIC_MCP_URL = (process.env.MCP_PUBLIC_URL ?? "https://inbox.koderlabs.net/mcp").replace(/\/$/, "");
|
|
106
|
+
var PUBLIC_AUTH_ORIGIN = (process.env.MCP_AUTH_ORIGIN ?? "https://inbox.koderlabs.net").replace(/\/$/, "");
|
|
107
|
+
var RESOURCE_METADATA_URL = `${PUBLIC_MCP_URL}/.well-known/oauth-protected-resource`;
|
|
108
|
+
var ALLOW_UNBOUND_TOKENS = process.env.MCP_ALLOW_UNBOUND_TOKENS === "true";
|
|
109
|
+
var MCP_INTROSPECT_KEY = assertIntrospectKeyConfigured(process.env);
|
|
110
|
+
var app = new Hono();
|
|
111
|
+
app.get("/healthz", (c) => c.json({ status: "ok", service: "instantinbox-mcp", port: PORT }));
|
|
112
|
+
function protectedResourceMetadata() {
|
|
113
|
+
return {
|
|
114
|
+
resource: PUBLIC_MCP_URL,
|
|
115
|
+
authorization_servers: [PUBLIC_AUTH_ORIGIN],
|
|
116
|
+
scopes_supported: [...ALL_SCOPES],
|
|
117
|
+
bearer_methods_supported: ["header"],
|
|
118
|
+
resource_documentation: "https://inbox.koderlabs.net/docs/mcp"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function authorizationServerMetadata() {
|
|
122
|
+
return {
|
|
123
|
+
issuer: PUBLIC_AUTH_ORIGIN,
|
|
124
|
+
authorization_endpoint: `${PUBLIC_AUTH_ORIGIN}/oauth/authorize`,
|
|
125
|
+
token_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/token`,
|
|
126
|
+
registration_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/register`,
|
|
127
|
+
revocation_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/revoke`,
|
|
128
|
+
introspection_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/introspect`,
|
|
129
|
+
response_types_supported: ["code"],
|
|
130
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
131
|
+
code_challenge_methods_supported: ["S256"],
|
|
132
|
+
token_endpoint_auth_methods_supported: ["none", "client_secret_post"],
|
|
133
|
+
resource_indicators_supported: true,
|
|
134
|
+
scopes_supported: [...ALL_SCOPES]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
var protectedRoute = (c) => {
|
|
138
|
+
c.header("Cache-Control", "public, max-age=3600");
|
|
139
|
+
return c.json(protectedResourceMetadata());
|
|
140
|
+
};
|
|
141
|
+
var authServerRoute = (c) => {
|
|
142
|
+
c.header("Cache-Control", "public, max-age=3600");
|
|
143
|
+
return c.json(authorizationServerMetadata());
|
|
144
|
+
};
|
|
145
|
+
app.get("/.well-known/oauth-protected-resource", protectedRoute);
|
|
146
|
+
app.get("/mcp/.well-known/oauth-protected-resource", protectedRoute);
|
|
147
|
+
app.get("/.well-known/oauth-authorization-server", authServerRoute);
|
|
148
|
+
app.get("/mcp/.well-known/oauth-authorization-server", authServerRoute);
|
|
149
|
+
var transports = /* @__PURE__ */ new Map();
|
|
150
|
+
app.all("/mcp", async (c) => {
|
|
151
|
+
const req = c.req.raw;
|
|
152
|
+
const method = req.method;
|
|
153
|
+
const origin = req.headers.get("origin");
|
|
154
|
+
if (origin && !ALLOWED_ORIGINS.has(origin)) {
|
|
155
|
+
return c.json({ error: "origin not allowed" }, 400);
|
|
156
|
+
}
|
|
157
|
+
if (method === "POST") {
|
|
158
|
+
const cl = req.headers.get("content-length");
|
|
159
|
+
if (cl && parseInt(cl, 10) > MAX_BODY_BYTES) {
|
|
160
|
+
return c.json({ error: `payload exceeds ${MAX_BODY_BYTES} bytes` }, 413);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const token = extractBearerToken(req.headers.get("authorization") ?? void 0);
|
|
164
|
+
const wwwAuth = `Bearer realm="instantinbox", resource_metadata="${RESOURCE_METADATA_URL}"`;
|
|
165
|
+
if (!token) {
|
|
166
|
+
c.header("WWW-Authenticate", wwwAuth);
|
|
167
|
+
return c.json({ error: "Missing Authorization: Bearer <token> header" }, 401);
|
|
168
|
+
}
|
|
169
|
+
let scopes;
|
|
170
|
+
let apiToken;
|
|
171
|
+
try {
|
|
172
|
+
if (token.startsWith("sk_live_") || token.startsWith("sk_test_") || token.split(".").length === 3) {
|
|
173
|
+
const identity = await validateSecretKey(token, API_BASE_URL);
|
|
174
|
+
scopes = identity.scopes;
|
|
175
|
+
apiToken = token;
|
|
176
|
+
} else {
|
|
177
|
+
const identity = await validateOAuthToken(
|
|
178
|
+
token,
|
|
179
|
+
API_BASE_URL,
|
|
180
|
+
MCP_INTROSPECT_KEY,
|
|
181
|
+
PUBLIC_MCP_URL,
|
|
182
|
+
ALLOW_UNBOUND_TOKENS
|
|
183
|
+
);
|
|
184
|
+
scopes = identity.scopes;
|
|
185
|
+
apiToken = token;
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
const msg = err instanceof Error ? err.message : "Auth failed";
|
|
189
|
+
c.header(
|
|
190
|
+
"WWW-Authenticate",
|
|
191
|
+
`${wwwAuth}, error="invalid_token", error_description="${msg.replace(/"/g, "'")}"`
|
|
192
|
+
);
|
|
193
|
+
return c.json({ error: msg }, 401);
|
|
194
|
+
}
|
|
195
|
+
const tokenKey = await hashToken(apiToken);
|
|
196
|
+
const rl = rateLimitHit(tokenKey);
|
|
197
|
+
if (!rl.ok) {
|
|
198
|
+
c.header("Retry-After", String(rl.retryAfterSec));
|
|
199
|
+
return c.json({ error: "rate limit exceeded" }, 429);
|
|
200
|
+
}
|
|
201
|
+
const apiClient = new ApiClient({ baseUrl: API_BASE_URL, token: apiToken });
|
|
202
|
+
const mcpServer = createMcpServer(() => ({ apiClient, scopes }));
|
|
203
|
+
if (method === "POST") {
|
|
204
|
+
const body = await req.json().catch(() => null);
|
|
205
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
206
|
+
if (isInitializeRequest(body)) {
|
|
207
|
+
const transport = new StreamableHTTPServerTransport({
|
|
208
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
209
|
+
onsessioninitialized: (sessionId2) => {
|
|
210
|
+
transports.set(sessionId2, transport);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
transport.onclose = () => {
|
|
214
|
+
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
215
|
+
};
|
|
216
|
+
await mcpServer.connect(transport);
|
|
217
|
+
await transport.handleRequest(c.env.incoming, c.env.outgoing, body);
|
|
218
|
+
return RESPONSE_ALREADY_SENT;
|
|
219
|
+
}
|
|
220
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
221
|
+
if (sessionId && transports.has(sessionId)) {
|
|
222
|
+
const transport = transports.get(sessionId);
|
|
223
|
+
await transport.handleRequest(c.env.incoming, c.env.outgoing, body);
|
|
224
|
+
return RESPONSE_ALREADY_SENT;
|
|
225
|
+
}
|
|
226
|
+
return c.json({ error: "Invalid or missing session" }, 400);
|
|
227
|
+
}
|
|
228
|
+
if (method === "GET") {
|
|
229
|
+
const sessionId = req.headers.get("mcp-session-id") ?? new URL(req.url).searchParams.get("sessionId");
|
|
230
|
+
if (sessionId && transports.has(sessionId)) {
|
|
231
|
+
const transport = transports.get(sessionId);
|
|
232
|
+
await transport.handleRequest(c.env.incoming, c.env.outgoing);
|
|
233
|
+
return RESPONSE_ALREADY_SENT;
|
|
234
|
+
}
|
|
235
|
+
return c.json({ error: "Unknown session for SSE" }, 404);
|
|
236
|
+
}
|
|
237
|
+
if (method === "DELETE") {
|
|
238
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
239
|
+
if (sessionId && transports.has(sessionId)) {
|
|
240
|
+
const transport = transports.get(sessionId);
|
|
241
|
+
await transport.close();
|
|
242
|
+
transports.delete(sessionId);
|
|
243
|
+
return c.json({ ok: true });
|
|
244
|
+
}
|
|
245
|
+
return c.json({ error: "Session not found" }, 404);
|
|
246
|
+
}
|
|
247
|
+
return c.json({ error: "Method not allowed" }, 405);
|
|
248
|
+
});
|
|
249
|
+
serve({ fetch: app.fetch, port: PORT }, (info) => {
|
|
250
|
+
console.log(`InstantInbox MCP server listening on port ${info.port}`);
|
|
251
|
+
console.log(` MCP endpoint: http://localhost:${info.port}/mcp`);
|
|
252
|
+
console.log(` Health: http://localhost:${info.port}/healthz`);
|
|
253
|
+
console.log(` API base: ${API_BASE_URL}`);
|
|
254
|
+
});
|
|
255
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth/oauth.ts"],"sourcesContent":["/**\n * HTTP transport entry — Hono app with Streamable HTTP MCP transport.\n *\n * Endpoints:\n * POST /mcp — MCP JSON-RPC (Streamable HTTP per spec)\n * GET /mcp — SSE stream for server-initiated messages\n * DELETE /mcp — close a session\n * GET /healthz — liveness for Docker / LB\n *\n * Auth: Bearer token in Authorization header. Three accepted shapes:\n * sk_live_* / sk_test_* → validated as a management/api key via /auth/me\n * <JWT> → validated as a user JWT via /auth/me\n * Otherwise → attempted OAuth introspection (returns 401 today)\n *\n * Port: 10405 (project port range 10400–10499).\n */\nimport { serve } from '@hono/node-server';\nimport type { HttpBindings } from '@hono/node-server';\nimport { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';\nimport { Hono } from 'hono';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\nimport { ALL_SCOPES } from '@koderlabs/inbox-shared';\nimport { createMcpServer } from './server.js';\nimport { ApiClient } from './api-client.js';\nimport { ScopeSet } from './auth/scopes.js';\nimport { extractBearerToken, validateSecretKey } from './auth/secret-key.js';\nimport { validateOAuthToken, assertIntrospectKeyConfigured } from './auth/oauth.js';\n\nconst PORT = parseInt(process.env.PORT ?? '10405', 10);\nconst API_BASE_URL = (process.env.API_BASE_URL ?? 'http://localhost:10402/api/v1').replace(/\\/$/, '');\n\n/** 1 MiB cap on POST /mcp body — protect against memory DoS via huge envelopes. */\nconst MAX_BODY_BYTES = parseInt(process.env.MCP_MAX_BODY_BYTES ?? `${1024 * 1024}`, 10);\n\n/**\n * MCP spec mandates Origin validation on Streamable-HTTP servers to prevent\n * DNS-rebinding attacks. Empty allowlist = allow requests with NO Origin\n * (typical for native clients like Claude Desktop, mcp-remote, curl).\n * Browser clients must match exactly — set MCP_ALLOWED_ORIGINS=...\n */\nconst ALLOWED_ORIGINS = new Set(\n (process.env.MCP_ALLOWED_ORIGINS ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n);\n\n/** Per-token rate limit, bucketed by SHA-1(token) so the raw key never leaks. */\nconst RATE_LIMIT_RPM = parseInt(process.env.MCP_RATE_LIMIT_RPM ?? '120', 10);\nconst RATE_WINDOW_MS = 60_000;\ntype RateBucket = { count: number; resetAt: number };\nconst rateBuckets = new Map<string, RateBucket>();\n\nfunction rateLimitHit(tokenKey: string): { ok: boolean; retryAfterSec: number } {\n const now = Date.now();\n const bucket = rateBuckets.get(tokenKey);\n if (!bucket || bucket.resetAt <= now) {\n rateBuckets.set(tokenKey, { count: 1, resetAt: now + RATE_WINDOW_MS });\n return { ok: true, retryAfterSec: 0 };\n }\n bucket.count += 1;\n if (bucket.count > RATE_LIMIT_RPM) {\n return { ok: false, retryAfterSec: Math.ceil((bucket.resetAt - now) / 1000) };\n }\n return { ok: true, retryAfterSec: 0 };\n}\nsetInterval(() => {\n const now = Date.now();\n for (const [k, b] of rateBuckets) if (b.resetAt <= now) rateBuckets.delete(k);\n}, RATE_WINDOW_MS).unref();\n\nasync function hashToken(token: string): Promise<string> {\n const data = new TextEncoder().encode(token);\n const digest = await crypto.subtle.digest('SHA-1', data);\n return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** Public-facing URL — used in OAuth resource-metadata discovery. */\nconst PUBLIC_MCP_URL = (process.env.MCP_PUBLIC_URL ?? 'https://inbox.koderlabs.net/mcp').replace(/\\/$/, '');\nconst PUBLIC_AUTH_ORIGIN = (process.env.MCP_AUTH_ORIGIN ?? 'https://inbox.koderlabs.net').replace(/\\/$/, '');\nconst RESOURCE_METADATA_URL = `${PUBLIC_MCP_URL}/.well-known/oauth-protected-resource`;\nconst ALLOW_UNBOUND_TOKENS = process.env.MCP_ALLOW_UNBOUND_TOKENS === 'true';\n\n// OAuth path is best-effort on inbox API today — never fail boot on missing key.\nconst MCP_INTROSPECT_KEY = assertIntrospectKeyConfigured(process.env);\n\nconst app = new Hono<{ Bindings: HttpBindings }>();\n\n// ── Health ────────────────────────────────────────────────────────────────────\n\napp.get('/healthz', (c) => c.json({ status: 'ok', service: 'instantinbox-mcp', port: PORT }));\n\n// ── OAuth discovery (RFC 9728 + 8414) — for future-proofing only. ────────────\n\nfunction protectedResourceMetadata() {\n return {\n resource: PUBLIC_MCP_URL,\n authorization_servers: [PUBLIC_AUTH_ORIGIN],\n scopes_supported: [...ALL_SCOPES],\n bearer_methods_supported: ['header'],\n resource_documentation: 'https://inbox.koderlabs.net/docs/mcp',\n };\n}\nfunction authorizationServerMetadata() {\n return {\n issuer: PUBLIC_AUTH_ORIGIN,\n authorization_endpoint: `${PUBLIC_AUTH_ORIGIN}/oauth/authorize`,\n token_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/token`,\n registration_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/register`,\n revocation_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/revoke`,\n introspection_endpoint: `${PUBLIC_AUTH_ORIGIN}/api/v1/oauth/introspect`,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none', 'client_secret_post'],\n resource_indicators_supported: true,\n scopes_supported: [...ALL_SCOPES],\n };\n}\nconst protectedRoute = (c: { header: (k: string, v: string) => void; json: (b: unknown) => Response }) => {\n c.header('Cache-Control', 'public, max-age=3600');\n return c.json(protectedResourceMetadata());\n};\nconst authServerRoute = (c: { header: (k: string, v: string) => void; json: (b: unknown) => Response }) => {\n c.header('Cache-Control', 'public, max-age=3600');\n return c.json(authorizationServerMetadata());\n};\napp.get('/.well-known/oauth-protected-resource', protectedRoute as never);\napp.get('/mcp/.well-known/oauth-protected-resource', protectedRoute as never);\napp.get('/.well-known/oauth-authorization-server', authServerRoute as never);\napp.get('/mcp/.well-known/oauth-authorization-server', authServerRoute as never);\n\n// ── MCP Streamable HTTP ──────────────────────────────────────────────────────\n\nconst transports = new Map<string, StreamableHTTPServerTransport>();\n\napp.all('/mcp', async (c) => {\n const req = c.req.raw;\n const method = req.method;\n\n // Origin allowlist — browser-rebinding defence. Native clients omit Origin.\n const origin = req.headers.get('origin');\n if (origin && !ALLOWED_ORIGINS.has(origin)) {\n return c.json({ error: 'origin not allowed' }, 400);\n }\n\n if (method === 'POST') {\n const cl = req.headers.get('content-length');\n if (cl && parseInt(cl, 10) > MAX_BODY_BYTES) {\n return c.json({ error: `payload exceeds ${MAX_BODY_BYTES} bytes` }, 413);\n }\n }\n\n const token = extractBearerToken(req.headers.get('authorization') ?? undefined);\n\n const wwwAuth = `Bearer realm=\"instantinbox\", resource_metadata=\"${RESOURCE_METADATA_URL}\"`;\n if (!token) {\n c.header('WWW-Authenticate', wwwAuth);\n return c.json({ error: 'Missing Authorization: Bearer <token> header' }, 401);\n }\n\n let scopes: ScopeSet;\n let apiToken: string;\n try {\n // Treat sk_* AND opaque JWT-shaped tokens both as \"validate via /auth/me\".\n // Anything else (rare today) attempts OAuth introspection.\n if (token.startsWith('sk_live_') || token.startsWith('sk_test_') || token.split('.').length === 3) {\n const identity = await validateSecretKey(token, API_BASE_URL);\n scopes = identity.scopes;\n apiToken = token;\n } else {\n const identity = await validateOAuthToken(\n token,\n API_BASE_URL,\n MCP_INTROSPECT_KEY,\n PUBLIC_MCP_URL,\n ALLOW_UNBOUND_TOKENS,\n );\n scopes = identity.scopes;\n apiToken = token;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Auth failed';\n c.header(\n 'WWW-Authenticate',\n `${wwwAuth}, error=\"invalid_token\", error_description=\"${msg.replace(/\"/g, \"'\")}\"`,\n );\n return c.json({ error: msg }, 401);\n }\n\n const tokenKey = await hashToken(apiToken);\n const rl = rateLimitHit(tokenKey);\n if (!rl.ok) {\n c.header('Retry-After', String(rl.retryAfterSec));\n return c.json({ error: 'rate limit exceeded' }, 429);\n }\n\n const apiClient = new ApiClient({ baseUrl: API_BASE_URL, token: apiToken });\n const mcpServer = createMcpServer(() => ({ apiClient, scopes }));\n\n if (method === 'POST') {\n const body = await req.json().catch(() => null);\n if (!body) return c.json({ error: 'Invalid JSON body' }, 400);\n\n if (isInitializeRequest(body)) {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => crypto.randomUUID(),\n onsessioninitialized: (sessionId) => { transports.set(sessionId, transport); },\n });\n transport.onclose = () => { if (transport.sessionId) transports.delete(transport.sessionId); };\n await mcpServer.connect(transport);\n await transport.handleRequest(c.env.incoming, c.env.outgoing, body);\n return RESPONSE_ALREADY_SENT;\n }\n\n const sessionId = req.headers.get('mcp-session-id');\n if (sessionId && transports.has(sessionId)) {\n const transport = transports.get(sessionId)!;\n await transport.handleRequest(c.env.incoming, c.env.outgoing, body);\n return RESPONSE_ALREADY_SENT;\n }\n return c.json({ error: 'Invalid or missing session' }, 400);\n }\n\n if (method === 'GET') {\n const sessionId = req.headers.get('mcp-session-id') ?? new URL(req.url).searchParams.get('sessionId');\n if (sessionId && transports.has(sessionId)) {\n const transport = transports.get(sessionId)!;\n await transport.handleRequest(c.env.incoming, c.env.outgoing);\n return RESPONSE_ALREADY_SENT;\n }\n return c.json({ error: 'Unknown session for SSE' }, 404);\n }\n\n if (method === 'DELETE') {\n const sessionId = req.headers.get('mcp-session-id');\n if (sessionId && transports.has(sessionId)) {\n const transport = transports.get(sessionId)!;\n await transport.close();\n transports.delete(sessionId);\n return c.json({ ok: true });\n }\n return c.json({ error: 'Session not found' }, 404);\n }\n\n return c.json({ error: 'Method not allowed' }, 405);\n});\n\nserve({ fetch: app.fetch, port: PORT }, (info) => {\n // eslint-disable-next-line no-console\n console.log(`InstantInbox MCP server listening on port ${info.port}`);\n // eslint-disable-next-line no-console\n console.log(` MCP endpoint: http://localhost:${info.port}/mcp`);\n // eslint-disable-next-line no-console\n console.log(` Health: http://localhost:${info.port}/healthz`);\n // eslint-disable-next-line no-console\n console.log(` API base: ${API_BASE_URL}`);\n});\n","/**\n * OAuth bearer-token validator (skeleton).\n *\n * The InstantInbox API does not yet expose an RFC 7662 introspection endpoint,\n * so this module is a placeholder that fails closed. Wire it up once\n * `POST /oauth/introspect` exists on apps/api.\n */\nimport { ApiError, AuthError } from '../errors.js';\nimport { ScopeSet } from './scopes.js';\n\nexport interface OAuthIdentity {\n userId: string;\n scopes: ScopeSet;\n kind: 'oauth';\n expiresAt: Date;\n resource: string[];\n}\n\n/**\n * Read the management key used to authenticate the MCP -> API introspection\n * call. Returns the raw value (no validation of shape) — OAuth flow is opt-in\n * and only enforced when a non-secret-key token reaches this code path.\n */\nexport function assertIntrospectKeyConfigured(env: NodeJS.ProcessEnv = process.env): string {\n // Inbox API has no /oauth/introspect endpoint yet, so this is best-effort.\n // Allow boot without a key — secret-key path is the priority.\n return env.MCP_INTROSPECT_KEY?.trim() ?? '';\n}\n\nexport async function validateOAuthToken(\n _token: string,\n apiBaseUrl: string,\n introspectKey: string,\n _expectedResource?: string,\n _allowUnboundResource = false,\n): Promise<OAuthIdentity> {\n if (!introspectKey) {\n throw new AuthError(\n 'config_error',\n 'OAuth introspection is not yet configured on this InstantInbox MCP. Use a JWT or sk_* key instead.',\n );\n }\n\n // Stub: try POST /oauth/introspect; if the API doesn't implement it, fail\n // with a clear message so the client knows OAuth isn't live.\n const url = `${apiBaseUrl.replace(/\\/$/, '')}/oauth/introspect`;\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${introspectKey}`,\n },\n body: JSON.stringify({ token: _token }),\n signal: AbortSignal.timeout(5000),\n });\n } catch (err) {\n throw new ApiError(`Cannot reach InstantInbox API: ${(err as Error).message}`);\n }\n\n if (res.status === 404) {\n throw new AuthError(\n 'config_error',\n 'OAuth introspection endpoint not yet implemented on InstantInbox API',\n );\n }\n if (!res.ok) {\n throw new AuthError('invalid_token', `introspect returned ${res.status}`);\n }\n\n let body: { active?: boolean; userId?: string; scopes?: string[]; expiresAt?: string; resource?: string[] };\n try {\n body = (await res.json()) as typeof body;\n } catch (err) {\n throw new ApiError(`Malformed introspect response: ${(err as Error).message}`);\n }\n\n if (!body.active || !body.userId || !Array.isArray(body.scopes)) {\n throw new AuthError('invalid_token', 'token inactive or malformed introspect payload');\n }\n\n return {\n userId: body.userId,\n scopes: new ScopeSet(body.scopes),\n kind: 'oauth',\n expiresAt: body.expiresAt ? new Date(body.expiresAt) : new Date(Date.now() + 3600_000),\n resource: body.resource ?? [],\n };\n}\n"],"mappings":"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","names":["sessionId"]}
|
package/dist/stdio.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @koderlabs/inbox-mcp — generated build, do not edit
|
|
3
|
+
import {
|
|
4
|
+
ApiClient,
|
|
5
|
+
createMcpServer,
|
|
6
|
+
validateSecretKey
|
|
7
|
+
} from "./chunk-T7SFANFP.js";
|
|
8
|
+
|
|
9
|
+
// src/stdio.ts
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
var API_BASE_URL = (process.env.INBOX_API_BASE_URL ?? "http://localhost:10402/api/v1").replace(/\/$/, "");
|
|
12
|
+
var API_KEY = process.env.INBOX_API_KEY ?? "";
|
|
13
|
+
async function main() {
|
|
14
|
+
if (!API_KEY) {
|
|
15
|
+
process.stderr.write(
|
|
16
|
+
"Error: INBOX_API_KEY environment variable is required.\nSet it to a user JWT or sk_live_* / sk_test_* key from your InstantInbox account.\n"
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
let scopes;
|
|
21
|
+
try {
|
|
22
|
+
const identity = await validateSecretKey(API_KEY, API_BASE_URL);
|
|
23
|
+
scopes = identity.scopes;
|
|
24
|
+
process.stderr.write(
|
|
25
|
+
`InstantInbox MCP stdio: authenticated as ${identity.email ?? identity.userId} (scopes: ${scopes.toArray().join(", ")})
|
|
26
|
+
`
|
|
27
|
+
);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
process.stderr.write(`InstantInbox MCP stdio: auth failed \u2014 ${err.message}
|
|
30
|
+
`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
const apiClient = new ApiClient({ baseUrl: API_BASE_URL, token: API_KEY });
|
|
35
|
+
const server = createMcpServer(() => ({ apiClient, scopes }));
|
|
36
|
+
const transport = new StdioServerTransport();
|
|
37
|
+
await server.connect(transport);
|
|
38
|
+
process.stderr.write("InstantInbox MCP server ready on stdio.\n");
|
|
39
|
+
}
|
|
40
|
+
main().catch((err) => {
|
|
41
|
+
process.stderr.write(`Fatal: ${err.message}
|
|
42
|
+
`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stdio.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * stdio transport entry — for `instantinbox-mcp` (Claude Desktop, etc).\n *\n * Env:\n * INBOX_API_BASE_URL — InstantInbox API (default http://localhost:10402/api/v1)\n * INBOX_API_KEY — Bearer token (sk_live_* / sk_test_* / user JWT)\n */\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createMcpServer } from './server.js';\nimport { ApiClient } from './api-client.js';\nimport { ScopeSet } from './auth/scopes.js';\nimport { validateSecretKey } from './auth/secret-key.js';\n\nconst API_BASE_URL = (process.env.INBOX_API_BASE_URL ?? 'http://localhost:10402/api/v1').replace(/\\/$/, '');\nconst API_KEY = process.env.INBOX_API_KEY ?? '';\n\nasync function main(): Promise<void> {\n if (!API_KEY) {\n process.stderr.write(\n 'Error: INBOX_API_KEY environment variable is required.\\n' +\n 'Set it to a user JWT or sk_live_* / sk_test_* key from your InstantInbox account.\\n',\n );\n process.exit(1);\n }\n\n // Validate once at startup — refuse to start on bad creds, never serve\n // tool calls with permissive scopes.\n let scopes: ScopeSet;\n try {\n const identity = await validateSecretKey(API_KEY, API_BASE_URL);\n scopes = identity.scopes;\n process.stderr.write(\n `InstantInbox MCP stdio: authenticated as ${identity.email ?? identity.userId} ` +\n `(scopes: ${scopes.toArray().join(', ')})\\n`,\n );\n } catch (err) {\n process.stderr.write(`InstantInbox MCP stdio: auth failed — ${(err as Error).message}\\n`);\n process.exit(2);\n throw err; // unreachable — narrows scopes for TS\n }\n\n const apiClient = new ApiClient({ baseUrl: API_BASE_URL, token: API_KEY });\n const server = createMcpServer(() => ({ apiClient, scopes }));\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write('InstantInbox MCP server ready on stdio.\\n');\n}\n\nmain().catch((err) => {\n process.stderr.write(`Fatal: ${err.message}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;AAQA,SAAS,4BAA4B;AAMrC,IAAM,gBAAgB,QAAQ,IAAI,sBAAsB,iCAAiC,QAAQ,OAAO,EAAE;AAC1G,IAAM,UAAU,QAAQ,IAAI,iBAAiB;AAE7C,eAAe,OAAsB;AACnC,MAAI,CAAC,SAAS;AACZ,YAAQ,OAAO;AAAA,MACb;AAAA,IAEF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAIA,MAAI;AACJ,MAAI;AACF,UAAM,WAAW,MAAM,kBAAkB,SAAS,YAAY;AAC9D,aAAS,SAAS;AAClB,YAAQ,OAAO;AAAA,MACb,4CAA4C,SAAS,SAAS,SAAS,MAAM,aACjE,OAAO,QAAQ,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,IACzC;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,8CAA0C,IAAc,OAAO;AAAA,CAAI;AACxF,YAAQ,KAAK,CAAC;AACd,UAAM;AAAA,EACR;AAEA,QAAM,YAAY,IAAI,UAAU,EAAE,SAAS,cAAc,OAAO,QAAQ,CAAC;AACzE,QAAM,SAAS,gBAAgB,OAAO,EAAE,WAAW,OAAO,EAAE;AAE5D,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAE9B,UAAQ,OAAO,MAAM,2CAA2C;AAClE;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,UAAU,IAAI,OAAO;AAAA,CAAI;AAC9C,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@koderlabs/inbox-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "InstantInbox MCP server — disposable email inboxes for AI agent E2E testing. Streamable HTTP + stdio transports.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"email",
|
|
10
|
+
"disposable-email",
|
|
11
|
+
"testing",
|
|
12
|
+
"ai-agent",
|
|
13
|
+
"claude",
|
|
14
|
+
"instantinbox"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"instantinbox-mcp": "./dist/stdio.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/jawaidgadiwala/inbox.git",
|
|
39
|
+
"directory": "apps/mcp"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/jawaidgadiwala/inbox/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://inbox.koderlabs.net",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"start": "node dist/index.js",
|
|
52
|
+
"start:stdio": "node dist/stdio.js",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"prepublishOnly": "pnpm build"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@hono/node-server": "^1.12.0",
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
60
|
+
"hono": "^4.6.0",
|
|
61
|
+
"zod": "^3.23.0",
|
|
62
|
+
"zod-to-json-schema": "^3.25.2"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@koderlabs/inbox-shared": "workspace:*",
|
|
66
|
+
"@types/node": "^22.0.0",
|
|
67
|
+
"tsup": "^8.3.0",
|
|
68
|
+
"typescript": "^5.7.0",
|
|
69
|
+
"vitest": "^2.1.0"
|
|
70
|
+
}
|
|
71
|
+
}
|