feat: embedded MCP server with user-scoped token authentication (#15383)#41980
feat: embedded MCP server with user-scoped token authentication (#15383)#41980salevine wants to merge 89 commits into
Conversation
…15383) Add an opt-in, same-instance Streamable HTTP MCP endpoint that acts as the calling Appsmith user. MCP clients authenticate with a user-scoped bearer token; the Node service forwards that token to existing /api/v1 endpoints so Spring Security reconstructs the real user and existing workspace/app/page ACLs authorize every operation. No privileged/internal credential is used. Server (CE, EE-overridable via *CE base + thin concrete subclass split): - UserMcpToken domain/repository/service + McpTokenController for create/list/ revoke of user-scoped tokens (SHA-256 pre-hash then bcrypt at rest, plaintext shown once, max 10 active tokens/user). - Bearer AuthenticationWebFilter (mcp_ prefix) reconstructs the token owner; invalid/revoked/disabled tokens return 401. - Migration076 creates the userMcpToken indexes (auto-index-creation is off). Node service (app/client/packages/mcp): - Streamable HTTP transport, loopback bind, /health endpoint, request body cap, per-request token revalidation, per-session token binding, and per-user + global session caps. - Tools: list_workspaces, list_applications, get_application_context, and import_application_artifact / import_partial_application_artifact (validated artifact upload through the existing import APIs). Client: - MCP token management UI in the user profile (create / copy-once / revoke). Deploy/CI: - Opt-in APPSMITH_MCP_ENABLED gate (default off) for supervisord autostart and the Caddy /mcp route; Dockerfile copy, mcp-build workflow, route health test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Whoops! Looks like you're using an outdated method of running the Cypress suite. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds an MCP server, secure user token lifecycle, User Profile token management, backend authentication, Docker runtime support, and CI workflows that build and package MCP artifacts. ChangesMCP app builder and server
Token lifecycle and UI
Deployment and CI
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 4❌ Failed checks (4 warnings)
✅ Passed checks (1 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Failed server tests
|
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (4)
app/client/packages/mcp/src/app.ts (1)
67-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
objectKeysfrom@appsmith/utilsinstead ofObject.keys.Static analysis flags this per the repo's internal lint rule for consistent object-key handling.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/client/packages/mcp/src/app.ts` at line 67, Replace the Object.keys call in the artifact emptiness check with the repository’s objectKeys utility imported from `@appsmith/utils`, while preserving the existing condition and behavior.Source: Linters/SAST tools
app/client/src/pages/UserProfile/McpTokens.test.tsx (1)
9-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFull-module mock of
McpTokenApiskips coverage oflist()'s response normalization.Mocking
McpTokenApi.listdirectly (rather than mockingApi.get) means this suite never exercises the array/response-unwrapping logic inside the reallist()implementation — see the concern raised inMcpTokenApi.ts.Also applies to: 32-38
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/client/src/pages/UserProfile/McpTokens.test.tsx` around lines 9 - 16, Replace the full-module mock of McpTokenApi with a mock of the underlying Api.get request, while retaining mocks for create and revoke as needed, so tests invoke the real McpTokenApi.list implementation and cover its array/response-unwrapping normalization logic..github/workflows/mcp-build.yml (1)
42-60: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider
persist-credentials: falseon checkout.zizmor flags all three checkout steps for credential persistence in the git config, which could be exfiltrated by any subsequent step/dependency script in this job.
🔒 Disable credential persistence
- name: Checkout the merged pull-request commit if: inputs.pr != 0 uses: actions/checkout@v4 with: fetch-tags: true ref: refs/pull/${{ inputs.pr }}/merge + persist-credentials: falseApply similarly to the other two checkout steps.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/mcp-build.yml around lines 42 - 60, All three checkout steps persist GitHub credentials in the local Git config. Add persist-credentials: false to the with configuration of the checkout steps identified by “Checkout the merged pull-request commit,” “Checkout the specified branch,” and “Checkout the head commit.”Source: Linters/SAST tools
app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java (1)
196-199: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider setting a stateless authentication success handler on the MCP filter.
AuthenticationWebFilterdefaults toWebSessionServerAuthenticationSuccessHandler, which creates a WebSession on each successful MCP token authentication. For bearer-token (stateless) auth, this is unnecessary session overhead. Set a no-op orSavedRequestServerAuthenticationSuccessHandlerto keep MCP auth stateless.♻️ Proposed fix
mcpTokenAuthenticationWebFilter.setServerAuthenticationConverter(mcpTokenAuthenticationConverter); mcpTokenAuthenticationWebFilter.setAuthenticationFailureHandler(failureHandler); +mcpTokenAuthenticationWebFilter.setAuthenticationSuccessHandler( + new ServerAuthenticationSuccessHandler() { + `@Override` + public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { + return webFilterExchange.getChain().filter(webFilterExchange.getExchange()); + } + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java` around lines 196 - 199, Configure a stateless authentication success handler on the AuthenticationWebFilter created in SecurityConfig for MCP token authentication, replacing the default WebSessionServerAuthenticationSuccessHandler; use an appropriate no-op or SavedRequestServerAuthenticationSuccessHandler while retaining the existing failure handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build-client-server.yml:
- Around line 118-128: Update the mcp-build job’s if condition to compare
needs.file-check.outputs.runId with the quoted string '0', matching the runId
comparisons used by the other jobs in this workflow.
In @.github/workflows/mcp-build.yml:
- Around line 84-91: Remove the duplicate “Lint” step running yarn lint from the
workflow, keeping only one lint invocation alongside the existing formatting
check.
In `@app/client/packages/mcp/build.js`:
- Around line 3-12: Update the esbuild configuration in the build script to
derive the target from only the major Node version, such as by splitting
process.versions.node before constructing the target string; alternatively use a
fixed major-only value like node20. Ensure the target passed in the
esbuild.build call is accepted by esbuild.
In `@app/client/packages/mcp/src/app.test.ts`:
- Around line 294-298: Update the mockResolvedValueOnce object in the “fails
safely when session token revalidation fails” test to Prettier’s multiline
object-literal format, preserving its existing username and isAnonymous values.
- Line 59: Insert a blank line immediately before the for loop iterating over
callIndex in app.test.ts, preserving the required
padding-line-between-statements ESLint formatting.
- Around line 69-83: Fix the Prettier formatting in the test around the
fullArtifact and partialArtifact assertions: add the required blank line before
the fullArtifact declaration and collapse the fullArtifact.text() await expect
assertion to one line, matching the project’s formatting rules.
In `@app/client/packages/mcp/src/app.ts`:
- Line 14: Fix the Prettier formatting violations in app.ts at the declarations
and code associated with MAX_ARTIFACT_BYTES and the flagged lines 33, 120, and
297; run Prettier on the file and verify the build formatting check passes.
- Around line 143-159: Update the request function to enforce a finite timeout
for every fetchFn call. Create an AbortController, schedule it to abort after
the configured timeout, pass its signal into the fetch options while preserving
any caller-provided signal behavior, and clear the timeout in a finally block so
completed requests do not retain timers.
- Around line 448-458: Add Origin/Host validation for the /mcp endpoint before
creating or handling the StreamableHTTPServerTransport in the surrounding
request handler. Reject requests whose Origin or Host is not an explicitly
allowed local/ configured value, using middleware or equivalent request checks
rather than transport defaults; ensure rejected requests do not create sessions
or reach MCP processing.
In `@app/client/packages/mcp/src/server.ts`:
- Around line 8-18: Update reportProcessFailure to terminate the MCP process
after recording the failure: retain the stderr message, then call
process.exit(1) rather than only setting process.exitCode. Keep the
uncaughtException and unhandledRejection handlers wired to this function, and
apply the repository’s Prettier formatting to the affected code.</codeેન
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java`:
- Around line 112-128: Update extractTokenId in UserMcpTokenServiceCEImpl to
handle a null token before calling startsWith, returning null for null or
invalid credentials so McpTokenAuthenticationManager can fall back to
Mono.empty() instead of throwing.
---
Nitpick comments:
In @.github/workflows/mcp-build.yml:
- Around line 42-60: All three checkout steps persist GitHub credentials in the
local Git config. Add persist-credentials: false to the with configuration of
the checkout steps identified by “Checkout the merged pull-request commit,”
“Checkout the specified branch,” and “Checkout the head commit.”
In `@app/client/packages/mcp/src/app.ts`:
- Line 67: Replace the Object.keys call in the artifact emptiness check with the
repository’s objectKeys utility imported from `@appsmith/utils`, while preserving
the existing condition and behavior.
In `@app/client/src/pages/UserProfile/McpTokens.test.tsx`:
- Around line 9-16: Replace the full-module mock of McpTokenApi with a mock of
the underlying Api.get request, while retaining mocks for create and revoke as
needed, so tests invoke the real McpTokenApi.list implementation and cover its
array/response-unwrapping normalization logic.
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java`:
- Around line 196-199: Configure a stateless authentication success handler on
the AuthenticationWebFilter created in SecurityConfig for MCP token
authentication, replacing the default
WebSessionServerAuthenticationSuccessHandler; use an appropriate no-op or
SavedRequestServerAuthenticationSuccessHandler while retaining the existing
failure handler.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3f3c434d-959f-49df-8c12-f94b0bd1a5e6
⛔ Files ignored due to path filters (1)
app/client/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (56)
.github/workflows/ad-hoc-docker-image.yml.github/workflows/build-client-server-count.yml.github/workflows/build-client-server.yml.github/workflows/build-docker-image.yml.github/workflows/docs/test-build-docker-image.md.github/workflows/github-release.yml.github/workflows/mcp-build.yml.github/workflows/on-demand-build-docker-image-deploy-preview.yml.github/workflows/playwright-e2e.yml.github/workflows/pr-cypress.yml.github/workflows/test-build-docker-image.ymlDockerfileapp/client/packages/mcp/.env.exampleapp/client/packages/mcp/.eslintignoreapp/client/packages/mcp/build.jsapp/client/packages/mcp/build.shapp/client/packages/mcp/jest.config.cjsapp/client/packages/mcp/package.jsonapp/client/packages/mcp/src/app.test.tsapp/client/packages/mcp/src/app.tsapp/client/packages/mcp/src/server.tsapp/client/packages/mcp/start-server.shapp/client/packages/mcp/tsconfig.jsonapp/client/src/api/McpTokenApi.tsapp/client/src/ce/constants/messages.tsapp/client/src/pages/UserProfile/McpTokens.test.tsxapp/client/src/pages/UserProfile/McpTokens.tsxapp/client/src/pages/UserProfile/index.tsxapp/server/appsmith-server/src/main/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverter.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/authentication/managers/McpTokenAuthenticationManager.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/authentication/tokens/McpTokenAuthentication.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/controllers/McpTokenController.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/McpTokenControllerCE.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/domains/UserMcpToken.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/UserMcpTokenCE.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/dtos/McpTokenResponseDTO.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076AddUserMcpTokenIndexes.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/repositories/UserMcpTokenRepository.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/UserMcpTokenRepositoryCE.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/services/UserMcpTokenService.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/services/UserMcpTokenServiceImpl.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCE.javaapp/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.javaapp/server/appsmith-server/src/test/java/com/appsmith/server/authentication/McpTokenAuthenticationWebFilterTest.javaapp/server/appsmith-server/src/test/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverterTest.javaapp/server/appsmith-server/src/test/java/com/appsmith/server/authentication/managers/McpTokenAuthenticationManagerTest.javaapp/server/appsmith-server/src/test/java/com/appsmith/server/services/UserMcpTokenServiceImplTest.javacontributions/ServerSetup.mddeploy/docker/fs/opt/appsmith/caddy-reconfigure.mjsdeploy/docker/fs/opt/appsmith/entrypoint.shdeploy/docker/fs/opt/appsmith/healthcheck.shdeploy/docker/fs/opt/appsmith/run-mcp.shdeploy/docker/fs/opt/appsmith/templates/supervisord/application_process/mcp.confdeploy/docker/route-tests/common/mcp-health.hurlscripts/local_testing.sh
| - name: Lint | ||
| run: yarn lint | ||
|
|
||
| - name: Check formatting | ||
| run: yarn prettier | ||
|
|
||
| - name: Lint | ||
| run: yarn lint |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Duplicate yarn lint step.
Lint runs at Line 85 and again at Line 91, wasting CI time for no benefit.
🧹 Remove the duplicate step
- name: Check formatting
run: yarn prettier
- - name: Lint
- run: yarn lint
-
- name: Run unit tests📝 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.
| - name: Lint | |
| run: yarn lint | |
| - name: Check formatting | |
| run: yarn prettier | |
| - name: Lint | |
| run: yarn lint | |
| - name: Lint | |
| run: yarn lint | |
| - name: Check formatting | |
| run: yarn prettier |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/mcp-build.yml around lines 84 - 91, Remove the duplicate
“Lint” step running yarn lint from the workflow, keeping only one lint
invocation alongside the existing formatting check.
|
/build-deploy-preview skip-tests=true |
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29135322682. |
Add the high-level page-spec app-builder that compiles to a validated Appsmith import artifact, and harden the MCP surface following an adversarial red-team pass. App-builder (app/client/packages/mcp/src/builder): - Zod page/app/edit spec + best-effort placement, curated widget templates, 64-column auto-layout, spec -> import-artifact compiler with depth and total-widget caps, presets, and a capability catalog. - New tools: get_capabilities, list_presets, get_preset, validate_app_spec (dry-run), build_application, edit_page (append-only, best-effort placement). - Removed the raw artifact-import tools (an arbitrary-content injection surface) in favour of the validated compiler. Security hardening (from the red-team review): - Atomic session admission so concurrent initializes cannot bypass the per-user / global session caps (whole-instance DoS). - Stop crash-looping: guard fire-and-forget transport.close(), log instead of process.exit on unhandledRejection, supervisord startsecs=5. - Do not evict a session on token mismatch (targeted DoS via a leaked session id). - Bound inbound sockets (requestTimeout / headersTimeout / maxConnections). - Token expiry (90d) enforced at authentication; block minting new MCP tokens from an MCP-authenticated principal (no post-revocation persistence). Tests: 39 Node tests (compiler, placement, edit, tool wiring, concurrent session caps, mismatch-no-evict) and Java unit tests for token expiry and the auth marker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
/build-deploy-preview skip-tests=true |
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29160630878. |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java (1)
38-47: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winExpired tokens still consume the active-token quota.
countByUserIdAndDeletedAtIsNullcounts non-deleted tokens regardless ofexpiresAt, so a user who accumulates expired (but un-revoked) tokens can be blocked from creating new ones atMAX_ACTIVE_TOKENS_PER_USERuntil they manually revoke. Consider excluding expired tokens from the count or purging them.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java` around lines 38 - 47, Update the active-token quota check in UserMcpTokenServiceCEImpl.create to exclude tokens whose expiresAt is in the past, using an expiry-aware repository query or purging expired tokens before counting. Preserve the existing deleted-token filtering and MAX_ACTIVE_TOKENS_PER_USER limit for currently valid tokens.
🧹 Nitpick comments (1)
docs/plans/2026-07-11-mcp-app-builder-design.md (1)
82-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a language specifier to the fenced code block.
Markdownlint MD040 flags code blocks without a language. Use
textorplaintextfor the directory structure.📝 Proposed fix
-``` +```text src/builder/ schema.ts Zod schema: page spec + app spec + edit spec + placement templates.ts curated getDefaults per widget (text,input,select,button,image,table,container) layout.ts vertical auto-placement on the 64-col grid; placement resolution compile.ts pageSpec[] -> import artifact; edit merge into existing DSL; depth/total-widget caps presets.ts form, table-detail, card-grid, crud capabilities.ts machine-readable widget/preset catalog</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@docs/plans/2026-07-11-mcp-app-builder-design.mdaround lines 82 - 90, Update
the fenced directory-structure block in the design document to include a text or
plaintext language specifier, preserving its contents and formatting.</details> <!-- cr-comment:v1:ff970ee68c5d25f17169e84b --> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.Inline comments:
In@app/client/packages/mcp/src/app.test.ts:
- Around line 297-304: Update the excluded-tool assertion in the test around
names so it verifies every listed tool is absent, rather than only rejecting the
case where all are present. Use expect.not.arrayContaining with the excluded
tool names or add individual not.toContain checks, while preserving the existing
tool-list validation.In
@app/client/packages/mcp/src/builder/compile.ts:
- Around line 285-301: The placement update after adding a node only grows the
targeted inner canvas, leaving its enclosing CONTAINER_WIDGET height stale for
inside edits. In the resolvePlacement inside-edit flow, after updating
placement.canvas.bottomRow, also update the parent container’s bottomRow to
encompass the child canvas’s new extent, preserving existing root-canvas sizing
behavior.In
@app/client/packages/mcp/src/builder/schema.ts:
- Around line 20-25: Update placementSchema to enforce mutual exclusivity
between its optional after and inside fields with a refinement, while continuing
to allow either field alone or neither field. Keep the existing non-empty string
validation and strict object behavior unchanged.In
@app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/managers/McpTokenAuthenticationManagerTest.java:
- Around line 99-104: Remove the unconditional unknown-source rate-limit stub
from the shared manager() helper. Stub
RateLimitConstants.BUCKET_KEY_FOR_MCP_AUTHENTICATION with "unknown" only in
tests that invoke that lookup, or mark the shared stub lenient, while preserving
the existing manager construction.
Outside diff comments:
In
@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java:
- Around line 38-47: Update the active-token quota check in
UserMcpTokenServiceCEImpl.create to exclude tokens whose expiresAt is in the
past, using an expiry-aware repository query or purging expired tokens before
counting. Preserve the existing deleted-token filtering and
MAX_ACTIVE_TOKENS_PER_USER limit for currently valid tokens.
Nitpick comments:
In@docs/plans/2026-07-11-mcp-app-builder-design.md:
- Around line 82-90: Update the fenced directory-structure block in the design
document to include a text or plaintext language specifier, preserving its
contents and formatting.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Path: .coderabbit.yaml **Review profile**: CHILL **Plan**: Pro **Run ID**: `a1429438-95f0-4da8-aec5-f218de3fea14` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between f07f6bf587b22f2c50a023f716837e70d80bcf29 and b55ba0aecbf5bae02c3f98acdaff88861624b3a8. </details> <details> <summary>📒 Files selected for processing (31)</summary> * `.github/workflows/build-client-server.yml` * `.github/workflows/mcp-build.yml` * `app/client/packages/mcp/build.js` * `app/client/packages/mcp/src/app.test.ts` * `app/client/packages/mcp/src/app.ts` * `app/client/packages/mcp/src/builder/builder.test.ts` * `app/client/packages/mcp/src/builder/capabilities.ts` * `app/client/packages/mcp/src/builder/compile.ts` * `app/client/packages/mcp/src/builder/layout.ts` * `app/client/packages/mcp/src/builder/presets.ts` * `app/client/packages/mcp/src/builder/schema.ts` * `app/client/packages/mcp/src/builder/templates.ts` * `app/client/packages/mcp/src/server.ts` * `app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverter.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/managers/McpTokenAuthenticationManager.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/tokens/McpTokenAuthentication.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/constants/RateLimitConstants.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/McpTokenControllerCE.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/UserMcpTokenCE.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/McpTokenResponseDTO.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/ratelimiting/RateLimitConfig.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/ratelimiting/ce/RateLimitServiceCE.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/ratelimiting/ce/RateLimitServiceCEImpl.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserMcpTokenServiceImpl.java` * `app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java` * `app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/McpTokenAuthenticationWebFilterTest.java` * `app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverterTest.java` * `app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/managers/McpTokenAuthenticationManagerTest.java` * `app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserMcpTokenServiceImplTest.java` * `deploy/docker/fs/opt/appsmith/templates/supervisord/application_process/mcp.conf` * `docs/plans/2026-07-11-mcp-app-builder-design.md` </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (8)</summary> * deploy/docker/fs/opt/appsmith/templates/supervisord/application_process/mcp.conf * app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverterTest.java * app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/UserMcpTokenCE.java * app/client/packages/mcp/build.js * app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/converters/McpTokenAuthenticationConverter.java * app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/McpTokenControllerCE.java * app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/McpTokenAuthenticationWebFilterTest.java * .github/workflows/build-client-server.yml </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
Failed server tests
|
…on-reservation leak From adversarial + UX + architect reviews: - healthcheck.sh: only health-check the mcp supervisord program when it exists, so a container with MCP disabled (the default) is not reported unhealthy fleet-wide. - Surface token expiry in the profile UI (list rows + created-token modal) — the server already returned expiresAt; the client was discarding it, so agents would die silently at 90 days. Fail closed on a null expiry server-side. - Release the MCP session reservation at onsessioninitialized (registration) instead of only in finally, so a stalled initialize SSE stream cannot pin the reservation and saturate the session caps. - Exclude packages/mcp from the client tsconfig (it has its own tsc, like rts) and use as-unknown-as casts in McpTokenApi so the client check-types passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java (1)
38-65: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚖️ Poor tradeoffCount-then-save is a TOCTOU on the active-token cap.
countByUserIdAndDeletedAtIsNullfollowed bysaveisn't atomic, so concurrentcreaterequests for the same user can each pass the check and push the active count pastMAX_ACTIVE_TOKENS_PER_USER. Low blast radius (a soft safety cap), so fine to defer — flagging for awareness.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java` around lines 38 - 65, Update UserMcpTokenServiceCEImpl.create so enforcing MAX_ACTIVE_TOKENS_PER_USER is atomic with token creation, preventing concurrent requests from passing the count check and exceeding the active-token cap. Replace the separate countByUserIdAndDeletedAtIsNull-then-save flow with the repository or transaction-level mechanism used for atomic enforcement, while preserving the existing error and response behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@deploy/docker/fs/opt/appsmith/healthcheck.sh`:
- Around line 3-8: Preserve health checks for supervisor-managed mongo and redis
while making MCP checks conditional. In the health-check logic around the
processes list and supervisorctl status invocation, revert to querying
unfiltered supervisor status so absent MCP remains naturally excluded, or
conditionally add mongo and redis alongside mcp if retaining filtering; ensure
the existing mongo and redis branches remain reachable.
- Line 5: Update the health-check branch that currently matches “server” to
match “backend”, aligning it with the backend entry in the processes list so the
HTTP endpoint probe runs in addition to the RUNNING check; alternatively,
support both names without changing the existing probe behavior.
---
Nitpick comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.java`:
- Around line 38-65: Update UserMcpTokenServiceCEImpl.create so enforcing
MAX_ACTIVE_TOKENS_PER_USER is atomic with token creation, preventing concurrent
requests from passing the count check and exceeding the active-token cap.
Replace the separate countByUserIdAndDeletedAtIsNull-then-save flow with the
repository or transaction-level mechanism used for atomic enforcement, while
preserving the existing error and response behavior.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 02f59e36-1a8a-4bd3-bbc4-72dc6e52768c
📒 Files selected for processing (8)
app/client/packages/mcp/src/app.tsapp/client/src/api/McpTokenApi.tsapp/client/src/ce/constants/messages.tsapp/client/src/pages/UserProfile/McpTokens.test.tsxapp/client/src/pages/UserProfile/McpTokens.tsxapp/client/tsconfig.jsonapp/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserMcpTokenServiceCEImpl.javadeploy/docker/fs/opt/appsmith/healthcheck.sh
✅ Files skipped from review due to trivial changes (1)
- app/client/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (5)
- app/client/src/ce/constants/messages.ts
- app/client/src/api/McpTokenApi.ts
- app/client/src/pages/UserProfile/McpTokens.test.tsx
- app/client/src/pages/UserProfile/McpTokens.tsx
- app/client/packages/mcp/src/app.ts
CI server-unit-tests failures: - CsrfTest: the MCP AuthenticationWebFilter, added at AUTHENTICATION order with a match-any matcher, collided with the form-login filter and broke POST /api/v1/login (No provider found for UsernamePasswordAuthenticationToken -> 500). Restrict the filter to only engage for 'Bearer mcp_' requests so it never touches other auth flows. - McpTokenAuthenticationManagerTest: make the shared rate-limit stub lenient (tests that reject non-MCP / rate-limited requests never reach it) and wrap the failure-path tryIncreaseCounter in Mono.defer so it is not eagerly evaluated on the success path (which NPE'd on the unstubbed mock). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failed server tests
|
Failed server tests
|
- SecurityConfig: add the MCP AuthenticationWebFilter BEFORE the AUTHENTICATION order instead of AT it. Two AuthenticationWebFilters at the same order break form-login's manager wiring, causing POST /api/v1/login to 500 (CsrfTest failure). - McpTokens.test.tsx: assert the full monospace font-family stack that actually renders; toHaveStyle requires the exact value, not a prefix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failed server tests
|
Root cause of the CsrfTest failure: McpTokenAuthenticationManager was the only ReactiveAuthenticationManager @component in the server, so Spring Boot auto-wired it as the global default authentication manager. Form-login (which sets no explicit manager) then used it and rejected UsernamePasswordAuthenticationToken with 'No provider found' -> POST /api/v1/login returned 500. Fix: drop @component from the manager and construct it directly in SecurityConfig for the MCP filter only, so the context has no global ReactiveAuthenticationManager bean and form-login resolves its default (UserDetailsService-based) manager as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds oracle and redshift to create_datasource. Both plugins' form.json
datasource config is the SAME endpoints[host,port] +
authentication{databaseName,username,password} + connection.mode shape the
existing postgres/mysql/mssql/mongo path already builds (verified against
oraclePlugin/redshiftPlugin form.json), so they reuse the builder verbatim —
default ports 1521 / 5439, created UNCONFIGURED with the password completed
in the Appsmith UI (credentials are never carried by MCP). Both are SQL, so
redshift-plugin joins SQL_PLUGIN_IDS (oracle already there) and create_query
works against them unchanged.
Per the "connect + reuse existing query builders" scope: Snowflake and
Databricks are intentionally NOT included — their datasource config is
bespoke (account/warehouse or host/httpPath+token, no endpoints[host,port]),
so they fit neither this shared builder nor the reuse constraint. They would
each need their own connection-config vocabulary (deferred).
No new config shape or injection surface (host/databaseName/username charset
gates unchanged), so no council — same posture as the existing DB path.
910 mcp tests pass (141 app.test); check-types, eslint, prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
…ilder
Adds a closed-vocabulary Redis command builder, the KV analog of
create_query / create_mongo_query. The agent never authors a raw Redis
command string or raw bindings — it passes { command, key, value?,
field?, seconds?, start?, stop? } and the compiler emits the plugin's
single-command actionConfiguration.body.
Injection model: the Redis plugin has no parameterization — it tokenizes
the body and runs exactly ONE command via jedis.sendCommand(cmd, args[])
(args passed separately, so no multi-command injection is possible). The
builder keeps it provably one command by (1) allow-listing the verb
(25 read/write commands; NO EVAL/SCRIPT/CONFIG/MODULE/FLUSH*/SHUTDOWN),
and (2) gating every literal arg to a single token (no whitespace/quote/
control/binding syntax). A widget arg emits a bare {{ Widget.prop }} that
eval resolves before the plugin tokenizes — still one command (a resolved
value with whitespace only changes arg COUNT, a runtime correctness
caveat, never command identity).
Like create_sheets_query, the Redis datasource is created in the Appsmith
UI (host/port, optional auth — its DB-index/optional-auth shape doesn't
fit the shared database builder) and queried here by datasourceId. Same
server-authoritative workspace resolution + datasource-accessibility
guard as create_mongo_query (no IDOR); data-flag + git-branch gated.
Council-review: security-reviewer / qa-engineer seated → APPROVE /
APPROVE-WITH-RISKS (no BLOCK). Security confirmed no injection bypass, a
clean allow-list, and the single-command guarantee. Applied QA's cheap
coverage follow-ups (HGET/HDEL compile, literal-omits-binding, Redis
idempotency).
PROVISIONAL pending live-DP verification (per the tool description):
validate a GET + a SET-with-binding against a real Redis datasource
before removing the notice. 914 mcp tests pass; types/lint/prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
…der)
Adds a closed-vocabulary chat-completion builder for the three AI plugins
(appsmithAi excluded per product decision) — the marquee "AI app" case
inside the closed vocabulary. The agent passes { model, messages,
maxTokens?, temperature? }; each message is { role, content } where
content is { literal } or { widget, property } — bind an input's text to
drive the prompt.
The tool resolves the provider from the datasource's plugin packageName
and compiles the provider-appropriate formData: OpenAI/Anthropic use
command "CHAT" + chatModel; Google AI uses "GENERATE_CONTENT" +
generateContentModel + a per-message type:"text"; maxTokens/temperature
apply to OpenAI/Anthropic only. Literal content is JSON-encoded (cannot
break out of its JSON position) and RAW_EXPRESSION-gated; a widget
reference emits {{ Widget.prop }} and registers formData.messages.data as
a binding. Model ids are charset-gated. No API key is ever accepted or
emitted (entered in the Appsmith UI). Same server-authoritative workspace
+ datasource-accessibility guard as create_mongo_query (no IDOR);
data-flag + git-branch gated.
Council-review: security-reviewer / qa-engineer seated → APPROVE-WITH-
RISKS / APPROVE-WITH-RISKS (no BLOCK). Security confirmed no
cross-trust-boundary injection (JSON.stringify escaping + single-pass
eval; the widget-value JSON-corruption is a same-tenant robustness
caveat, now documented). Applied QA's idempotency test.
PROVISIONAL pending live-DP verification (per the tool description):
confirm each provider accepts the emitted formData and that a {{ }} bind
inside messages.data resolves at runtime, before removing the notice.
924 mcp tests pass; types/lint/prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
Adds a closed-vocabulary S3 file-action builder. The agent picks an
operation and supplies a bucket/key/body; the compiler emits the S3
plugin's actionConfiguration.formData:
- list { bucket, prefix? } → LIST (unsigned object paths)
- read { bucket, path } → READ_FILE
- upload { bucket, path, body } → UPLOAD_FILE_FROM_BODY
- delete { bucket, path } → DELETE_FILE
Same proven safety contract as create_mongo_query: smartSubstitution is
forced ON so widget references (path/body) are PARAMETERIZED at runtime,
not string-concatenated. Bucket is DNS-charset-gated, keys are
path-charset-gated, literal upload bodies are RAW_EXPRESSION-gated. The
allow-list excludes bucket-wide/multi-file ops (no LIST_BUCKETS, no
DELETE_MULTIPLE). No AWS credentials are accepted or emitted (entered in
the Appsmith UI). Same server-authoritative workspace + datasource
guard (no IDOR); data-flag + git-branch gated.
Council-review: security-reviewer seated → APPROVE (no BLOCK) — judged a
low-novelty faithful clone of the reviewed Mongo builder with tighter
S3-specific charset gates; no injection/escalation/IDOR/secret exposure.
The panel was right-sized to a solo security check per repo guidance
since the pattern is already blessed.
PROVISIONAL pending live-DP verification (per the tool description).
932 mcp tests pass; types/lint/prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
Adds the fourth and final requested query builder. A GraphQL operation is
itself a query language, so — unlike SQL/Mongo — the compiler accepts the
operation STRING plus a separate structured VARIABLES map. Runtime data
enters ONLY through GraphQL variables (parameterized); the operation
string is agent-authored but gated, consistent with the existing
create_rest_api posture (which already accepts an agent-authored body).
The GraphQL plugin stores a REST POST: body = the operation,
pluginSpecifiedTemplates[0]=true (JSON smart substitution ON → bound
variables are JSON-encoded), [1] = the variables JSON. The operation is
gated against a binding-OPEN sequence ({{ , ${ , backtick) but ALLOWS }}
(a legitimate GraphQL closing-brace pair — an Appsmith binding requires
the {{ open), and must start with {/query/mutation/subscription. Variable
names are identifier-gated; values are literals (JSON-encoded) or widget
refs emitted as SAFE_BINDING-checked {{ }} mustaches. Same
server-authoritative workspace + datasource guard (no IDOR); data-flag +
git-branch gated.
Council-review: security-reviewer / qa-engineer seated → APPROVE-WITH-
RISKS / APPROVE-WITH-RISKS (no BLOCK). Security confirmed the {{-only gate
is bypass-proof (body stored verbatim, no post-gate normalization) and
the design is consistent with create_rest_api. Applied both follow-ups: a
SAFE_BINDING re-check on emitted variable bindings (parity with restApi),
and edge tests (graphql idempotency, all-literal variables).
PROVISIONAL pending live-DP verification: confirm the plugin honors the
pluginSpecifiedTemplates layout and that a {{ }} in [1].value resolves +
JSON-escapes at runtime, before removing the notice.
939 mcp tests pass; types/lint/prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
… roadmap Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
Closes the deferred widget backlog so all of Part A can be verified in one
live-DP pass:
- richtext (RICH_TEXT_EDITOR_WIDGET) — a WYSIWYG editor. Its defaultText
renders as HTML, so the agent's defaultValue is gated to PLAIN TEXT:
safeText + a refine rejecting any `<`/`>`. No tag can be authored, so
the seed renders as an inert text node; users author rich content at
runtime. inputType is locked to "html" (schema is .strict(), no
agent-settable inputType/markdown path).
- jsonform (JSON_FORM_WIDGET) — a form whose fields auto-generate from a
data object. Emits sourceData = JSON.stringify({field → scalar}) with
autoGenerateForm:true, schema:{}; keys are columnKey-gated, values are
scalarCell. No binding is emitted (literal JSON only).
- statbox (STATBOX_WIDGET) — a KPI stat card. Composed via the container
path: the compiler stacks caption/value/subtext TEXT children (+ an
optional corner ICON_BUTTON) built from gated title/value/subtext/icon
into the inner canvas — pure composition of already-reviewed widgets.
Council-review: security-reviewer seated → APPROVE (no BLOCK). Confirmed
the richtext `<`/`>` gate makes the HTML sink render inert text (entities
don't re-parse), jsonform's literal JSON can't break out or bind, and
statbox adds no new sink. Solo review — right-sized (richtext is the only
real surface; the gate is airtight).
PROVISIONAL pending live-DP: confirm jsonform auto-derives fields from the
emitted sourceData string, and that statbox's stacked composition renders
acceptably as a KPI card. 945 mcp tests pass; types/lint/prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29757826796. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
Live-DP verification found the CHART_WIDGET rejects chartType
"SCATTER_CHART" at runtime ("Error in Chart Data/Configuration:
Disallowed value: SCATTER_CHART"). The widget's allowedValues
(ChartWidget/widget/propertyConfig.ts) are exactly LINE/BAR/PIE/COLUMN/
AREA (+ the CUSTOM_ECHART/CUSTOM_FUSION we exclude); SCATTER_CHART exists
only as an ECharts sub-type reachable through CUSTOM_ECHART, not as a
standalone type. Ch1's chart-type expansion was wrong — there are no
extra fixed types beyond the original five.
Removed SCATTER_CHART from the chartType enum, the WidgetSpec type, the
capability catalog, and the Ch1 test (now COLUMN_CHART). Ch1's multi-
series binding, axis labels, and data labels are unaffected and were
confirmed rendering on the live DP (a COLUMN_CHART with two series shows
grouped bars + legend + value labels + X/Y axis titles).
945 mcp tests pass; check-types, prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
Live-DP finding: an app built via MCP left its data-bound table/text EMPTY
on load. The MCP creates queries AFTER the app layout, so the server's
on-load binding analysis never marks them executeOnLoad — every generated
query defaulted to "Manual" run behaviour, so a widget bound to
{{ Query.data }} showed nothing until the query was triggered by hand.
Fix: the query builders now set executeOnLoad on the created action for
READ/FETCH operations, so a bound widget populates on load without a
manual trigger. Mutating operations stay manual (user-triggered):
- create_query: SELECT → true; INSERT/UPDATE/DELETE → false
- create_sheets_query: read → true; append → false
- create_mongo_query: FIND → true; INSERT → false
- create_rest_api: GET → true; POST/PUT/PATCH/DELETE → false
- create_redis_query: read commands (GET/HGETALL/LRANGE/...) → true
- create_graphql_query: query → true; mutation/subscription → false
Verified end-to-end on the deploy preview: a Sheets-backed task app whose
GetTasks (read) query now populates the table on load, while AddTask
(append) stays manual behind the form's button. create_sheets_query read
+ append are both confirmed working live against a real Google Sheet.
946 mcp tests pass; check-types, prettier clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N19EppQVZ55hwByVy4HEX8
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29769390687. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
…ection The four MCP settings (server toggle, data tools, JS objects, token lifetime) move out of Instance > Configuration into a dedicated "MCP Server (BETA)" left-nav entry under Instance, with the robot-2 icon. New mcp-server category slug; settings themselves are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GxaT27Svc98pbqTV2e7ht9
Every MCP-built app got the hardcoded #4F70FE card. Pick from the same pastel palette (appColors) and icon set (AppIconCollection) the Applications UI uses, indexed by an FNV-1a hash of the app name so compileApp stays deterministic (same spec -> byte-identical artifact). Lists are copied in because the MCP package doesn't depend on the client bundle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GxaT27Svc98pbqTV2e7ht9
Every build_application/edit_page output now gets automatic visual design, inferred from spec structure alone (agents still supply no style values): - roles: pageTitle, sectionHeader, kpi, field, action, body - typography scale per role (title 1.875rem, headers 1.25rem, KPIs 1.5rem centered; body text drops the old always-bold default) - row packing: fields pair two-up, 2-4 KPIs share a row, buttons after fields right-align like a form footer; narrow canvases (modals) fall back to full-width stacking - spacing rhythm: 1-row gaps within groups, 3 before sections, 2 after the title Deterministic (pure function of the spec), no wrapper containers, and identical treatment on build and edit paths. Reviewed by the architect/security/QA panel; all findings addressed, including a key-allowlist regression test pinning the compiler-owned prop surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GxaT27Svc98pbqTV2e7ht9
Two agent-quality upgrades from live task-app feedback: Screenshot decoding (the server never sees images — the calling agent does): a `screenshot` guide teaching a three-pass method (layout skeleton -> widget mapping with honest approximations -> theme/accent extraction), a `recreate_from_screenshot` MCP prompt walking agents through it, and three approximation presets — view-switcher (tabs over views), status-board (kanban stand-in), timeline (gantt stand-in: bar chart + table). Discovery wired through get_capabilities and the connect-time server instructions. Close the loop (an app that shows stale data or dead-end selections is broken, not minimal): an advisory write classifier (non-GET REST / mutating SQL keyword; separate from the fail-closed run_action gate) powers a wire_event `refreshHint` when a write is wired with no follow-up read, plus two inspect_page rules — `write-no-refresh` and `selection-unused` (query-bound tables only, live inspection only, store-bound results tables excluded). Server instructions and the CRUD recipe now carry the checklist: every write chains a re-read; every actionable table's selection must feed something. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GxaT27Svc98pbqTV2e7ht9
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29851749566. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
…caveat get_datasource_structure now walks the Google Sheets hierarchy (spreadsheets -> sheet names -> header-row columns) over the datasource trigger endpoint with server-chosen selector requestTypes, so agents discover sheetUrl/sheetName/columns instead of guessing. run_action auto-runs Sheets FETCH_MANY/FETCH_DETAILS reads via a strict fail-closed classifier: the action's plugin AND datasource must both resolve to the Sheets package in server-authoritative workspace lists, with the workspace derived from the application rather than the action document. create_sheets_query's PROVISIONAL note is replaced after live end-to-end verification of read + append against an authorized datasource. Council: security + architect + qa reviewed; security's initial BLOCKED (plugin-name identity fallback spoofable via a planted action document) resolved with strict resolution, datasource verification, and regression tests. headerRow capped at 100 so the columns selector cannot page through row data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHtV6vMJYDd1chRfnzn1H8
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29865930552. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
Live verification on the DP surfaced a 403: the server-side McpAllowlistWebFilter mirrors the Node API client's endpoint set, and get_datasource_structure's new Sheets discovery posts to /api/v1/datasources/{id}/trigger, which was not on the allowlist. Adds the POST trigger rule (the endpoint enforces datasource EXECUTE permission per request; the MCP only ever sends the three closed selector requestTypes) plus a filter test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHtV6vMJYDd1chRfnzn1H8
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29868365983. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
…ist contract test Three externally-reported findings, all verified and fixed. (1) prepare/confirm commit and publish bound their confirmations to a page-list-only fingerprint, blind to widget/query/JS/theme drift between prepare and confirm; a new fingerprintAppContent composes the page list, every page's layout DSL, actions with updatedAt, JS collections, and theme (id-sorted, fail-closed reads), and prepare_publish now returns the content revision confirm_publish requires. (2) McpAllowlistWebFilter was missing every git-flow route and allowed PUT (unserved) instead of PATCH for JS-collection updates, 403ing the advertised git tools and update_js_object for real MCP tokens; five git rules added and the verb corrected. (3) routeContract.test.ts now parses ALLOW_RULES out of the Java filter (comment-insensitive) and asserts every Node wrapper route is allowlisted, closing the gap class for good. Drift regression tests added at widget (commit) and query (publish) level. Targeted security review: APPROVE WITH RISKS, land as-is; its two robustness recommendations (id-sorted fingerprint arrays, comment-insensitive allowlist parse) are included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHtV6vMJYDd1chRfnzn1H8
Hacktron Security Check - SkippedReason: Billing required for Code Review seats
|
|
Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/29927826426. |
|
Deploy-Preview-URL: https://ce-41980.dp.appsmith.com |
What & why
Adds an opt-out, same-instance Streamable HTTP MCP server that lets an MCP client (e.g. an AI agent) act as a specific Appsmith user. The client authenticates with a user-scoped bearer token; the Node service forwards that token to the existing
/api/v1endpoints, so Spring Security reconstructs the real user and the existing workspace/app/page ACLs authorize every operation. No privileged or instance-wide credential is used.How it works
Server (CE, EE-overridable)
UserMcpTokendomain + repository + service +McpTokenControllerfor create / list / revoke of user-scoped tokens. Every layer follows the CE-base + thin concrete-subclass split (*CE/*CEImpl) so EE can override.AuthenticationWebFilter(only engages for themcp_prefix) reconstructs the token owner. Invalid / revoked / disabled-user tokens return 401.Migration076creates theuserMcpTokenindexes (the instance runs withauto-index-creation=false, so@Indexedalone is inert).Node service (
app/client/packages/mcp)/healthendpoint, request-body size cap, per-request token revalidation, per-session token binding (constant-time compare), and per-user + global session caps.list_workspaces,list_applications,get_application_context, andimport_application_artifact/import_partial_application_artifact— writes go through the existing validated import / partial-import APIs (no raw DSL/Mongo writes).Client
Rollout / deploy
APPSMITH_MCP_ENABLED=1gates both the supervisord autostart and the Caddy/mcproute; a disabled instance never starts the Node process and returns 404 for/mcp. Existing instances are unaffected until an admin opts in and a user deliberately issues + uses a token.mcp-buildCI workflow, and a/mcp/healthroute test.Testing
mcp_/ anonymous → 401), session binding & revalidation, TTL/expiry, per-user (429) and global (503) caps, artifact validation, malformed/oversized body handling. All pass.check-types, ESLint (0 errors), and the MCP package typecheck locally.Security review
Reviewed by a multi-agent council (architecture, security, QA, data-migration, DX, UX, product). Key hardening landed from that review: 401 (not 500) on bad tokens, the CE/EE split, the index migration, the
mcp_-prefix + non-anonymous/megate (closes an unauthenticated-session DoS), per-user session cap, and the opt-in deploy gate.Follow-ups (tracked, not blocking an opt-in ship)
🤖 Generated with Claude Code
Automation
/ok-to-test tags="@tag.All"
Summary by CodeRabbit
Summary
/mcpand/mcp/healthrouting, included in Docker images/packaging workflows.Warning
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/29927822227
Commit: a101e09
Cypress dashboard.
Tags: @tag.All
Spec:
It seems like no tests ran 😔. We are not able to recognize it, please check workflow here.
Wed, 22 Jul 2026 20:28:49 UTC