@kynver-app/mcp-inbox 0.1.2 → 0.1.4

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/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{StdioServerTransport as M}from"@modelcontextprotocol/sdk/server/stdio.js";import{existsSync as v,readFileSync as S}from"node:fs";import{dirname as a,join as u}from"node:path";import{fileURLToPath as m}from"node:url";function T(n){let e=a(m(n));for(let r=0;r<6;r+=1){if(v(u(e,"package.json")))return e;let i=a(e);if(i===e)break;e=i}throw new Error(`package.json not found above ${a(m(n))}`)}function E(n,e){let r=u(T(n),"package.json"),o=JSON.parse(S(r,"utf8"))[e];if(typeof o!="string"||!o.trim())throw new Error(`Missing package.json ${e} at ${r}`);return o}function c(n=import.meta.url){return E(n,"version")}function K(n){return n.some(e=>e==="--version"||e==="-v")}function N(n,e){console.log(e?`${e} ${n}`:n),process.exit(0)}function g(n,e=import.meta.url,r){return K(n)?(N(c(e),r),!0):!1}import{McpServer as $}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as t}from"zod";import{AsyncLocalStorage as P}from"node:async_hooks";var _=new P,l=process.env.KYNVER_API_KEY??"";function k(){let n=process.env.KYNVER_API_URL?.trim();if(!n)return"https://kynver.com";let e;try{e=new URL(n)}catch{throw new Error(`Invalid KYNVER_API_URL: ${n}`)}let r=e.hostname==="localhost"||e.hostname==="127.0.0.1";if(e.protocol!=="https:"&&!(e.protocol==="http:"&&r))throw new Error(`KYNVER_API_URL must be https (got "${e.protocol}//${e.hostname}"); API keys are sent to this origin.`);return n.replace(/\/$/,"")}function A(){return k()}function I(n){let e=n.startsWith("/")?n:`/${n}`;if(e.includes("..")||e.includes("\0"))throw new Error("Invalid API path: path traversal is not allowed")}function f(n){I(n);let e=n.startsWith("/")?n:`/${n}`,r=e.startsWith("/api")?e:`/api${e}`;return`${A()}${r}`}function y(){let n={"Content-Type":"application/json"};l&&(n.Authorization=`Bearer ${l}`);let e=_.getStore(),r=e?.userId??process.env.KYNVER_USER_ID?.trim(),i=process.env.KYNVER_SERVICE_SECRET?.trim();if(r){if(e?.sessionCredential)return e.authKind==="runnerToken"?(delete n.Authorization,n["X-Kynver-Runner-Token"]=e.sessionCredential):n.Authorization=`Bearer ${e.sessionCredential}`,n;let o=process.env.KYNVER_RUNNER_TOKEN?.trim();if(o){if(e?.authKind==="apiKey"||e?.authKind==="runnerToken")throw new Error("User-authenticated session did not propagate its credential; refusing the deployment-wide KYNVER_RUNNER_TOKEN (cross-tenant risk).");if(e?.authKind!=="internal")return n["X-Kynver-Runner-Token"]=o,n}if(!i)throw new Error("User context requires authentication: set KYNVER_RUNNER_TOKEN (preferred) or KYNVER_SERVICE_SECRET in the MCP server environment.");n["X-Kynver-User-Id"]=r,n["X-Kynver-Service-Secret"]=i}return n}async function x(n){let e=await n.text();if(!n.ok){let r=e;try{let i=JSON.parse(e);r=i.error??i.message??e}catch{}throw new Error(r||`HTTP ${n.status}`)}if(e)try{return JSON.parse(e)}catch{return e}}async function p(n){let e=await fetch(f(n),{method:"GET",headers:y()});return x(e)}async function s(n,e){let r=await fetch(f(n),{method:"POST",headers:y(),body:JSON.stringify(e)});return x(r)}function h(){return!!l}function b(){return"No API key configured. Set KYNVER_API_KEY in your MCP server config."}function d(n,e,r,i){return{name:n,description:e,inputSchemaZod:r,execute:async o=>h()?i(o):b()}}var C=d("inbox_list","List recent messages in your Kynver inbox, or read one message in full when id is set. Returns compact from/to/subject/preview unless reading a single id.",t.object({id:t.string().optional().describe("Message id to read in full"),limit:t.number().int().min(1).max(50).optional().describe("Max messages to list (default 20)")}),async n=>{let e=new URLSearchParams;n.id&&e.set("id",n.id),n.limit&&e.set("limit",String(n.limit));let r=e.toString();return JSON.stringify(await p(r?`/agent-inbox/messages?${r}`:"/agent-inbox/messages"))}),V=d("inbox_send","Send email from your Kynver inbox address. Replies thread when inReplyTo is the prior message id header.",t.object({to:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).describe("Recipient email or list; 50 total across to/cc/bcc"),cc:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).optional(),bcc:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).optional(),subject:t.string().min(1).max(998).describe("Subject line"),text:t.string().min(1).max(1e5).describe("Plain-text body"),inReplyTo:t.string().max(998).optional().describe("Prior RFC Message-ID, not Resend UUID"),references:t.array(t.string().max(998)).min(1).max(20).optional().describe("Ordered RFC Message-IDs; inReplyTo is appended if needed"),attachments:t.array(t.object({filename:t.string().min(1).max(255),content:t.string().min(4).max(4194304).describe("Canonical base64 bytes, not a URL/path. 2 MiB total decoded"),contentType:t.string().max(127).optional()}).strict()).max(10).optional(),idempotencyKey:t.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional().describe("Unique logical-send key; reuse identical payload/key on retries within provider retention, never for a new send")}),async n=>JSON.stringify(await s("/agent-inbox/send",n))),U=d("inbox_address","Get your Kynver inbox address, or create one on first use. Optional localPart is the local-part on the existing Kynver mail domain.",t.object({localPart:t.string().optional().describe("Requested local part on first create (ignored if you already have an address)"),displayName:t.string().optional().describe("From-name on first create")}),async n=>{if(n.localPart||n.displayName)return JSON.stringify(await s("/agent-inbox",n));let e=await p("/agent-inbox");return e.inbox?JSON.stringify(e):JSON.stringify(await s("/agent-inbox",{}))}),R=[C,V,U];var L=c(import.meta.url);function j(n){return{content:[{type:"text",text:n}]}}function w(){let n=new $({name:"kynver-mcp-inbox",version:L},{capabilities:{tools:{}}});for(let e of R)n.registerTool(e.name,{description:e.description,inputSchema:e.inputSchemaZod},async r=>j(await e.execute(r)));return n}g(process.argv.slice(2),import.meta.url,"kynver-mcp-inbox");var Y=w(),J=new M;await Y.connect(J);
2
+ import{StdioServerTransport as Y}from"@modelcontextprotocol/sdk/server/stdio.js";import{existsSync as S,readFileSync as k}from"node:fs";import{dirname as d,join as g}from"node:path";import{fileURLToPath as u}from"node:url";function A(e){let n=d(u(e));for(let r=0;r<6;r+=1){if(S(g(n,"package.json")))return n;let i=d(n);if(i===n)break;n=i}throw new Error(`package.json not found above ${d(u(e))}`)}function P(e,n){let r=g(A(e),"package.json"),o=JSON.parse(k(r,"utf8"))[n];if(typeof o!="string"||!o.trim())throw new Error(`Missing package.json ${n} at ${r}`);return o}function c(e=import.meta.url){return P(e,"version")}function N(e){return e.some(n=>n==="--version"||n==="-v")}function E(e,n){console.log(n?`${n} ${e}`:e),process.exit(0)}function f(e,n=import.meta.url,r){return N(e)?(E(c(n),r),!0):!1}import{McpServer as $}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as t}from"zod";import{AsyncLocalStorage as K}from"node:async_hooks";var I=new K,l=process.env.KYNVER_API_KEY??"";function _(){let e=process.env.KYNVER_API_URL?.trim();if(!e)return"https://kynver.com";let n;try{n=new URL(e)}catch{throw new Error(`Invalid KYNVER_API_URL: ${e}`)}let r=n.hostname==="localhost"||n.hostname==="127.0.0.1";if(n.protocol!=="https:"&&!(n.protocol==="http:"&&r))throw new Error(`KYNVER_API_URL must be https (got "${n.protocol}//${n.hostname}"); API keys are sent to this origin.`);return e.replace(/\/$/,"")}function O(){return _()}function C(e){let n=e.startsWith("/")?e:`/${e}`;if(n.includes("..")||n.includes("\0"))throw new Error("Invalid API path: path traversal is not allowed")}function x(e){C(e);let n=e.startsWith("/")?e:`/${e}`,r=n.startsWith("/api")?n:`/api${n}`;return`${O()}${r}`}function h(){let e={"Content-Type":"application/json"};l&&(e.Authorization=`Bearer ${l}`);let n=I.getStore(),r=n?.userId??process.env.KYNVER_USER_ID?.trim(),i=process.env.KYNVER_SERVICE_SECRET?.trim();if(r){if(n?.sessionCredential)return n.authKind==="runnerToken"?(delete e.Authorization,e["X-Kynver-Runner-Token"]=n.sessionCredential):e.Authorization=`Bearer ${n.sessionCredential}`,e;let o=process.env.KYNVER_RUNNER_TOKEN?.trim();if(o){if(n?.authKind==="apiKey"||n?.authKind==="runnerToken")throw new Error("User-authenticated session did not propagate its credential; refusing the deployment-wide KYNVER_RUNNER_TOKEN (cross-tenant risk).");if(n?.authKind!=="internal")return e["X-Kynver-Runner-Token"]=o,e}if(!i)throw new Error("User context requires authentication: set KYNVER_RUNNER_TOKEN (preferred) or KYNVER_SERVICE_SECRET in the MCP server environment.");e["X-Kynver-User-Id"]=r,e["X-Kynver-Service-Secret"]=i}return e}async function y(e){let n=await e.text();if(!e.ok){let r=n;try{let i=JSON.parse(n);r=i.error??i.message??n}catch{}throw new Error(r||`HTTP ${e.status}`)}if(n)try{return JSON.parse(n)}catch{return n}}async function s(e){let n=await fetch(x(e),{method:"GET",headers:h()});return y(n)}async function a(e,n){let r=await fetch(x(e),{method:"POST",headers:h(),body:JSON.stringify(n)});return y(r)}function b(){return!!l}function w(){return"No API key configured. Set KYNVER_API_KEY in your MCP server config."}function p(e,n,r,i){return{name:e,description:n,inputSchemaZod:r,execute:async o=>b()?i(o):w()}}var m={inboxAddress:t.string().email().max(254).optional().describe("Admin only: use this owned inbox instead of your primary one (e.g. a second address)"),inboxId:t.string().max(64).optional().describe("Admin only: owned inbox id instead of your primary one")};function v(e,n){return n.inboxAddress&&e.set("inboxAddress",n.inboxAddress),n.inboxId&&e.set("inboxId",n.inboxId),e}var U=p("inbox_list","List recent messages in your Kynver inbox, or read one message in full when id is set. Returns compact from/to/subject/preview unless reading a single id. Admins may pass inboxAddress/inboxId to read an additional owned inbox.",t.object({id:t.string().optional().describe("Message id to read in full"),limit:t.number().int().min(1).max(50).optional().describe("Max messages to list (default 20)"),...m}),async e=>{let n=v(new URLSearchParams,e);e.id&&n.set("id",e.id),e.limit&&n.set("limit",String(e.limit));let r=n.toString();return JSON.stringify(await s(r?`/agent-inbox/messages?${r}`:"/agent-inbox/messages"))}),M=p("inbox_send",["Send email from your Kynver inbox address.","Preview first: call with dryRun=true to get exactly what would go out (recipients, subject, threading headers, attachments, final text and html) plus warnings and a previewToken; nothing is sent. Then send the IDENTICAL payload without dryRun and with that previewToken \u2014 the server refuses the send if anything changed.","Threading: to reply, pass replyToMessageId (the Kynver id from inbox_list) or inReplyTo (the prior RFC Message-ID). A RE:/FW: subject without either auto-threads to the single clear earlier message in this inbox with the same subject and overlapping recipients; otherwise the result carries a reply_subject_without_thread warning (dry run lists threadCandidates).",'Formatting: markdown in text (bold, headings, links) is rendered to an HTML part and stripped from the plain-text part; pass format="plain" to send text exactly as written.',"The result reports threading, formatting and warnings. Admins may pass inboxAddress/inboxId to send from an additional owned inbox."].join(" "),t.object({to:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).describe("Recipient email or list; 50 total across to/cc/bcc"),cc:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).optional(),bcc:t.union([t.string().email(),t.array(t.string().email()).min(1).max(50)]).optional(),subject:t.string().min(1).max(998).describe("Subject line"),text:t.string().min(1).max(1e5).describe("Body text; markdown is rendered unless format=plain"),inReplyTo:t.string().max(998).optional().describe("Prior RFC Message-ID, not Resend UUID"),references:t.array(t.string().max(998)).min(1).max(20).optional().describe("Ordered RFC Message-IDs; inReplyTo is appended if needed"),attachments:t.array(t.object({filename:t.string().min(1).max(255),content:t.string().min(4).max(4194304).describe("Canonical base64 bytes, not a URL/path. 2 MiB total decoded"),contentType:t.string().max(127).optional()}).strict()).max(10).optional(),idempotencyKey:t.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional().describe("Unique logical-send key; reuse identical payload/key on retries within provider retention, never for a new send"),dryRun:t.boolean().optional().describe("Preview only: return the final message, warnings and a previewToken; sends nothing"),previewToken:t.string().max(80).optional().describe("Token from the dry run; the send is refused unless the final message is identical"),replyToMessageId:t.string().max(64).optional().describe("Kynver inbox message id to reply to; threading headers are resolved server-side"),format:t.enum(["auto","plain","markdown"]).optional().describe("auto (default): render detected markdown to HTML and clean the text part; plain: send text as written; markdown: always render"),html:t.string().min(1).max(2e5).optional().describe("Optional HTML part; the text part is still cleaned of markdown"),...m}),async e=>JSON.stringify(await a("/agent-inbox/send",e))),V=p("inbox_address","Get your Kynver inbox address, or create one on first use. Optional localPart is the local-part on the existing Kynver mail domain. Admins: additional=true with localPart creates (idempotently) an extra inbox; list=true lists every owned inbox; inboxAddress/inboxId reads one.",t.object({localPart:t.string().optional().describe("Requested local part on first create (ignored if you already have an address), or of the additional inbox"),displayName:t.string().optional().describe("From-name on first create or for the additional inbox"),additional:t.boolean().optional().describe("Admin only: create an additional inbox at localPart"),list:t.boolean().optional().describe("Admin only: list every inbox you own, primary first"),...m}),async e=>{if(e.additional)return e.localPart?JSON.stringify(await a("/agent-inbox/inboxes",{localPart:e.localPart,displayName:e.displayName})):JSON.stringify({error:"localPart is required when additional=true"});if(e.list)return JSON.stringify(await s("/agent-inbox/inboxes"));if(e.inboxAddress||e.inboxId)return JSON.stringify(await s(`/agent-inbox?${v(new URLSearchParams,e)}`));if(e.localPart||e.displayName)return JSON.stringify(await a("/agent-inbox",{localPart:e.localPart,displayName:e.displayName}));let n=await s("/agent-inbox");return n.inbox?JSON.stringify(n):JSON.stringify(await a("/agent-inbox",{}))}),R=[U,M,V];var j=c(import.meta.url);function J(e){return{content:[{type:"text",text:e}]}}function T(){let e=new $({name:"kynver-mcp-inbox",version:j},{capabilities:{tools:{}}});for(let n of R)e.registerTool(n.name,{description:n.description,inputSchema:n.inputSchemaZod},async r=>J(await n.execute(r)));return e}f(process.argv.slice(2),import.meta.url,"kynver-mcp-inbox");var F=T(),q=new Y;await F.connect(q);
package/dist/sse.js CHANGED
@@ -1 +1 @@
1
- import{createServer as oe}from"node:http";import{randomUUID as se,timingSafeEqual as ie}from"node:crypto";import{SSEServerTransport as ae}from"@modelcontextprotocol/sdk/server/sse.js";import{StreamableHTTPServerTransport as ce}from"@modelcontextprotocol/sdk/server/streamableHttp.js";var ue=1*1024*1024,le=["apiKey","secret","userId"],de=new Set(["1","true","yes","on"]);function pe(){let e=process.env.KYNVER_ALLOW_SERVICE_SECRET_IMPERSONATION?.trim().toLowerCase();return e!==void 0&&de.has(e)?!1:process.env.NODE_ENV==="production"||process.env.VERCEL_ENV==="production"}function v(e,t){let r=e.headers[t];return typeof r=="string"?r:Array.isArray(r)?r[0]??null:null}function D(e,t){let r=Buffer.from(e),o=Buffer.from(t);return r.length!==o.length?!1:ie(r,o)}function j(e){return le.some(t=>e.searchParams.has(t))}function me(){return process.env.MCP_SSE_HOST?.trim()||"127.0.0.1"}function x(e,t){let r=process.env[e];if(!r)return t;let o=parseInt(r,10);return Number.isFinite(o)&&o>0?o:t}function fe(){let e=process.env.KYNVER_API_URL?.trim();if(!e)return"https://kynver.com";let t;try{t=new URL(e)}catch{throw new Error(`Invalid KYNVER_API_URL: ${e}`)}let r=t.hostname==="localhost"||t.hostname==="127.0.0.1";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw new Error(`KYNVER_API_URL must be https (got "${t.protocol}//${t.hostname}"); API keys are sent to this origin.`);return e.replace(/\/$/,"")}function Y(e){let t=v(e,"x-forwarded-for");return t?t.split(",")[0].trim():e.socket?.remoteAddress??"unknown"}function ge(e,t){let r=new Map;return{check(o){let c=Date.now(),l=r.get(o);return!l||c>=l.resetAt?(r.set(o,{count:1,resetAt:c+t}),!0):l.count>=e?!1:(l.count+=1,!0)}}}function T(e,t=ue){return new Promise((r,o)=>{let c=[],l=0;e.on("data",f=>{if(l+=f.length,l>t){e.destroy(),o(new Error("Request body too large"));return}c.push(f)}),e.on("end",()=>{try{r(JSON.parse(Buffer.concat(c).toString()))}catch(f){o(f)}}),e.on("error",o)})}function P(e){let t=v(e,"authorization");return t?/^Bearer\s+(.+)$/i.exec(t)?.[1]?.trim()??"":""}function _(e,t){let r=v(e,"x-kynver-user-id"),o=v(e,"x-kynver-service-secret");return!r||!t||!o||pe()||!D(o,t)?null:{userId:r,auth:{kind:"internal",userId:r}}}function z(e,t,r,o){if(r.kind==="apiKey"){let l=P(e);return l.length>0&&D(l,r.apiKey)}let c=_(e,o);return c!==null&&c.userId===r.userId&&c.auth.kind==="internal"}async function B(e,t){try{let r=await fetch(`${e.replace(/\/$/,"")}/api/users/me`,{headers:{Authorization:`Bearer ${t}`}});return r.ok?(await r.json()).id??null:null}catch{return null}}function he(e,t){return new URL(e.url??"/",`http://localhost:${t}`)}function a(e,t,r){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(r))}function X(e){let t=parseInt(process.env.PORT??String(e.defaultPort),10),r=fe(),o=process.env.KYNVER_SERVICE_SECRET??"",c=me(),l={},f={},O=x("MCP_SSE_MAX_SESSIONS",200),C=x("MCP_SSE_MAX_SESSIONS_PER_USER",20),U=x("MCP_SSE_IDLE_TIMEOUT_MS",300*1e3),re=x("MCP_SSE_RATE_LIMIT_PER_MIN",120),w=ge(re,60*1e3);setInterval(()=>{let s=Date.now();for(let[n,g]of Object.entries(l))s-g.lastActivity>U&&(delete l[n],g.transport.close().catch(()=>{}));for(let[n,g]of Object.entries(f))s-g.lastActivity>U&&(delete f[n],g.transport.close().catch(()=>{}))},60*1e3).unref?.();let L=()=>Object.keys(l).length+Object.keys(f).length,$=s=>Object.values(l).filter(n=>n.userId===s).length+Object.values(f).filter(n=>n.userId===s).length,V=oe(async(s,n)=>{let g=he(s,t),E=g.pathname;if(s.method==="GET"&&E==="/sse"){if(!w.check(`sse:${Y(s)}`)){a(n,429,{error:"Too many requests. Slow down and retry."});return}if(j(g)){a(n,400,{error:"Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers."});return}let p=null,u=null,m=o?_(s,o):null;if(m)p=m.userId,u=m.auth;else{let y=P(s);y&&(p=await B(r,y),p&&(u={kind:"apiKey",apiKey:y}))}if(!p||!u){a(n,401,{error:"Unauthorized. Pass API key via Authorization: Bearer <key> or internal X-Kynver-User-Id + X-Kynver-Service-Secret headers."});return}if(L()>=O){a(n,503,{error:"Server at capacity. Try again shortly."});return}if($(p)>=C){a(n,429,{error:"Too many concurrent connections for this account."});return}console.log(`[MCP SSE] New connection (${e.label}, user: ${p.slice(0,8)}...)`);let h=new ae("/messages",n);l[h.sessionId]={transport:h,userId:p,auth:u,lastActivity:Date.now()},n.on("close",()=>{delete l[h.sessionId]}),await(await e.createMcpServer()).connect(h);return}if(s.method==="POST"&&E==="/messages"){let p=g.searchParams.get("sessionId"),u=p?l[p]:void 0;if(!u){a(n,400,{error:"No active SSE session for this sessionId"});return}if(!z(s,g,u.auth,o)){a(n,401,{error:"Unauthorized. Re-send the same credentials used to open the SSE session."});return}if(!w.check(`msg:${u.userId}`)){a(n,429,{error:"Too many requests. Slow down and retry."});return}u.lastActivity=Date.now();try{let m=await T(s);await e.requestContext.run({userId:u.userId,authKind:u.auth.kind,sessionCredential:u.auth.kind==="apiKey"?u.auth.apiKey:void 0},async()=>{await u.transport.handlePostMessage(s,n,m)})}catch(m){a(n,400,{error:m instanceof Error?m.message:"Invalid request body"})}return}if(E==="/mcp"){if(s.method!=="POST"&&s.method!=="GET"&&s.method!=="DELETE"){n.writeHead(405,{"Content-Type":"application/json",Allow:"GET, POST, DELETE"}),n.end(JSON.stringify({error:"Method not allowed"}));return}if(j(g)){a(n,400,{error:"Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers."});return}let p=v(s,"mcp-session-id");if(p){let d=f[p];if(!d){a(n,404,{error:"Unknown or expired MCP session. Re-initialize."});return}if(!z(s,g,d.auth,o)){a(n,401,{error:"Unauthorized. Re-send the same credentials used to initialize the MCP session."});return}if(!w.check(`msg:${d.userId}`)){a(n,429,{error:"Too many requests. Slow down and retry."});return}d.lastActivity=Date.now();try{let I=s.method==="POST"?await T(s):void 0;await e.requestContext.run({userId:d.userId},async()=>{await d.transport.handleRequest(s,n,I)})}catch(I){n.headersSent||a(n,400,{error:I instanceof Error?I.message:"Invalid request body"})}return}if(s.method!=="POST"){a(n,400,{error:"Missing mcp-session-id header. Open a session with an initialize POST first."});return}if(!w.check(`mcp:${Y(s)}`)){a(n,429,{error:"Too many requests. Slow down and retry."});return}let u=null,m=null,h=o?_(s,o):null;if(h)u=h.userId,m=h.auth;else{let d=P(s);d&&(u=await B(r,d),u&&(m={kind:"apiKey",apiKey:d}))}if(!u||!m){a(n,401,{error:"Unauthorized. Pass API key via Authorization: Bearer <key> or internal X-Kynver-User-Id + X-Kynver-Service-Secret headers."});return}if(L()>=O){a(n,503,{error:"Server at capacity. Try again shortly."});return}if($(u)>=C){a(n,429,{error:"Too many concurrent connections for this account."});return}let b;try{b=await T(s)}catch(d){a(n,400,{error:d instanceof Error?d.message:"Invalid request body"});return}let y=u,ne=m,S=new ce({sessionIdGenerator:()=>se(),onsessioninitialized:d=>{f[d]={transport:S,userId:y,auth:ne,lastActivity:Date.now()},console.log(`[MCP HTTP] New session (${e.label}, user: ${y.slice(0,8)}...)`)}});S.onclose=()=>{S.sessionId&&delete f[S.sessionId]},await(await e.createMcpServer()).connect(S);try{await e.requestContext.run({userId:y},async()=>{await S.handleRequest(s,n,b)})}catch(d){n.headersSent||a(n,400,{error:d instanceof Error?d.message:"Invalid request"})}return}if(s.method==="GET"&&E==="/health"){n.writeHead(200,{"Content-Type":"text/plain"}),n.end("ok");return}a(n,404,{error:"Not found"})});return V.listen(t,c,()=>{console.log(`[MCP SSE] ${e.label} listening on ${c}:${t}`)}),V}import{McpServer as Ke}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as i}from"zod";import{AsyncLocalStorage as ye}from"node:async_hooks";var A=new ye,K=process.env.KYNVER_API_KEY??"";function Se(){let e=process.env.KYNVER_API_URL?.trim();if(!e)return"https://kynver.com";let t;try{t=new URL(e)}catch{throw new Error(`Invalid KYNVER_API_URL: ${e}`)}let r=t.hostname==="localhost"||t.hostname==="127.0.0.1";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw new Error(`KYNVER_API_URL must be https (got "${t.protocol}//${t.hostname}"); API keys are sent to this origin.`);return e.replace(/\/$/,"")}function ve(){return Se()}function we(e){let t=e.startsWith("/")?e:`/${e}`;if(t.includes("..")||t.includes("\0"))throw new Error("Invalid API path: path traversal is not allowed")}function J(e){we(e);let t=e.startsWith("/")?e:`/${e}`,r=t.startsWith("/api")?t:`/api${t}`;return`${ve()}${r}`}function F(){let e={"Content-Type":"application/json"};K&&(e.Authorization=`Bearer ${K}`);let t=A.getStore(),r=t?.userId??process.env.KYNVER_USER_ID?.trim(),o=process.env.KYNVER_SERVICE_SECRET?.trim();if(r){if(t?.sessionCredential)return t.authKind==="runnerToken"?(delete e.Authorization,e["X-Kynver-Runner-Token"]=t.sessionCredential):e.Authorization=`Bearer ${t.sessionCredential}`,e;let c=process.env.KYNVER_RUNNER_TOKEN?.trim();if(c){if(t?.authKind==="apiKey"||t?.authKind==="runnerToken")throw new Error("User-authenticated session did not propagate its credential; refusing the deployment-wide KYNVER_RUNNER_TOKEN (cross-tenant risk).");if(t?.authKind!=="internal")return e["X-Kynver-Runner-Token"]=c,e}if(!o)throw new Error("User context requires authentication: set KYNVER_RUNNER_TOKEN (preferred) or KYNVER_SERVICE_SECRET in the MCP server environment.");e["X-Kynver-User-Id"]=r,e["X-Kynver-Service-Secret"]=o}return e}async function H(e){let t=await e.text();if(!e.ok){let r=t;try{let o=JSON.parse(t);r=o.error??o.message??t}catch{}throw new Error(r||`HTTP ${e.status}`)}if(t)try{return JSON.parse(t)}catch{return t}}async function k(e){let t=await fetch(J(e),{method:"GET",headers:F()});return H(t)}async function R(e,t){let r=await fetch(J(e),{method:"POST",headers:F(),body:JSON.stringify(t)});return H(r)}function G(){return!!K}function W(){return"No API key configured. Set KYNVER_API_KEY in your MCP server config."}function M(e,t,r,o){return{name:e,description:t,inputSchemaZod:r,execute:async c=>G()?o(c):W()}}var Ie=M("inbox_list","List recent messages in your Kynver inbox, or read one message in full when id is set. Returns compact from/to/subject/preview unless reading a single id.",i.object({id:i.string().optional().describe("Message id to read in full"),limit:i.number().int().min(1).max(50).optional().describe("Max messages to list (default 20)")}),async e=>{let t=new URLSearchParams;e.id&&t.set("id",e.id),e.limit&&t.set("limit",String(e.limit));let r=t.toString();return JSON.stringify(await k(r?`/agent-inbox/messages?${r}`:"/agent-inbox/messages"))}),xe=M("inbox_send","Send email from your Kynver inbox address. Replies thread when inReplyTo is the prior message id header.",i.object({to:i.union([i.string().email(),i.array(i.string().email()).min(1).max(50)]).describe("Recipient email or list; 50 total across to/cc/bcc"),cc:i.union([i.string().email(),i.array(i.string().email()).min(1).max(50)]).optional(),bcc:i.union([i.string().email(),i.array(i.string().email()).min(1).max(50)]).optional(),subject:i.string().min(1).max(998).describe("Subject line"),text:i.string().min(1).max(1e5).describe("Plain-text body"),inReplyTo:i.string().max(998).optional().describe("Prior RFC Message-ID, not Resend UUID"),references:i.array(i.string().max(998)).min(1).max(20).optional().describe("Ordered RFC Message-IDs; inReplyTo is appended if needed"),attachments:i.array(i.object({filename:i.string().min(1).max(255),content:i.string().min(4).max(4194304).describe("Canonical base64 bytes, not a URL/path. 2 MiB total decoded"),contentType:i.string().max(127).optional()}).strict()).max(10).optional(),idempotencyKey:i.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional().describe("Unique logical-send key; reuse identical payload/key on retries within provider retention, never for a new send")}),async e=>JSON.stringify(await R("/agent-inbox/send",e))),Re=M("inbox_address","Get your Kynver inbox address, or create one on first use. Optional localPart is the local-part on the existing Kynver mail domain.",i.object({localPart:i.string().optional().describe("Requested local part on first create (ignored if you already have an address)"),displayName:i.string().optional().describe("From-name on first create")}),async e=>{if(e.localPart||e.displayName)return JSON.stringify(await R("/agent-inbox",e));let t=await k("/agent-inbox");return t.inbox?JSON.stringify(t):JSON.stringify(await R("/agent-inbox",{}))}),Z=[Ie,xe,Re];import{existsSync as be,readFileSync as Te}from"node:fs";import{dirname as N,join as q}from"node:path";import{fileURLToPath as Q}from"node:url";function Pe(e){let t=N(Q(e));for(let r=0;r<6;r+=1){if(be(q(t,"package.json")))return t;let o=N(t);if(o===t)break;t=o}throw new Error(`package.json not found above ${N(Q(e))}`)}function _e(e,t){let r=q(Pe(e),"package.json"),c=JSON.parse(Te(r,"utf8"))[t];if(typeof c!="string"||!c.trim())throw new Error(`Missing package.json ${t} at ${r}`);return c}function ee(e=import.meta.url){return _e(e,"version")}var Ae=ee(import.meta.url);function ke(e){return{content:[{type:"text",text:e}]}}function te(){let e=new Ke({name:"kynver-mcp-inbox",version:Ae},{capabilities:{tools:{}}});for(let t of Z)e.registerTool(t.name,{description:t.description,inputSchema:t.inputSchemaZod},async r=>ke(await t.execute(r)));return e}X({label:"Inbox server",defaultPort:3109,createMcpServer:te,requestContext:A});
1
+ import{createServer as se}from"node:http";import{randomUUID as ae,timingSafeEqual as ce}from"node:crypto";import{SSEServerTransport as de}from"@modelcontextprotocol/sdk/server/sse.js";import{StreamableHTTPServerTransport as le}from"@modelcontextprotocol/sdk/server/streamableHttp.js";var ue=1*1024*1024,pe=["apiKey","secret","userId"],me=new Set(["1","true","yes","on"]);function fe(){let e=process.env.KYNVER_ALLOW_SERVICE_SECRET_IMPERSONATION?.trim().toLowerCase();return e!==void 0&&me.has(e)?!1:process.env.NODE_ENV==="production"||process.env.VERCEL_ENV==="production"}function w(e,t){let n=e.headers[t];return typeof n=="string"?n:Array.isArray(n)?n[0]??null:null}function J(e,t){let n=Buffer.from(e),i=Buffer.from(t);return n.length!==i.length?!1:ce(n,i)}function Y(e){return pe.some(t=>e.searchParams.has(t))}function he(){return process.env.MCP_SSE_HOST?.trim()||"127.0.0.1"}function T(e,t){let n=process.env[e];if(!n)return t;let i=parseInt(n,10);return Number.isFinite(i)&&i>0?i:t}function ge(){let e=process.env.KYNVER_API_URL?.trim();if(!e)return"https://kynver.com";let t;try{t=new URL(e)}catch{throw new Error(`Invalid KYNVER_API_URL: ${e}`)}let n=t.hostname==="localhost"||t.hostname==="127.0.0.1";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw new Error(`KYNVER_API_URL must be https (got "${t.protocol}//${t.hostname}"); API keys are sent to this origin.`);return e.replace(/\/$/,"")}function z(e){let t=w(e,"x-forwarded-for");return t?t.split(",")[0].trim():e.socket?.remoteAddress??"unknown"}function ye(e,t){let n=new Map;return{check(i){let c=Date.now(),l=n.get(i);return!l||c>=l.resetAt?(n.set(i,{count:1,resetAt:c+t}),!0):l.count>=e?!1:(l.count+=1,!0)}}}function P(e,t=ue){return new Promise((n,i)=>{let c=[],l=0;e.on("data",f=>{if(l+=f.length,l>t){e.destroy(),i(new Error("Request body too large"));return}c.push(f)}),e.on("end",()=>{try{n(JSON.parse(Buffer.concat(c).toString()))}catch(f){i(f)}}),e.on("error",i)})}function A(e){let t=w(e,"authorization");return t?/^Bearer\s+(.+)$/i.exec(t)?.[1]?.trim()??"":""}function k(e,t){let n=w(e,"x-kynver-user-id"),i=w(e,"x-kynver-service-secret");return!n||!t||!i||fe()||!J(i,t)?null:{userId:n,auth:{kind:"internal",userId:n}}}function B(e,t,n,i){if(n.kind==="apiKey"){let l=A(e);return l.length>0&&J(l,n.apiKey)}let c=k(e,i);return c!==null&&c.userId===n.userId&&c.auth.kind==="internal"}async function D(e,t){try{let n=await fetch(`${e.replace(/\/$/,"")}/api/users/me`,{headers:{Authorization:`Bearer ${t}`}});return n.ok?(await n.json()).id??null:null}catch{return null}}function Se(e,t){return new URL(e.url??"/",`http://localhost:${t}`)}function a(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n))}function F(e){let t=parseInt(process.env.PORT??String(e.defaultPort),10),n=ge(),i=process.env.KYNVER_SERVICE_SECRET??"",c=he(),l={},f={},C=T("MCP_SSE_MAX_SESSIONS",200),U=T("MCP_SSE_MAX_SESSIONS_PER_USER",20),L=T("MCP_SSE_IDLE_TIMEOUT_MS",300*1e3),oe=T("MCP_SSE_RATE_LIMIT_PER_MIN",120),b=ye(oe,60*1e3);setInterval(()=>{let s=Date.now();for(let[r,h]of Object.entries(l))s-h.lastActivity>L&&(delete l[r],h.transport.close().catch(()=>{}));for(let[r,h]of Object.entries(f))s-h.lastActivity>L&&(delete f[r],h.transport.close().catch(()=>{}))},60*1e3).unref?.();let $=()=>Object.keys(l).length+Object.keys(f).length,V=s=>Object.values(l).filter(r=>r.userId===s).length+Object.values(f).filter(r=>r.userId===s).length,j=se(async(s,r)=>{let h=Se(s,t),I=h.pathname;if(s.method==="GET"&&I==="/sse"){if(!b.check(`sse:${z(s)}`)){a(r,429,{error:"Too many requests. Slow down and retry."});return}if(Y(h)){a(r,400,{error:"Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers."});return}let p=null,d=null,m=i?k(s,i):null;if(m)p=m.userId,d=m.auth;else{let y=A(s);y&&(p=await D(n,y),p&&(d={kind:"apiKey",apiKey:y}))}if(!p||!d){a(r,401,{error:"Unauthorized. Pass API key via Authorization: Bearer <key> or internal X-Kynver-User-Id + X-Kynver-Service-Secret headers."});return}if($()>=C){a(r,503,{error:"Server at capacity. Try again shortly."});return}if(V(p)>=U){a(r,429,{error:"Too many concurrent connections for this account."});return}console.log(`[MCP SSE] New connection (${e.label}, user: ${p.slice(0,8)}...)`);let g=new de("/messages",r);l[g.sessionId]={transport:g,userId:p,auth:d,lastActivity:Date.now()},r.on("close",()=>{delete l[g.sessionId]}),await(await e.createMcpServer()).connect(g);return}if(s.method==="POST"&&I==="/messages"){let p=h.searchParams.get("sessionId"),d=p?l[p]:void 0;if(!d){a(r,400,{error:"No active SSE session for this sessionId"});return}if(!B(s,h,d.auth,i)){a(r,401,{error:"Unauthorized. Re-send the same credentials used to open the SSE session."});return}if(!b.check(`msg:${d.userId}`)){a(r,429,{error:"Too many requests. Slow down and retry."});return}d.lastActivity=Date.now();try{let m=await P(s);await e.requestContext.run({userId:d.userId,authKind:d.auth.kind,sessionCredential:d.auth.kind==="apiKey"?d.auth.apiKey:void 0},async()=>{await d.transport.handlePostMessage(s,r,m)})}catch(m){a(r,400,{error:m instanceof Error?m.message:"Invalid request body"})}return}if(I==="/mcp"){if(s.method!=="POST"&&s.method!=="GET"&&s.method!=="DELETE"){r.writeHead(405,{"Content-Type":"application/json",Allow:"GET, POST, DELETE"}),r.end(JSON.stringify({error:"Method not allowed"}));return}if(Y(h)){a(r,400,{error:"Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers."});return}let p=w(s,"mcp-session-id");if(p){let u=f[p];if(!u){a(r,404,{error:"Unknown or expired MCP session. Re-initialize."});return}if(!B(s,h,u.auth,i)){a(r,401,{error:"Unauthorized. Re-send the same credentials used to initialize the MCP session."});return}if(!b.check(`msg:${u.userId}`)){a(r,429,{error:"Too many requests. Slow down and retry."});return}u.lastActivity=Date.now();try{let E=s.method==="POST"?await P(s):void 0;await e.requestContext.run({userId:u.userId},async()=>{await u.transport.handleRequest(s,r,E)})}catch(E){r.headersSent||a(r,400,{error:E instanceof Error?E.message:"Invalid request body"})}return}if(s.method!=="POST"){a(r,400,{error:"Missing mcp-session-id header. Open a session with an initialize POST first."});return}if(!b.check(`mcp:${z(s)}`)){a(r,429,{error:"Too many requests. Slow down and retry."});return}let d=null,m=null,g=i?k(s,i):null;if(g)d=g.userId,m=g.auth;else{let u=A(s);u&&(d=await D(n,u),d&&(m={kind:"apiKey",apiKey:u}))}if(!d||!m){a(r,401,{error:"Unauthorized. Pass API key via Authorization: Bearer <key> or internal X-Kynver-User-Id + X-Kynver-Service-Secret headers."});return}if($()>=C){a(r,503,{error:"Server at capacity. Try again shortly."});return}if(V(d)>=U){a(r,429,{error:"Too many concurrent connections for this account."});return}let R;try{R=await P(s)}catch(u){a(r,400,{error:u instanceof Error?u.message:"Invalid request body"});return}let y=d,ie=m,S=new le({sessionIdGenerator:()=>ae(),onsessioninitialized:u=>{f[u]={transport:S,userId:y,auth:ie,lastActivity:Date.now()},console.log(`[MCP HTTP] New session (${e.label}, user: ${y.slice(0,8)}...)`)}});S.onclose=()=>{S.sessionId&&delete f[S.sessionId]},await(await e.createMcpServer()).connect(S);try{await e.requestContext.run({userId:y},async()=>{await S.handleRequest(s,r,R)})}catch(u){r.headersSent||a(r,400,{error:u instanceof Error?u.message:"Invalid request"})}return}if(s.method==="GET"&&I==="/health"){r.writeHead(200,{"Content-Type":"text/plain"}),r.end("ok");return}a(r,404,{error:"Not found"})});return j.listen(t,c,()=>{console.log(`[MCP SSE] ${e.label} listening on ${c}:${t}`)}),j}import{McpServer as Ke}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as o}from"zod";import{AsyncLocalStorage as we}from"node:async_hooks";var K=new we,_=process.env.KYNVER_API_KEY??"";function xe(){let e=process.env.KYNVER_API_URL?.trim();if(!e)return"https://kynver.com";let t;try{t=new URL(e)}catch{throw new Error(`Invalid KYNVER_API_URL: ${e}`)}let n=t.hostname==="localhost"||t.hostname==="127.0.0.1";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw new Error(`KYNVER_API_URL must be https (got "${t.protocol}//${t.hostname}"); API keys are sent to this origin.`);return e.replace(/\/$/,"")}function ve(){return xe()}function be(e){let t=e.startsWith("/")?e:`/${e}`;if(t.includes("..")||t.includes("\0"))throw new Error("Invalid API path: path traversal is not allowed")}function X(e){be(e);let t=e.startsWith("/")?e:`/${e}`,n=t.startsWith("/api")?t:`/api${t}`;return`${ve()}${n}`}function H(){let e={"Content-Type":"application/json"};_&&(e.Authorization=`Bearer ${_}`);let t=K.getStore(),n=t?.userId??process.env.KYNVER_USER_ID?.trim(),i=process.env.KYNVER_SERVICE_SECRET?.trim();if(n){if(t?.sessionCredential)return t.authKind==="runnerToken"?(delete e.Authorization,e["X-Kynver-Runner-Token"]=t.sessionCredential):e.Authorization=`Bearer ${t.sessionCredential}`,e;let c=process.env.KYNVER_RUNNER_TOKEN?.trim();if(c){if(t?.authKind==="apiKey"||t?.authKind==="runnerToken")throw new Error("User-authenticated session did not propagate its credential; refusing the deployment-wide KYNVER_RUNNER_TOKEN (cross-tenant risk).");if(t?.authKind!=="internal")return e["X-Kynver-Runner-Token"]=c,e}if(!i)throw new Error("User context requires authentication: set KYNVER_RUNNER_TOKEN (preferred) or KYNVER_SERVICE_SECRET in the MCP server environment.");e["X-Kynver-User-Id"]=n,e["X-Kynver-Service-Secret"]=i}return e}async function G(e){let t=await e.text();if(!e.ok){let n=t;try{let i=JSON.parse(t);n=i.error??i.message??t}catch{}throw new Error(n||`HTTP ${e.status}`)}if(t)try{return JSON.parse(t)}catch{return t}}async function x(e){let t=await fetch(X(e),{method:"GET",headers:H()});return G(t)}async function v(e,t){let n=await fetch(X(e),{method:"POST",headers:H(),body:JSON.stringify(t)});return G(n)}function W(){return!!_}function Z(){return"No API key configured. Set KYNVER_API_KEY in your MCP server config."}function N(e,t,n,i){return{name:e,description:t,inputSchemaZod:n,execute:async c=>W()?i(c):Z()}}var M={inboxAddress:o.string().email().max(254).optional().describe("Admin only: use this owned inbox instead of your primary one (e.g. a second address)"),inboxId:o.string().max(64).optional().describe("Admin only: owned inbox id instead of your primary one")};function Q(e,t){return t.inboxAddress&&e.set("inboxAddress",t.inboxAddress),t.inboxId&&e.set("inboxId",t.inboxId),e}var Ee=N("inbox_list","List recent messages in your Kynver inbox, or read one message in full when id is set. Returns compact from/to/subject/preview unless reading a single id. Admins may pass inboxAddress/inboxId to read an additional owned inbox.",o.object({id:o.string().optional().describe("Message id to read in full"),limit:o.number().int().min(1).max(50).optional().describe("Max messages to list (default 20)"),...M}),async e=>{let t=Q(new URLSearchParams,e);e.id&&t.set("id",e.id),e.limit&&t.set("limit",String(e.limit));let n=t.toString();return JSON.stringify(await x(n?`/agent-inbox/messages?${n}`:"/agent-inbox/messages"))}),Te=N("inbox_send",["Send email from your Kynver inbox address.","Preview first: call with dryRun=true to get exactly what would go out (recipients, subject, threading headers, attachments, final text and html) plus warnings and a previewToken; nothing is sent. Then send the IDENTICAL payload without dryRun and with that previewToken \u2014 the server refuses the send if anything changed.","Threading: to reply, pass replyToMessageId (the Kynver id from inbox_list) or inReplyTo (the prior RFC Message-ID). A RE:/FW: subject without either auto-threads to the single clear earlier message in this inbox with the same subject and overlapping recipients; otherwise the result carries a reply_subject_without_thread warning (dry run lists threadCandidates).",'Formatting: markdown in text (bold, headings, links) is rendered to an HTML part and stripped from the plain-text part; pass format="plain" to send text exactly as written.',"The result reports threading, formatting and warnings. Admins may pass inboxAddress/inboxId to send from an additional owned inbox."].join(" "),o.object({to:o.union([o.string().email(),o.array(o.string().email()).min(1).max(50)]).describe("Recipient email or list; 50 total across to/cc/bcc"),cc:o.union([o.string().email(),o.array(o.string().email()).min(1).max(50)]).optional(),bcc:o.union([o.string().email(),o.array(o.string().email()).min(1).max(50)]).optional(),subject:o.string().min(1).max(998).describe("Subject line"),text:o.string().min(1).max(1e5).describe("Body text; markdown is rendered unless format=plain"),inReplyTo:o.string().max(998).optional().describe("Prior RFC Message-ID, not Resend UUID"),references:o.array(o.string().max(998)).min(1).max(20).optional().describe("Ordered RFC Message-IDs; inReplyTo is appended if needed"),attachments:o.array(o.object({filename:o.string().min(1).max(255),content:o.string().min(4).max(4194304).describe("Canonical base64 bytes, not a URL/path. 2 MiB total decoded"),contentType:o.string().max(127).optional()}).strict()).max(10).optional(),idempotencyKey:o.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional().describe("Unique logical-send key; reuse identical payload/key on retries within provider retention, never for a new send"),dryRun:o.boolean().optional().describe("Preview only: return the final message, warnings and a previewToken; sends nothing"),previewToken:o.string().max(80).optional().describe("Token from the dry run; the send is refused unless the final message is identical"),replyToMessageId:o.string().max(64).optional().describe("Kynver inbox message id to reply to; threading headers are resolved server-side"),format:o.enum(["auto","plain","markdown"]).optional().describe("auto (default): render detected markdown to HTML and clean the text part; plain: send text as written; markdown: always render"),html:o.string().min(1).max(2e5).optional().describe("Optional HTML part; the text part is still cleaned of markdown"),...M}),async e=>JSON.stringify(await v("/agent-inbox/send",e))),Re=N("inbox_address","Get your Kynver inbox address, or create one on first use. Optional localPart is the local-part on the existing Kynver mail domain. Admins: additional=true with localPart creates (idempotently) an extra inbox; list=true lists every owned inbox; inboxAddress/inboxId reads one.",o.object({localPart:o.string().optional().describe("Requested local part on first create (ignored if you already have an address), or of the additional inbox"),displayName:o.string().optional().describe("From-name on first create or for the additional inbox"),additional:o.boolean().optional().describe("Admin only: create an additional inbox at localPart"),list:o.boolean().optional().describe("Admin only: list every inbox you own, primary first"),...M}),async e=>{if(e.additional)return e.localPart?JSON.stringify(await v("/agent-inbox/inboxes",{localPart:e.localPart,displayName:e.displayName})):JSON.stringify({error:"localPart is required when additional=true"});if(e.list)return JSON.stringify(await x("/agent-inbox/inboxes"));if(e.inboxAddress||e.inboxId)return JSON.stringify(await x(`/agent-inbox?${Q(new URLSearchParams,e)}`));if(e.localPart||e.displayName)return JSON.stringify(await v("/agent-inbox",{localPart:e.localPart,displayName:e.displayName}));let t=await x("/agent-inbox");return t.inbox?JSON.stringify(t):JSON.stringify(await v("/agent-inbox",{}))}),q=[Ee,Te,Re];import{existsSync as Pe,readFileSync as Ae}from"node:fs";import{dirname as O,join as te}from"node:path";import{fileURLToPath as ee}from"node:url";function ke(e){let t=O(ee(e));for(let n=0;n<6;n+=1){if(Pe(te(t,"package.json")))return t;let i=O(t);if(i===t)break;t=i}throw new Error(`package.json not found above ${O(ee(e))}`)}function _e(e,t){let n=te(ke(e),"package.json"),c=JSON.parse(Ae(n,"utf8"))[t];if(typeof c!="string"||!c.trim())throw new Error(`Missing package.json ${t} at ${n}`);return c}function ne(e=import.meta.url){return _e(e,"version")}var Ne=ne(import.meta.url);function Me(e){return{content:[{type:"text",text:e}]}}function re(){let e=new Ke({name:"kynver-mcp-inbox",version:Ne},{capabilities:{tools:{}}});for(let t of q)e.registerTool(t.name,{description:t.description,inputSchema:t.inputSchemaZod},async n=>Me(await t.execute(n)));return e}F({label:"Inbox server",defaultPort:3109,createMcpServer:re,requestContext:K});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-inbox",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/__tests__/**",