Skip to content

Feature staging#18

Open
dadadadas111 wants to merge 15 commits intomasterfrom
feature-staging
Open

Feature staging#18
dadadadas111 wants to merge 15 commits intomasterfrom
feature-staging

Conversation

@dadadadas111
Copy link
Owner

@dadadadas111 dadadadas111 commented Mar 25, 2026

Summary by CodeRabbit

  • New Features

    • Subdomain-aware OAuth discovery and new subdomain-scoped OAuth endpoints (authorize, login, token, register).
    • Login page form action can be overridden.
    • Device widget adds a host bridge for integrated tool calls, theme and locale updates.
  • Bug Fixes / Improvements

    • WWW-Authenticate resource URLs now use computed subdomain origins.
    • Token endpoint CORS preflight handling.
    • Stronger validation and rounding for device-control commands.
  • Documentation

    • Clarified tool parameter descriptions (ranges/units) and added UI resource links for the device app.

…nx routing

Nginx rewrites {alias}.domain.com/* → /mcp/{alias}/* but auth/discovery
routes lived under /auth/{alias}/* and /.well-known/*, causing 404s for
all OAuth flows through subdomains.

Add McpAuthController under /mcp/:alias with matching OAuth + RFC 9728/8414
discovery routes, subdomain-aware metadata URLs, and configurable login
form action. Existing base-domain routes unchanged.
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds subdomain-aware OAuth discovery and endpoints: a URL builder, subdomain-specific discovery metadata methods, a new McpAuthController (authorize/login/token/register) mounted at mcp/:alias, login template hook for custom form action, MCP integration changes, tool metadata tweaks, and stricter device-control validation/rounding.

Changes

Cohort / File(s) Summary
URL Utilities
src/common/utils/url.utils.ts
Added buildSubdomainUrl(baseUrl, alias) to compute origin URLs that prefix hostnames with alias, skipping localhost/IPv4 hosts.
Discovery Service
src/auth/services/discovery.service.ts
Added getSubdomainResourceMetadata(alias) and getSubdomainAuthServerMetadata(alias) to return RFC-style discovery metadata using computed subdomain origins.
Login Template
src/auth/templates/login-page.template.ts
Extended generateLoginPage(...) signature with optional loginActionUrl to override the form POST target while preserving the previous default.
MCP Auth Controller
src/mcp/mcp-auth.controller.ts
New McpAuthController mounted at mcp/:alias providing discovery endpoints, /authorize, /login, OPTIONS /token CORS handling, POST /token (basic auth parsing, code/refresh handling), and POST /register.
MCP Integration
src/mcp/mcp.controller.ts, src/mcp/mcp.module.ts
Replaced per-alias path links with buildSubdomainUrl() for WWW-Authenticate resource_metadata; McpModule now registers McpAuthController.
Auth Module Exports
src/auth/auth.module.ts
AuthModule now exports both OAuthService and DiscoveryService.
Tool Descriptions
src/tools/definitions/control-device-simple.tool.ts
Updated descriptive strings for control actions (brightness, kelvin, mode) and adjusted human-readable ranges/units; no validation logic changes.
Tool Executor Logic
src/tools/services/tool-executor.service.ts
Added requireValue and validateRange helpers; enforce ranges, round numeric payloads (e.g., brightness -> Math.round(v * 10)), and replace generic errors with BadRequestException for invalid control actions.
Tool Metadata
src/tools/definitions/get-device.tool.ts, src/tools/definitions/list-devices.tool.ts
Added _meta['ui/resourceUri'] = 'ui://widget/device-app.html'.
Widget Bridge & UI
views/widgets/device-app.html
Introduced _bridge host-bridge module, switched tool calls to _bridge.callServerTool(...), updated event handling and theme/locale propagation; removed legacy window.openai fallbacks.

Sequence Diagram

sequenceDiagram
    participant Client
    participant McpAuthCtrl as McpAuthController
    participant DiscoverySvc as DiscoveryService
    participant OAuthSvc as OAuthService
    participant AuthSvc as AuthService

    rect rgba(173,216,230,0.5)
    Note over Client,McpAuthCtrl: Discovery metadata request
    Client->>McpAuthCtrl: GET /mcp/:alias/.well-known/oauth-protected-resource
    McpAuthCtrl->>DiscoverySvc: getSubdomainResourceMetadata(alias)
    DiscoverySvc-->>McpAuthCtrl: resource metadata
    McpAuthCtrl-->>Client: JSON metadata
    end

    rect rgba(144,238,144,0.5)
    Note over Client,OAuthSvc: Authorization & login
    Client->>McpAuthCtrl: GET /mcp/:alias/authorize?client_id=...
    McpAuthCtrl-->>Client: HTML login form (generateLoginPage)
    Client->>McpAuthCtrl: POST /mcp/:alias/login (credentials)
    McpAuthCtrl->>OAuthSvc: handleLogin(alias, credentials)
    OAuthSvc->>AuthSvc: authenticate user
    AuthSvc-->>OAuthSvc: auth code
    OAuthSvc-->>McpAuthCtrl: auth code
    McpAuthCtrl-->>Client: Redirect with code & state
    end

    rect rgba(255,182,193,0.5)
    Note over Client,OAuthSvc: Token exchange
    Client->>McpAuthCtrl: POST /mcp/:alias/token (code or refresh_token)
    McpAuthCtrl->>OAuthSvc: exchangeAuthorizationCode / refreshToken
    OAuthSvc-->>McpAuthCtrl: access token / refresh token response
    McpAuthCtrl-->>Client: token JSON
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • Feature: staging #16: Overlaps with discovery metadata generation and login-page signature changes for subdomain/alias-aware OAuth endpoints.
  • Feat/chatgpt widgets #12: Modifies tool-executor.service.ts logic and may interact with the new validation/rounding helpers.
  • Feat/chatgpt widgets #13: Directly modifies LIST_DEVICES_TOOL metadata; related to the _meta.ui/resourceUri additions.

Poem

🐰 I tunneled through hosts with a shimmy and hop,
Subdomains awake as discovery lamps pop,
Forms now point where aliases steer,
Codes and tokens hop ever near,
A carrot for every OAuth stop! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature staging' is vague and generic, lacking specific details about the actual changes made in this comprehensive pull request. Revise the title to describe the primary change, such as 'Add subdomain-based OAuth discovery and authentication endpoints' or 'Implement MCP auth controller with subdomain routing'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/auth/services/discovery.service.ts (1)

3-3: Use path alias for internal imports.

As per coding guidelines, internal imports should use the @/* path alias.

Suggested fix
-import { buildSubdomainUrl } from '../../common/utils/url.utils';
+import { buildSubdomainUrl } from '@/common/utils/url.utils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/auth/services/discovery.service.ts` at line 3, Replace the relative
import of buildSubdomainUrl with the project path-alias form: update the import
in discovery.service.ts that currently references '../../common/utils/url.utils'
so it imports buildSubdomainUrl from '@/common/utils/url.utils' (i.e., use the
`@/` path alias for internal imports).
src/mcp/mcp-auth.controller.ts (1)

17-24: Use path aliases for internal imports.

As per coding guidelines, internal imports should use the @/* path alias pointing to src/*.

Suggested fix
-import { OAuthService } from '../auth/services/oauth.service';
-import { DiscoveryService } from '../auth/services/discovery.service';
-import { AuthorizeQueryDto } from '../auth/dto/authorize.dto';
-import { TokenRequestDto } from '../auth/dto/token-request.dto';
-import { TokenResponseDto } from '../auth/dto/token-response.dto';
-import { generateLoginPage } from '../auth/templates/login-page.template';
-import { PartnerMetaService } from '../alias/partner-meta.service';
-import { AliasService } from '../alias/alias.service';
+import { OAuthService } from '@/auth/services/oauth.service';
+import { DiscoveryService } from '@/auth/services/discovery.service';
+import { AuthorizeQueryDto } from '@/auth/dto/authorize.dto';
+import { TokenRequestDto } from '@/auth/dto/token-request.dto';
+import { TokenResponseDto } from '@/auth/dto/token-response.dto';
+import { generateLoginPage } from '@/auth/templates/login-page.template';
+import { PartnerMetaService } from '@/alias/partner-meta.service';
+import { AliasService } from '@/alias/alias.service';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp-auth.controller.ts` around lines 17 - 24, Replace the relative
internal imports in mcp-auth.controller.ts with the project path alias (`@/`*) so
internal modules use '@/...' instead of '../...'; update imports that reference
OAuthService, DiscoveryService, AuthorizeQueryDto, TokenRequestDto,
TokenResponseDto, generateLoginPage, PartnerMetaService, and AliasService to
their corresponding '@/' paths (e.g. '@/auth/services/oauth.service') so they
resolve via the configured path alias.
src/mcp/mcp.controller.ts (1)

20-20: Use path alias for internal imports.

As per coding guidelines, internal imports should use the @/* path alias pointing to src/*.

Suggested fix
-import { buildSubdomainUrl } from '../common/utils/url.utils';
+import { buildSubdomainUrl } from '@/common/utils/url.utils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp.controller.ts` at line 20, Replace the relative internal import
with the project path-alias: change the import that references buildSubdomainUrl
(currently "import { buildSubdomainUrl } from '../common/utils/url.utils';") to
use the "@/..." alias pointing at the same module under src (so the symbol
buildSubdomainUrl is imported from the aliased path). Ensure the new import
follows the same named import syntax and that any linter/TS config recognizes
the alias so mcp.controller.ts continues to compile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcp/mcp-auth.controller.ts`:
- Around line 147-158: The tokenOptions method sets Access-Control-Allow-Origin
to '*' while also setting Access-Control-Allow-Credentials to 'true', which
violates CORS; update tokenOptions to either remove the
Access-Control-Allow-Credentials header (if credentialed requests are not
needed) or replace the wildcard origin with a concrete origin value (e.g., read
from config/env and set that value in the Access-Control-Allow-Origin header) so
that credentials are only allowed with a specific origin; ensure the chosen
approach updates the headers set in tokenOptions(`@Res`() res: Response) and keep
the rest of the preflight headers unchanged.
- Around line 77-78: The form action currently passed to generateLoginPage is an
absolute '/login' which breaks subdomain-scoped routes; update the call in
mcp-auth.controller.ts (generateLoginPage(alias, query, meta ?? undefined,
undefined, '/login')) to use a relative path 'login' so the browser posts to the
current /mcp/:alias/authorize context, or construct the proper absolute path
using the alias (e.g., `/mcp/${alias}/login`) so the form submits to the correct
/mcp/:alias/login endpoint.
- Around line 136-142: The generateLoginPage call in the error path currently
passes a hardcoded '/login' as the form action; change that to use the same
action value used for the normal (non-error) render so the re-rendered form
posts to the correct endpoint—replace the '/login' argument in the
generateLoginPage(...) invocation with the same variable/constant used in the
successful render (e.g., the login action variable or oauthParams.action /
LOGIN_POST_PATH used elsewhere) so both paths use the identical form action.

---

Nitpick comments:
In `@src/auth/services/discovery.service.ts`:
- Line 3: Replace the relative import of buildSubdomainUrl with the project
path-alias form: update the import in discovery.service.ts that currently
references '../../common/utils/url.utils' so it imports buildSubdomainUrl from
'@/common/utils/url.utils' (i.e., use the `@/` path alias for internal imports).

In `@src/mcp/mcp-auth.controller.ts`:
- Around line 17-24: Replace the relative internal imports in
mcp-auth.controller.ts with the project path alias (`@/`*) so internal modules use
'@/...' instead of '../...'; update imports that reference OAuthService,
DiscoveryService, AuthorizeQueryDto, TokenRequestDto, TokenResponseDto,
generateLoginPage, PartnerMetaService, and AliasService to their corresponding
'@/' paths (e.g. '@/auth/services/oauth.service') so they resolve via the
configured path alias.

In `@src/mcp/mcp.controller.ts`:
- Line 20: Replace the relative internal import with the project path-alias:
change the import that references buildSubdomainUrl (currently "import {
buildSubdomainUrl } from '../common/utils/url.utils';") to use the "@/..." alias
pointing at the same module under src (so the symbol buildSubdomainUrl is
imported from the aliased path). Ensure the new import follows the same named
import syntax and that any linter/TS config recognizes the alias so
mcp.controller.ts continues to compile.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4996e5ed-b611-4d86-bded-067b9d549c49

📥 Commits

Reviewing files that changed from the base of the PR and between 7242150 and a8167a8.

📒 Files selected for processing (6)
  • src/auth/services/discovery.service.ts
  • src/auth/templates/login-page.template.ts
  • src/common/utils/url.utils.ts
  • src/mcp/mcp-auth.controller.ts
  • src/mcp/mcp.controller.ts
  • src/mcp/mcp.module.ts

Comment on lines +77 to +78
const html = generateLoginPage(alias, query, meta ?? undefined, undefined, '/login');
res.status(HttpStatus.OK).contentType('text/html').send(html);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect form action URL will break the login flow.

The loginActionUrl is set to '/login' which is an absolute path pointing to the root /login endpoint, not the subdomain-scoped /mcp/:alias/login. The form submission will fail with a 404.

Proposed fix

Use a relative path 'login' (without leading slash) so the browser resolves it relative to the current /mcp/:alias/authorize path:

-    const html = generateLoginPage(alias, query, meta ?? undefined, undefined, '/login');
+    const html = generateLoginPage(alias, query, meta ?? undefined, undefined, 'login');

Alternatively, construct the absolute path:

-    const html = generateLoginPage(alias, query, meta ?? undefined, undefined, '/login');
+    const html = generateLoginPage(alias, query, meta ?? undefined, undefined, `/mcp/${alias}/login`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const html = generateLoginPage(alias, query, meta ?? undefined, undefined, '/login');
res.status(HttpStatus.OK).contentType('text/html').send(html);
const html = generateLoginPage(alias, query, meta ?? undefined, undefined, 'login');
res.status(HttpStatus.OK).contentType('text/html').send(html);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp-auth.controller.ts` around lines 77 - 78, The form action
currently passed to generateLoginPage is an absolute '/login' which breaks
subdomain-scoped routes; update the call in mcp-auth.controller.ts
(generateLoginPage(alias, query, meta ?? undefined, undefined, '/login')) to use
a relative path 'login' so the browser posts to the current
/mcp/:alias/authorize context, or construct the proper absolute path using the
alias (e.g., `/mcp/${alias}/login`) so the form submits to the correct
/mcp/:alias/login endpoint.

Comment on lines +136 to +142
const html = generateLoginPage(
alias,
oauthParams,
meta ?? undefined,
err.message || 'Login failed',
'/login',
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same incorrect form action URL in the error path.

Apply the same fix here to ensure the re-rendered login form posts to the correct endpoint.

Proposed fix
       const html = generateLoginPage(
         alias,
         oauthParams,
         meta ?? undefined,
         err.message || 'Login failed',
-        '/login',
+        'login',
       );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const html = generateLoginPage(
alias,
oauthParams,
meta ?? undefined,
err.message || 'Login failed',
'/login',
);
const html = generateLoginPage(
alias,
oauthParams,
meta ?? undefined,
err.message || 'Login failed',
'login',
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp-auth.controller.ts` around lines 136 - 142, The generateLoginPage
call in the error path currently passes a hardcoded '/login' as the form action;
change that to use the same action value used for the normal (non-error) render
so the re-rendered form posts to the correct endpoint—replace the '/login'
argument in the generateLoginPage(...) invocation with the same
variable/constant used in the successful render (e.g., the login action variable
or oauthParams.action / LOGIN_POST_PATH used elsewhere) so both paths use the
identical form action.

Comment on lines +147 to +158
@Options('token')
tokenOptions(@Res() res: Response): void {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.header(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, Accept, x-admin-api-key, x-project-api-key, mcp-protocol-version',
);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Max-Age', '86400');
res.status(HttpStatus.NO_CONTENT).send();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalid CORS configuration: wildcard origin with credentials.

Setting Access-Control-Allow-Origin: '*' together with Access-Control-Allow-Credentials: 'true' violates the CORS specification. Browsers will reject this configuration and block the preflight response.

Either:

  • Remove Access-Control-Allow-Credentials if you don't need credentialed requests, or
  • Use a specific origin instead of '*' when credentials are required
Proposed fix (option 1: remove credentials)
   `@Options`('token')
   tokenOptions(`@Res`() res: Response): void {
     res.header('Access-Control-Allow-Origin', '*');
     res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
     res.header(
       'Access-Control-Allow-Headers',
       'Content-Type, Authorization, Accept, x-admin-api-key, x-project-api-key, mcp-protocol-version',
     );
-    res.header('Access-Control-Allow-Credentials', 'true');
     res.header('Access-Control-Max-Age', '86400');
     res.status(HttpStatus.NO_CONTENT).send();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Options('token')
tokenOptions(@Res() res: Response): void {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.header(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, Accept, x-admin-api-key, x-project-api-key, mcp-protocol-version',
);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Max-Age', '86400');
res.status(HttpStatus.NO_CONTENT).send();
}
`@Options`('token')
tokenOptions(`@Res`() res: Response): void {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.header(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, Accept, x-admin-api-key, x-project-api-key, mcp-protocol-version',
);
res.header('Access-Control-Max-Age', '86400');
res.status(HttpStatus.NO_CONTENT).send();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp-auth.controller.ts` around lines 147 - 158, The tokenOptions
method sets Access-Control-Allow-Origin to '*' while also setting
Access-Control-Allow-Credentials to 'true', which violates CORS; update
tokenOptions to either remove the Access-Control-Allow-Credentials header (if
credentialed requests are not needed) or replace the wildcard origin with a
concrete origin value (e.g., read from config/env and set that value in the
Access-Control-Allow-Origin header) so that credentials are only allowed with a
specific origin; ensure the chosen approach updates the headers set in
tokenOptions(`@Res`() res: Response) and keep the rest of the preflight headers
unchanged.

Replace static client_id stub with real DCR backed by Redis storage.
Clients are registered per-alias with scrypt-hashed secrets, exact
redirect_uri matching, and configurable per-tenant limits.

- POST /auth/:alias/register returns 201 with client_id + optional secret
- Token endpoint validates registered clients (Basic Auth + client_secret_post)
- Backward compatible: REQUIRE_CLIENT_REGISTRATION defaults to false
- Discovery metadata cleaned up to only advertise implemented endpoints
…ce_simple

Brightness now accepts 0-100 (percent) and converts to 0-1000 server-side.
All set_* actions validate ranges with clear error messages (BadRequestException):
  - set_brightness: 0-100%
  - set_kelvin: 0-65000
  - set_temperature: 15-30°C
  - set_mode: 0-4 (AUTO/COOL/DRY/HEAT/FAN)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tools/services/tool-executor.service.ts`:
- Around line 131-142: requireValue currently only checks for null/undefined and
lets non-numeric inputs (''/false/NaN) through, causing downstream
Math.round/validateRange to operate on invalid numbers; update
requireValue(value: number | null | undefined, action: string) to perform
runtime numeric validation by verifying typeof value === 'number' and
Number.isFinite(value) (reject NaN/Infinity and non-number primitives) and throw
BadRequestException with a clear message referencing the action when validation
fails; keep validateRange as-is but rely on the strengthened requireValue to
ensure callers (e.g., places that call Math.round or validateRange) never
receive invalid numbers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5b31d19-901b-4b77-ac6a-8b1cd802c895

📥 Commits

Reviewing files that changed from the base of the PR and between dea977e and de0ae2e.

📒 Files selected for processing (2)
  • src/tools/definitions/control-device-simple.tool.ts
  • src/tools/services/tool-executor.service.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tools/definitions/control-device-simple.tool.ts

Comment on lines +131 to +142
private requireValue(value: number | null | undefined, action: string): number {
if (value == null) {
throw new BadRequestException(`value is required for ${action} action`);
}
return value;
}

private validateRange(value: number, min: number, max: number, label: string): void {
if (value < min || value > max) {
throw new BadRequestException(`${label} must be ${min}-${max}. Got: ${value}`);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does the current @modelcontextprotocol/sdk Node server validate incoming tool-call arguments against a tool's inputSchema before invoking the handler, or must servers perform their own runtime type validation?

💡 Result:

No, the @modelcontextprotocol/sdk Node server does not automatically validate incoming tool-call arguments against a tool's inputSchema before invoking the handler. Servers must perform their own runtime type validation, typically using Zod schemas provided during tool registration via methods like server.tool or McpServer.registerTool. The SDK uses Zod (peer dependency) to parse and type-check arguments in the handler function parameters, but developers implement validation logic in handlers or rely on Zod's type inference. Official spec requires servers to "Validate all tool inputs" (security consideration), and tutorials show manual Zod validation in handlers. GitHub issues discuss schema inconsistencies and validation bugs, confirming it's developer responsibility.

Citations:


Add runtime type validation to requireValue() helper.

The @modelcontextprotocol/sdk does not validate incoming tool arguments against inputSchema before invoking handlers—servers must perform their own runtime validation. The current requireValue() only rejects null/undefined, allowing malformed non-numeric values like '', false, or NaN to bypass validation. These coerce silently in validateRange() and Math.round(), sending 0/NaN commands to devices instead of rejecting the request.

Fix
-  private requireValue(value: number | null | undefined, action: string): number {
+  private requireValue(value: unknown, action: string): number {
     if (value == null) {
       throw new BadRequestException(`value is required for ${action} action`);
     }
+    if (typeof value !== 'number' || !Number.isFinite(value)) {
+      throw new BadRequestException(`value for ${action} must be a finite number`);
+    }
     return value;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private requireValue(value: number | null | undefined, action: string): number {
if (value == null) {
throw new BadRequestException(`value is required for ${action} action`);
}
return value;
}
private validateRange(value: number, min: number, max: number, label: string): void {
if (value < min || value > max) {
throw new BadRequestException(`${label} must be ${min}-${max}. Got: ${value}`);
}
}
private requireValue(value: unknown, action: string): number {
if (value == null) {
throw new BadRequestException(`value is required for ${action} action`);
}
if (typeof value !== 'number' || !Number.isFinite(value)) {
throw new BadRequestException(`value for ${action} must be a finite number`);
}
return value;
}
private validateRange(value: number, min: number, max: number, label: string): void {
if (value < min || value > max) {
throw new BadRequestException(`${label} must be ${min}-${max}. Got: ${value}`);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/services/tool-executor.service.ts` around lines 131 - 142,
requireValue currently only checks for null/undefined and lets non-numeric
inputs (''/false/NaN) through, causing downstream Math.round/validateRange to
operate on invalid numbers; update requireValue(value: number | null |
undefined, action: string) to perform runtime numeric validation by verifying
typeof value === 'number' and Number.isFinite(value) (reject NaN/Infinity and
non-number primitives) and throw BadRequestException with a clear message
referencing the action when validation fails; keep validateRange as-is but rely
on the strengthened requireValue to ensure callers (e.g., places that call
Math.round or validateRange) never receive invalid numbers.

Replace ChatGPT-specific window.openai.* API with standard MCP Apps
JSON-RPC postMessage protocol. Widget now works on ChatGPT, Claude
Desktop, and GitHub Copilot (VS Code 1.109+).

- Add minimal _bridge object speaking JSON-RPC 2.0 over postMessage
- Replace all window.openai.callTool → _bridge.callServerTool (7 sites)
- Replace initialization from window.openai.toolOutput → bridge connect
- Add flat _meta["ui/resourceUri"] to tool defs for Claude compat
- Keep window.openai.* as fallback for legacy ChatGPT versions
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@views/widgets/device-app.html`:
- Around line 1433-1435: The widget caches the current language in _lang inside
t(), so if the host locale arrives asynchronously (e.g., after connect()
resolves or later via host context) the UI stays in the old language; update the
onhostcontextchanged handler (the _bridge.on('onhostcontextchanged', ...)
listener) to invalidate the translation cache and re-render: clear or reset the
cached _lang used by t() (e.g., set _lang = undefined/null) and trigger the same
render/update path you use after changing <html lang> so translations are
recomputed when the host locale arrives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 195cb588-f3f9-4b69-ae69-df48d982f389

📥 Commits

Reviewing files that changed from the base of the PR and between de0ae2e and ccf0e73.

📒 Files selected for processing (3)
  • src/tools/definitions/get-device.tool.ts
  • src/tools/definitions/list-devices.tool.ts
  • views/widgets/device-app.html
✅ Files skipped from review due to trivial changes (2)
  • src/tools/definitions/get-device.tool.ts
  • src/tools/definitions/list-devices.tool.ts

Comment on lines +1433 to +1435
_bridge.on('onhostcontextchanged', function (ctx) {
if (ctx && ctx.theme) applyTheme(ctx.theme);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Invalidate translations when locale arrives asynchronously.

Line 2646 updates <html lang>, but t() caches _lang on first use. If the 200 ms fallback at Lines 2651-2655 renders before connect() resolves, or the host changes locale later, the widget stays in the previous language until reload.

🌐 Suggested fix
         _bridge.on('onhostcontextchanged', function (ctx) {
           if (ctx && ctx.theme) applyTheme(ctx.theme);
+          if (ctx && ctx.locale && ctx.locale !== document.documentElement.lang) {
+            document.documentElement.lang = ctx.locale;
+            _lang = null;
+            if (_currentView && _currentData) renderView(_currentView, _currentData);
+          }
         });
@@
         _bridge.connect().then(function (ctx) {
           if (ctx && ctx.theme) applyTheme(ctx.theme);
           if (ctx && ctx.locale) {
             document.documentElement.lang = ctx.locale;
+            _lang = null;
+            if (_currentView && _currentData) renderView(_currentView, _currentData);
           }
         });

Also applies to: 2643-2655

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/widgets/device-app.html` around lines 1433 - 1435, The widget caches
the current language in _lang inside t(), so if the host locale arrives
asynchronously (e.g., after connect() resolves or later via host context) the UI
stays in the old language; update the onhostcontextchanged handler (the
_bridge.on('onhostcontextchanged', ...) listener) to invalidate the translation
cache and re-render: clear or reset the cached _lang used by t() (e.g., set
_lang = undefined/null) and trigger the same render/update path you use after
changing <html lang> so translations are recomputed when the host locale
arrives.

The ext-apps SDK sends { method: 'tools/call' } for callServerTool(),
not 'ui/callServerTool'. The host proxies standard MCP methods directly
to the server. Wrong method name caused tool calls from the widget to
hang on GitHub Copilot (no response to unknown method).
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
views/widgets/device-app.html (1)

2641-2646: ⚠️ Potential issue | 🟡 Minor

Locale set in connect() may not apply to already-cached translations.

Setting document.documentElement.lang here won't affect translations if t() was already called (e.g., during the 200ms fallback render or initial loading text). Reset _lang = null and consider re-rendering if a view was already displayed.

🌐 Suggested fix
 _bridge.connect().then(function (ctx) {
   if (ctx && ctx.theme) applyTheme(ctx.theme);
   if (ctx && ctx.locale) {
     document.documentElement.lang = ctx.locale;
+    _lang = null;
+    if (_currentView && _currentData) renderView(_currentView, _currentData);
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/widgets/device-app.html` around lines 2641 - 2646, The locale set in
the _bridge.connect() callback by assigning document.documentElement.lang won't
refresh already-cached translations used by t(); reset the internal cache
variable (_lang = null) after setting the lang and trigger a re-render of the
current view if one is displayed so translations are recomputed; locate the
connect callback where _bridge.connect().then(...) and applyTheme(ctx.theme) are
used, set document.documentElement.lang = ctx.locale, then clear _lang and call
the view re-render function (or invoke the existing render/update routine) to
update displayed text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@views/widgets/device-app.html`:
- Around line 1228-1237: The _send function leaves pending JSON-RPC requests in
the _pending map indefinitely; add a per-request timeout inside _send that
rejects the Promise and deletes _pending[id] if no response arrives within
(e.g.) 30s, and ensure you clear that timer when the response handler
resolves/rejects the same id; reference the _send function, the _pending map,
the generated id (_id) and the existing response handling path so the timer is
stored per-request and cleaned up to avoid memory leaks.

---

Duplicate comments:
In `@views/widgets/device-app.html`:
- Around line 2641-2646: The locale set in the _bridge.connect() callback by
assigning document.documentElement.lang won't refresh already-cached
translations used by t(); reset the internal cache variable (_lang = null) after
setting the lang and trigger a re-render of the current view if one is displayed
so translations are recomputed; locate the connect callback where
_bridge.connect().then(...) and applyTheme(ctx.theme) are used, set
document.documentElement.lang = ctx.locale, then clear _lang and call the view
re-render function (or invoke the existing render/update routine) to update
displayed text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb2aeee9-af6e-41dc-a012-98368189cce0

📥 Commits

Reviewing files that changed from the base of the PR and between ccf0e73 and 3ac8760.

📒 Files selected for processing (1)
  • views/widgets/device-app.html

Comment on lines +1228 to +1237
function _send(method, params) {
var id = ++_id;
return new Promise(function (resolve, reject) {
_pending[id] = { resolve: resolve, reject: reject };
window.parent.postMessage(
{ jsonrpc: '2.0', method: method, params: params || {}, id: id },
'*',
);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing timeout for pending JSON-RPC requests.

The _send function stores callbacks in _pending indefinitely. If the parent frame never responds (e.g., host doesn't support the protocol, network issues, or parent crashes), the Promise never resolves and the callback leaks memory.

Consider adding a timeout to reject stale requests:

🛡️ Suggested fix
 function _send(method, params) {
   var id = ++_id;
   return new Promise(function (resolve, reject) {
-    _pending[id] = { resolve: resolve, reject: reject };
+    var timeoutId = setTimeout(function () {
+      if (_pending[id]) {
+        delete _pending[id];
+        reject({ code: -32000, message: 'Request timeout' });
+      }
+    }, 10000);
+    _pending[id] = {
+      resolve: function (r) { clearTimeout(timeoutId); resolve(r); },
+      reject: function (e) { clearTimeout(timeoutId); reject(e); }
+    };
     window.parent.postMessage(
       { jsonrpc: '2.0', method: method, params: params || {}, id: id },
       '*',
     );
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _send(method, params) {
var id = ++_id;
return new Promise(function (resolve, reject) {
_pending[id] = { resolve: resolve, reject: reject };
window.parent.postMessage(
{ jsonrpc: '2.0', method: method, params: params || {}, id: id },
'*',
);
});
}
function _send(method, params) {
var id = ++_id;
return new Promise(function (resolve, reject) {
var timeoutId = setTimeout(function () {
if (_pending[id]) {
delete _pending[id];
reject({ code: -32000, message: 'Request timeout' });
}
}, 10000);
_pending[id] = {
resolve: function (r) { clearTimeout(timeoutId); resolve(r); },
reject: function (e) { clearTimeout(timeoutId); reject(e); }
};
window.parent.postMessage(
{ jsonrpc: '2.0', method: method, params: params || {}, id: id },
'*',
);
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/widgets/device-app.html` around lines 1228 - 1237, The _send function
leaves pending JSON-RPC requests in the _pending map indefinitely; add a
per-request timeout inside _send that rejects the Promise and deletes
_pending[id] if no response arrives within (e.g.) 30s, and ensure you clear that
timer when the response handler resolves/rejects the same id; reference the
_send function, the _pending map, the generated id (_id) and the existing
response handling path so the timer is stored per-request and cleaned up to
avoid memory leaks.

Model-visible tool that opens the widget control UI when the user wants
to control a device without specifying exact actions. Reuses the same
executeWidgetControlDevice handler (_view: 'control'). AI description
guides tool selection: vague requests → interactive_device widget,
specific commands → control_device_simple.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant