Skip to content

feat: add TypeScript/Skybridge emitter (--target skybridge) - #10

Merged
gabrielekarra merged 6 commits into
mainfrom
feat/skybridge-emitter
May 8, 2026
Merged

feat: add TypeScript/Skybridge emitter (--target skybridge)#10
gabrielekarra merged 6 commits into
mainfrom
feat/skybridge-emitter

Conversation

@gabrielekarra

@gabrielekarra gabrielekarra commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds --target skybridge to both generate and build subcommands, generating a TypeScript MCP server + ChatGPT App with LLM-generated React views per tool
  • New emitter at src/mcp_anything/emit/typescript_skybridge/ with phase.py and prompts.py; wired into engine.py
  • Adds call_llm_for_text to llm_client.py for free-form text generation (used by Skybridge for React view source)
  • Ships examples/spotify-playlist-generator/ as a worked end-to-end example with three generated React views

Test plan

  • Run pytest tests/test_emit_skybridge.py -v — new Skybridge emitter unit/integration tests
  • Run mcp-anything build --brief examples/spotify-playlist-generator/spotify-playlist-domain.yaml --target skybridge and verify the skybridge/ output tree matches the example
  • Confirm --target fastmcp and --target mcp-use still work unchanged
  • Check mcp-anything build --help shows skybridge in choices

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a TypeScript/Skybridge codegen backend with CLI support and automatic React UI views
    • Included a complete Spotify playlist-generator example (server, UI, build/dev configs, and Docker)
  • Documentation

    • New docs and README for the Spotify example and Skybridge usage
    • Added tool specs, domain model, quick queries, and conformance report scaffold
  • Tests

    • New unit tests validating emitted Skybridge project structure and artifacts
  • Chores

    • Conformance reports now explicitly mark skipped evaluations when live eval is not run

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@gabrielekarra has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 47 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfd7d36c-a86f-4a78-9c7f-367b0b65eef3

📥 Commits

Reviewing files that changed from the base of the PR and between e677169 and a59ffa2.

📒 Files selected for processing (13)
  • examples/spotify-playlist-generator/server/Dockerfile
  • examples/spotify-playlist-generator/server/eval_cases.json
  • examples/spotify-playlist-generator/server/skybridge/Dockerfile
  • examples/spotify-playlist-generator/server/skybridge/README.md
  • examples/spotify-playlist-generator/server/skybridge/package.json
  • examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx
  • src/mcp_anything/analysis/flask_fastapi_analyzer.py
  • src/mcp_anything/cli.py
  • src/mcp_anything/emit/typescript_skybridge/phase.py
  • src/mcp_anything/emit/typescript_skybridge/prompts.py
  • src/mcp_anything/pipeline/llm_client.py
  • src/mcp_anything/pipeline/tool_design.py
  • tests/test_flask_fastapi.py
📝 Walkthrough

Walkthrough

Adds a TypeScript/Skybridge emit phase and emitter to the mcp-anything pipeline and a complete Spotify playlist-generator example: TypeScript MCP server, three React Skybridge views, discovery/telemetry, build/runtime configs, Dockerfiles, evaluation fixtures, tests, and conformance/reporting updates.

Changes

Skybridge Emit Backend

Layer / File(s) Summary
Schema & Public Contracts
src/mcp_anything/models/validation.py, examples/.../skybridge/.skybridge/views.d.ts, src/mcp_anything/emit/typescript_skybridge/__init__.py, domain/tool spec files
Adds ConformanceReport.skipped and view-name augmentation entries; public contract/type entries for Skybridge views and package export.
Prompt Contract & Helpers
src/mcp_anything/emit/typescript_skybridge/prompts.py, src/mcp_anything/pipeline/llm_client.py
Adds build_view_prompt(...), _to_pascal(...), and call_llm_for_text(...) to drive Claude-based TSX view generation with strict output constraints.
Core Emitter Implementation
src/mcp_anything/emit/typescript_skybridge/phase.py
Implements TypeScriptSkybridgeEmitPhase and TypeScriptSkybridgeEmitter to generate server.ts, per-tool TSX views (LLM or placeholders), discovery/telemetry, Vite/HTML, Dockerfile, package.json, tsconfig.json, README; optional tsc checks and contract validation.
Pipeline Integration
src/mcp_anything/pipeline/engine.py, src/mcp_anything/emit/typescript_skybridge/__init__.py
Engine dispatches --target skybridge to the new emit phase; package re-exports the new phase.
Validation Model & Harness
src/mcp_anything/models/validation.py, src/mcp_anything/pipeline/validation_harness.py
Adds skipped flag to ConformanceReport and makes harness emit a skipped/unvalidated report when live eval is not requested.
Tests / CI
tests/test_emit_skybridge.py
New tests validating emitter output layout, server/package/tsconfig/Dockerfile contents, placeholder views, Zod/HTTP rendering, emitted file list, and contract checks.
Docs
CLAUDE.md
Adds TypeScript/Skybridge as a Phase 3 emit target and references the new emitter implementation file.

Spotify Playlist Generator Example

Layer / File(s) Summary
Domain & Tool Specs
examples/spotify-playlist-generator/spotify-playlist-domain.yaml, .../server/tool_spec.yaml, .../server/descriptions.yaml, .../server/domain_model.json
Defines server/domain metadata, three tools (generate_spotify_playlist, setup_spotify_oauth, exchange_spotify_code), parameters, use cases, and glossary; configures transport/telemetry/discovery.
Evaluation & Quick Queries
examples/spotify-playlist-generator/server/eval_cases.json, .../quick_queries.json, .../conformance_report.json
Six evaluation cases, three quick queries, and an initial conformance report (marked skipped when eval not run).
Server Implementation
examples/spotify-playlist-generator/server/skybridge/src/server.ts
TypeScript Skybridge MCP server: spotifyFetch wrapper, token strategies, OAuth helpers, mood-to-search, duration-based selection, optional playlist creation (batched), Zod input validation, discovery endpoint, and telemetry instrumentation.
Discovery & Telemetry
examples/spotify-playlist-generator/server/skybridge/src/discovery.ts, .../telemetry.ts
Discovery metadata provider and telemetry recorder that logs locally and optionally posts to a remote endpoint with a 1s timeout.
React Tool Views
examples/spotify-playlist-generator/server/skybridge/src/views/*.tsx, .../.skybridge/views.d.ts
Three React views using useCallTool, response-shape normalization helpers, UI for preview/create/OAuth exchange, and TypeScript augmentation for view names.
Build & Runtime
examples/spotify-playlist-generator/server/skybridge/package.json, .../tsconfig.json, vite.config.ts, index.html
Package metadata (Node 22+/pnpm engines), dependencies (skybridge, zod, React), tsconfig with strict/react-jsx, Vite plugin, and HTML entrypoint.
Docker & Deployment
examples/spotify-playlist-generator/server/Dockerfile, examples/.../skybridge/Dockerfile
Multi-stage Docker builds: builder installs pnpm deps and builds; runtime copies dist and node_modules into node:22-slim, sets MCP transport env, and runs node dist/server.js.
Docs & SKILL
examples/spotify-playlist-generator/server/skybridge/README.md, examples/.../SKILL.md
README and SKILL.md document server purpose, auth flows, tool parameters, quick-start, and required environment variables.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Type-MCP/mcp-anything#9: Introduced the domain-pipeline/emit architecture and base EmitPhase infrastructure extended here with the TypeScript/Skybridge emit phase and example.

Poem

🐰 A Skybridge appears, a third path bright,
Where TypeScript servers and React take flight,
Views and tools hum for ChatGPT's delight,
Playlists bloom from mood in evening light,
The pipeline hops forward — code done right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new TypeScript/Skybridge emitter backend with the --target skybridge option.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/skybridge-emitter

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 19

♻️ Duplicate comments (1)
examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx (1)

8-28: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same unguarded JSON.parse issue as parseData in exchange_spotify_code.tsx.

getContent has the identical structural defect — every matched branch calls JSON.parse and returns immediately with no try/catch, so malformed JSON from the server will crash the component. The fix and the shared-utility refactor described in the exchange_spotify_code.tsx comments apply here equally.

🤖 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
`@examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx`
around lines 8 - 28, The getContent function currently calls JSON.parse on
multiple branches without error handling; wrap each JSON.parse call in a
try/catch (or better, extract a shared safeParseJson utility and call it) so
malformed JSON doesn't throw and crash the component; update getContent to use
the safeParseJson(resultString) helper (or inline try/catch) and return a
sensible fallback (null or the original value) on parse failure, referencing the
getContent function and the new safeParseJson helper to locate and change the
code.
🧹 Nitpick comments (2)
examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx (1)

9-29: ⚡ Quick win

parseData / getContent are duplicated verbatim across both view files.

The same normalization logic appears as parseData here and as getContent in setup_spotify_oauth.tsx (and almost certainly in generate_spotify_playlist.tsx as well). Extract it to a shared utils/parseToolData.ts file so the fix to the JSON.parse issue above only needs to be applied once.

♻️ Proposed refactor

Create src/views/utils/parseToolData.ts:

export function parseToolData(data: unknown): any {
  if (!data) return null;
  if (typeof data === "object" && "result" in data && typeof (data as any).result === "string") {
    try { return JSON.parse((data as any).result); } catch { return (data as any).result; }
  }
  // ... remaining branches
  return data;
}

Then in each view:

-import { useState } from "react";
+import { useState } from "react";
+import { parseToolData } from "./utils/parseToolData";
 
-function parseData(data: unknown): any { ... }
 
 function ExchangeSpotifyCodeView() {
   ...
-  const payload = parseData(data);
+  const payload = parseToolData(data);
🤖 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
`@examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx`
around lines 9 - 29, Duplicate parsing logic exists as parseData and getContent;
extract and consolidate it into a single exported function parseToolData in a
new module (e.g., src/views/utils/parseToolData.ts) and import it where
parseData/getContent are used (setup_spotify_oauth.tsx,
generate_spotify_playlist.tsx, exchange_spotify_code.tsx). Update parseToolData
to preserve the existing branches but wrap each JSON.parse call in try/catch and
return the original string (or the original field) on parse failure instead of
throwing; keep the same null/array/object handling and return the raw data as
the final fallback. Ensure callers replace parseData/getContent with
parseToolData to remove duplication.
src/mcp_anything/emit/typescript_skybridge/phase.py (1)

297-308: 🏗️ Heavy lift

MCP resources and prompts are never generated alongside tools.

_emit_views generates one React TSX view per tool but _emit_server only registers tools and the single discovery resource. The coding guidelines require: "MCP resources and prompts must be generated alongside tools to enable rich context for LLM tool use." Domain context (description, use cases, glossary) is already available in self.domain_model and could be serialised as MCP resource entries and prompt templates in server.ts.

As per coding guidelines: "MCP resources and prompts must be generated alongside tools to enable rich context for LLM tool use."

🤖 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 `@src/mcp_anything/emit/typescript_skybridge/phase.py` around lines 297 - 308,
Update the emit pipeline to serialize MCP "resource" and "prompt" entries
alongside each tool: in _emit_views (and/or in _emit_server) after collecting
domain_desc, use_cases, and glossary from self.domain_model, create MCP resource
objects and prompt templates for each tool in self.design.tools and ensure they
are written/registered to server.ts the same way tools are registered; reference
_emit_views, _emit_server, _generate_view, self.domain_model, and
self.design.tools to locate where to add creation of resource entries
(serialising domain_desc/use_cases/glossary) and prompt templates and then call
the same write/registration path used for tools so each tool has an accompanying
MCP resource and prompt.
🤖 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 `@examples/spotify-playlist-generator/server/conformance_report.json`:
- Around line 61-65: The report currently sets "passed": true even when
"coverage_ratio": 0.0 < "threshold": 0.8 and "eval_run": false, which is
misleading; update the report-generation logic that emits the "passed" field
(the code that writes "passed", "coverage_ratio", "threshold", and "eval_run")
so that when eval_run is false you do not claim success — either set "passed" to
false or null, or add an explicit "skipped": true field alongside "eval_run":
false; ensure the emitted JSON reflects the chosen approach consistently
whenever no evaluation has run.

In `@examples/spotify-playlist-generator/server/Dockerfile`:
- Around line 12-19: The runtime Docker stage runs as root; add a non-root user
and set ownership of /app so the server doesn't run as root: create a user (and
group) in the runtime stage, chown the WORKDIR (/app) and copied artifacts to
that user, and add a USER directive before CMD (referencing the Dockerfile
WORKDIR /app, COPY --from=builder lines, ENV MCP_TRANSPORT and CMD
["node","dist/server.js"] to locate where to apply changes).

In `@examples/spotify-playlist-generator/server/eval_cases.json`:
- Around line 6-8: The eval_cases.json file contains placeholder test cases with
empty "input_params", empty "expected_output_pattern", and null
"expected_error"; update each referenced case (including the blocks at lines
6-8, 15-18, 24-27, 33-36, 42-45, 51-54) to include realistic "input_params"
objects matching the server API (e.g., seed tracks, mood, length), set a
meaningful non-empty "expected_output_pattern" (regex or substring that must
appear in successful playlist responses), and for edge cases provide explicit
"expected_error" values (error codes or messages) instead of null so the eval
can assert failure paths; ensure field names remain "input_params",
"expected_output_pattern", and "expected_error" so existing eval runner (which
reads those keys) continues to work.

In `@examples/spotify-playlist-generator/server/skybridge/Dockerfile`:
- Around line 13-22: The final Dockerfile stage runs as root; create a non-root
user and group (e.g., "appuser"), chown the WORKDIR and the copied artifacts
(dist, node_modules, package*.json) to that user after the COPY steps, and
switch to that user before CMD by adding a USER instruction; update references
around WORKDIR, COPY, and CMD in the final stage to ensure the process runs
unprivileged.

In `@examples/spotify-playlist-generator/server/skybridge/package.json`:
- Line 17: Replace the unstable "latest" tags with explicit semver ranges to
make installs reproducible: update the skybridge dependency entries for
"skybridge" and "@skybridge/devtools" (the JSON keys named "skybridge" and
"@skybridge/devtools") to the concrete version or semver range you developed
against (e.g. "1.2.3" or "^1.2.3") so pnpm produces a meaningful lockfile and
repeated installs resolve the same versions.
- Around line 6-8: The engines.node constraint is too strict and incompatible
with Vite 7.3.1; update the "engines.node" field in package.json (the engines
object) to a Node range that satisfies Vite 7 (for example ">=20.19.0") so Node
20.19+ and newer (including 22.12+) are allowed; edit the engines.node value
accordingly in the package.json file.
- Around line 18-19: Update the package.json dependency constraint for zod from
"^3.22.0" to "^3.25.0" so it satisfies the SDK's peer requirement; edit the
"zod" entry in the dependencies block (the package.json file containing the
"zod" key) to use "^3.25.0" and then run your package manager install to refresh
lockfile and ensure the resolved version is >=3.25.0.

In `@examples/spotify-playlist-generator/server/skybridge/src/server.ts`:
- Around line 291-316: The telemetry call in the registerTool handler always
records "ok" in the finally block even if the handler throws, so change the
pattern to capture success vs error: either introduce a status variable (e.g.,
let status = "ok"; try { ... } catch (e) { status = "error"; throw e; } finally
{ recordCall("setup_spotify_oauth", Date.now() - start, status); }) or move
recordCall("setup_spotify_oauth", ..., "ok") into the successful path and call
recordCall(..., "error") in the catch; apply the same change for the other
handlers that call recordCall with their tool names (the handlers that currently
pass "ok" in finally).

In
`@examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx`:
- Around line 9-29: The parseData function currently calls JSON.parse directly
in every branch (result, content, content[0].text, and when data is a string)
which will throw a SyntaxError for malformed JSON and crash the view; wrap each
JSON.parse invocation inside a try/catch (within parseData) and on parse failure
return a safe fallback (e.g., null or the original data) instead of letting the
error propagate so the React render won't crash; update parseData to try parsing
for the "result" branch, the "content" string branch, the "content[0].text"
branch, and the direct string branch, catching JSON.parse errors and returning a
safe value.

In `@src/mcp_anything/emit/typescript_skybridge/phase.py`:
- Around line 237-263: The emitted fetch call in _render_call for http_call
omits any authentication headers, so generated clients cannot call protected
APIs; update _render_call (the http_call generation branch) to read the auth
configuration from tool.impl (e.g., impl.auth_type) or the server/backend config
and inject the correct header pattern into the fetch headers: for API key emit a
header using an env var like process.env["<SERVER>_API_KEY"], for Bearer/OAuth2
emit Authorization: `Bearer ${process.env["<SERVER>_ACCESS_TOKEN"]}` (or token
fetch flow), and for Basic emit Authorization: `Basic
${Buffer.from(username+":"+password).toString("base64")}` or env-driven
equivalents; ensure the headers object in the generated fetch includes these
values when applicable and keep existing Content-Type logic intact.
- Around line 342-349: The post-processing block for tsx currently uses an
ambiguous loop variable `l` and swallows all errors with `except Exception:
pass`; change the list comprehension to use a clear name (e.g., iterate `line`
over `raw_lines` after assigning `raw_lines = tsx.splitlines()`), and replace
the bare except with `except Exception as exc:` that logs a warning via the
module logger (logging.getLogger(__name__).warning) including context such as
`tool.name` and the exception `exc`, then fall back to the placeholder behavior;
keep the original trimming and return of `tsx` otherwise.
- Around line 494-498: The Dockerfile header comment in _emit_dockerfile
incorrectly states "Requires Node.js 24+" while the FROM line uses node:22-slim;
update the generated comment inside the content string in _emit_dockerfile to
match the actual base image and project requirements (e.g., change to "Requires
Node.js 22+ as per Skybridge SDK requirements" or otherwise align with
package.json/README) so the comment and the FROM node tag are consistent for
builds generated for self.design.server_name.
- Around line 520-550: _emit_package_json always writes a fixed dependency set
and does not add the required "websockets>=12.0" when the design contains
protocol_call tools; update _emit_package_json to inspect self.design (e.g.
iterate self.design.tools or whatever collection holds tool definitions) and if
any tool has type or kind "protocol_call" add pkg["dependencies"]["websockets"]
= ">=12.0" before serializing, then write package.json as before so generated
servers that use protocol_call include the websockets dependency.
- Around line 520-550: The engines.node constraint in _emit_package_json
currently uses ">=22.0.0" which is too permissive for Vite 7; update the
pkg["engines"]["node"] value to ">=22.12.0" in the _emit_package_json method so
installs cannot select Node 22.0–22.11, and adjust any related comments about
the minimum Node version (e.g., Dockerfile base-image comment and any
"node:22.x" base image note) to state "node:22.12+" or ">=22.12.0" so
documentation matches the new engine constraint.
- Around line 195-214: The Zod schema builder in _render_zod_shape never adds
the required "verbose" flag, so add a final entry after the loop that appends a
"verbose" field (boolean, optional, default compact) to the returned shape—e.g.
use z.boolean().optional().describe("Return verbose details when true; default
false (compact)"). Also ensure the generator that emits the TypeScript call
signature (the same code that uses _render_call) accepts a verbose parameter so
callers can opt in; _render_call already skips passing verbose to the HTTP
payload, so keep that behavior and only expose verbose in the input schema and
TS signature.
- Around line 180-193: The generated template in _render_tool_registration
hardcodes "ok" in the finally block so failures appear successful; change the
template to track success/failure (e.g., declare a status variable = "ok" before
try, set status = "error" in catch, and call recordCall("{tool.name}",
Date.now() - start, status) in finally) or alternatively call recordCall inside
both the try (on success) and catch (on failure); update the template around the
try/catch/finally that includes call_code to ensure actual exceptions set the
recorded status instead of always "ok".

In `@src/mcp_anything/emit/typescript_skybridge/prompts.py`:
- Around line 44-49: The prompt contains a contradictory state name: replace any
mention of the `"loading"` state with the runtime-accurate `"pending"` state and
update the instructions so the component must handle exactly the four states
`"idle"`, `"pending"`, `"success"`, and `"error"`; ensure
`useCallTool("{tool_name}")` usage and returned shape `{ status, data, error,
callTool }` are documented, require assigning `data` to `const result: any =
data` before use, and explicitly require the component to render distinct views
for `"idle"`, `"pending"`, `"success"`, and `"error"` (so tests like
TestPlaceholderViews.test_all_states_handled see `"pending"`).

In `@src/mcp_anything/pipeline/llm_client.py`:
- Around line 83-105: call_llm_for_text currently does a single anthropic
client.messages.create call and lacks the project's LLM retry/error handling;
wrap the LLM call in the project's LLM JSON retry utility (e.g., llm_json_retry
or the repo's named retry helper) and add structured error handling to catch
transient errors and surface structured failure info (include the model, prompt
metadata, and the caught exception) rather than letting exceptions propagate
raw; locate the anthropic import and the client.messages.create call inside
call_llm_for_text and replace the direct invocation with a retry-wrapped call
that returns the same stripped text on success and raises a controlled exception
or returns a well-formed error per the project's LLM error conventions on
repeated failure.

In `@tests/test_emit_skybridge.py`:
- Around line 92-105: _replace tempfile.mkdtemp() in _run_emitter with a
TemporaryDirectory-backed approach so the temp dir is cleaned up by the caller;
e.g., have _run_emitter return the TemporaryDirectory handle alongside
(TypeScriptSkybridgeEmitter, Path) so callers can control lifecycle, or convert
_run_emitter into a pytest fixture that yields (emitter, out) and performs
shutil.rmtree/TemporaryDirectory cleanup in teardown; update the function that
constructs the temp dir (currently calling mkdtemp) and ensure
emitter.emit_all() is still called before returning so tests can inspect the out
Path while allowing proper cleanup via the returned TemporaryDirectory or
fixture.

---

Duplicate comments:
In
`@examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx`:
- Around line 8-28: The getContent function currently calls JSON.parse on
multiple branches without error handling; wrap each JSON.parse call in a
try/catch (or better, extract a shared safeParseJson utility and call it) so
malformed JSON doesn't throw and crash the component; update getContent to use
the safeParseJson(resultString) helper (or inline try/catch) and return a
sensible fallback (null or the original value) on parse failure, referencing the
getContent function and the new safeParseJson helper to locate and change the
code.

---

Nitpick comments:
In
`@examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx`:
- Around line 9-29: Duplicate parsing logic exists as parseData and getContent;
extract and consolidate it into a single exported function parseToolData in a
new module (e.g., src/views/utils/parseToolData.ts) and import it where
parseData/getContent are used (setup_spotify_oauth.tsx,
generate_spotify_playlist.tsx, exchange_spotify_code.tsx). Update parseToolData
to preserve the existing branches but wrap each JSON.parse call in try/catch and
return the original string (or the original field) on parse failure instead of
throwing; keep the same null/array/object handling and return the raw data as
the final fallback. Ensure callers replace parseData/getContent with
parseToolData to remove duplication.

In `@src/mcp_anything/emit/typescript_skybridge/phase.py`:
- Around line 297-308: Update the emit pipeline to serialize MCP "resource" and
"prompt" entries alongside each tool: in _emit_views (and/or in _emit_server)
after collecting domain_desc, use_cases, and glossary from self.domain_model,
create MCP resource objects and prompt templates for each tool in
self.design.tools and ensure they are written/registered to server.ts the same
way tools are registered; reference _emit_views, _emit_server, _generate_view,
self.domain_model, and self.design.tools to locate where to add creation of
resource entries (serialising domain_desc/use_cases/glossary) and prompt
templates and then call the same write/registration path used for tools so each
tool has an accompanying MCP resource and prompt.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9311760-7758-4403-9f97-74982f358590

📥 Commits

Reviewing files that changed from the base of the PR and between 44de641 and 21fa5b7.

⛔ Files ignored due to path filters (1)
  • examples/spotify-playlist-generator/server/skybridge/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • CLAUDE.md
  • examples/spotify-playlist-generator/server/Dockerfile
  • examples/spotify-playlist-generator/server/SKILL.md
  • examples/spotify-playlist-generator/server/conformance_report.json
  • examples/spotify-playlist-generator/server/descriptions.yaml
  • examples/spotify-playlist-generator/server/domain_model.json
  • examples/spotify-playlist-generator/server/eval_cases.json
  • examples/spotify-playlist-generator/server/quick_queries.json
  • examples/spotify-playlist-generator/server/skybridge/.skybridge/views.d.ts
  • examples/spotify-playlist-generator/server/skybridge/Dockerfile
  • examples/spotify-playlist-generator/server/skybridge/README.md
  • examples/spotify-playlist-generator/server/skybridge/index.html
  • examples/spotify-playlist-generator/server/skybridge/package.json
  • examples/spotify-playlist-generator/server/skybridge/src/discovery.ts
  • examples/spotify-playlist-generator/server/skybridge/src/server.ts
  • examples/spotify-playlist-generator/server/skybridge/src/telemetry.ts
  • examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx
  • examples/spotify-playlist-generator/server/skybridge/src/views/generate_spotify_playlist.tsx
  • examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx
  • examples/spotify-playlist-generator/server/skybridge/tsconfig.json
  • examples/spotify-playlist-generator/server/skybridge/vite.config.ts
  • examples/spotify-playlist-generator/server/tool_spec.yaml
  • examples/spotify-playlist-generator/spotify-playlist-domain.yaml
  • src/mcp_anything/cli.py
  • src/mcp_anything/emit/typescript_skybridge/__init__.py
  • src/mcp_anything/emit/typescript_skybridge/phase.py
  • src/mcp_anything/emit/typescript_skybridge/prompts.py
  • src/mcp_anything/pipeline/engine.py
  • src/mcp_anything/pipeline/llm_client.py
  • tests/test_emit_skybridge.py

Comment thread examples/spotify-playlist-generator/server/conformance_report.json
Comment thread examples/spotify-playlist-generator/server/Dockerfile
Comment thread examples/spotify-playlist-generator/server/eval_cases.json Outdated
Comment thread examples/spotify-playlist-generator/server/skybridge/Dockerfile
Comment thread examples/spotify-playlist-generator/server/skybridge/package.json
Comment thread src/mcp_anything/emit/typescript_skybridge/phase.py
Comment thread src/mcp_anything/emit/typescript_skybridge/phase.py
Comment thread src/mcp_anything/emit/typescript_skybridge/prompts.py Outdated
Comment thread src/mcp_anything/pipeline/llm_client.py Outdated
Comment thread tests/test_emit_skybridge.py
gabrielekarra and others added 2 commits May 8, 2026 02:50
New emit backend generates a TypeScript MCP server + ChatGPT App with
LLM-generated React views for each tool. Wires --target skybridge into
cli.py and engine.py, adds call_llm_for_text to llm_client.py, and ships
the spotify-playlist-generator as a worked example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Source-side fixes (skybridge emitter):
- Telemetry status tracked via try/catch — errors no longer reported as "ok"
- Zod schema: inject verbose flag on every tool (C-10)
- HTTP call: emit Authorization/API key headers from backend.auth config
- Generated Dockerfile: USER node, --chown copies, Node 22.12+ comment
- package.json: engines.node >=22.12.0 (Vite 7), zod ^3.25.0, conditional ws dep
- LLM view fallback: log exception via logger instead of swallowing silently
- Rename ambiguous loop variable `l` → `line`

Other source fixes:
- prompts.py: state list now consistently "pending" (not "loading")
- llm_client.call_llm_for_text: add retry/error handling matching call_llm_for_json
- validation_harness: when no live eval ran, set passed=False + skipped=true
- ConformanceReport.skipped field added
- test_emit_skybridge: track and clean up mkdtemp dirs via autouse fixture

Example artifacts updated to match the new emitter output:
- spotify-playlist-generator Dockerfiles, package.json, server.ts, view
- conformance_report.json: passed=false, skipped=true

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gabrielekarra
gabrielekarra force-pushed the feat/skybridge-emitter branch from 21fa5b7 to e677169 Compare May 8, 2026 01:00
@gabrielekarra

Copy link
Copy Markdown
Owner Author

@CodeRabbit

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@gabrielekarra It looks like your message might be incomplete! How can I help you with this PR? Here are a few things I can assist with:

  • 🔍 Answer questions about the implementation (e.g., the Skybridge emitter, LLM view generation, contract validation)
  • 🧪 Review tests in tests/test_emit_skybridge.py or suggest additional coverage
  • 📝 Generate a summary of the changes or specific files
  • 🐛 Investigate issues in the codebase with deeper analysis
  • 🔧 Plan code changes if you'd like to refactor or extend something
  • 📋 Create a GitHub issue for any follow-up work

Just let me know what you need! 🐇

@gabrielekarra

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@gabrielekarra

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
examples/spotify-playlist-generator/server/skybridge/package.json (1)

17-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

"latest" tags on skybridge and @skybridge/devtools make installs non-reproducible.

Every fresh pnpm install (including each Docker build) can resolve a different, potentially breaking version. Pin both to an explicit semver range.

🔧 Proposed fix
-    "skybridge": "latest",
+    "skybridge": "^1.0.0",
-    "@skybridge/devtools": "latest",
+    "@skybridge/devtools": "^1.0.0",

Replace ^1.0.0 with the concrete version you developed against.

Also applies to: 22-22

🤖 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 `@examples/spotify-playlist-generator/server/skybridge/package.json` at line
17, The package.json currently uses the non-reproducible "latest" tag for the
dependencies "skybridge" and "@skybridge/devtools"; change both entries to a
pinned semver range or concrete version you developed against (e.g., replace
"latest" with the specific semver like "^1.0.0" or an exact version number) so
installs and Docker builds are reproducible; update the entries for "skybridge"
and "@skybridge/devtools" accordingly.
🧹 Nitpick comments (4)
examples/spotify-playlist-generator/server/skybridge/tsconfig.json (1)

3-5: ⚡ Quick win

Use ESNext/Bundler module settings for a Vite project.

"module": "NodeNext" + "moduleResolution": "NodeNext" targets a bare Node.js runtime. The bundler moduleResolution is designed for use with bundlers and, unlike Node.js resolution modes, never requires file extensions on relative paths in imports. With NodeNext, TypeScript enforces .js extensions on every relative import — when using NodeNext, you need to use .js extensions when importing TypeScript files. That conflicts with how Vite resolves modules at build time and will produce incorrect type-checking semantics for the .tsx view files.

The bundler configuration is appropriate when using webpack, Vite, or other bundlers that resolve modules during the build process.

Additionally, "declaration": true with "outDir": "dist" emits .d.ts files from tsc — the pattern for libraries. When using a bundler like Vite, moduleResolution: "bundler" and module: "ESNext" should be used, designed for situations where TypeScript is not responsible for emitting code. If tsc is used only for type-checking here, "noEmit": true should replace "declaration": true.

Since this tsconfig is generated by phase.py, the fix should be applied to the emitter template as well.

🔧 Proposed fix
-    "target": "ES2022",
-    "module": "NodeNext",
-    "moduleResolution": "NodeNext",
+    "target": "ES2022",
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
     "strict": true,
     "skipLibCheck": true,
-    "outDir": "dist",
-    "rootDir": "src",
-    "declaration": true,
+    "noEmit": true,
     "esModuleInterop": true,
     "jsx": "react-jsx",
     "jsxImportSource": "react"
🤖 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 `@examples/spotify-playlist-generator/server/skybridge/tsconfig.json` around
lines 3 - 5, Update the tsconfig emitter so Vite builds use bundler-style
resolution: change "module" from "NodeNext" to "ESNext" and "moduleResolution"
from "NodeNext" to "bundler"; remove or disable "declaration": true and instead
set "noEmit": true (or ensure no declaration emission) since Vite handles
bundling; keep "target" as appropriate (e.g., "ES2022"/"ESNext"); apply the same
changes in the phase.py tsconfig emitter template so generated tsconfig.json
files use "module":"ESNext", "moduleResolution":"bundler" and no declaration
emission.
examples/spotify-playlist-generator/server/skybridge/src/server.ts (1)

91-158: 💤 Low value

Unguarded JSON.parse masks upstream non-JSON errors.

Both tokenRequest (line 104) and spotifyFetch (line 152) call JSON.parse(text) before checking resp.ok. If a proxy/CDN/load balancer in front of Spotify returns an HTML error page (e.g., 502 Bad Gateway, captive portal), the parse throws SyntaxError: Unexpected token '<' and the actual HTTP status (resp.status, path) is lost from the error message. Wrap each parse so the !resp.ok branch can still surface a useful error.

🛡️ Proposed defensive fix
-  const text = await resp.text();
-  const payload = text ? JSON.parse(text) : {};
+  const text = await resp.text();
+  let payload: any = {};
+  if (text) {
+    try { payload = JSON.parse(text); } catch { /* keep payload empty; raw text used below */ }
+  }
   if (!resp.ok) {
-    throw new Error(`Spotify token request failed (${resp.status}): ${payload.error_description ?? text}`);
+    throw new Error(`Spotify token request failed (${resp.status}): ${payload?.error_description ?? text.slice(0, 200)}`);
   }

(Apply analogous change inside spotifyFetch.)

🤖 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 `@examples/spotify-playlist-generator/server/skybridge/src/server.ts` around
lines 91 - 158, tokenRequest and spotifyFetch call JSON.parse(text) unguarded
which throws on non-JSON error pages and hides HTTP status; change both
functions (tokenRequest and spotifyFetch) to parse JSON defensively: attempt
JSON.parse in a try/catch (or only parse when resp.headers indicates JSON) and
fall back to raw text on parse failure so the !resp.ok branch can include
resp.status, path (for spotifyFetch) and the raw text in the thrown Error;
ensure returned payload is the parsed object when parse succeeds and the raw
text or an empty object when it fails.
tests/test_emit_skybridge.py (1)

299-328: ⚡ Quick win

Consider adding a test that pins the C-10 verbose flag injection.

_render_zod_shape now auto-injects a "verbose" boolean for tools that don't declare one. There's no test asserting this contract — a regression that drops the injection (or accidentally injects a duplicate when the tool already declares verbose) would slip through. A two-line assertion in TestZodSchema would lock it in.

✅ Suggested test additions
def test_verbose_flag_auto_injected(self) -> None:
    _, out = _run_emitter()  # _make_design tools have no verbose param
    content = (out / "src" / "server.ts").read_text()
    assert '"verbose": z.boolean().optional()' in content

def test_verbose_flag_not_duplicated(self) -> None:
    tool = ToolSpec(
        name="echo",
        description="Echo with verbose support.",
        parameters=[
            ParameterSpec(name="verbose", type="boolean", required=False, description="Verbose"),
        ],
        impl=ToolImpl(strategy="stub"),
    )
    _, out = _run_emitter(_make_design(tools=[tool]))
    content = (out / "src" / "server.ts").read_text()
    # Exactly one verbose entry (the user's), no auto-injected duplicate.
    assert content.count('"verbose":') == 1
🤖 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 `@tests/test_emit_skybridge.py` around lines 299 - 328, Add two tests to
TestZodSchema to lock in the auto-injection behavior of _render_zod_shape:
create test_verbose_flag_auto_injected that calls _run_emitter (using the
default _make_design with no verbose param), reads "src/server.ts" and asserts
that '"verbose": z.boolean().optional()' appears; and create
test_verbose_flag_not_duplicated that builds a ToolSpec with a user-declared
ParameterSpec(name="verbose", type="boolean", required=False), runs
_run_emitter(_make_design(tools=[tool])), reads the output and asserts
content.count('"verbose":') == 1 to ensure no duplicate injection. Ensure tests
reference TestZodSchema, _run_emitter, _make_design, ToolSpec and ParameterSpec.
src/mcp_anything/emit/typescript_skybridge/phase.py (1)

61-67: 💤 Low value

Silent fallback on malformed domain model hides configuration bugs.

_load_domain_model returns None on any validation error, so a typo or schema-breaking change in the manifest's domain section silently downgrades the LLM view prompt to "no domain context" instead of surfacing the problem. At minimum, log the exception so operators can diagnose why their generated views are noticeably less context-aware.

🪵 Proposed fix — log instead of swallow
     def _load_domain_model(self, ctx: PipelineContext) -> Optional[DomainModel]:
         if ctx.manifest.domain_model:
             try:
                 return DomainModel.model_validate(ctx.manifest.domain_model)
-            except Exception:
+            except Exception as exc:
+                import logging
+                logging.getLogger(__name__).warning(
+                    "Failed to load domain model for Skybridge emit; views will lack domain context: %s",
+                    exc,
+                )
                 return None
         return None
🤖 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 `@src/mcp_anything/emit/typescript_skybridge/phase.py` around lines 61 - 67,
The _load_domain_model method currently swallows validation errors from
DomainModel.model_validate, causing silent fallback; modify _load_domain_model
to catch Exception as e and log the error (including stack trace) before
returning None so operators can diagnose malformed manifest domain sections —
use a module/class logger (e.g., logging.getLogger(__name__) or self.logger) and
logging.exception or logger.error(..., exc_info=True) when handling the
exception inside _load_domain_model(PipelineContext) where
DomainModel.model_validate is called.
🤖 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 `@examples/spotify-playlist-generator/server/Dockerfile`:
- Around line 6-9: The Dockerfile currently copies only skybridge/package.json
before running pnpm install, causing pnpm to regenerate the lockfile; change the
COPY order to copy both skybridge/package.json and skybridge/pnpm-lock.yaml (so
pnpm-lock.yaml is present during install) and add the --frozen-lockfile flag to
the pnpm install command (keep the subsequent COPY skybridge/. after install).
This ensures builds use the committed lockfile and prevents pnpm from updating
dependencies during image build.

In `@examples/spotify-playlist-generator/server/eval_cases.json`:
- Line 5: The eval cases use placeholder tool_name values like
"uc_01"/"uc_02"/"uc_03" which don't match the registered tool identifiers;
update each tool_name entry in eval_cases.json to the actual registered tool
names ("generate_spotify_playlist", "setup_spotify_oauth",
"exchange_spotify_code") so the runner will route to the real tools (match each
uc_0X to its corresponding registered name). Ensure every case that currently
has "uc_01"/"uc_02"/"uc_03" is replaced consistently so tool routing will work
with the names used in descriptions.yaml and views.d.ts.

In `@examples/spotify-playlist-generator/server/skybridge/Dockerfile`:
- Around line 6-11: The Dockerfile currently copies only package.json before
running pnpm install, which causes the lockfile to be regenerated each build;
modify the pre-install copy to include pnpm-lock.yaml (copy package.json and
pnpm-lock.yaml together) and change the install command in the RUN that uses
corepack/pnpm to run pnpm install --frozen-lockfile so builds use the committed
lockfile and avoid silently drifting dependency versions (update the COPY and
the RUN that calls corepack enable pnpm && pnpm install).

In `@examples/spotify-playlist-generator/server/skybridge/README.md`:
- Line 15: Update the README entry that currently reads "Node.js 22+" to
"Node.js 22.12+" so it matches the project's package.json engines field
(>=22.12.0) and Vite 7's minimum requirement; locate the string "Node.js 22+" in
examples/spotify-playlist-generator/server/skybridge/README.md and replace it
with "Node.js 22.12+".

In
`@examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx`:
- Around line 8-28: getContent currently calls JSON.parse in multiple branches
and will throw a SyntaxError on malformed JSON; replace those direct JSON.parse
calls with the same safeParse helper used in exchange_spotify_code.tsx (or
implement an equivalent try/catch wrapper) so parsing failures return null or
the original data instead of throwing; update calls inside getContent (the
branches checking "result", "content", content[0].text, and the string case) to
use safeParse(...) and preserve the existing return semantics when parse fails.

In `@src/mcp_anything/cli.py`:
- Around line 53-58: The CLI currently allows "--target skybridge" for the
"generate" command but the legacy pipeline (ALL_PHASES / implement handler in
engine.py) has no skybridge implement branch and will silently fall back to
Python/FastMCP; remove "skybridge" from the choices list passed to
gen.add_argument("--target", choices=[...]) in src/mcp_anything/cli.py so that
"generate" only accepts "fastmcp" and "mcp-use" (and update the help string to
reflect this), leaving "skybridge" available only for the build path that uses
DOMAIN_PHASES/emit.

---

Duplicate comments:
In `@examples/spotify-playlist-generator/server/skybridge/package.json`:
- Line 17: The package.json currently uses the non-reproducible "latest" tag for
the dependencies "skybridge" and "@skybridge/devtools"; change both entries to a
pinned semver range or concrete version you developed against (e.g., replace
"latest" with the specific semver like "^1.0.0" or an exact version number) so
installs and Docker builds are reproducible; update the entries for "skybridge"
and "@skybridge/devtools" accordingly.

---

Nitpick comments:
In `@examples/spotify-playlist-generator/server/skybridge/src/server.ts`:
- Around line 91-158: tokenRequest and spotifyFetch call JSON.parse(text)
unguarded which throws on non-JSON error pages and hides HTTP status; change
both functions (tokenRequest and spotifyFetch) to parse JSON defensively:
attempt JSON.parse in a try/catch (or only parse when resp.headers indicates
JSON) and fall back to raw text on parse failure so the !resp.ok branch can
include resp.status, path (for spotifyFetch) and the raw text in the thrown
Error; ensure returned payload is the parsed object when parse succeeds and the
raw text or an empty object when it fails.

In `@examples/spotify-playlist-generator/server/skybridge/tsconfig.json`:
- Around line 3-5: Update the tsconfig emitter so Vite builds use bundler-style
resolution: change "module" from "NodeNext" to "ESNext" and "moduleResolution"
from "NodeNext" to "bundler"; remove or disable "declaration": true and instead
set "noEmit": true (or ensure no declaration emission) since Vite handles
bundling; keep "target" as appropriate (e.g., "ES2022"/"ESNext"); apply the same
changes in the phase.py tsconfig emitter template so generated tsconfig.json
files use "module":"ESNext", "moduleResolution":"bundler" and no declaration
emission.

In `@src/mcp_anything/emit/typescript_skybridge/phase.py`:
- Around line 61-67: The _load_domain_model method currently swallows validation
errors from DomainModel.model_validate, causing silent fallback; modify
_load_domain_model to catch Exception as e and log the error (including stack
trace) before returning None so operators can diagnose malformed manifest domain
sections — use a module/class logger (e.g., logging.getLogger(__name__) or
self.logger) and logging.exception or logger.error(..., exc_info=True) when
handling the exception inside _load_domain_model(PipelineContext) where
DomainModel.model_validate is called.

In `@tests/test_emit_skybridge.py`:
- Around line 299-328: Add two tests to TestZodSchema to lock in the
auto-injection behavior of _render_zod_shape: create
test_verbose_flag_auto_injected that calls _run_emitter (using the default
_make_design with no verbose param), reads "src/server.ts" and asserts that
'"verbose": z.boolean().optional()' appears; and create
test_verbose_flag_not_duplicated that builds a ToolSpec with a user-declared
ParameterSpec(name="verbose", type="boolean", required=False), runs
_run_emitter(_make_design(tools=[tool])), reads the output and asserts
content.count('"verbose":') == 1 to ensure no duplicate injection. Ensure tests
reference TestZodSchema, _run_emitter, _make_design, ToolSpec and ParameterSpec.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3cb5f9c1-b123-49ae-922e-3f7c3be322ff

📥 Commits

Reviewing files that changed from the base of the PR and between c54eb88 and e677169.

⛔ Files ignored due to path filters (1)
  • examples/spotify-playlist-generator/server/skybridge/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • CLAUDE.md
  • examples/spotify-playlist-generator/server/Dockerfile
  • examples/spotify-playlist-generator/server/SKILL.md
  • examples/spotify-playlist-generator/server/conformance_report.json
  • examples/spotify-playlist-generator/server/descriptions.yaml
  • examples/spotify-playlist-generator/server/domain_model.json
  • examples/spotify-playlist-generator/server/eval_cases.json
  • examples/spotify-playlist-generator/server/quick_queries.json
  • examples/spotify-playlist-generator/server/skybridge/.skybridge/views.d.ts
  • examples/spotify-playlist-generator/server/skybridge/Dockerfile
  • examples/spotify-playlist-generator/server/skybridge/README.md
  • examples/spotify-playlist-generator/server/skybridge/index.html
  • examples/spotify-playlist-generator/server/skybridge/package.json
  • examples/spotify-playlist-generator/server/skybridge/src/discovery.ts
  • examples/spotify-playlist-generator/server/skybridge/src/server.ts
  • examples/spotify-playlist-generator/server/skybridge/src/telemetry.ts
  • examples/spotify-playlist-generator/server/skybridge/src/views/exchange_spotify_code.tsx
  • examples/spotify-playlist-generator/server/skybridge/src/views/generate_spotify_playlist.tsx
  • examples/spotify-playlist-generator/server/skybridge/src/views/setup_spotify_oauth.tsx
  • examples/spotify-playlist-generator/server/skybridge/tsconfig.json
  • examples/spotify-playlist-generator/server/skybridge/vite.config.ts
  • examples/spotify-playlist-generator/server/tool_spec.yaml
  • examples/spotify-playlist-generator/spotify-playlist-domain.yaml
  • src/mcp_anything/cli.py
  • src/mcp_anything/emit/typescript_skybridge/__init__.py
  • src/mcp_anything/emit/typescript_skybridge/phase.py
  • src/mcp_anything/emit/typescript_skybridge/prompts.py
  • src/mcp_anything/models/validation.py
  • src/mcp_anything/pipeline/engine.py
  • src/mcp_anything/pipeline/llm_client.py
  • src/mcp_anything/pipeline/validation_harness.py
  • tests/test_emit_skybridge.py

Comment thread examples/spotify-playlist-generator/server/Dockerfile Outdated
Comment thread examples/spotify-playlist-generator/server/eval_cases.json Outdated
Comment thread examples/spotify-playlist-generator/server/skybridge/Dockerfile
Comment thread examples/spotify-playlist-generator/server/skybridge/README.md Outdated
Comment thread src/mcp_anything/cli.py
- cli.py: drop 'skybridge' from `generate --target` choices since the
  legacy pipeline silently falls back to Python/FastMCP; Skybridge is
  build-only.
- skybridge emitter: pin `skybridge` and `@skybridge/devtools` to
  ^0.36.2 instead of `latest` for reproducible installs.
- example skybridge/package.json: same pinning.
- example Dockerfiles (outer wrapper + skybridge/): copy pnpm-lock.yaml
  before install and use `pnpm install --frozen-lockfile`.
- example skybridge/README.md: tighten Node minimum from 22+ to 22.12+
  to match Vite 7's requirement and the pinned engines.node.
- example setup_spotify_oauth.tsx: mirror the safeParse helper used in
  exchange_spotify_code.tsx so malformed JSON does not crash the view.
- example eval_cases.json: replace placeholder uc_01/uc_02/uc_03 tool
  names with the real registered tool names and populate realistic
  input_params and expected_output_pattern values.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@gabrielekarra

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

gabrielekarra and others added 3 commits May 8, 2026 10:05
Adds `pnpm dev:ui` (runs `vite`) so users can view the generated React
tool views in the browser. Updates the generated README to document both
`dev` (MCP server) and `dev:ui` (Vite UI at localhost:5173).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, and view API

Three root-cause fixes so generated servers work without manual patches:

1. Path param auto-detection (phase.py): emitter scans http_path for {param}
   templates and forces style="path", so path params are never serialized as
   query strings even when arg_mapping is empty or missing.

2. arg_mapping in tool design (tool_design.py): LLM prompt now requires arg_mapping
   on every http_call tool with path/query/body guidance. Parser validates and
   stores it into ToolImpl so the emitter has ground-truth param styles.

3. OpenAPI-first routing (tool_design.py + cli.py): when a spec file is provided
   as data source, routes and param styles are extracted deterministically
   (_openapi_to_tool_specs) before any LLM reshape — prevents invented paths.

Also: tsx in devDependencies (was missing, broke skybridge dev), correct skybridge
CLI scripts (dev/build/start), view prompt fixes (useViewState signature,
displayMode values, callTool args), max_tokens=16000 for view generation,
max_tokens truncation warning in llm_client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The FastAPI analyzer treated `async def f(req: SomeModel)` as a single
opaque `object` param, so generated MCP tools exposed a `req` blob
instead of the model's fields. Now we walk the file's BaseModel
subclasses (and follow imports for cross-file models) and expand the
body into one ParameterSpec per field, with location="body" so the
existing HTTP body codegen handles them correctly. Path-param
collisions and non-Pydantic classes fall through unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@gabrielekarra
gabrielekarra merged commit e25e491 into main May 8, 2026
@gabrielekarra
gabrielekarra deleted the feat/skybridge-emitter branch May 9, 2026 18:45
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