From 5e6a1c7882d69c41a6a41d12985b19958f169299 Mon Sep 17 00:00:00 2001 From: shreyash0502 Date: Tue, 30 Jun 2026 12:46:41 +0530 Subject: [PATCH 1/2] feat: add MCP input gate for UiPath Sites plugin --- .../uipath-sites/.codex-plugin/plugin.json | 8 +- plugins/uipath-sites/.mcp.json | 12 + plugins/uipath-sites/README.md | 19 +- .../mcp/uipath-sites-inputs/server.cjs | 362 ++++++++++++++++++ .../skills/uipath-coded-sites/SKILL.md | 6 +- .../references/codex-overrides.md | 7 +- 6 files changed, 401 insertions(+), 13 deletions(-) create mode 100644 plugins/uipath-sites/.mcp.json create mode 100644 plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs diff --git a/plugins/uipath-sites/.codex-plugin/plugin.json b/plugins/uipath-sites/.codex-plugin/plugin.json index fcea64bbe..df4057360 100644 --- a/plugins/uipath-sites/.codex-plugin/plugin.json +++ b/plugins/uipath-sites/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "uipath-sites", - "version": "0.1.0", - "description": "Build polished UiPath coded apps and deploy them with uip codedapp.", + "version": "0.2.0", + "description": "Build polished UiPath coded apps with structured setup input and deploy them with uip codedapp.", "author": { "name": "UiPath", "url": "https://www.uipath.com" @@ -17,13 +17,15 @@ "codex" ], "skills": "./skills/", + "mcpServers": "./.mcp.json", "interface": { "displayName": "UiPath Sites", "shortDescription": "Build polished UiPath coded apps.", - "longDescription": "UiPath Sites helps users generate polished UiPath coded apps instead of Sites-hosted apps. It routes matched UiPath app requests to the coded-app workflow, applies frontend-design guidance for UI quality, and keeps build, package, publish, and deploy aligned with uip codedapp.", + "longDescription": "UiPath Sites helps users generate polished UiPath coded apps with a structured setup input gate instead of Sites-hosted apps. It routes matched UiPath app requests to the coded-app workflow, applies frontend-design guidance for UI quality, and keeps build, package, publish, and deploy aligned with uip codedapp.", "developerName": "UiPath", "category": "Productivity", "capabilities": [ + "Interactive", "Write", "Run" ], diff --git a/plugins/uipath-sites/.mcp.json b/plugins/uipath-sites/.mcp.json new file mode 100644 index 000000000..ebcceff72 --- /dev/null +++ b/plugins/uipath-sites/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "uipath-sites-inputs": { + "command": "node", + "args": [ + "./mcp/uipath-sites-inputs/server.cjs" + ], + "startup_timeout_sec": 10, + "tool_timeout_sec": 300 + } + } +} diff --git a/plugins/uipath-sites/README.md b/plugins/uipath-sites/README.md index f2da8af1d..a6733e809 100644 --- a/plugins/uipath-sites/README.md +++ b/plugins/uipath-sites/README.md @@ -4,9 +4,12 @@ UiPath Sites is a Codex companion plugin for building UiPath coded apps from `@U It keeps the app-building experience, but for matched UiPath requests it produces a UiPath coded app instead of a Sites-hosted app. Build, package, publish, and deploy are handled through the standard `uipath-coded-apps` workflow and `uip codedapp`. +This branch includes an experimental bundled MCP server for the setup input gate. When the Codex runtime supports MCP form elicitation, the plugin can collect app type, environment, app name, client ID, organization, tenant, and folder in a structured form before generation starts. If the runtime does not support the form, the skill falls back to normal Codex chat input. + ## What It Does - Routes `@UiPath Sites` and matched `@Sites` prompts into UiPath coded-app generation. +- Provides an optional MCP form input gate for required UiPath setup values. - Keeps the final deployment target as UiPath, not Sites hosting. - Applies frontend-design guidance for visual polish only. - Ensures the UiPath CLI and UiPath Codex skills are available at session start. @@ -24,16 +27,18 @@ Build an Action Center task inbox for filtering work, reviewing task details, an 1. User starts with `@UiPath Sites`, or an `@Sites` prompt that mentions UiPath or coded-app intent. 2. The plugin routes the request to the UiPath coded-app path. -3. Codex builds a static Vite + React coded app. -4. The app uses `@uipath/uipath-typescript` and `uipath.json`. -5. Functional app behavior follows `uipath-coded-apps` and its relevant references. -6. Frontend overrides apply only visual presentation and must not replace coded-app functional patterns. -7. Normal Sites hosting, Cloudflare Worker output, D1, R2, and SIWC are skipped. -8. The `uipath-coded-apps` skill handles local validation and `uip codedapp` pack, publish, and deploy. +3. The bundled MCP input tool collects required setup values when MCP form elicitation is available; otherwise Codex asks in chat. +4. Codex builds a static Vite + React coded app. +5. The app uses `@uipath/uipath-typescript` and `uipath.json`. +6. Functional app behavior follows `uipath-coded-apps` and its relevant references. +7. Frontend overrides apply only visual presentation and must not replace coded-app functional patterns. +8. Normal Sites hosting, Cloudflare Worker output, D1, R2, and SIWC are skipped. +9. The `uipath-coded-apps` skill handles local validation and `uip codedapp` pack, publish, and deploy. ## Ownership - UiPath Sites owns routing, Sites-hosting suppression, and the visual companion layer. +- The bundled MCP server owns optional structured setup input collection only. - `uipath-coded-apps` owns coded-app structure, SDK usage, auth/config, scopes, service-specific references, runtime data handling, functional UI behavior, build, pack, publish, and deploy. - `frontend-design-overrides.md` owns visual presentation only and must not redefine functional UI mechanics. @@ -62,5 +67,5 @@ uip skills install --agent codex - `0.1.0`: preview release for internal/public testing. - `0.1.x`: metadata, docs, prompt, hook, and bug fixes. -- `0.2.0`: routing, bootstrap, or supported-app behavior changes. +- `0.2.0`: experimental bundled MCP setup input form. - `1.0.0`: stable release after marketplace validation and repeated successful demos. diff --git a/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs b/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs new file mode 100644 index 000000000..e6f57c069 --- /dev/null +++ b/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs @@ -0,0 +1,362 @@ +#!/usr/bin/env node +"use strict"; + +const readline = require("node:readline"); + +const SERVER_NAME = "uipath-sites-inputs"; +const SERVER_VERSION = "0.1.0"; +const DEFAULT_REDIRECT_URI = "http://localhost:5173"; +const DEFAULT_SCOPES_MESSAGE = + "Use the OAuth scopes required by the installed uipath-coded-apps skill for this app type and target services."; + +let nextRequestId = 1; +let clientSupportsFormElicitation = false; +const pendingClientRequests = new Map(); + +const inputSchema = { + type: "object", + additionalProperties: false, + properties: { + promptSummary: { + type: "string", + description: "Short summary of the app the user asked to build." + }, + oauthScopes: { + type: "string", + description: + "Exact OAuth scope string required by uipath-coded-apps for this app. The form will show it next to the client ID field." + }, + redirectUri: { + type: "string", + description: "Redirect URI expected by the generated coded app.", + default: DEFAULT_REDIRECT_URI + }, + knownInputs: { + type: "object", + additionalProperties: true, + description: + "Values already supplied by the user. These are used as form defaults and are returned unchanged unless the user edits them." + } + } +}; + +function setupInputSchema(knownInputs = {}, redirectUri = DEFAULT_REDIRECT_URI, oauthScopes = DEFAULT_SCOPES_MESSAGE) { + return { + type: "object", + additionalProperties: false, + required: [ + "appType", + "environment", + "appName", + "externalClientId", + "organizationName", + "tenantName", + "deploymentFolder" + ], + properties: { + appType: { + type: "string", + title: "App type", + description: "Choose the UiPath coded app type.", + enum: ["Coded Web App", "Coded Action App"], + default: knownInputs.appType || "Coded Web App" + }, + environment: { + type: "string", + title: "UiPath environment", + description: "Target UiPath environment.", + enum: ["cloud", "staging", "alpha"], + default: knownInputs.environment || "cloud" + }, + appName: { + type: "string", + title: "App name", + description: "UiPath coded app package/display name.", + default: knownInputs.appName || "" + }, + externalClientId: { + type: "string", + title: "External client ID", + description: + `OAuth external application client ID. It must be configured with redirect URI ${redirectUri} and scopes: ${oauthScopes}`, + default: knownInputs.externalClientId || "" + }, + organizationName: { + type: "string", + title: "Organization name", + description: "UiPath organization name.", + default: knownInputs.organizationName || knownInputs.orgName || "" + }, + tenantName: { + type: "string", + title: "Tenant name", + description: "UiPath tenant name.", + default: knownInputs.tenantName || "DefaultTenant" + }, + deploymentFolder: { + type: "string", + title: "Deployment folder", + description: "Folder key or folder name to resolve for publish/deploy.", + default: knownInputs.deploymentFolder || knownInputs.folderName || knownInputs.folderKey || "" + } + } + }; +} + +function send(message) { + process.stdout.write(`${JSON.stringify(message)}\n`); +} + +function result(id, value) { + send({ jsonrpc: "2.0", id, result: value }); +} + +function error(id, code, message, data) { + send({ jsonrpc: "2.0", id, error: { code, message, data } }); +} + +function sendClientRequest(method, params, timeoutMs = 240000) { + const id = `uipath-sites-${nextRequestId++}`; + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + pendingClientRequests.delete(id); + reject(new Error("Timed out waiting for the MCP client to complete the input form.")); + }, timeoutMs); + + pendingClientRequests.set(id, { + resolve: (value) => { + clearTimeout(timeout); + resolve(value); + }, + reject: (reason) => { + clearTimeout(timeout); + reject(reason); + } + }); + + send({ jsonrpc: "2.0", id, method, params }); + }); +} + +function textContent(text) { + return [{ type: "text", text }]; +} + +function fallbackResult(reason, args = {}) { + const redirectUri = args.redirectUri || DEFAULT_REDIRECT_URI; + const oauthScopes = args.oauthScopes || DEFAULT_SCOPES_MESSAGE; + const fallbackPrompt = [ + "MCP form input is unavailable for this Codex runtime.", + "Ask the user for these setup inputs before writing files or running commands:", + "1. App type: Coded Web App or Coded Action App", + "2. UiPath environment: cloud, staging, or alpha", + "3. App name", + "4. External client ID", + ` - Required redirect URI: ${redirectUri}`, + ` - Required scopes: ${oauthScopes}`, + "5. Organization name", + "6. Tenant name", + "7. Deployment folder key or folder name" + ].join("\n"); + + return { + content: textContent(`${reason}\n\n${fallbackPrompt}`), + structuredContent: { + status: "fallback_required", + reason, + redirectUri, + oauthScopes, + fallbackPrompt + }, + isError: false + }; +} + +function normalizedInputs(content, args) { + const redirectUri = args.redirectUri || DEFAULT_REDIRECT_URI; + const oauthScopes = args.oauthScopes || DEFAULT_SCOPES_MESSAGE; + + return { + appType: content.appType, + environment: content.environment, + appName: content.appName, + externalClientId: content.externalClientId, + organizationName: content.organizationName, + tenantName: content.tenantName, + deploymentFolder: content.deploymentFolder, + redirectUri, + oauthScopes + }; +} + +async function collectUiPathCodedAppInputs(args = {}) { + if (!clientSupportsFormElicitation) { + return fallbackResult("The MCP client did not advertise form elicitation support.", args); + } + + const redirectUri = args.redirectUri || DEFAULT_REDIRECT_URI; + const oauthScopes = args.oauthScopes || DEFAULT_SCOPES_MESSAGE; + const knownInputs = args.knownInputs && typeof args.knownInputs === "object" ? args.knownInputs : {}; + const appSummary = args.promptSummary ? `\n\nApp request: ${args.promptSummary}` : ""; + + let elicitationResult; + try { + elicitationResult = await sendClientRequest("elicitation/create", { + mode: "form", + message: + "Provide UiPath coded-app setup inputs. The client ID must already be configured with the redirect URI and scopes shown in the form." + + appSummary, + requestedSchema: setupInputSchema(knownInputs, redirectUri, oauthScopes) + }); + } catch (cause) { + return fallbackResult(cause.message || "The MCP input form could not be completed.", args); + } + + if (!elicitationResult || elicitationResult.action !== "accept" || !elicitationResult.content) { + return fallbackResult("The MCP input form was cancelled or declined.", args); + } + + const inputs = normalizedInputs(elicitationResult.content, args); + + return { + content: textContent( + "Collected UiPath coded-app setup inputs. Use structuredContent.inputs as the initial setup gate result before generating files." + ), + structuredContent: { + status: "complete", + inputs, + nextSteps: [ + "Continue with uipath-coded-apps instructions.", + "Use these inputs consistently in uipath.json, local verification, pack, publish, and deploy.", + "Do not ask the user for these same setup values again unless validation fails." + ] + }, + isError: false + }; +} + +function listTools() { + return { + tools: [ + { + name: "collect_uipath_coded_app_inputs", + title: "Collect UiPath coded-app setup inputs", + description: + "Collect the mandatory UiPath Sites setup inputs through MCP form elicitation before coded-app generation starts. Falls back with a normal chat prompt if form elicitation is unavailable.", + inputSchema, + outputSchema: { + type: "object", + additionalProperties: true, + properties: { + status: { type: "string", enum: ["complete", "fallback_required"] }, + inputs: { type: "object", additionalProperties: true }, + fallbackPrompt: { type: "string" } + } + }, + annotations: { + title: "Collect UiPath setup inputs", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: false, + openWorldHint: false + } + } + ] + }; +} + +async function handleRequest(message) { + const { id, method, params } = message; + + switch (method) { + case "initialize": { + const clientCapabilities = params && params.capabilities ? params.capabilities : {}; + clientSupportsFormElicitation = Boolean( + clientCapabilities.elicitation && clientCapabilities.elicitation.form + ); + + result(id, { + protocolVersion: params && params.protocolVersion ? params.protocolVersion : "2025-06-18", + capabilities: { + tools: {} + }, + serverInfo: { + name: SERVER_NAME, + version: SERVER_VERSION + }, + instructions: + "Use collect_uipath_coded_app_inputs at the start of a matched UiPath Sites coded-app flow. If it returns fallback_required, ask the fallbackPrompt in normal Codex chat." + }); + return; + } + case "ping": + result(id, {}); + return; + case "tools/list": + result(id, listTools()); + return; + case "tools/call": { + if (!params || params.name !== "collect_uipath_coded_app_inputs") { + error(id, -32602, `Unknown tool: ${params && params.name ? params.name : ""}`); + return; + } + + const toolResult = await collectUiPathCodedAppInputs(params.arguments || {}); + result(id, toolResult); + return; + } + default: + error(id, -32601, `Method not found: ${method}`); + } +} + +function handleClientResponse(message) { + const pending = pendingClientRequests.get(message.id); + if (!pending) { + return false; + } + + pendingClientRequests.delete(message.id); + if (message.error) { + pending.reject(new Error(message.error.message || "MCP client returned an error.")); + } else { + pending.resolve(message.result); + } + return true; +} + +const rl = readline.createInterface({ + input: process.stdin, + crlfDelay: Infinity +}); + +rl.on("line", (line) => { + if (!line.trim()) { + return; + } + + let message; + try { + message = JSON.parse(line); + } catch (cause) { + error(null, -32700, "Parse error", { cause: cause.message }); + return; + } + + if (message.id !== undefined && !message.method && handleClientResponse(message)) { + return; + } + + if (!message.method) { + return; + } + + if (message.id === undefined || message.id === null) { + return; + } + + handleRequest(message).catch((cause) => { + error(message.id, -32603, cause.message || "Internal error"); + }); +}); diff --git a/plugins/uipath-sites/skills/uipath-coded-sites/SKILL.md b/plugins/uipath-sites/skills/uipath-coded-sites/SKILL.md index 784586460..7d8677916 100644 --- a/plugins/uipath-sites/skills/uipath-coded-sites/SKILL.md +++ b/plugins/uipath-sites/skills/uipath-coded-sites/SKILL.md @@ -10,7 +10,7 @@ Use this skill when the user wants a UiPath coded app through either entry point - `@UiPath Sites` - `@Sites` with UiPath or coded-app intent -This skill is the router/orchestrator only. Keep the detailed behavior in the reference files and the installed `uipath-coded-apps` skill. +This skill is the router/orchestrator only. Keep the detailed behavior in the reference files, the bundled MCP input tool, and the installed `uipath-coded-apps` skill. When this skill matches, use it as the only app-generation routing skill. Do not pair it with external design skills such as `interface-design`, `ui-ux-pro-max`, or `frontend-design`; load this skill's [references/frontend-design-overrides.md](references/frontend-design-overrides.md) instead. @@ -18,6 +18,7 @@ When this skill matches, use it as the only app-generation routing skill. Do not - This `uipath-coded-sites` skill owns routing, reference orchestration, and skill boundaries only. - [references/codex-overrides.md](references/codex-overrides.md) owns Codex behavior: mandatory initial input collection, Claude-specific question-tool translation, localhost usage, login mismatch handling, deploy command override, and skill boundary reinforcement. +- The bundled `uipath-sites-inputs` MCP server owns optional structured setup input collection for the mandatory initial gate. It does not own app generation, SDK behavior, auth semantics, build, publish, or deploy. - [references/uipath-typescript.md](references/uipath-typescript.md) owns Sites-to-coded-app compatibility: static Vite/React shape, relative assets, `base: './'`, `getAppBase()`, and no Sites hosting output. - The installed `uipath-coded-apps` skill owns all coded-app functional behavior: app type, project structure, SDK usage, auth/config, OAuth scopes, service-specific references, runtime data handling, functional UI correctness, local validation, build, pack, publish, and deploy. - [references/frontend-design-overrides.md](references/frontend-design-overrides.md) owns visual presentation only: professional app shell, typography, palette, spacing, hierarchy, interaction polish, responsive styling, and accessibility styling. @@ -26,7 +27,7 @@ When this skill matches, use it as the only app-generation routing skill. Do not For every matched request, load and apply these sources in order before generating files or running commands: -1. [references/codex-overrides.md](references/codex-overrides.md) for Codex behavior, mandatory initial input collection, local-run rules, login mismatch handling, deploy command behavior, and skill boundaries. +1. [references/codex-overrides.md](references/codex-overrides.md) for Codex behavior, mandatory initial input collection, MCP input tool preference, local-run rules, login mismatch handling, deploy command behavior, and skill boundaries. 2. The installed `uipath-coded-apps` skill and all relevant `uipath-coded-apps/references/*` files for app type, service APIs, functional UI behavior, SDK patterns, local validation, and deploy flow. 3. [references/uipath-typescript.md](references/uipath-typescript.md) for Sites-to-UiPath-coded-app compatibility rules. 4. [references/frontend-design-overrides.md](references/frontend-design-overrides.md) for visual presentation guidance only. @@ -37,6 +38,7 @@ If these sources conflict, use this ownership rule: - `uipath-coded-apps` wins for coded-app technical and functional behavior. - `codex-overrides.md` wins for Codex-specific interaction and command adaptation. +- the bundled MCP input tool is only an input-collection transport; it must follow the input requirements from `codex-overrides.md` and `uipath-coded-apps`. - `uipath-typescript.md` wins for Sites-to-coded-app compatibility. - `frontend-design-overrides.md` may style the UI, but must not replace functional patterns from `uipath-coded-apps`. diff --git a/plugins/uipath-sites/skills/uipath-coded-sites/references/codex-overrides.md b/plugins/uipath-sites/skills/uipath-coded-sites/references/codex-overrides.md index 9daab1c50..bc42a3b04 100644 --- a/plugins/uipath-sites/skills/uipath-coded-sites/references/codex-overrides.md +++ b/plugins/uipath-sites/skills/uipath-coded-sites/references/codex-overrides.md @@ -5,6 +5,11 @@ Use these rules only to adapt the upstream `uipath-coded-apps` skill to Codex. O ## Mandatory initial input gate - The initial setup gate is mandatory for every matched UiPath Sites request, including both `@UiPath Sites` and `@Sites` with UiPath coded-app intent. +- Prefer the bundled `uipath-sites-inputs` MCP tool `collect_uipath_coded_app_inputs` for this gate when it is available. +- When using the MCP input tool, pass any values already provided by the user as `knownInputs`, pass the app request as `promptSummary`, and pass the exact `redirectUri` and `oauthScopes` required by `uipath-coded-apps` when those are known. +- If the MCP tool returns `status: complete`, treat `structuredContent.inputs` as the answered initial setup gate and do not ask those same setup questions again. +- If the MCP tool returns `status: fallback_required`, ask the returned `fallbackPrompt` in normal Codex chat and wait for the user's reply before continuing. +- If the MCP tool is not available in the current runtime, fall back to `request_user_input` when exposed, otherwise normal Codex chat. - The first assistant response after activating this flow must ask for missing setup inputs, or state which setup inputs were already provided and ask only for the missing ones. - Collect all required setup inputs at the start of the coded-app workflow, before writing files, building, or launching the local app. - Do not defer required setup questions until just before local verification or deployment. @@ -20,7 +25,7 @@ Use these rules only to adapt the upstream `uipath-coded-apps` skill to Codex. O - If the user already supplied any of these values in the prompt, do not ask again for those values; ask only for the missing ones. - When asking for the client ID, include the redirect URI and OAuth scope requirements from `uipath-coded-apps` in the same prompt. - If the upstream skill says to use `AskUserQuestion` or any Claude-specific question tool, ask the user directly in normal Codex chat instead. -- If Codex exposes a structured input tool such as `request_user_input`, prefer that for the initial setup questions. +- If Codex exposes a structured input tool such as `request_user_input`, prefer that after the bundled MCP input tool path is unavailable. - If a structured input tool is not available in the current runtime, ask the same questions directly in normal Codex chat and wait for the user's reply before proceeding. - Do not skip required questions for app type, environment, app name, client ID, org, tenant, folder, or any other input the upstream skill requires. From e50e5c556199d6e4615d05cb492020973d740202 Mon Sep 17 00:00:00 2001 From: shreyash0502 Date: Tue, 30 Jun 2026 14:05:06 +0530 Subject: [PATCH 2/2] fix: tolerate MCP elicitation response variants --- .../mcp/uipath-sites-inputs/server.cjs | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs b/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs index e6f57c069..5cd59d5b8 100644 --- a/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs +++ b/plugins/uipath-sites/mcp/uipath-sites-inputs/server.cjs @@ -43,7 +43,6 @@ const inputSchema = { function setupInputSchema(knownInputs = {}, redirectUri = DEFAULT_REDIRECT_URI, oauthScopes = DEFAULT_SCOPES_MESSAGE) { return { type: "object", - additionalProperties: false, required: [ "appType", "environment", @@ -56,46 +55,39 @@ function setupInputSchema(knownInputs = {}, redirectUri = DEFAULT_REDIRECT_URI, properties: { appType: { type: "string", - title: "App type", description: "Choose the UiPath coded app type.", enum: ["Coded Web App", "Coded Action App"], default: knownInputs.appType || "Coded Web App" }, environment: { type: "string", - title: "UiPath environment", description: "Target UiPath environment.", enum: ["cloud", "staging", "alpha"], default: knownInputs.environment || "cloud" }, appName: { type: "string", - title: "App name", description: "UiPath coded app package/display name.", default: knownInputs.appName || "" }, externalClientId: { type: "string", - title: "External client ID", description: `OAuth external application client ID. It must be configured with redirect URI ${redirectUri} and scopes: ${oauthScopes}`, default: knownInputs.externalClientId || "" }, organizationName: { type: "string", - title: "Organization name", description: "UiPath organization name.", default: knownInputs.organizationName || knownInputs.orgName || "" }, tenantName: { type: "string", - title: "Tenant name", description: "UiPath tenant name.", default: knownInputs.tenantName || "DefaultTenant" }, deploymentFolder: { type: "string", - title: "Deployment folder", description: "Folder key or folder name to resolve for publish/deploy.", default: knownInputs.deploymentFolder || knownInputs.folderName || knownInputs.folderKey || "" } @@ -143,7 +135,7 @@ function textContent(text) { return [{ type: "text", text }]; } -function fallbackResult(reason, args = {}) { +function fallbackResult(reason, args = {}, diagnostic = {}) { const redirectUri = args.redirectUri || DEFAULT_REDIRECT_URI; const oauthScopes = args.oauthScopes || DEFAULT_SCOPES_MESSAGE; const fallbackPrompt = [ @@ -165,6 +157,7 @@ function fallbackResult(reason, args = {}) { structuredContent: { status: "fallback_required", reason, + diagnostic, redirectUri, oauthScopes, fallbackPrompt @@ -173,6 +166,42 @@ function fallbackResult(reason, args = {}) { }; } +function getElicitationData(elicitationResult) { + if (!elicitationResult || elicitationResult.action !== "accept") { + return null; + } + + // Wire-level MCP currently uses `content`, while some SDK helpers expose + // accepted form values as `data`. Accept both so client/server version + // differences do not force a fallback after a successful form submission. + if (elicitationResult.content && typeof elicitationResult.content === "object") { + return elicitationResult.content; + } + if (elicitationResult.data && typeof elicitationResult.data === "object") { + return elicitationResult.data; + } + return null; +} + +function resultDiagnostic(elicitationResult) { + if (!elicitationResult || typeof elicitationResult !== "object") { + return { resultType: typeof elicitationResult }; + } + + return { + action: elicitationResult.action || null, + topLevelKeys: Object.keys(elicitationResult), + contentKeys: + elicitationResult.content && typeof elicitationResult.content === "object" + ? Object.keys(elicitationResult.content) + : [], + dataKeys: + elicitationResult.data && typeof elicitationResult.data === "object" + ? Object.keys(elicitationResult.data) + : [] + }; +} + function normalizedInputs(content, args) { const redirectUri = args.redirectUri || DEFAULT_REDIRECT_URI; const oauthScopes = args.oauthScopes || DEFAULT_SCOPES_MESSAGE; @@ -213,11 +242,17 @@ async function collectUiPathCodedAppInputs(args = {}) { return fallbackResult(cause.message || "The MCP input form could not be completed.", args); } - if (!elicitationResult || elicitationResult.action !== "accept" || !elicitationResult.content) { - return fallbackResult("The MCP input form was cancelled or declined.", args); + const elicitationData = getElicitationData(elicitationResult); + + if (!elicitationData) { + return fallbackResult( + "The MCP input form was cancelled, declined, or returned no accepted form data.", + args, + resultDiagnostic(elicitationResult) + ); } - const inputs = normalizedInputs(elicitationResult.content, args); + const inputs = normalizedInputs(elicitationData, args); return { content: textContent(