@robota-sdk/agent-transport-mcp 3.0.0-beta.79 → 3.0.0-beta.81

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Model Context Protocol (MCP) server transport for the Robota SDK. It exposes a running
4
4
  `IInteractiveSession` as an MCP server so MCP-aware clients (e.g. Claude Desktop, IDE
5
- integrations) can drive the agent and call its system commands as MCP tools.
5
+ integrations) can drive the agent and call its canonical, permission-wrapped runtime tools.
6
6
 
7
7
  ## Installation
8
8
 
@@ -14,32 +14,36 @@ pnpm add @robota-sdk/agent-transport-mcp
14
14
 
15
15
  ## Usage
16
16
 
17
- `createAgentMcpServer` builds an MCP server from an `IInteractiveSession`. By default each
18
- system command is registered as a separate MCP tool (`exposeCommands`). `createMcpTransport`
19
- wraps it for the SDK's transport registry.
17
+ `createAgentMcpServer` asynchronously validates and exposes the session runtime catalog.
18
+ `createMcpTransport` wraps it for the SDK transport registry. Runtime tools retain the canonical
19
+ names supplied by the session. The built-in `agent_submit` extension accepts `{ prompt: string }`
20
+ and returns its own turn response. Pass `submitTool: { name: 'robota_submit', description: '...' }`
21
+ to any of the three server/transport constructors when preserving an existing Robota MCP client.
22
+ Runtime tool results are serialized execution envelopes; failures set `isError`. Request cancellation
23
+ reaches only the corresponding invocation. Prompts and resources are unsupported.
20
24
 
21
25
  ```typescript
22
26
  import { createAgentMcpServer } from '@robota-sdk/agent-transport-mcp';
23
- import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
27
+ import type { IMcpTransportSession } from '@robota-sdk/agent-transport-mcp';
24
28
 
25
- declare const session: IInteractiveSession;
26
- const server = createAgentMcpServer({
27
- name: 'robota-agent',
29
+ declare const session: IMcpTransportSession;
30
+ const server = await createAgentMcpServer({
31
+ name: 'my-agent',
28
32
  version: '1.0.0',
29
- session, // an IInteractiveSession
30
- exposeCommands: true,
33
+ session,
31
34
  });
32
35
 
33
- // Connect `server` to your MCP stdio/SSE transport of choice.
36
+ // Connect `server` to your MCP carrier of choice.
34
37
  ```
35
38
 
36
39
  ## Exports
37
40
 
38
- | Symbol | Kind | Description |
39
- | ---------------------- | --------- | --------------------------------------------------------- |
40
- | `createAgentMcpServer` | function | `(options: IAgentMcpOptions)` — MCP server for a session |
41
- | `createMcpTransport` | function | `(options: IMcpTransportOptions)` — SDK transport wrapper |
42
- | `IAgentMcpOptions` | interface | `{ name, version, session, exposeCommands? }` |
43
- | `IMcpTransportOptions` | interface | Transport-registry options |
41
+ | Symbol | Kind | Description |
42
+ | ------------------------ | --------- | --------------------------------------------------------- |
43
+ | `createAgentMcpServer` | function | `(options: IAgentMcpOptions)` — MCP server for a session |
44
+ | `createMcpTransport` | function | `(options: IMcpTransportOptions)` — SDK transport wrapper |
45
+ | `IAgentMcpOptions` | interface | `{ name, version, session, submitTool? }` |
46
+ | `IMcpSubmitToolIdentity` | interface | Optional host-selected submission name and description |
47
+ | `IMcpTransportOptions` | interface | Transport-registry options |
44
48
 
45
49
  See [docs/SPEC.md](./docs/SPEC.md) for the full contract.
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@modelcontextprotocol/sdk/server/index.js"),t=require("@modelcontextprotocol/sdk/types.js");function n(n){let{name:i,version:a,session:o,exposeCommands:s=!0}=n,c=new e.Server({name:i,version:a},{capabilities:{tools:{}}}),l=[{name:`submit`,description:`Submit a prompt to the AI agent and wait for the response`,inputSchema:{type:`object`,properties:{prompt:{type:`string`,description:`The prompt to send to the agent`}},required:[`prompt`]}}];if(s)for(let e of o.listCommands())l.push({name:`command_${e.name}`,description:e.description,inputSchema:{type:`object`,properties:{args:{type:`string`,description:`Command arguments`}}}});return c.setRequestHandler(t.ListToolsRequestSchema,async()=>({tools:l})),c.setRequestHandler(t.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;if(t===`submit`){let e=n?.prompt;return e?{content:[{type:`text`,text:(await r(o,e)).response}]}:{content:[{type:`text`,text:`Error: prompt is required`}],isError:!0}}if(t.startsWith(`command_`)){let e=t.slice(8),r=n?.args??``,i=await o.executeCommand(e,r);return{content:[{type:`text`,text:i?.message??`Unknown command: ${e}`}],isError:!i}}return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0}}),c}function r(e,t){return new Promise((n,r)=>{let i=e=>{s(),n(e)},a=e=>{s(),n(e)},o=e=>{s(),r(e)},s=()=>{e.off(`complete`,i),e.off(`interrupted`,a),e.off(`error`,o)};e.on(`complete`,i),e.on(`interrupted`,a),e.on(`error`,o),e.submit(t).catch(e=>{s(),r(e instanceof Error?e:Error(String(e)))})})}function i(e){let t=null,r=null;return{name:`mcp`,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);r=n({...e,session:t})},async stop(){r&&=(await r.close(),null)},getServer(){if(!r)throw Error(`Transport not started. Call start() first.`);return r}}}exports.createAgentMcpServer=n,exports.createMcpTransport=i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@modelcontextprotocol/sdk/server/index.js"),t=require("@modelcontextprotocol/sdk/types.js"),n=require("@robota-sdk/agent-interface-session"),r=require("node:stream"),i=require("@modelcontextprotocol/sdk/server/stdio.js"),a=require("node:http"),o=require("node:net"),s=require("@modelcontextprotocol/node"),c=require("@modelcontextprotocol/server"),l=require("@robota-sdk/agent-transport/node");const u={name:`agent_submit`,description:`Submit a prompt to the agent and await its turn`};function d(e=u){if(typeof e.name!=`string`||!/^[A-Za-z0-9._-]{1,128}$/.test(e.name))throw Error(`MCP submit tool name must contain 1–128 ASCII letters, digits, dots, hyphens, or underscores`);return t.ToolSchema.parse({name:e.name,description:e.description,inputSchema:{type:`object`,properties:{prompt:{type:`string`,minLength:1}},required:[`prompt`],additionalProperties:!1}})}async function f(e,n){let r=new Set([n]);return(await e.listRuntimeTools()).map(e=>{if(r.has(e.name))throw Error(`Duplicate or reserved MCP tool name: ${e.name}`);return r.add(e.name),t.ToolSchema.parse({name:e.name,description:e.description,inputSchema:e.parameters})})}function p(e){return{content:[{type:`text`,text:e}],isError:!0}}function m(e){return e===null||typeof e==`string`||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):Array.isArray(e)?e.every(m):typeof e==`object`&&!!e&&Object.values(e).every(m)}function h(e){return Object.values(e).every(m)}async function g(e,t,r,i,a){let o;try{i.throwIfAborted(),o=await f(e,a)}catch(e){return p(e instanceof Error?e.message:String(e))}if(t===a){if(typeof r.prompt!=`string`||r.prompt.length===0||Object.keys(r).some(e=>e!==`prompt`))return p(`${a} requires a non-empty string prompt and no other arguments`);try{return{content:[{type:`text`,text:(await(await e.submit(r.prompt,void 0,void 0,{signal:i})).completed).response}]}}catch(e){if(!(0,n.isTurnNotRunError)(e)&&!i.aborted)throw e;return p(e instanceof Error?e.message:String(e))}}if(!o.some(e=>e.name===t))return p(`Unknown tool: ${t}`);if(!h(r))return p(`Tool arguments must contain JSON values`);try{let n=await e.invokeRuntimeTool(t,r,{signal:i});return{content:[{type:`text`,text:JSON.stringify(n)}],isError:!n.success}}catch(e){return p(e instanceof Error?e.message:String(e))}}async function _(n){let{name:r,version:i,session:a}=n,o=d(n.submitTool);await f(a,o.name);let s=new e.Server({name:r,version:i},{capabilities:{tools:{}}});return s.setRequestHandler(t.ListToolsRequestSchema,async()=>({tools:[...await f(a,o.name),o]})),s.setRequestHandler(t.CallToolRequestSchema,async(e,t)=>g(a,e.params.name,e.params.arguments??{},t.signal,o.name)),s}function v(e){let t=null,n=null,a=null,o=null,s=null,c=null,l=e.stdin??process.stdin,u=e.stdout??process.stdout,d=null,f=null,p=Promise.resolve(),m=()=>{p=new Promise((e,t)=>{d=e,f=t}),p.catch(()=>void 0)},h=()=>{d?.(),c?.(Error(`MCP input closed during startup`))},g=()=>{d?.(),c?.(Error(`MCP output closed during startup`))},v=e=>{f?.(e),c?.(e)},y=()=>{l.off(`end`,h),l.off(`close`,h),u.off(`close`,g),u.off(`error`,v),s&&=(l.unpipe(s),s.destroy(),null)},b=e=>Object.assign(Error(`MCP transport ${e}.`),{name:`TransportLifecycleError`,code:e,transportName:`mcp`});return{name:`mcp`,lifecycle:Object.freeze({kind:`service`}),attach(e){if(n||a||o)throw b(`already-started`);t=e,m()},async start(){if(!t)throw b(`not-attached`);if(n||a||o)throw b(`already-started`);let p=t,m=new Promise((e,t)=>{c=t}),x=new r.PassThrough;s=x,l.once(`end`,h),l.once(`close`,h),u.once(`close`,g),u.on(`error`,v),l.pipe(x),a=(async()=>{let t=null,r=setTimeout(()=>c?.(Error(`MCP carrier startup timed out`)),15e3);try{t=await Promise.race([_({name:e.name,version:e.version,session:p,...e.submitTool===void 0?{}:{submitTool:e.submitTool}}),m]);let r=new i.StdioServerTransport(x,u);r.onclose=()=>d?.(),r.onerror=e=>f?.(e),await t.connect(r),n=t,t=null}catch(e){throw y(),t&&await t.close().catch(()=>void 0),e}finally{clearTimeout(r),c=null}})();try{await a}finally{a=null}},stop(){return o||(o=(async()=>{try{c?.(Error(`MCP carrier startup stopped`)),a&&await a.catch(()=>void 0),n&&await n.close()}finally{y(),n=null,t=null,d?.()}})().finally(()=>{o=null}),o)},getServer(){if(!n)throw Error(`Transport not started. Call start() first.`);return n},waitForClose(){return p}}}const y=6e4,b=4096,x=/^[\x21\x23-\x5b\x5d-\x7e]+$/,S=new o.BlockList;S.addSubnet(`127.0.0.0`,8,`ipv4`),S.addAddress(`::1`,`ipv6`);const C=new o.BlockList;C.addSubnet(`10.0.0.0`,8,`ipv4`),C.addSubnet(`172.16.0.0`,12,`ipv4`),C.addSubnet(`192.168.0.0`,16,`ipv4`),C.addSubnet(`169.254.0.0`,16,`ipv4`),C.addSubnet(`100.64.0.0`,10,`ipv4`),C.addSubnet(`fc00::`,7,`ipv6`),C.addSubnet(`fe80::`,10,`ipv6`);function w(e){if(e===void 0)return;let t=e.trim().toLowerCase(),n=t.startsWith(`::ffff:`)?t.slice(7):void 0;return n!==void 0&&(0,o.isIP)(n)===4?n:(0,o.isIP)(t)===0?void 0:t}function T(e){if(e===void 0)return`unknown`;let t=(0,o.isIP)(e)===4?`ipv4`:`ipv6`;return S.check(e,t)?`loopback`:C.check(e,t)?`private`:`public`}function E(e){let t;try{t=new URL(e)}catch{throw Error(`MCP HTTP public URL must be an absolute https URL`)}if(t.protocol!==`https:`)throw Error(`MCP HTTP public URL must use https`);if(t.username!==``||t.password!==``||t.search!==``||t.hash!==``)throw Error(`MCP HTTP public URL must not carry credentials, a query or a fragment`);return t}function D(e){let t=new Map;return{fail(n){let r=e(),i=t.get(n);if(i===void 0||r-i.startedAt>=y){if(t.delete(n),t.size>=b){for(let[e,n]of t)r-n.startedAt>=y&&t.delete(e);let e=t.keys().next();t.size>=b&&e.done!==!0&&t.delete(e.value)}i={count:0,startedAt:r},t.set(n,i)}i.count+=1;let a=y-(r-i.startedAt);return{throttled:i.count>20,retryAfterSeconds:Math.max(1,Math.ceil(a/1e3))}}}}function O(e,t=()=>performance.now()){let n=E(e.publicUrl),r;try{r=new URL(e.issuer)}catch{throw Error(`MCP HTTP authorization issuer must be an absolute https URL`)}if(r.protocol!==`https:`)throw Error(`MCP HTTP authorization issuer must use https`);if(e.scopes.length===0||!e.scopes.every(e=>x.test(e)))throw Error(`MCP HTTP authorization needs at least one valid scope`);let i=new Set;for(let t of e.trustedProxies??[]){let e=w(t);if(e===void 0)throw Error(`MCP HTTP trusted proxy must be a literal IP address`);i.add(e)}let a=n.pathname,o=`/.well-known/oauth-protected-resource${a===`/`?``:a}`,s=`${n.origin}${o}`,c=new Set([n.host]);n.port===``&&c.add(`${n.hostname}:443`);let u=JSON.stringify({resource:e.publicUrl,authorization_servers:[e.issuer],scopes_supported:[...e.scopes],bearer_methods_supported:[`header`]}),d=e.scopes.join(` `),f={missing:`Bearer resource_metadata="${s}"`,invalid:`Bearer error="invalid_token", resource_metadata="${s}"`,scope:`Bearer error="insufficient_scope", scope="${d}", resource_metadata="${s}"`},p=D(t);function m(e){let t=w(e.socket.remoteAddress);if(t===void 0||!i.has(t))return t;let n=e.headers[`x-forwarded-for`],r=(Array.isArray(n)?n.join(`,`):n??``).split(`,`);for(let e=r.length-1;e>=0;--e){let n=w(r[e]);if(n===void 0)return t;if(!i.has(n))return n}return t}function h(t,n,r){let i=m(t),a=T(i);if(r===`keys-unavailable`){e.audit?.({refusal:r,remote:a,throttled:!1}),n.writeHead(503).end();return}let o=p.fail(i??`unknown`);if(e.audit?.({refusal:r,remote:a,throttled:o.throttled}),o.throttled)n.writeHead(429,{"Retry-After":String(o.retryAfterSeconds)}).end();else if(r===`missing-scope`)n.writeHead(403,{"WWW-Authenticate":f.scope}).end();else{let e=r===`missing-token`?f.missing:f.invalid;n.writeHead(401,{"WWW-Authenticate":e}).end()}}return{endpointPath:a,async admit(t,r){let i=t.headers.host?.toLowerCase(),s=t.headers.origin;if(i===void 0||!c.has(i)||s!==void 0&&s!==n.origin)return r.writeHead(403).end(),!1;if(t.url===o)return t.method===`GET`?(r.writeHead(200,{"Content-Type":`application/json`}).end(u),!1):(r.writeHead(405,{Allow:`GET`}).end(),!1);if(t.url!==a)return r.writeHead(404).end(),!1;let d=(0,l.bearerCredential)(t.headers.authorization);if(d===void 0)return h(t,r,`missing-token`),!1;let f=await e.verifier.verify(d);return f.admitted?!0:(h(t,r,f.refusal),!1)}}}const k=`127.0.0.1`,A=1024*1024;function j(e){if(e!==void 0&&(!Number.isInteger(e)||e<0||e>65535))throw Error(`MCP HTTP port must be an integer in 0..65535`)}function M(e,t,n){let r=d(e.submitTool),i,o,l,u=!1,p=!1,m=0,h,_,v=new Promise((e,t)=>{h=e,_=t});return v.catch(()=>void 0),{async start(){if(u||l)throw Error(`MCP HTTP host already started`);if(u=!0,await f(e.session,r.name),p)throw Error(`MCP HTTP host stopped during startup`);o=(0,c.createMcpHandler)(()=>{let t=new c.Server({name:e.name,version:e.version},{capabilities:{tools:{}}});return t.setRequestHandler(`tools/list`,async()=>({tools:JSON.parse(JSON.stringify([...await f(e.session,r.name),r]))})),t.setRequestHandler(`tools/call`,async(t,n)=>g(e.session,t.params.name,t.params.arguments??{},n.mcpReq.signal,r.name)),t},{maxSubscriptions:0});let d=(0,s.toNodeHandler)(o),v=(e,t)=>{if(m>=16){t.writeHead(503).end();return}m+=1,(async()=>{try{let n;if(e.method===`POST`){let r=Number(e.headers[`content-length`]);if(Number.isFinite(r)&&r>A){t.writeHead(413).end();return}let i=[],a=0;for await(let n of e){let r=Buffer.isBuffer(n)?n:Buffer.from(n);if(a+=r.length,a>A){t.writeHead(413).end(),e.destroy();return}i.push(r)}try{n=JSON.parse(Buffer.concat(i).toString(`utf8`))}catch{t.writeHead(400).end();return}}await d(e,t,n)}catch{t.headersSent?t.destroy():t.writeHead(500).end()}finally{--m}})()},y=(0,a.createServer)({maxHeaderSize:16*1024},(e,t)=>{let r=y.address();if(!r||typeof r==`string`){t.writeHead(503).end();return}let i=n(e,t,r.port);i===!0?v(e,t):i!==!1&&i.then(n=>{n&&v(e,t)},()=>{t.headersSent?t.destroy():t.writeHead(500).end()})});y.maxConnections=32,y.headersTimeout=5e3,y.requestTimeout=15e3,y.on(`error`,e=>_?.(e)),y.on(`close`,()=>h?.());try{await new Promise((n,r)=>{y.once(`error`,r),y.listen(e.port??0,t,()=>{y.off(`error`,r),n()})})}catch(e){throw await o.close(),e}if(p)throw await new Promise(e=>y.close(()=>e())),await o.close(),Error(`MCP HTTP host stopped during startup`);return i=y,y.address()},stop(){return l||(p=!0,l=(async()=>{let e=o,t=i;if(!t){await e?.close(),h?.();return}let n=new Promise((e,n)=>{t.close(t=>t?n(t):e()),t.closeAllConnections()}),r;try{await Promise.race([Promise.all([e?.close(),n]).then(()=>void 0),new Promise((e,t)=>{r=setTimeout(()=>t(Error(`MCP HTTP close timed out`)),5e3)})])}finally{r&&clearTimeout(r)}})(),l)},waitForClose(){return v}}}function N(e){if(e.host!==void 0&&e.host!==k)throw Error(`MCP HTTP host must bind numeric IPv4 loopback`);j(e.port);let t=(0,l.mintTransportToken)(),n=M(e,k,(e,n,r)=>{let i=`${k}:${r}`,a=`http://${i}`;if(e.url!==`/mcp`||e.headers.host!==i||e.headers.origin!==void 0&&e.headers.origin!==a)return n.writeHead(403).end(),!1;let o=e.headers.authorization;return typeof o!=`string`||!(0,l.credentialMatches)(t,(0,l.bearerCredential)(o))?(n.writeHead(401).end(),!1):!0});return{async start(){let e=await n.start();return{url:`http://${k}:${e.port}/mcp`,token:t}},stop:n.stop,waitForClose:n.waitForClose}}function P(e){let t=e.host??k;if((0,o.isIP)(t)===0)throw Error(`MCP HTTP host must bind a literal IP address`);j(e.port);let n=O(e.authorization),r=M(e,t,(e,t)=>n.admit(e,t));return{async start(){let t=await r.start();return{listening:`${t.family===`IPv6`?`[${t.address}]`:t.address}:${t.port}`,url:e.authorization.publicUrl}},stop:r.stop,waitForClose:r.waitForClose}}exports.createAgentMcpServer=_,exports.createMcpHttpHost=N,exports.createMcpRemoteHttpHost=P,exports.createMcpTransport=v;
@@ -0,0 +1,124 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { ISessionRuntimeTools, ISessionTurnSubmission } from "@robota-sdk/agent-interface-session";
3
+ import "@modelcontextprotocol/sdk/types.js";
4
+ import { IAccessTokenVerifier, ITransportAdapter, TAccessTokenRefusal } from "@robota-sdk/agent-interface-transport";
5
+ import { Readable, Writable } from "node:stream";
6
+ //#region src/mcp-session.d.ts
7
+ /** The exact session capabilities consumed by the MCP transport. */
8
+ interface IMcpTransportSession extends ISessionTurnSubmission, ISessionRuntimeTools {}
9
+ //#endregion
10
+ //#region src/mcp-tool-surface.d.ts
11
+ interface IMcpSubmitToolIdentity {
12
+ readonly name: string;
13
+ readonly description: string;
14
+ }
15
+ //#endregion
16
+ //#region src/mcp-server.d.ts
17
+ interface IAgentMcpOptions {
18
+ name: string;
19
+ version: string;
20
+ session: IMcpTransportSession;
21
+ /** Host-owned identity for the submission extension. */
22
+ submitTool?: IMcpSubmitToolIdentity;
23
+ }
24
+ /** Validate the catalog before the caller connects a carrier. */
25
+ declare function createAgentMcpServer(options: IAgentMcpOptions): Promise<Server>;
26
+ //#endregion
27
+ //#region src/mcp-transport.d.ts
28
+ interface IMcpTransportOptions {
29
+ /** Name for the MCP server. */
30
+ name: string;
31
+ /** Version string. */
32
+ version: string;
33
+ /** Process-owned streams; defaults to the current process stdio. */
34
+ stdin?: Readable;
35
+ stdout?: Writable;
36
+ /** Host-owned identity for the submission extension. */
37
+ submitTool?: IMcpSubmitToolIdentity;
38
+ }
39
+ interface IMcpTransport extends ITransportAdapter<IMcpTransportSession> {
40
+ getServer(): Server;
41
+ /** Settles when the carrier closes or its input ends; rejects on carrier failure. */
42
+ waitForClose(): Promise<void>;
43
+ }
44
+ declare function createMcpTransport(options: IMcpTransportOptions): IMcpTransport;
45
+ //#endregion
46
+ //#region src/mcp-remote-authorization.d.ts
47
+ /** Why a remote request was refused: a verifier refusal, or no bearer token at all. */
48
+ type TMcpRemoteRefusal = TAccessTokenRefusal | 'missing-token';
49
+ /** A coarse class of the refused peer's address; the address itself is never reported. */
50
+ type TMcpRemoteAddressClass = 'loopback' | 'private' | 'public' | 'unknown';
51
+ /** One refused request, as the audit sink sees it. Carries no token text and no claim value. */
52
+ interface IMcpRemoteAuditRecord {
53
+ readonly refusal: TMcpRemoteRefusal;
54
+ readonly remote: TMcpRemoteAddressClass;
55
+ /** True when the refusal was answered 429 because the address exceeded its failure budget. */
56
+ readonly throttled: boolean;
57
+ }
58
+ /** How the remote host decides who may reach the session. */
59
+ interface IMcpRemoteAuthorization {
60
+ /** The `https` URL clients use for the MCP endpoint; also the resource identifier (`aud`). */
61
+ readonly publicUrl: string;
62
+ /** The authorization server named in the protected-resource metadata. */
63
+ readonly issuer: string;
64
+ /** Scopes a token must carry; advertised as `scopes_supported` and in `insufficient_scope`. */
65
+ readonly scopes: readonly string[];
66
+ /** Decides each presented token. Built for the same issuer, resource and scopes. */
67
+ readonly verifier: IAccessTokenVerifier;
68
+ /** Proxy addresses whose `X-Forwarded-For` is believed. Absent: the header is ignored. */
69
+ readonly trustedProxies?: readonly string[];
70
+ /** Receives one content-free record per refused request. */
71
+ readonly audit?: (record: IMcpRemoteAuditRecord) => void;
72
+ }
73
+ //#endregion
74
+ //#region src/mcp-http-host.d.ts
75
+ interface IMcpHttpHostOptions {
76
+ name: string;
77
+ version: string;
78
+ session: IMcpTransportSession;
79
+ /** Defaults to an OS-assigned port. */
80
+ port?: number;
81
+ /** Accepted only for numeric IPv4 loopback; provided for explicit bind-refusal checks. */
82
+ host?: string;
83
+ /** Host-owned identity for the submission extension. */
84
+ submitTool?: IMcpSubmitToolIdentity;
85
+ }
86
+ interface IMcpHttpHost {
87
+ start(): Promise<{
88
+ url: string;
89
+ token: string;
90
+ }>;
91
+ stop(): Promise<void>;
92
+ waitForClose(): Promise<void>;
93
+ }
94
+ interface IMcpRemoteHttpHostOptions {
95
+ name: string;
96
+ version: string;
97
+ session: IMcpTransportSession;
98
+ /** Defaults to an OS-assigned port. */
99
+ port?: number;
100
+ /** Literal IP address to bind. Defaults to `127.0.0.1`, for a proxy on the same machine. */
101
+ host?: string;
102
+ /** Who may reach the session, decided from OAuth access tokens. Required: there is no open mode. */
103
+ authorization: IMcpRemoteAuthorization;
104
+ /** Host-owned identity for the submission extension. */
105
+ submitTool?: IMcpSubmitToolIdentity;
106
+ }
107
+ interface IMcpRemoteHttpHost {
108
+ /** Resolves with the address actually bound and the public URL clients are told to use. */
109
+ start(): Promise<{
110
+ listening: string;
111
+ url: string;
112
+ }>;
113
+ stop(): Promise<void>;
114
+ waitForClose(): Promise<void>;
115
+ }
116
+ declare function createMcpHttpHost(options: IMcpHttpHostOptions): IMcpHttpHost;
117
+ /**
118
+ * A host reachable beyond loopback. It never mints or accepts the loopback bearer: every request
119
+ * is admitted by an OAuth access token checked against the injected verifier.
120
+ */
121
+ declare function createMcpRemoteHttpHost(options: IMcpRemoteHttpHostOptions): IMcpRemoteHttpHost;
122
+ //#endregion
123
+ export { type IAgentMcpOptions, type IMcpHttpHost, type IMcpHttpHostOptions, type IMcpRemoteAuditRecord, type IMcpRemoteAuthorization, type IMcpRemoteHttpHost, type IMcpRemoteHttpHostOptions, type IMcpSubmitToolIdentity, type IMcpTransport, type IMcpTransportOptions, type IMcpTransportSession, type TMcpRemoteAddressClass, type TMcpRemoteRefusal, createAgentMcpServer, createMcpHttpHost, createMcpRemoteHttpHost, createMcpTransport };
124
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/mcp-session.ts","../../src/mcp-tool-surface.ts","../../src/mcp-server.ts","../../src/mcp-transport.ts","../../src/mcp-remote-authorization.ts","../../src/mcp-http-host.ts"],"mappings":";;;;;;;UAMiB,6BAA6B,wBAAwB;;;UCCrD;WACN;WACA;;;;UCAM;EACf;EACA;EACA,SAAS;;EAET,aAAa;;;iBAIO,qBAAqB,SAAS,mBAAmB,QAAQ;;;UCM9D;;EAEf;;EAEA;;EAEA,QAAQ;EACR,SAAS;;EAET,aAAa;;UAGE,sBAAsB,kBAAkB;EACvD,aAAa;;EAEb,gBAAgB;;iBAGF,mBAAmB,SAAS,uBAAuB;;;;KCFvD,oBAAoB;;KAGpB;;UAGK;WACN,SAAS;WACT,QAAQ;;WAER;;;UAIM;;WAEN;;WAEA;;WAEA;;WAEA,UAAU;;WAEV;;WAEA,SAAS,QAAQ;;;;UChCX;EACf;EACA;EACA,SAAS;;EAET;;EAEA;;EAEA,aAAa;;UAGE;EACf,SAAS;IAAU;IAAa;;EAChC,QAAQ;EACR,gBAAgB;;UAGD;EACf;EACA;EACA,SAAS;;EAET;;EAEA;;EAEA,eAAe;;EAEf,aAAa;;UAGE;;EAEf,SAAS;IAAU;IAAmB;;EACtC,QAAQ;EACR,gBAAgB;;iBA6MF,kBAAkB,SAAS,sBAAsB;;;;;iBA2CjD,wBAAwB,SAAS,4BAA4B"}
@@ -1,33 +1,28 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { IInteractiveSession, ITransportAdapter } from "@robota-sdk/agent-interface-transport";
3
-
2
+ import "@modelcontextprotocol/sdk/types.js";
3
+ import { ISessionRuntimeTools, ISessionTurnSubmission } from "@robota-sdk/agent-interface-session";
4
+ import { Readable, Writable } from "node:stream";
5
+ import { IAccessTokenVerifier, ITransportAdapter, TAccessTokenRefusal } from "@robota-sdk/agent-interface-transport";
6
+ //#region src/mcp-session.d.ts
7
+ /** The exact session capabilities consumed by the MCP transport. */
8
+ interface IMcpTransportSession extends ISessionTurnSubmission, ISessionRuntimeTools {}
9
+ //#endregion
10
+ //#region src/mcp-tool-surface.d.ts
11
+ interface IMcpSubmitToolIdentity {
12
+ readonly name: string;
13
+ readonly description: string;
14
+ }
15
+ //#endregion
4
16
  //#region src/mcp-server.d.ts
5
17
  interface IAgentMcpOptions {
6
- /** Name for the MCP server. */
7
18
  name: string;
8
- /** Version string. */
9
19
  version: string;
10
- /** IInteractiveSession to expose. */
11
- session: IInteractiveSession;
12
- /** If true, register each system command as a separate MCP tool. Default: true. */
13
- exposeCommands?: boolean;
20
+ session: IMcpTransportSession;
21
+ /** Host-owned identity for the submission extension. */
22
+ submitTool?: IMcpSubmitToolIdentity;
14
23
  }
15
- /**
16
- * Create an MCP server that exposes IInteractiveSession over Model Context Protocol.
17
- *
18
- * Usage:
19
- * ```typescript
20
- * const server = createAgentMcpServer({
21
- * name: 'robota-agent',
22
- * version: '1.0.0',
23
- * session: interactiveSession,
24
- * });
25
- *
26
- * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
27
- * await server.connect(new StdioServerTransport());
28
- * ```
29
- */
30
- declare function createAgentMcpServer(options: IAgentMcpOptions): Server;
24
+ /** Validate the catalog before the caller connects a carrier. */
25
+ declare function createAgentMcpServer(options: IAgentMcpOptions): Promise<Server>;
31
26
  //#endregion
32
27
  //#region src/mcp-transport.d.ts
33
28
  interface IMcpTransportOptions {
@@ -35,12 +30,95 @@ interface IMcpTransportOptions {
35
30
  name: string;
36
31
  /** Version string. */
37
32
  version: string;
38
- /** If true, register each system command as a separate MCP tool. Default: true. */
39
- exposeCommands?: boolean;
33
+ /** Process-owned streams; defaults to the current process stdio. */
34
+ stdin?: Readable;
35
+ stdout?: Writable;
36
+ /** Host-owned identity for the submission extension. */
37
+ submitTool?: IMcpSubmitToolIdentity;
40
38
  }
41
- declare function createMcpTransport(options: IMcpTransportOptions): ITransportAdapter<IInteractiveSession> & {
39
+ interface IMcpTransport extends ITransportAdapter<IMcpTransportSession> {
42
40
  getServer(): Server;
43
- };
41
+ /** Settles when the carrier closes or its input ends; rejects on carrier failure. */
42
+ waitForClose(): Promise<void>;
43
+ }
44
+ declare function createMcpTransport(options: IMcpTransportOptions): IMcpTransport;
45
+ //#endregion
46
+ //#region src/mcp-remote-authorization.d.ts
47
+ /** Why a remote request was refused: a verifier refusal, or no bearer token at all. */
48
+ type TMcpRemoteRefusal = TAccessTokenRefusal | 'missing-token';
49
+ /** A coarse class of the refused peer's address; the address itself is never reported. */
50
+ type TMcpRemoteAddressClass = 'loopback' | 'private' | 'public' | 'unknown';
51
+ /** One refused request, as the audit sink sees it. Carries no token text and no claim value. */
52
+ interface IMcpRemoteAuditRecord {
53
+ readonly refusal: TMcpRemoteRefusal;
54
+ readonly remote: TMcpRemoteAddressClass;
55
+ /** True when the refusal was answered 429 because the address exceeded its failure budget. */
56
+ readonly throttled: boolean;
57
+ }
58
+ /** How the remote host decides who may reach the session. */
59
+ interface IMcpRemoteAuthorization {
60
+ /** The `https` URL clients use for the MCP endpoint; also the resource identifier (`aud`). */
61
+ readonly publicUrl: string;
62
+ /** The authorization server named in the protected-resource metadata. */
63
+ readonly issuer: string;
64
+ /** Scopes a token must carry; advertised as `scopes_supported` and in `insufficient_scope`. */
65
+ readonly scopes: readonly string[];
66
+ /** Decides each presented token. Built for the same issuer, resource and scopes. */
67
+ readonly verifier: IAccessTokenVerifier;
68
+ /** Proxy addresses whose `X-Forwarded-For` is believed. Absent: the header is ignored. */
69
+ readonly trustedProxies?: readonly string[];
70
+ /** Receives one content-free record per refused request. */
71
+ readonly audit?: (record: IMcpRemoteAuditRecord) => void;
72
+ }
73
+ //#endregion
74
+ //#region src/mcp-http-host.d.ts
75
+ interface IMcpHttpHostOptions {
76
+ name: string;
77
+ version: string;
78
+ session: IMcpTransportSession;
79
+ /** Defaults to an OS-assigned port. */
80
+ port?: number;
81
+ /** Accepted only for numeric IPv4 loopback; provided for explicit bind-refusal checks. */
82
+ host?: string;
83
+ /** Host-owned identity for the submission extension. */
84
+ submitTool?: IMcpSubmitToolIdentity;
85
+ }
86
+ interface IMcpHttpHost {
87
+ start(): Promise<{
88
+ url: string;
89
+ token: string;
90
+ }>;
91
+ stop(): Promise<void>;
92
+ waitForClose(): Promise<void>;
93
+ }
94
+ interface IMcpRemoteHttpHostOptions {
95
+ name: string;
96
+ version: string;
97
+ session: IMcpTransportSession;
98
+ /** Defaults to an OS-assigned port. */
99
+ port?: number;
100
+ /** Literal IP address to bind. Defaults to `127.0.0.1`, for a proxy on the same machine. */
101
+ host?: string;
102
+ /** Who may reach the session, decided from OAuth access tokens. Required: there is no open mode. */
103
+ authorization: IMcpRemoteAuthorization;
104
+ /** Host-owned identity for the submission extension. */
105
+ submitTool?: IMcpSubmitToolIdentity;
106
+ }
107
+ interface IMcpRemoteHttpHost {
108
+ /** Resolves with the address actually bound and the public URL clients are told to use. */
109
+ start(): Promise<{
110
+ listening: string;
111
+ url: string;
112
+ }>;
113
+ stop(): Promise<void>;
114
+ waitForClose(): Promise<void>;
115
+ }
116
+ declare function createMcpHttpHost(options: IMcpHttpHostOptions): IMcpHttpHost;
117
+ /**
118
+ * A host reachable beyond loopback. It never mints or accepts the loopback bearer: every request
119
+ * is admitted by an OAuth access token checked against the injected verifier.
120
+ */
121
+ declare function createMcpRemoteHttpHost(options: IMcpRemoteHttpHostOptions): IMcpRemoteHttpHost;
44
122
  //#endregion
45
- export { type IAgentMcpOptions, type IMcpTransportOptions, createAgentMcpServer, createMcpTransport };
123
+ export { type IAgentMcpOptions, type IMcpHttpHost, type IMcpHttpHostOptions, type IMcpRemoteAuditRecord, type IMcpRemoteAuthorization, type IMcpRemoteHttpHost, type IMcpRemoteHttpHostOptions, type IMcpSubmitToolIdentity, type IMcpTransport, type IMcpTransportOptions, type IMcpTransportSession, type TMcpRemoteAddressClass, type TMcpRemoteRefusal, createAgentMcpServer, createMcpHttpHost, createMcpRemoteHttpHost, createMcpTransport };
46
124
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/mcp-server.ts","../../src/mcp-transport.ts"],"mappings":";;;;UAaiB,gBAAA;EAMf;EAJA,IAAA;EAMA;EAJA,OAAA;EAIc;EAFd,OAAA,EAAS,mBAAmB;EAoBM;EAlBlC,cAAA;AAAA;;;;;AAkBqE;;;;AC3BvE;;;;;;;iBD2BgB,oBAAA,CAAqB,OAAA,EAAS,gBAAA,GAAmB,MAAM;;;UC3BtD,oBAAA;EDKf;ECHA,IAAA;EDKS;ECHT,OAAA;EDKc;ECHd,cAAA;AAAA;AAAA,iBAGc,kBAAA,CACd,OAAA,EAAS,oBAAA,GACR,iBAAA,CAAkB,mBAAA;EAAyB,SAAA,IAAa,MAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/mcp-session.ts","../../src/mcp-tool-surface.ts","../../src/mcp-server.ts","../../src/mcp-transport.ts","../../src/mcp-remote-authorization.ts","../../src/mcp-http-host.ts"],"mappings":";;;;;;;UAMiB,6BAA6B,wBAAwB;;;UCCrD;WACN;WACA;;;;UCAM;EACf;EACA;EACA,SAAS;;EAET,aAAa;;;iBAIO,qBAAqB,SAAS,mBAAmB,QAAQ;;;UCM9D;;EAEf;;EAEA;;EAEA,QAAQ;EACR,SAAS;;EAET,aAAa;;UAGE,sBAAsB,kBAAkB;EACvD,aAAa;;EAEb,gBAAgB;;iBAGF,mBAAmB,SAAS,uBAAuB;;;;KCFvD,oBAAoB;;KAGpB;;UAGK;WACN,SAAS;WACT,QAAQ;;WAER;;;UAIM;;WAEN;;WAEA;;WAEA;;WAEA,UAAU;;WAEV;;WAEA,SAAS,QAAQ;;;;UChCX;EACf;EACA;EACA,SAAS;;EAET;;EAEA;;EAEA,aAAa;;UAGE;EACf,SAAS;IAAU;IAAa;;EAChC,QAAQ;EACR,gBAAgB;;UAGD;EACf;EACA;EACA,SAAS;;EAET;;EAEA;;EAEA,eAAe;;EAEf,aAAa;;UAGE;;EAEf,SAAS;IAAU;IAAmB;;EACtC,QAAQ;EACR,gBAAgB;;iBA6MF,kBAAkB,SAAS,sBAAsB;;;;;iBA2CjD,wBAAwB,SAAS,4BAA4B"}
@@ -1,2 +1,2 @@
1
- import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as t,ListToolsRequestSchema as n}from"@modelcontextprotocol/sdk/types.js";function r(r){let{name:a,version:o,session:s,exposeCommands:c=!0}=r,l=new e({name:a,version:o},{capabilities:{tools:{}}}),u=[{name:`submit`,description:`Submit a prompt to the AI agent and wait for the response`,inputSchema:{type:`object`,properties:{prompt:{type:`string`,description:`The prompt to send to the agent`}},required:[`prompt`]}}];if(c)for(let e of s.listCommands())u.push({name:`command_${e.name}`,description:e.description,inputSchema:{type:`object`,properties:{args:{type:`string`,description:`Command arguments`}}}});return l.setRequestHandler(n,async()=>({tools:u})),l.setRequestHandler(t,async e=>{let{name:t,arguments:n}=e.params;if(t===`submit`){let e=n?.prompt;return e?{content:[{type:`text`,text:(await i(s,e)).response}]}:{content:[{type:`text`,text:`Error: prompt is required`}],isError:!0}}if(t.startsWith(`command_`)){let e=t.slice(8),r=n?.args??``,i=await s.executeCommand(e,r);return{content:[{type:`text`,text:i?.message??`Unknown command: ${e}`}],isError:!i}}return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0}}),l}function i(e,t){return new Promise((n,r)=>{let i=e=>{s(),n(e)},a=e=>{s(),n(e)},o=e=>{s(),r(e)},s=()=>{e.off(`complete`,i),e.off(`interrupted`,a),e.off(`error`,o)};e.on(`complete`,i),e.on(`interrupted`,a),e.on(`error`,o),e.submit(t).catch(e=>{s(),r(e instanceof Error?e:Error(String(e)))})})}function a(e){let t=null,n=null;return{name:`mcp`,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);n=r({...e,session:t})},async stop(){n&&=(await n.close(),null)},getServer(){if(!n)throw Error(`Transport not started. Call start() first.`);return n}}}export{r as createAgentMcpServer,a as createMcpTransport};
1
+ import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as t,ListToolsRequestSchema as n,ToolSchema as r}from"@modelcontextprotocol/sdk/types.js";import{isTurnNotRunError as i}from"@robota-sdk/agent-interface-session";import{PassThrough as a}from"node:stream";import{StdioServerTransport as o}from"@modelcontextprotocol/sdk/server/stdio.js";import{createServer as s}from"node:http";import{BlockList as c,isIP as l}from"node:net";import{toNodeHandler as u}from"@modelcontextprotocol/node";import{Server as d,createMcpHandler as f}from"@modelcontextprotocol/server";import{bearerCredential as p,credentialMatches as m,mintTransportToken as h}from"@robota-sdk/agent-transport/node";const g={name:`agent_submit`,description:`Submit a prompt to the agent and await its turn`};function _(e=g){if(typeof e.name!=`string`||!/^[A-Za-z0-9._-]{1,128}$/.test(e.name))throw Error(`MCP submit tool name must contain 1–128 ASCII letters, digits, dots, hyphens, or underscores`);return r.parse({name:e.name,description:e.description,inputSchema:{type:`object`,properties:{prompt:{type:`string`,minLength:1}},required:[`prompt`],additionalProperties:!1}})}async function v(e,t){let n=new Set([t]);return(await e.listRuntimeTools()).map(e=>{if(n.has(e.name))throw Error(`Duplicate or reserved MCP tool name: ${e.name}`);return n.add(e.name),r.parse({name:e.name,description:e.description,inputSchema:e.parameters})})}function y(e){return{content:[{type:`text`,text:e}],isError:!0}}function b(e){return e===null||typeof e==`string`||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):Array.isArray(e)?e.every(b):typeof e==`object`&&!!e&&Object.values(e).every(b)}function x(e){return Object.values(e).every(b)}async function S(e,t,n,r,a){let o;try{r.throwIfAborted(),o=await v(e,a)}catch(e){return y(e instanceof Error?e.message:String(e))}if(t===a){if(typeof n.prompt!=`string`||n.prompt.length===0||Object.keys(n).some(e=>e!==`prompt`))return y(`${a} requires a non-empty string prompt and no other arguments`);try{return{content:[{type:`text`,text:(await(await e.submit(n.prompt,void 0,void 0,{signal:r})).completed).response}]}}catch(e){if(!i(e)&&!r.aborted)throw e;return y(e instanceof Error?e.message:String(e))}}if(!o.some(e=>e.name===t))return y(`Unknown tool: ${t}`);if(!x(n))return y(`Tool arguments must contain JSON values`);try{let i=await e.invokeRuntimeTool(t,n,{signal:r});return{content:[{type:`text`,text:JSON.stringify(i)}],isError:!i.success}}catch(e){return y(e instanceof Error?e.message:String(e))}}async function C(r){let{name:i,version:a,session:o}=r,s=_(r.submitTool);await v(o,s.name);let c=new e({name:i,version:a},{capabilities:{tools:{}}});return c.setRequestHandler(n,async()=>({tools:[...await v(o,s.name),s]})),c.setRequestHandler(t,async(e,t)=>S(o,e.params.name,e.params.arguments??{},t.signal,s.name)),c}function w(e){let t=null,n=null,r=null,i=null,s=null,c=null,l=e.stdin??process.stdin,u=e.stdout??process.stdout,d=null,f=null,p=Promise.resolve(),m=()=>{p=new Promise((e,t)=>{d=e,f=t}),p.catch(()=>void 0)},h=()=>{d?.(),c?.(Error(`MCP input closed during startup`))},g=()=>{d?.(),c?.(Error(`MCP output closed during startup`))},_=e=>{f?.(e),c?.(e)},v=()=>{l.off(`end`,h),l.off(`close`,h),u.off(`close`,g),u.off(`error`,_),s&&=(l.unpipe(s),s.destroy(),null)},y=e=>Object.assign(Error(`MCP transport ${e}.`),{name:`TransportLifecycleError`,code:e,transportName:`mcp`});return{name:`mcp`,lifecycle:Object.freeze({kind:`service`}),attach(e){if(n||r||i)throw y(`already-started`);t=e,m()},async start(){if(!t)throw y(`not-attached`);if(n||r||i)throw y(`already-started`);let p=t,m=new Promise((e,t)=>{c=t}),b=new a;s=b,l.once(`end`,h),l.once(`close`,h),u.once(`close`,g),u.on(`error`,_),l.pipe(b),r=(async()=>{let t=null,r=setTimeout(()=>c?.(Error(`MCP carrier startup timed out`)),15e3);try{t=await Promise.race([C({name:e.name,version:e.version,session:p,...e.submitTool===void 0?{}:{submitTool:e.submitTool}}),m]);let r=new o(b,u);r.onclose=()=>d?.(),r.onerror=e=>f?.(e),await t.connect(r),n=t,t=null}catch(e){throw v(),t&&await t.close().catch(()=>void 0),e}finally{clearTimeout(r),c=null}})();try{await r}finally{r=null}},stop(){return i||(i=(async()=>{try{c?.(Error(`MCP carrier startup stopped`)),r&&await r.catch(()=>void 0),n&&await n.close()}finally{v(),n=null,t=null,d?.()}})().finally(()=>{i=null}),i)},getServer(){if(!n)throw Error(`Transport not started. Call start() first.`);return n},waitForClose(){return p}}}const T=6e4,E=4096,D=/^[\x21\x23-\x5b\x5d-\x7e]+$/,O=new c;O.addSubnet(`127.0.0.0`,8,`ipv4`),O.addAddress(`::1`,`ipv6`);const k=new c;k.addSubnet(`10.0.0.0`,8,`ipv4`),k.addSubnet(`172.16.0.0`,12,`ipv4`),k.addSubnet(`192.168.0.0`,16,`ipv4`),k.addSubnet(`169.254.0.0`,16,`ipv4`),k.addSubnet(`100.64.0.0`,10,`ipv4`),k.addSubnet(`fc00::`,7,`ipv6`),k.addSubnet(`fe80::`,10,`ipv6`);function A(e){if(e===void 0)return;let t=e.trim().toLowerCase(),n=t.startsWith(`::ffff:`)?t.slice(7):void 0;return n!==void 0&&l(n)===4?n:l(t)===0?void 0:t}function j(e){if(e===void 0)return`unknown`;let t=l(e)===4?`ipv4`:`ipv6`;return O.check(e,t)?`loopback`:k.check(e,t)?`private`:`public`}function M(e){let t;try{t=new URL(e)}catch{throw Error(`MCP HTTP public URL must be an absolute https URL`)}if(t.protocol!==`https:`)throw Error(`MCP HTTP public URL must use https`);if(t.username!==``||t.password!==``||t.search!==``||t.hash!==``)throw Error(`MCP HTTP public URL must not carry credentials, a query or a fragment`);return t}function N(e){let t=new Map;return{fail(n){let r=e(),i=t.get(n);if(i===void 0||r-i.startedAt>=T){if(t.delete(n),t.size>=E){for(let[e,n]of t)r-n.startedAt>=T&&t.delete(e);let e=t.keys().next();t.size>=E&&e.done!==!0&&t.delete(e.value)}i={count:0,startedAt:r},t.set(n,i)}i.count+=1;let a=T-(r-i.startedAt);return{throttled:i.count>20,retryAfterSeconds:Math.max(1,Math.ceil(a/1e3))}}}}function P(e,t=()=>performance.now()){let n=M(e.publicUrl),r;try{r=new URL(e.issuer)}catch{throw Error(`MCP HTTP authorization issuer must be an absolute https URL`)}if(r.protocol!==`https:`)throw Error(`MCP HTTP authorization issuer must use https`);if(e.scopes.length===0||!e.scopes.every(e=>D.test(e)))throw Error(`MCP HTTP authorization needs at least one valid scope`);let i=new Set;for(let t of e.trustedProxies??[]){let e=A(t);if(e===void 0)throw Error(`MCP HTTP trusted proxy must be a literal IP address`);i.add(e)}let a=n.pathname,o=`/.well-known/oauth-protected-resource${a===`/`?``:a}`,s=`${n.origin}${o}`,c=new Set([n.host]);n.port===``&&c.add(`${n.hostname}:443`);let l=JSON.stringify({resource:e.publicUrl,authorization_servers:[e.issuer],scopes_supported:[...e.scopes],bearer_methods_supported:[`header`]}),u=e.scopes.join(` `),d={missing:`Bearer resource_metadata="${s}"`,invalid:`Bearer error="invalid_token", resource_metadata="${s}"`,scope:`Bearer error="insufficient_scope", scope="${u}", resource_metadata="${s}"`},f=N(t);function m(e){let t=A(e.socket.remoteAddress);if(t===void 0||!i.has(t))return t;let n=e.headers[`x-forwarded-for`],r=(Array.isArray(n)?n.join(`,`):n??``).split(`,`);for(let e=r.length-1;e>=0;--e){let n=A(r[e]);if(n===void 0)return t;if(!i.has(n))return n}return t}function h(t,n,r){let i=m(t),a=j(i);if(r===`keys-unavailable`){e.audit?.({refusal:r,remote:a,throttled:!1}),n.writeHead(503).end();return}let o=f.fail(i??`unknown`);if(e.audit?.({refusal:r,remote:a,throttled:o.throttled}),o.throttled)n.writeHead(429,{"Retry-After":String(o.retryAfterSeconds)}).end();else if(r===`missing-scope`)n.writeHead(403,{"WWW-Authenticate":d.scope}).end();else{let e=r===`missing-token`?d.missing:d.invalid;n.writeHead(401,{"WWW-Authenticate":e}).end()}}return{endpointPath:a,async admit(t,r){let i=t.headers.host?.toLowerCase(),s=t.headers.origin;if(i===void 0||!c.has(i)||s!==void 0&&s!==n.origin)return r.writeHead(403).end(),!1;if(t.url===o)return t.method===`GET`?(r.writeHead(200,{"Content-Type":`application/json`}).end(l),!1):(r.writeHead(405,{Allow:`GET`}).end(),!1);if(t.url!==a)return r.writeHead(404).end(),!1;let u=p(t.headers.authorization);if(u===void 0)return h(t,r,`missing-token`),!1;let d=await e.verifier.verify(u);return d.admitted?!0:(h(t,r,d.refusal),!1)}}}const F=`127.0.0.1`,I=1024*1024;function L(e){if(e!==void 0&&(!Number.isInteger(e)||e<0||e>65535))throw Error(`MCP HTTP port must be an integer in 0..65535`)}function R(e,t,n){let r=_(e.submitTool),i,a,o,c=!1,l=!1,p=0,m,h,g=new Promise((e,t)=>{m=e,h=t});return g.catch(()=>void 0),{async start(){if(c||o)throw Error(`MCP HTTP host already started`);if(c=!0,await v(e.session,r.name),l)throw Error(`MCP HTTP host stopped during startup`);a=f(()=>{let t=new d({name:e.name,version:e.version},{capabilities:{tools:{}}});return t.setRequestHandler(`tools/list`,async()=>({tools:JSON.parse(JSON.stringify([...await v(e.session,r.name),r]))})),t.setRequestHandler(`tools/call`,async(t,n)=>S(e.session,t.params.name,t.params.arguments??{},n.mcpReq.signal,r.name)),t},{maxSubscriptions:0});let g=u(a),_=(e,t)=>{if(p>=16){t.writeHead(503).end();return}p+=1,(async()=>{try{let n;if(e.method===`POST`){let r=Number(e.headers[`content-length`]);if(Number.isFinite(r)&&r>I){t.writeHead(413).end();return}let i=[],a=0;for await(let n of e){let r=Buffer.isBuffer(n)?n:Buffer.from(n);if(a+=r.length,a>I){t.writeHead(413).end(),e.destroy();return}i.push(r)}try{n=JSON.parse(Buffer.concat(i).toString(`utf8`))}catch{t.writeHead(400).end();return}}await g(e,t,n)}catch{t.headersSent?t.destroy():t.writeHead(500).end()}finally{--p}})()},y=s({maxHeaderSize:16*1024},(e,t)=>{let r=y.address();if(!r||typeof r==`string`){t.writeHead(503).end();return}let i=n(e,t,r.port);i===!0?_(e,t):i!==!1&&i.then(n=>{n&&_(e,t)},()=>{t.headersSent?t.destroy():t.writeHead(500).end()})});y.maxConnections=32,y.headersTimeout=5e3,y.requestTimeout=15e3,y.on(`error`,e=>h?.(e)),y.on(`close`,()=>m?.());try{await new Promise((n,r)=>{y.once(`error`,r),y.listen(e.port??0,t,()=>{y.off(`error`,r),n()})})}catch(e){throw await a.close(),e}if(l)throw await new Promise(e=>y.close(()=>e())),await a.close(),Error(`MCP HTTP host stopped during startup`);return i=y,y.address()},stop(){return o||(l=!0,o=(async()=>{let e=a,t=i;if(!t){await e?.close(),m?.();return}let n=new Promise((e,n)=>{t.close(t=>t?n(t):e()),t.closeAllConnections()}),r;try{await Promise.race([Promise.all([e?.close(),n]).then(()=>void 0),new Promise((e,t)=>{r=setTimeout(()=>t(Error(`MCP HTTP close timed out`)),5e3)})])}finally{r&&clearTimeout(r)}})(),o)},waitForClose(){return g}}}function z(e){if(e.host!==void 0&&e.host!==F)throw Error(`MCP HTTP host must bind numeric IPv4 loopback`);L(e.port);let t=h(),n=R(e,F,(e,n,r)=>{let i=`${F}:${r}`,a=`http://${i}`;if(e.url!==`/mcp`||e.headers.host!==i||e.headers.origin!==void 0&&e.headers.origin!==a)return n.writeHead(403).end(),!1;let o=e.headers.authorization;return typeof o!=`string`||!m(t,p(o))?(n.writeHead(401).end(),!1):!0});return{async start(){let e=await n.start();return{url:`http://${F}:${e.port}/mcp`,token:t}},stop:n.stop,waitForClose:n.waitForClose}}function B(e){let t=e.host??F;if(l(t)===0)throw Error(`MCP HTTP host must bind a literal IP address`);L(e.port);let n=P(e.authorization),r=R(e,t,(e,t)=>n.admit(e,t));return{async start(){let t=await r.start();return{listening:`${t.family===`IPv6`?`[${t.address}]`:t.address}:${t.port}`,url:e.authorization.publicUrl}},stop:r.stop,waitForClose:r.waitForClose}}export{C as createAgentMcpServer,z as createMcpHttpHost,B as createMcpRemoteHttpHost,w as createMcpTransport};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/mcp-server.ts","../../src/mcp-transport.ts"],"sourcesContent":["/**\n * MCP transport adapter — exposes IInteractiveSession as an MCP server.\n *\n * Uses the low-level MCP Server class to avoid TypeScript depth issues\n * with McpServer.registerTool() generics. Registers tools/list and\n * tools/call handlers directly.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';\n\nimport type { IExecutionResult, IInteractiveSession } from '@robota-sdk/agent-interface-transport';\n\nexport interface IAgentMcpOptions {\n /** Name for the MCP server. */\n name: string;\n /** Version string. */\n version: string;\n /** IInteractiveSession to expose. */\n session: IInteractiveSession;\n /** If true, register each system command as a separate MCP tool. Default: true. */\n exposeCommands?: boolean;\n}\n\n/**\n * Create an MCP server that exposes IInteractiveSession over Model Context Protocol.\n *\n * Usage:\n * ```typescript\n * const server = createAgentMcpServer({\n * name: 'robota-agent',\n * version: '1.0.0',\n * session: interactiveSession,\n * });\n *\n * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n * await server.connect(new StdioServerTransport());\n * ```\n */\nexport function createAgentMcpServer(options: IAgentMcpOptions): Server {\n const { name, version, session, exposeCommands = true } = options;\n\n const server = new Server({ name, version }, { capabilities: { tools: {} } });\n\n // Build tool definitions\n const tools: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n }> = [\n {\n name: 'submit',\n description: 'Submit a prompt to the AI agent and wait for the response',\n inputSchema: {\n type: 'object',\n properties: {\n prompt: { type: 'string', description: 'The prompt to send to the agent' },\n },\n required: ['prompt'],\n },\n },\n ];\n\n if (exposeCommands) {\n for (const cmd of session.listCommands()) {\n tools.push({\n name: `command_${cmd.name}`,\n description: cmd.description,\n inputSchema: {\n type: 'object',\n properties: {\n args: { type: 'string', description: 'Command arguments' },\n },\n },\n });\n }\n }\n\n // tools/list handler\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools,\n }));\n\n // tools/call handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name: toolName, arguments: toolArgs } = request.params;\n\n if (toolName === 'submit') {\n const prompt = (toolArgs as Record<string, string>)?.prompt;\n if (!prompt) {\n return {\n content: [{ type: 'text', text: 'Error: prompt is required' }],\n isError: true,\n };\n }\n const result = await waitForCompletion(session, prompt);\n return {\n content: [{ type: 'text', text: result.response }],\n };\n }\n\n // System commands: command_<name>\n if (toolName.startsWith('command_')) {\n const cmdName = toolName.slice('command_'.length);\n const args = (toolArgs as Record<string, string>)?.args ?? '';\n const result = await session.executeCommand(cmdName, args);\n return {\n content: [\n {\n type: 'text',\n text: result?.message ?? `Unknown command: ${cmdName}`,\n },\n ],\n isError: !result,\n };\n }\n\n return {\n content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],\n isError: true,\n };\n });\n\n return server;\n}\n\n/**\n * Submit a prompt and wait for the complete/interrupted/error event.\n */\nfunction waitForCompletion(\n session: IInteractiveSession,\n prompt: string,\n): Promise<IExecutionResult> {\n return new Promise((resolve, reject) => {\n const onComplete = (result: IExecutionResult): void => {\n cleanup();\n resolve(result);\n };\n const onInterrupted = (result: IExecutionResult): void => {\n cleanup();\n resolve(result);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const cleanup = (): void => {\n session.off('complete', onComplete);\n session.off('interrupted', onInterrupted);\n session.off('error', onError);\n };\n\n session.on('complete', onComplete);\n session.on('interrupted', onInterrupted);\n session.on('error', onError);\n\n session.submit(prompt).catch((err) => {\n cleanup();\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n });\n}\n","/**\n * ITransportAdapter implementation for MCP transport.\n *\n * Wraps createAgentMcpServer into the unified ITransportAdapter interface\n * while exposing the underlying MCP Server via getServer().\n */\n\nimport { createAgentMcpServer } from './mcp-server.js';\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { IInteractiveSession, ITransportAdapter } from '@robota-sdk/agent-interface-transport';\n\nexport interface IMcpTransportOptions {\n /** Name for the MCP server. */\n name: string;\n /** Version string. */\n version: string;\n /** If true, register each system command as a separate MCP tool. Default: true. */\n exposeCommands?: boolean;\n}\n\nexport function createMcpTransport(\n options: IMcpTransportOptions,\n): ITransportAdapter<IInteractiveSession> & { getServer(): Server } {\n let session: IInteractiveSession | null = null;\n let server: Server | null = null;\n\n return {\n name: 'mcp',\n attach(s: IInteractiveSession) {\n session = s;\n },\n async start() {\n if (!session) throw new Error('No session attached. Call attach() first.');\n server = createAgentMcpServer({ ...options, session });\n },\n async stop() {\n if (server) {\n await server.close();\n server = null;\n }\n },\n getServer() {\n if (!server) throw new Error('Transport not started. Call start() first.');\n return server;\n },\n };\n}\n"],"mappings":"0KAuCA,SAAgB,EAAqB,EAAmC,CACtE,GAAM,CAAE,OAAM,UAAS,UAAS,iBAAiB,IAAS,EAEpD,EAAS,IAAI,EAAO,CAAE,OAAM,SAAQ,EAAG,CAAE,aAAc,CAAE,MAAO,CAAC,CAAE,CAAE,CAAC,EAGtE,EAID,CACH,CACE,KAAM,SACN,YAAa,4DACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,OAAQ,CAAE,KAAM,SAAU,YAAa,iCAAkC,CAC3E,EACA,SAAU,CAAC,QAAQ,CACrB,CACF,CACF,EAEA,GAAI,EACF,IAAK,IAAM,KAAO,EAAQ,aAAa,EACrC,EAAM,KAAK,CACT,KAAM,WAAW,EAAI,OACrB,YAAa,EAAI,YACjB,YAAa,CACX,KAAM,SACN,WAAY,CACV,KAAM,CAAE,KAAM,SAAU,YAAa,mBAAoB,CAC3D,CACF,CACF,CAAC,EAiDL,OA5CA,EAAO,kBAAkB,EAAwB,UAAa,CAC5D,OACF,EAAE,EAGF,EAAO,kBAAkB,EAAuB,KAAO,IAAY,CACjE,GAAM,CAAE,KAAM,EAAU,UAAW,GAAa,EAAQ,OAExD,GAAI,IAAa,SAAU,CACzB,IAAM,EAAU,GAAqC,OAQrD,OAPK,EAOE,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,MAAM,MAFb,EAAkB,EAAS,CAAM,EAAA,CAEb,QAAS,CAAC,CACnD,EARS,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,2BAA4B,CAAC,EAC7D,QAAS,EACX,CAMJ,CAGA,GAAI,EAAS,WAAW,UAAU,EAAG,CACnC,IAAM,EAAU,EAAS,MAAM,CAAiB,EAC1C,EAAQ,GAAqC,MAAQ,GACrD,EAAS,MAAM,EAAQ,eAAe,EAAS,CAAI,EACzD,MAAO,CACL,QAAS,CACP,CACE,KAAM,OACN,KAAM,GAAQ,SAAW,oBAAoB,GAC/C,CACF,EACA,QAAS,CAAC,CACZ,CACF,CAEA,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,iBAAiB,GAAW,CAAC,EAC7D,QAAS,EACX,CACF,CAAC,EAEM,CACT,CAKA,SAAS,EACP,EACA,EAC2B,CAC3B,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAc,GAAmC,CACrD,EAAQ,EACR,EAAQ,CAAM,CAChB,EACM,EAAiB,GAAmC,CACxD,EAAQ,EACR,EAAQ,CAAM,CAChB,EACM,EAAW,GAAuB,CACtC,EAAQ,EACR,EAAO,CAAK,CACd,EAEM,MAAsB,CAC1B,EAAQ,IAAI,WAAY,CAAU,EAClC,EAAQ,IAAI,cAAe,CAAa,EACxC,EAAQ,IAAI,QAAS,CAAO,CAC9B,EAEA,EAAQ,GAAG,WAAY,CAAU,EACjC,EAAQ,GAAG,cAAe,CAAa,EACvC,EAAQ,GAAG,QAAS,CAAO,EAE3B,EAAQ,OAAO,CAAM,CAAC,CAAC,MAAO,GAAQ,CACpC,EAAQ,EACR,EAAO,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,CAC5D,CAAC,CACH,CAAC,CACH,CC7IA,SAAgB,EACd,EACkE,CAClE,IAAI,EAAsC,KACtC,EAAwB,KAE5B,MAAO,CACL,KAAM,MACN,OAAO,EAAwB,CAC7B,EAAU,CACZ,EACA,MAAM,OAAQ,CACZ,GAAI,CAAC,EAAS,MAAU,MAAM,2CAA2C,EACzE,EAAS,EAAqB,CAAE,GAAG,EAAS,SAAQ,CAAC,CACvD,EACA,MAAM,MAAO,CACX,AAEE,KADA,MAAM,EAAO,MAAM,EACV,KAEb,EACA,WAAY,CACV,GAAI,CAAC,EAAQ,MAAU,MAAM,4CAA4C,EACzE,OAAO,CACT,CACF,CACF"}
1
+ {"version":3,"file":"index.js","names":["Server"],"sources":["../../src/mcp-tool-surface.ts","../../src/mcp-server.ts","../../src/mcp-transport.ts","../../src/mcp-remote-authorization.ts","../../src/mcp-http-host.ts"],"sourcesContent":["/** One canonical tool catalog and invocation path shared by stdio and HTTP MCP carriers. */\nimport { ToolSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { isTurnNotRunError } from '@robota-sdk/agent-interface-session';\n\nimport type { IMcpTransportSession } from './mcp-session.js';\nimport type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface IMcpSubmitToolIdentity {\n readonly name: string;\n readonly description: string;\n}\n\nconst DEFAULT_SUBMIT_TOOL: IMcpSubmitToolIdentity = {\n name: 'agent_submit',\n description: 'Submit a prompt to the agent and await its turn',\n};\n\nexport function resolveSubmitTool(identity: IMcpSubmitToolIdentity = DEFAULT_SUBMIT_TOOL): Tool {\n if (typeof identity.name !== 'string' || !/^[A-Za-z0-9._-]{1,128}$/.test(identity.name)) {\n throw new Error(\n 'MCP submit tool name must contain 1–128 ASCII letters, digits, dots, hyphens, or underscores',\n );\n }\n return ToolSchema.parse({\n name: identity.name,\n description: identity.description,\n inputSchema: {\n type: 'object' as const,\n properties: { prompt: { type: 'string', minLength: 1 } },\n required: ['prompt'],\n additionalProperties: false,\n },\n });\n}\n\nexport async function readCatalog(\n session: IMcpTransportSession,\n submitToolName: string,\n): Promise<Tool[]> {\n const names = new Set([submitToolName]);\n return (await session.listRuntimeTools()).map((schema) => {\n if (names.has(schema.name)) {\n throw new Error(`Duplicate or reserved MCP tool name: ${schema.name}`);\n }\n names.add(schema.name);\n return ToolSchema.parse({\n name: schema.name,\n description: schema.description,\n inputSchema: schema.parameters,\n });\n });\n}\n\nfunction toolError(message: string): CallToolResult {\n return { content: [{ type: 'text' as const, text: message }], isError: true };\n}\n\nfunction isJsonValue(value: unknown): boolean {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (Array.isArray(value)) return value.every(isJsonValue);\n return typeof value === 'object' && value !== null && Object.values(value).every(isJsonValue);\n}\n\nfunction isRuntimeParameters(\n value: Record<string, unknown>,\n): value is Parameters<IMcpTransportSession['invokeRuntimeTool']>[1] {\n return Object.values(value).every(isJsonValue);\n}\n\nexport async function invokeMcpTool(\n session: IMcpTransportSession,\n toolName: string,\n parameters: Record<string, unknown>,\n signal: AbortSignal,\n submitToolName: string,\n): Promise<CallToolResult> {\n let catalog: Tool[];\n try {\n signal.throwIfAborted();\n catalog = await readCatalog(session, submitToolName);\n } catch (error) {\n return toolError(error instanceof Error ? error.message : String(error));\n }\n if (toolName === submitToolName) {\n if (\n typeof parameters.prompt !== 'string' ||\n parameters.prompt.length === 0 ||\n Object.keys(parameters).some((key) => key !== 'prompt')\n ) {\n return toolError(\n `${submitToolName} requires a non-empty string prompt and no other arguments`,\n );\n }\n try {\n const handle = await session.submit(parameters.prompt, undefined, undefined, { signal });\n const result = await handle.completed;\n return { content: [{ type: 'text', text: result.response }] };\n } catch (error) {\n if (!isTurnNotRunError(error) && !signal.aborted) throw error;\n return toolError(error instanceof Error ? error.message : String(error));\n }\n }\n if (!catalog.some((tool) => tool.name === toolName))\n return toolError(`Unknown tool: ${toolName}`);\n if (!isRuntimeParameters(parameters)) return toolError('Tool arguments must contain JSON values');\n try {\n const result = await session.invokeRuntimeTool(toolName, parameters, { signal });\n return {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n isError: !result.success,\n };\n } catch (error) {\n return toolError(error instanceof Error ? error.message : String(error));\n }\n}\n","/** MCP translates the canonical session tool port; execution policy stays in the session. */\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';\n\nimport { invokeMcpTool, readCatalog, resolveSubmitTool } from './mcp-tool-surface.js';\n\nimport type { IMcpTransportSession } from './mcp-session.js';\nimport type { IMcpSubmitToolIdentity } from './mcp-tool-surface.js';\n\nexport interface IAgentMcpOptions {\n name: string;\n version: string;\n session: IMcpTransportSession;\n /** Host-owned identity for the submission extension. */\n submitTool?: IMcpSubmitToolIdentity;\n}\n\n/** Validate the catalog before the caller connects a carrier. */\nexport async function createAgentMcpServer(options: IAgentMcpOptions): Promise<Server> {\n const { name, version, session } = options;\n const submitTool = resolveSubmitTool(options.submitTool);\n await readCatalog(session, submitTool.name);\n const server = new Server({ name, version }, { capabilities: { tools: {} } });\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [...(await readCatalog(session, submitTool.name)), submitTool],\n }));\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) =>\n invokeMcpTool(\n session,\n request.params.name,\n request.params.arguments ?? {},\n extra.signal,\n submitTool.name,\n ),\n );\n return server;\n}\n","/**\n * ITransportAdapter implementation for MCP transport.\n *\n * Connects the official SDK stdio carrier to the canonical session MCP server.\n * The adapter owns carrier readiness and teardown; the product shell owns process policy.\n */\n\nimport { PassThrough } from 'node:stream';\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { createAgentMcpServer } from './mcp-server.js';\n\nimport type { IMcpTransportSession } from './mcp-session.js';\nimport type { IMcpSubmitToolIdentity } from './mcp-tool-surface.js';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type {\n ITransportAdapter,\n ITransportLifecycleError,\n} from '@robota-sdk/agent-interface-transport';\nimport type { Readable, Writable } from 'node:stream';\n\nconst STARTUP_TIMEOUT_MS = 15000;\n\nexport interface IMcpTransportOptions {\n /** Name for the MCP server. */\n name: string;\n /** Version string. */\n version: string;\n /** Process-owned streams; defaults to the current process stdio. */\n stdin?: Readable;\n stdout?: Writable;\n /** Host-owned identity for the submission extension. */\n submitTool?: IMcpSubmitToolIdentity;\n}\n\nexport interface IMcpTransport extends ITransportAdapter<IMcpTransportSession> {\n getServer(): Server;\n /** Settles when the carrier closes or its input ends; rejects on carrier failure. */\n waitForClose(): Promise<void>;\n}\n\nexport function createMcpTransport(options: IMcpTransportOptions): IMcpTransport {\n let session: IMcpTransportSession | null = null;\n let server: Server | null = null;\n let starting: Promise<void> | null = null;\n let stopping: Promise<void> | null = null;\n let inputBridge: PassThrough | null = null;\n let cancelStart: ((error: Error) => void) | null = null;\n const stdin = options.stdin ?? process.stdin;\n const stdout = options.stdout ?? process.stdout;\n let resolveClose: (() => void) | null = null;\n let rejectClose: ((error: Error) => void) | null = null;\n let closePromise: Promise<void> = Promise.resolve();\n const resetClose = (): void => {\n closePromise = new Promise<void>((resolve, reject) => {\n resolveClose = resolve;\n rejectClose = reject;\n });\n // A carrier may fail between connect and the process owner awaiting this promise.\n // allow-fallback: waitForClose retains the rejection for the process owner; this observer only prevents an early unhandled-rejection warning.\n void closePromise.catch(() => undefined);\n };\n const onInputClose = (): void => {\n resolveClose?.();\n cancelStart?.(new Error('MCP input closed during startup'));\n };\n const onOutputClose = (): void => {\n resolveClose?.();\n cancelStart?.(new Error('MCP output closed during startup'));\n };\n const onOutputError = (error: Error): void => {\n rejectClose?.(error);\n cancelStart?.(error);\n };\n const removeInputListeners = (): void => {\n stdin.off('end', onInputClose);\n stdin.off('close', onInputClose);\n stdout.off('close', onOutputClose);\n stdout.off('error', onOutputError);\n if (inputBridge) {\n stdin.unpipe(inputBridge);\n inputBridge.destroy();\n inputBridge = null;\n }\n };\n const lifecycleError = (code: ITransportLifecycleError['code']): ITransportLifecycleError =>\n Object.assign(new Error(`MCP transport ${code}.`), {\n name: 'TransportLifecycleError' as const,\n code,\n transportName: 'mcp',\n });\n\n return {\n name: 'mcp',\n lifecycle: Object.freeze({ kind: 'service' }),\n attach(s: IMcpTransportSession) {\n if (server || starting || stopping) throw lifecycleError('already-started');\n session = s;\n resetClose();\n },\n async start() {\n if (!session) throw lifecycleError('not-attached');\n if (server || starting || stopping) throw lifecycleError('already-started');\n const attachedSession = session;\n const cancelled = new Promise<never>((_resolve, reject) => {\n cancelStart = reject;\n });\n const bridge = new PassThrough();\n inputBridge = bridge;\n stdin.once('end', onInputClose);\n stdin.once('close', onInputClose);\n stdout.once('close', onOutputClose);\n stdout.on('error', onOutputError);\n // Preserve early frames with backpressure while the canonical tool catalog is validated.\n // Piping also exposes EOF before the SDK begins reading.\n stdin.pipe(bridge);\n starting = (async () => {\n let candidate: Server | null = null;\n const timeout = setTimeout(\n () => cancelStart?.(new Error('MCP carrier startup timed out')),\n STARTUP_TIMEOUT_MS,\n );\n try {\n candidate = await Promise.race([\n createAgentMcpServer({\n name: options.name,\n version: options.version,\n session: attachedSession,\n ...(options.submitTool !== undefined ? { submitTool: options.submitTool } : {}),\n }),\n cancelled,\n ]);\n const nextCarrier = new StdioServerTransport(bridge, stdout);\n nextCarrier.onclose = () => resolveClose?.();\n nextCarrier.onerror = (error) => rejectClose?.(error);\n await candidate.connect(nextCarrier);\n server = candidate;\n candidate = null;\n } catch (error) {\n removeInputListeners();\n // allow-fallback: the startup error is already being thrown; a failed best-effort close must not replace it.\n if (candidate) await candidate.close().catch(() => undefined);\n throw error;\n } finally {\n clearTimeout(timeout);\n cancelStart = null;\n }\n })();\n try {\n await starting;\n } finally {\n starting = null;\n }\n },\n stop() {\n if (stopping) return stopping;\n stopping = (async () => {\n try {\n cancelStart?.(new Error('MCP carrier startup stopped'));\n // allow-fallback: stop explicitly cancels startup, so its expected rejection is consumed before carrier teardown.\n if (starting) await starting.catch(() => undefined);\n if (server) await server.close();\n } finally {\n removeInputListeners();\n server = null;\n session = null;\n resolveClose?.();\n }\n })().finally(() => {\n stopping = null;\n });\n return stopping;\n },\n getServer() {\n if (!server) throw new Error('Transport not started. Call start() first.');\n return server;\n },\n waitForClose() {\n return closePromise;\n },\n };\n}\n","/**\n * The admission gate of the remote MCP HTTP host: an OAuth resource server in the sense of\n * RFC 6750 (bearer challenges) and RFC 9728 (protected-resource metadata).\n *\n * The host is reached through its PUBLIC URL, usually behind a reverse proxy, so every name the gate\n * checks comes from that URL rather than from the listener: the endpoint path and the metadata path\n * (a proxy prefix therefore works when the proxy forwards the path unchanged), and the Host and\n * Origin a request must carry (the proxy must preserve Host). The listener's own address is never a\n * name a client uses.\n *\n * Refusals answer with an empty body and, for a token problem, only the standard challenge: an error\n * code from RFC 6750 and where the metadata lives. Nothing describes WHY in prose, so a refusal\n * cannot leak which check failed beyond the code a client is entitled to.\n *\n * The token is verified before anything is counted, and only failures are counted, so a peer holding\n * a valid token is never throttled — however many failures share its address. Each failure is also\n * handed to an audit sink as a closed-set reason and a coarse address class, never token text.\n */\n\nimport { BlockList, isIP } from 'node:net';\n\nimport { bearerCredential } from '@robota-sdk/agent-transport/node';\n\nimport type {\n IAccessTokenVerifier,\n TAccessTokenRefusal,\n} from '@robota-sdk/agent-interface-transport';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst WELL_KNOWN_PREFIX = '/.well-known/oauth-protected-resource';\n/** Window over which one address's failures are counted. */\nconst FAILURE_WINDOW_MS = 60_000;\n/** Failures one address may have in a window before its further failures answer 429. */\nconst MAX_FAILURES_PER_WINDOW = 20;\n/** Addresses tracked at once; the oldest is forgotten first, so a spray cannot grow memory. */\nconst MAX_TRACKED_ADDRESSES = 4096;\n/** RFC 6749 scope-token characters: printable ASCII except space, `\"` and `\\`. */\nconst SCOPE_TOKEN = /^[\\x21\\x23-\\x5b\\x5d-\\x7e]+$/;\n\n/** Why a remote request was refused: a verifier refusal, or no bearer token at all. */\nexport type TMcpRemoteRefusal = TAccessTokenRefusal | 'missing-token';\n\n/** A coarse class of the refused peer's address; the address itself is never reported. */\nexport type TMcpRemoteAddressClass = 'loopback' | 'private' | 'public' | 'unknown';\n\n/** One refused request, as the audit sink sees it. Carries no token text and no claim value. */\nexport interface IMcpRemoteAuditRecord {\n readonly refusal: TMcpRemoteRefusal;\n readonly remote: TMcpRemoteAddressClass;\n /** True when the refusal was answered 429 because the address exceeded its failure budget. */\n readonly throttled: boolean;\n}\n\n/** How the remote host decides who may reach the session. */\nexport interface IMcpRemoteAuthorization {\n /** The `https` URL clients use for the MCP endpoint; also the resource identifier (`aud`). */\n readonly publicUrl: string;\n /** The authorization server named in the protected-resource metadata. */\n readonly issuer: string;\n /** Scopes a token must carry; advertised as `scopes_supported` and in `insufficient_scope`. */\n readonly scopes: readonly string[];\n /** Decides each presented token. Built for the same issuer, resource and scopes. */\n readonly verifier: IAccessTokenVerifier;\n /** Proxy addresses whose `X-Forwarded-For` is believed. Absent: the header is ignored. */\n readonly trustedProxies?: readonly string[];\n /** Receives one content-free record per refused request. */\n readonly audit?: (record: IMcpRemoteAuditRecord) => void;\n}\n\nexport interface IMcpRemoteGate {\n readonly endpointPath: string;\n /** Resolves true when the request may reach MCP; otherwise the response has been written. */\n admit(req: IncomingMessage, res: ServerResponse): Promise<boolean>;\n}\n\nconst LOOPBACK_RANGES = new BlockList();\nLOOPBACK_RANGES.addSubnet('127.0.0.0', 8, 'ipv4');\nLOOPBACK_RANGES.addAddress('::1', 'ipv6');\nconst PRIVATE_RANGES = new BlockList();\nPRIVATE_RANGES.addSubnet('10.0.0.0', 8, 'ipv4');\nPRIVATE_RANGES.addSubnet('172.16.0.0', 12, 'ipv4');\nPRIVATE_RANGES.addSubnet('192.168.0.0', 16, 'ipv4');\nPRIVATE_RANGES.addSubnet('169.254.0.0', 16, 'ipv4');\nPRIVATE_RANGES.addSubnet('100.64.0.0', 10, 'ipv4');\nPRIVATE_RANGES.addSubnet('fc00::', 7, 'ipv6');\nPRIVATE_RANGES.addSubnet('fe80::', 10, 'ipv6');\n\n/** A literal address in one spelling: IPv4-mapped IPv6 folded to IPv4, IPv6 lowercased. */\nfunction normalizeAddress(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const text = value.trim().toLowerCase();\n const mapped = text.startsWith('::ffff:') ? text.slice('::ffff:'.length) : undefined;\n if (mapped !== undefined && isIP(mapped) === 4) return mapped;\n return isIP(text) === 0 ? undefined : text;\n}\n\nfunction classifyAddress(address: string | undefined): TMcpRemoteAddressClass {\n if (address === undefined) return 'unknown';\n const family = isIP(address) === 4 ? 'ipv4' : 'ipv6';\n if (LOOPBACK_RANGES.check(address, family)) return 'loopback';\n if (PRIVATE_RANGES.check(address, family)) return 'private';\n return 'public';\n}\n\nfunction parsePublicUrl(publicUrl: string): URL {\n let url: URL;\n try {\n url = new URL(publicUrl);\n } catch {\n throw new Error('MCP HTTP public URL must be an absolute https URL');\n }\n if (url.protocol !== 'https:') throw new Error('MCP HTTP public URL must use https');\n if (url.username !== '' || url.password !== '' || url.search !== '' || url.hash !== '') {\n throw new Error('MCP HTTP public URL must not carry credentials, a query or a fragment');\n }\n return url;\n}\n\ninterface IFailureWindow {\n count: number;\n startedAt: number;\n}\n\n/** Counts failures per address in fixed windows. Only failures are ever recorded here. */\nfunction createFailureCounter(now: () => number) {\n const windows = new Map<string, IFailureWindow>();\n return {\n /** Records one failure; answers whether the address is now over budget, and for how long. */\n fail(address: string): { throttled: boolean; retryAfterSeconds: number } {\n const at = now();\n let entry = windows.get(address);\n if (entry === undefined || at - entry.startedAt >= FAILURE_WINDOW_MS) {\n windows.delete(address);\n if (windows.size >= MAX_TRACKED_ADDRESSES) {\n for (const [key, value] of windows) {\n if (at - value.startedAt >= FAILURE_WINDOW_MS) windows.delete(key);\n }\n const oldest = windows.keys().next();\n if (windows.size >= MAX_TRACKED_ADDRESSES && oldest.done !== true) {\n windows.delete(oldest.value);\n }\n }\n entry = { count: 0, startedAt: at };\n windows.set(address, entry);\n }\n entry.count += 1;\n const remainingMs = FAILURE_WINDOW_MS - (at - entry.startedAt);\n return {\n throttled: entry.count > MAX_FAILURES_PER_WINDOW,\n retryAfterSeconds: Math.max(1, Math.ceil(remainingMs / 1000)),\n };\n },\n };\n}\n\n/**\n * Builds the gate. Throws when the configuration cannot describe a safe resource server: a public\n * URL that is not plain `https`, a non-`https` issuer, no scope or a scope a challenge cannot quote,\n * or a trusted proxy that is not a literal address.\n */\nexport function createMcpRemoteGate(\n authorization: IMcpRemoteAuthorization,\n now: () => number = () => performance.now(),\n): IMcpRemoteGate {\n const url = parsePublicUrl(authorization.publicUrl);\n let issuer: URL;\n try {\n issuer = new URL(authorization.issuer);\n } catch {\n throw new Error('MCP HTTP authorization issuer must be an absolute https URL');\n }\n if (issuer.protocol !== 'https:') throw new Error('MCP HTTP authorization issuer must use https');\n if (\n authorization.scopes.length === 0 ||\n !authorization.scopes.every((scope) => SCOPE_TOKEN.test(scope))\n ) {\n throw new Error('MCP HTTP authorization needs at least one valid scope');\n }\n const trustedProxies = new Set<string>();\n for (const proxy of authorization.trustedProxies ?? []) {\n const address = normalizeAddress(proxy);\n if (address === undefined)\n throw new Error('MCP HTTP trusted proxy must be a literal IP address');\n trustedProxies.add(address);\n }\n\n const endpointPath = url.pathname;\n // RFC 9728 §3.1: the well-known segment goes between the host and the resource's path, with a\n // lone terminating slash dropped.\n const wellKnownPath = `${WELL_KNOWN_PREFIX}${endpointPath === '/' ? '' : endpointPath}`;\n const resourceMetadataUrl = `${url.origin}${wellKnownPath}`;\n const expectedHosts = new Set([url.host]);\n if (url.port === '') expectedHosts.add(`${url.hostname}:443`);\n const metadata = JSON.stringify({\n resource: authorization.publicUrl,\n authorization_servers: [authorization.issuer],\n scopes_supported: [...authorization.scopes],\n bearer_methods_supported: ['header'],\n });\n const scopeList = authorization.scopes.join(' ');\n const challenges = {\n missing: `Bearer resource_metadata=\"${resourceMetadataUrl}\"`,\n invalid: `Bearer error=\"invalid_token\", resource_metadata=\"${resourceMetadataUrl}\"`,\n scope: `Bearer error=\"insufficient_scope\", scope=\"${scopeList}\", resource_metadata=\"${resourceMetadataUrl}\"`,\n };\n const failures = createFailureCounter(now);\n\n function remoteAddress(req: IncomingMessage): string | undefined {\n const peer = normalizeAddress(req.socket.remoteAddress);\n if (peer === undefined || !trustedProxies.has(peer)) return peer;\n const header = req.headers['x-forwarded-for'];\n const hops = (Array.isArray(header) ? header.join(',') : (header ?? '')).split(',');\n // Walk from the proxy inward: the nearest hop not itself a trusted proxy is the client, as the\n // trusted proxy saw it. Hops further left were written by the client and are not believed.\n for (let index = hops.length - 1; index >= 0; index -= 1) {\n const hop = normalizeAddress(hops[index]);\n if (hop === undefined) return peer;\n if (!trustedProxies.has(hop)) return hop;\n }\n return peer;\n }\n\n function refuse(req: IncomingMessage, res: ServerResponse, refusal: TMcpRemoteRefusal): void {\n const address = remoteAddress(req);\n const remote = classifyAddress(address);\n if (refusal === 'keys-unavailable') {\n // The issuer, not the peer, failed: not counted against the peer, and not a token verdict.\n authorization.audit?.({ refusal, remote, throttled: false });\n res.writeHead(503).end();\n return;\n }\n const budget = failures.fail(address ?? 'unknown');\n authorization.audit?.({ refusal, remote, throttled: budget.throttled });\n if (budget.throttled) {\n res.writeHead(429, { 'Retry-After': String(budget.retryAfterSeconds) }).end();\n } else if (refusal === 'missing-scope') {\n res.writeHead(403, { 'WWW-Authenticate': challenges.scope }).end();\n } else {\n const challenge = refusal === 'missing-token' ? challenges.missing : challenges.invalid;\n res.writeHead(401, { 'WWW-Authenticate': challenge }).end();\n }\n }\n\n return {\n endpointPath,\n async admit(req, res) {\n const host = req.headers.host?.toLowerCase();\n const origin = req.headers.origin;\n if (\n host === undefined ||\n !expectedHosts.has(host) ||\n (origin !== undefined && origin !== url.origin)\n ) {\n res.writeHead(403).end();\n return false;\n }\n if (req.url === wellKnownPath) {\n if (req.method !== 'GET') {\n res.writeHead(405, { Allow: 'GET' }).end();\n return false;\n }\n res.writeHead(200, { 'Content-Type': 'application/json' }).end(metadata);\n return false;\n }\n if (req.url !== endpointPath) {\n res.writeHead(404).end();\n return false;\n }\n const token = bearerCredential(req.headers.authorization);\n if (token === undefined) {\n refuse(req, res, 'missing-token');\n return false;\n }\n const verdict = await authorization.verifier.verify(token);\n if (verdict.admitted) return true;\n refuse(req, res, verdict.refusal);\n return false;\n },\n };\n}\n","/**\n * Streamable HTTP carriers for one process-owned MCP session: an authenticated loopback host\n * admitted by a minted bearer, and a remote host admitted by OAuth access tokens. Both share one\n * listener lifecycle and one stateless MCP handler; they differ only in the gate in front of it.\n */\nimport { createServer } from 'node:http';\nimport { isIP } from 'node:net';\n\nimport { toNodeHandler } from '@modelcontextprotocol/node';\nimport { createMcpHandler, Server } from '@modelcontextprotocol/server';\nimport {\n bearerCredential,\n credentialMatches,\n mintTransportToken,\n} from '@robota-sdk/agent-transport/node';\n\nimport { createMcpRemoteGate } from './mcp-remote-authorization.js';\nimport { invokeMcpTool, readCatalog, resolveSubmitTool } from './mcp-tool-surface.js';\n\nimport type { IMcpRemoteAuthorization } from './mcp-remote-authorization.js';\nimport type { IMcpTransportSession } from './mcp-session.js';\nimport type { IMcpSubmitToolIdentity } from './mcp-tool-surface.js';\nimport type { Tool as ModernTool } from '@modelcontextprotocol/server';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\nconst LOOPBACK = '127.0.0.1';\nconst MAX_BODY_BYTES = 1024 * 1024;\nconst MAX_ACTIVE_REQUESTS = 16;\nconst MAX_CONNECTIONS = 32;\nconst HEADERS_TIMEOUT_MS = 5000;\nconst REQUEST_TIMEOUT_MS = 15000;\nconst CLOSE_TIMEOUT_MS = 5000;\n\nexport interface IMcpHttpHostOptions {\n name: string;\n version: string;\n session: IMcpTransportSession;\n /** Defaults to an OS-assigned port. */\n port?: number;\n /** Accepted only for numeric IPv4 loopback; provided for explicit bind-refusal checks. */\n host?: string;\n /** Host-owned identity for the submission extension. */\n submitTool?: IMcpSubmitToolIdentity;\n}\n\nexport interface IMcpHttpHost {\n start(): Promise<{ url: string; token: string }>;\n stop(): Promise<void>;\n waitForClose(): Promise<void>;\n}\n\nexport interface IMcpRemoteHttpHostOptions {\n name: string;\n version: string;\n session: IMcpTransportSession;\n /** Defaults to an OS-assigned port. */\n port?: number;\n /** Literal IP address to bind. Defaults to `127.0.0.1`, for a proxy on the same machine. */\n host?: string;\n /** Who may reach the session, decided from OAuth access tokens. Required: there is no open mode. */\n authorization: IMcpRemoteAuthorization;\n /** Host-owned identity for the submission extension. */\n submitTool?: IMcpSubmitToolIdentity;\n}\n\nexport interface IMcpRemoteHttpHost {\n /** Resolves with the address actually bound and the public URL clients are told to use. */\n start(): Promise<{ listening: string; url: string }>;\n stop(): Promise<void>;\n waitForClose(): Promise<void>;\n}\n\n/** Decides one request before its body is read. False means the response has been written. */\ntype TRequestGate = (\n req: IncomingMessage,\n res: ServerResponse,\n port: number,\n) => boolean | Promise<boolean>;\n\ninterface IHostCore {\n start(): Promise<AddressInfo>;\n stop(): Promise<void>;\n waitForClose(): Promise<void>;\n}\n\nfunction validatePort(port: number | undefined): void {\n if (port !== undefined && (!Number.isInteger(port) || port < 0 || port > 65535)) {\n throw new Error('MCP HTTP port must be an integer in 0..65535');\n }\n}\n\nfunction createHostCore(\n options: Omit<IMcpHttpHostOptions, 'host'>,\n bindHost: string,\n gate: TRequestGate,\n): IHostCore {\n const submitTool = resolveSubmitTool(options.submitTool);\n let listener: ReturnType<typeof createServer> | undefined;\n let handler: ReturnType<typeof createMcpHandler> | undefined;\n let stopPromise: Promise<void> | undefined;\n let startCalled = false;\n let stoppingRequested = false;\n let active = 0;\n let resolveClose: (() => void) | undefined;\n let rejectClose: ((error: Error) => void) | undefined;\n const closed = new Promise<void>((resolve, reject) => {\n resolveClose = resolve;\n rejectClose = reject;\n });\n void closed.catch(() => undefined);\n\n return {\n async start() {\n if (startCalled || stopPromise) throw new Error('MCP HTTP host already started');\n startCalled = true;\n await readCatalog(options.session, submitTool.name);\n if (stoppingRequested) throw new Error('MCP HTTP host stopped during startup');\n handler = createMcpHandler(\n () => {\n const server = new Server(\n { name: options.name, version: options.version },\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler('tools/list', async () => ({\n // The v1 catalog validator returns a wider schema type than SDK v2's JSON-only Tool.\n // Normalize at this SDK boundary while keeping one canonical runtime catalog owner.\n tools: JSON.parse(\n JSON.stringify([\n ...(await readCatalog(options.session, submitTool.name)),\n submitTool,\n ]),\n ) as ModernTool[],\n }));\n server.setRequestHandler('tools/call', async (request, ctx) =>\n invokeMcpTool(\n options.session,\n request.params.name,\n request.params.arguments ?? {},\n ctx.mcpReq.signal,\n submitTool.name,\n ),\n );\n return server;\n },\n { maxSubscriptions: 0 },\n );\n const nodeHandler = toNodeHandler(handler);\n const serve = (req: IncomingMessage, res: ServerResponse): void => {\n if (active >= MAX_ACTIVE_REQUESTS) {\n res.writeHead(503).end();\n return;\n }\n active += 1;\n void (async () => {\n try {\n let parsedBody: unknown;\n if (req.method === 'POST') {\n const declared = Number(req.headers['content-length']);\n if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {\n res.writeHead(413).end();\n return;\n }\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array);\n size += bytes.length;\n if (size > MAX_BODY_BYTES) {\n res.writeHead(413).end();\n req.destroy();\n return;\n }\n chunks.push(bytes);\n }\n try {\n parsedBody = JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown;\n } catch {\n res.writeHead(400).end();\n return;\n }\n }\n await nodeHandler(req, res, parsedBody);\n } catch {\n if (!res.headersSent) res.writeHead(500).end();\n else res.destroy();\n } finally {\n active -= 1;\n }\n })();\n };\n const server = createServer({ maxHeaderSize: 16 * 1024 }, (req, res) => {\n const address = server.address();\n if (!address || typeof address === 'string') {\n res.writeHead(503).end();\n return;\n }\n const verdict = gate(req, res, address.port);\n if (verdict === true) {\n serve(req, res);\n } else if (verdict !== false) {\n verdict.then(\n (admitted) => {\n if (admitted) serve(req, res);\n },\n () => {\n if (!res.headersSent) res.writeHead(500).end();\n else res.destroy();\n },\n );\n }\n });\n server.maxConnections = MAX_CONNECTIONS;\n server.headersTimeout = HEADERS_TIMEOUT_MS;\n server.requestTimeout = REQUEST_TIMEOUT_MS;\n server.on('error', (error) => rejectClose?.(error));\n server.on('close', () => resolveClose?.());\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 0, bindHost, () => {\n server.off('error', reject);\n resolve();\n });\n });\n } catch (error) {\n await handler.close();\n throw error;\n }\n if (stoppingRequested) {\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await handler.close();\n throw new Error('MCP HTTP host stopped during startup');\n }\n listener = server;\n return server.address() as AddressInfo;\n },\n stop() {\n if (stopPromise) return stopPromise;\n stoppingRequested = true;\n stopPromise = (async () => {\n const currentHandler = handler;\n const currentListener = listener;\n if (!currentListener) {\n await currentHandler?.close();\n resolveClose?.();\n return;\n }\n const close = new Promise<void>((resolve, reject) => {\n currentListener.close((error) => (error ? reject(error) : resolve()));\n currentListener.closeAllConnections();\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n Promise.all([currentHandler?.close(), close]).then(() => undefined),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error('MCP HTTP close timed out')),\n CLOSE_TIMEOUT_MS,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n })();\n return stopPromise;\n },\n waitForClose() {\n return closed;\n },\n };\n}\n\nexport function createMcpHttpHost(options: IMcpHttpHostOptions): IMcpHttpHost {\n if (options.host !== undefined && options.host !== LOOPBACK) {\n throw new Error('MCP HTTP host must bind numeric IPv4 loopback');\n }\n validatePort(options.port);\n\n const token = mintTransportToken();\n const core = createHostCore(options, LOOPBACK, (req, res, port) => {\n const expectedHost = `${LOOPBACK}:${port}`;\n const expectedOrigin = `http://${expectedHost}`;\n if (\n req.url !== '/mcp' ||\n req.headers.host !== expectedHost ||\n (req.headers.origin !== undefined && req.headers.origin !== expectedOrigin)\n ) {\n res.writeHead(403).end();\n return false;\n }\n const authorization = req.headers.authorization;\n if (\n typeof authorization !== 'string' ||\n !credentialMatches(token, bearerCredential(authorization))\n ) {\n res.writeHead(401).end();\n return false;\n }\n return true;\n });\n\n return {\n async start() {\n const address = await core.start();\n return { url: `http://${LOOPBACK}:${address.port}/mcp`, token };\n },\n stop: core.stop,\n waitForClose: core.waitForClose,\n };\n}\n\n/**\n * A host reachable beyond loopback. It never mints or accepts the loopback bearer: every request\n * is admitted by an OAuth access token checked against the injected verifier.\n */\nexport function createMcpRemoteHttpHost(options: IMcpRemoteHttpHostOptions): IMcpRemoteHttpHost {\n const bindHost = options.host ?? LOOPBACK;\n if (isIP(bindHost) === 0) throw new Error('MCP HTTP host must bind a literal IP address');\n validatePort(options.port);\n const gate = createMcpRemoteGate(options.authorization);\n const core = createHostCore(options, bindHost, (req, res) => gate.admit(req, res));\n\n return {\n async start() {\n const address = await core.start();\n const host = address.family === 'IPv6' ? `[${address.address}]` : address.address;\n return { listening: `${host}:${address.port}`, url: options.authorization.publicUrl };\n },\n stop: core.stop,\n waitForClose: core.waitForClose,\n };\n}\n"],"mappings":"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"}