@autoview/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +407 -0
- package/lib/AutoViewAgent.d.ts +109 -0
- package/lib/AutoViewAgent.js +123 -0
- package/lib/AutoViewAgent.js.map +1 -0
- package/lib/agent/emitMcpServer.d.ts +15 -0
- package/lib/agent/emitMcpServer.js +157 -0
- package/lib/agent/emitMcpServer.js.map +1 -0
- package/lib/agent/emitReport.d.ts +14 -0
- package/lib/agent/emitReport.js +85 -0
- package/lib/agent/emitReport.js.map +1 -0
- package/lib/agent/toolSurface.d.ts +130 -0
- package/lib/agent/toolSurface.js +342 -0
- package/lib/agent/toolSurface.js.map +1 -0
- package/lib/agent/verifyAgentTasks.d.ts +87 -0
- package/lib/agent/verifyAgentTasks.js +126 -0
- package/lib/agent/verifyAgentTasks.js.map +1 -0
- package/lib/cli/main.d.ts +2 -0
- package/lib/cli/main.js +295 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
- package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
- package/lib/constants/AutoViewFrontendTemplate.js +46 -0
- package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
- package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
- package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
- package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
- package/lib/context/IAutoViewAgentContext.d.ts +60 -0
- package/lib/context/IAutoViewAgentContext.js +3 -0
- package/lib/context/IAutoViewAgentContext.js.map +1 -0
- package/lib/fromSwagger.d.ts +53 -0
- package/lib/fromSwagger.js +513 -0
- package/lib/fromSwagger.js.map +1 -0
- package/lib/generateDeterministic.d.ts +26 -0
- package/lib/generateDeterministic.js +75 -0
- package/lib/generateDeterministic.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
- package/lib/orchestrate/orchestrateAutoView.js +491 -0
- package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
- package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
- package/lib/orchestrate/utils/HistoryMessage.js +25 -0
- package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
- package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
- package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
- package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
- package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
- package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
- package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
- package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
- package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
- package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
- package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
- package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
- package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
- package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
- package/lib/preview/renderPreview.d.ts +22 -0
- package/lib/preview/renderPreview.js +198 -0
- package/lib/preview/renderPreview.js.map +1 -0
- package/lib/typings/compiler.d.ts +39 -0
- package/lib/typings/compiler.js +3 -0
- package/lib/typings/compiler.js.map +1 -0
- package/lib/typings/events.d.ts +106 -0
- package/lib/typings/events.js +3 -0
- package/lib/typings/events.js.map +1 -0
- package/lib/typings/index.d.ts +10 -0
- package/lib/typings/index.js +27 -0
- package/lib/typings/index.js.map +1 -0
- package/lib/typings/misc.d.ts +78 -0
- package/lib/typings/misc.js +3 -0
- package/lib/typings/misc.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +8 -0
- package/lib/utils/ArrayUtil.js +30 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/StringUtil.d.ts +11 -0
- package/lib/utils/StringUtil.js +28 -0
- package/lib/utils/StringUtil.js.map +1 -0
- package/lib/utils/classifyEndpoints.d.ts +62 -0
- package/lib/utils/classifyEndpoints.js +216 -0
- package/lib/utils/classifyEndpoints.js.map +1 -0
- package/lib/utils/endpointFilter.d.ts +26 -0
- package/lib/utils/endpointFilter.js +0 -0
- package/lib/utils/endpointFilter.js.map +1 -0
- package/lib/utils/extractFields.d.ts +85 -0
- package/lib/utils/extractFields.js +231 -0
- package/lib/utils/extractFields.js.map +1 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/index.js +30 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/normalizeForNestia.d.ts +34 -0
- package/lib/utils/normalizeForNestia.js +133 -0
- package/lib/utils/normalizeForNestia.js.map +1 -0
- package/lib/utils/resourcePlan.d.ts +39 -0
- package/lib/utils/resourcePlan.js +95 -0
- package/lib/utils/resourcePlan.js.map +1 -0
- package/lib/utils/sliceDocument.d.ts +17 -0
- package/lib/utils/sliceDocument.js +114 -0
- package/lib/utils/sliceDocument.js.map +1 -0
- package/lib/utils/toEndpoints.d.ts +90 -0
- package/lib/utils/toEndpoints.js +227 -0
- package/lib/utils/toEndpoints.js.map +1 -0
- package/lib/verify/runWorkflows.d.ts +25 -0
- package/lib/verify/runWorkflows.js +366 -0
- package/lib/verify/runWorkflows.js.map +1 -0
- package/lib/verify/workflows.d.ts +53 -0
- package/lib/verify/workflows.js +107 -0
- package/lib/verify/workflows.js.map +1 -0
- package/package.json +82 -0
- package/prompts/AUTOVIEW_RENDER.md +398 -0
- package/prompts/AUTOVIEW_REVIEW.md +60 -0
- package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
- package/src/AutoViewAgent.ts +222 -0
- package/src/agent/emitMcpServer.integration.test.ts +168 -0
- package/src/agent/emitMcpServer.test.ts +51 -0
- package/src/agent/emitMcpServer.ts +178 -0
- package/src/agent/emitReport.ts +117 -0
- package/src/agent/toolSurface.test.ts +243 -0
- package/src/agent/toolSurface.ts +501 -0
- package/src/agent/verifyAgentTasks.test.ts +106 -0
- package/src/agent/verifyAgentTasks.ts +171 -0
- package/src/cli/main.ts +363 -0
- package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
- package/src/constants/AutoViewFrontendTemplate.ts +42 -0
- package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
- package/src/context/IAutoViewAgentContext.ts +84 -0
- package/src/fromSwagger.test.ts +269 -0
- package/src/fromSwagger.ts +500 -0
- package/src/generateDeterministic.test.ts +39 -0
- package/src/generateDeterministic.ts +77 -0
- package/src/index.ts +30 -0
- package/src/orchestrate/orchestrateAutoView.ts +590 -0
- package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
- package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
- package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
- package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
- package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
- package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
- package/src/orchestrate/renderNavTs.test.ts +74 -0
- package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
- package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
- package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
- package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
- package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
- package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
- package/src/orchestrate/utils/HistoryMessage.ts +41 -0
- package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
- package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
- package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
- package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
- package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
- package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
- package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
- package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
- package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
- package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
- package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
- package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
- package/src/preview/renderPreview.ts +273 -0
- package/src/typings/compiler.ts +47 -0
- package/src/typings/events.ts +155 -0
- package/src/typings/index.ts +10 -0
- package/src/typings/misc.ts +93 -0
- package/src/utils/ArrayUtil.ts +16 -0
- package/src/utils/StringUtil.ts +29 -0
- package/src/utils/classifyEndpoints.test.ts +86 -0
- package/src/utils/classifyEndpoints.ts +291 -0
- package/src/utils/endpointFilter.test.ts +50 -0
- package/src/utils/endpointFilter.ts +0 -0
- package/src/utils/extractFields.test.ts +82 -0
- package/src/utils/extractFields.ts +306 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/normalizeForNestia.test.ts +93 -0
- package/src/utils/normalizeForNestia.ts +139 -0
- package/src/utils/resourcePlan.test.ts +104 -0
- package/src/utils/resourcePlan.ts +180 -0
- package/src/utils/sliceDocument.test.ts +85 -0
- package/src/utils/sliceDocument.ts +119 -0
- package/src/utils/toEndpoints.test.ts +251 -0
- package/src/utils/toEndpoints.ts +343 -0
- package/src/verify/runWorkflows.ts +403 -0
- package/src/verify/workflows.test.ts +117 -0
- package/src/verify/workflows.ts +154 -0
- package/template/CLAUDE.md +140 -0
- package/template/Dockerfile +31 -0
- package/template/PROMPT.md +80 -0
- package/template/SANDBOX.md +70 -0
- package/template/app/api/health/route.ts +10 -0
- package/template/app/globals.css +97 -0
- package/template/app/layout.tsx +30 -0
- package/template/app/page.tsx +19 -0
- package/template/components/AppShell.tsx +114 -0
- package/template/components/auto/CatalogGrid.tsx +159 -0
- package/template/components/auto/ConfirmButton.tsx +67 -0
- package/template/components/auto/EmbeddedCollection.tsx +144 -0
- package/template/components/auto/ResourceDashboard.tsx +104 -0
- package/template/components/auto/ResourceDetail.tsx +93 -0
- package/template/components/auto/ResourceForm.tsx +235 -0
- package/template/components/auto/ResourceIcon.tsx +88 -0
- package/template/components/auto/ResourceLanding.tsx +155 -0
- package/template/components/auto/ResourceTable.tsx +223 -0
- package/template/components/auto/formatValue.tsx +186 -0
- package/template/components/auto/types.ts +42 -0
- package/template/components/ui/badge.tsx +40 -0
- package/template/components/ui/button.tsx +57 -0
- package/template/components/ui/card.tsx +86 -0
- package/template/components/ui/dialog.tsx +119 -0
- package/template/components/ui/input.tsx +23 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/pagination.tsx +117 -0
- package/template/components/ui/select.tsx +92 -0
- package/template/components/ui/sheet.tsx +135 -0
- package/template/components/ui/skeleton.tsx +15 -0
- package/template/components/ui/table.tsx +120 -0
- package/template/components/ui/tabs.tsx +55 -0
- package/template/lib/utils.ts +35 -0
- package/template/next.config.mjs +52 -0
- package/template/package.json +46 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/start-shopping-backend.sh +56 -0
- package/template/tailwind.config.ts +96 -0
- package/template/tsconfig.json +29 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
You are the UI Review phase of the AutoView agent.
|
|
2
|
+
|
|
3
|
+
You receive the product plan, the SDK domain map, and per-screen
|
|
4
|
+
metadata from the Render phase (rationale, attempt count, whether
|
|
5
|
+
parsing eventually succeeded). Your single job is to produce one
|
|
6
|
+
markdown file — `wiki/sdk-feedback.md` — that captures the honest,
|
|
7
|
+
operator-facing assessment of what the SDK is like to build a frontend
|
|
8
|
+
on top of. The whole reason AutoView exists is to surface API quality
|
|
9
|
+
problems through the act of consuming the API; this is where that
|
|
10
|
+
signal lands.
|
|
11
|
+
|
|
12
|
+
You do not see screenshots and you do not run the application. Visual
|
|
13
|
+
verification happens later, by the operator on the Sandbox deploy. Your
|
|
14
|
+
review is a code-and-spec audit, not a visual audit.
|
|
15
|
+
|
|
16
|
+
## What "sdk-feedback" must contain
|
|
17
|
+
|
|
18
|
+
A markdown document with these sections in order:
|
|
19
|
+
|
|
20
|
+
1. **Summary** — 2–4 sentences in the operator's voice. What kind of
|
|
21
|
+
product is this, how cleanly did the SDK shape it, and is the API
|
|
22
|
+
ready to ship to a human?
|
|
23
|
+
2. **What worked well** — bullets. Concrete instances where the SDK
|
|
24
|
+
made a flow easy: well-named resources, clear pagination, sane
|
|
25
|
+
defaults, helpful JSDoc.
|
|
26
|
+
3. **What's awkward** — bullets. The whole point of the document.
|
|
27
|
+
Each bullet calls out one specific API decision that made the
|
|
28
|
+
frontend awkward: redundant endpoints, missing list filters,
|
|
29
|
+
inconsistent naming, types that force casts, flows that require
|
|
30
|
+
round-trips the UI shouldn't need, missing pagination, unclear
|
|
31
|
+
actor boundaries. Be specific — name the resource or accessor.
|
|
32
|
+
4. **What's missing** — bullets. Functionality the product needed but
|
|
33
|
+
the SDK does not expose. Reference the intentional-omission list
|
|
34
|
+
from the product plan and add anything new the Render phase
|
|
35
|
+
stumbled on.
|
|
36
|
+
5. **Pages that broke** — bullets. List every screen where
|
|
37
|
+
`ok === false` in the render metadata. One bullet each:
|
|
38
|
+
path, ui pattern, attempts spent, the parser diagnostic. These
|
|
39
|
+
are the pages the operator should look at first when deploying
|
|
40
|
+
to Sandbox.
|
|
41
|
+
|
|
42
|
+
If a section has nothing to report, write "None observed" — do not
|
|
43
|
+
delete the section.
|
|
44
|
+
|
|
45
|
+
## Mindset
|
|
46
|
+
|
|
47
|
+
- Be specific and short. Operator-facing markdown, not LLM prose.
|
|
48
|
+
- Name resources and accessors. "shoppings.customers.orders.create
|
|
49
|
+
forces a two-step round-trip because…" is signal. "Some endpoints
|
|
50
|
+
felt awkward" is noise.
|
|
51
|
+
- Do not invent problems. If the SDK is clean, write "the API is
|
|
52
|
+
clean; the awkwardness is in the product, not the platform."
|
|
53
|
+
- Do not write Korean. The Sandbox operator reading this is the
|
|
54
|
+
same team that reads the rest of `wiki/`.
|
|
55
|
+
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
Call `writeSdkFeedback` exactly once with the complete markdown body
|
|
59
|
+
and a one-sentence narrative summary. The orchestrator drops the
|
|
60
|
+
markdown into `frontend/wiki/sdk-feedback.md` verbatim.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
You are the SDK Study phase of the AutoView agent.
|
|
2
|
+
|
|
3
|
+
Your single job is to read an entire TypeScript SDK and produce a domain
|
|
4
|
+
map that the downstream Product Plan phase can use to design a real
|
|
5
|
+
frontend product. You are not designing screens, components, or user
|
|
6
|
+
flows yet — only mapping the surface area honestly.
|
|
7
|
+
|
|
8
|
+
## Inputs you will receive
|
|
9
|
+
|
|
10
|
+
- A list of TypeScript files from the SDK (paths + contents), generated
|
|
11
|
+
by AutoBE (or `@samchon/shopping-api` when the agent is exercised
|
|
12
|
+
against the bundled reference).
|
|
13
|
+
- Every JSDoc comment in those files is authoritative — treat it as the
|
|
14
|
+
source of truth for purpose, constraints, and actor expectations.
|
|
15
|
+
- An OpenAPI document derived from the SDK, attached for cross-reference
|
|
16
|
+
only. When the SDK and the OpenAPI disagree, the SDK wins.
|
|
17
|
+
|
|
18
|
+
## What "resources" mean
|
|
19
|
+
|
|
20
|
+
A resource is a coherent group of endpoints organized around the same
|
|
21
|
+
namespace prefix and the same noun. `shoppings.customers.sales.*` is one
|
|
22
|
+
resource (`sales`) viewed by the customer actor. `shoppings.admins.sales.*`
|
|
23
|
+
is the same resource viewed by the admin actor — list them as one
|
|
24
|
+
resource and record both actor views.
|
|
25
|
+
|
|
26
|
+
For each resource, record:
|
|
27
|
+
|
|
28
|
+
- `name`: short noun (e.g. `sales`, `orders`, `channels`, `categories`).
|
|
29
|
+
- `namespace`: dotted accessor path (e.g. `shoppings.customers.sales`).
|
|
30
|
+
If the resource is exposed under multiple namespaces (per actor),
|
|
31
|
+
list the customer-facing one first and mention the others in
|
|
32
|
+
`notes`.
|
|
33
|
+
- `purpose`: one short sentence in the operator's voice. **Why** does
|
|
34
|
+
this resource exist in the product? Not "CRUD on Sale" — "Browse and
|
|
35
|
+
buy products that sellers have published".
|
|
36
|
+
- `actorsInvolved`: subset of the actors you identified.
|
|
37
|
+
- `notes`: anything subtle worth carrying into the product plan —
|
|
38
|
+
pagination shape, multi-actor exposure, soft-delete semantics, etc.
|
|
39
|
+
|
|
40
|
+
## What "actors" mean
|
|
41
|
+
|
|
42
|
+
Actors are roles the SDK serves. Infer them from:
|
|
43
|
+
|
|
44
|
+
1. Namespace segmentation (`customers`, `sellers`, `admins`, `guests`).
|
|
45
|
+
2. Authentication shape (`IAuthorized<"customer">` vs. `IAuthorized<"admin">`).
|
|
46
|
+
3. JSDoc references to operator types.
|
|
47
|
+
|
|
48
|
+
Common actors: `customer`, `seller`, `administrator`, `guest`. Do not
|
|
49
|
+
invent actors the SDK does not support. If only one actor is present,
|
|
50
|
+
record exactly one.
|
|
51
|
+
|
|
52
|
+
For each actor, record:
|
|
53
|
+
|
|
54
|
+
- `name`: lower-case role identifier (e.g. `customer`).
|
|
55
|
+
- `journeys`: 1–5 end-to-end goals this actor accomplishes by
|
|
56
|
+
composing several endpoints. Phrase each as an arrow chain
|
|
57
|
+
(`sign up → browse catalog → add to cart → checkout`). These are the
|
|
58
|
+
raw material the Product Plan phase uses to decide which screens
|
|
59
|
+
exist.
|
|
60
|
+
|
|
61
|
+
## What "notable constraints" mean
|
|
62
|
+
|
|
63
|
+
Things a frontend developer needs to know up-front that are not
|
|
64
|
+
obvious from a single endpoint signature:
|
|
65
|
+
|
|
66
|
+
- Soft-delete vs. hard-delete behavior.
|
|
67
|
+
- Pagination conventions (cursor vs. offset; default page size).
|
|
68
|
+
- Authentication scopes and where they are required.
|
|
69
|
+
- Idempotency keys, optimistic-concurrency tokens, or audit fields.
|
|
70
|
+
- Anything the SDK comments call out as a footgun.
|
|
71
|
+
|
|
72
|
+
List these as standalone strings. One sentence each. Do not
|
|
73
|
+
fabricate constraints you cannot point to in the SDK.
|
|
74
|
+
|
|
75
|
+
## What you must NOT do
|
|
76
|
+
|
|
77
|
+
- Do **not** design screens. No "Catalog page", "Cart page", or
|
|
78
|
+
navigation. That is the Product Plan phase's job.
|
|
79
|
+
- Do **not** invent endpoints the SDK does not expose.
|
|
80
|
+
- Do **not** drop resources that look noisy. Record them all; the
|
|
81
|
+
Product Plan phase decides what to omit.
|
|
82
|
+
- Do **not** write Korean — output is consumed by downstream prompts
|
|
83
|
+
and must stay in English.
|
|
84
|
+
|
|
85
|
+
## Output
|
|
86
|
+
|
|
87
|
+
Call the `buildSdkMap` function exactly once with the full domain map.
|
|
88
|
+
The next phase reads the map verbatim — make it complete, accurate, and
|
|
89
|
+
brief.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
import { IAgenticaVendor, MicroAgentica } from "@agentica/core";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AutoBeFunctionCallingMetric,
|
|
6
|
+
IAutoBeCompiler,
|
|
7
|
+
IAutoBeTokenUsageJson,
|
|
8
|
+
} from "./typings";
|
|
9
|
+
|
|
10
|
+
import { AutoViewInterfaceCompiler } from "./compiler/AutoViewInterfaceCompiler";
|
|
11
|
+
import {
|
|
12
|
+
AutoViewDispatchEvent,
|
|
13
|
+
IAutoViewAgentContext,
|
|
14
|
+
IAutoViewConversateProps,
|
|
15
|
+
IAutoViewConversateResult,
|
|
16
|
+
} from "./context/IAutoViewAgentContext";
|
|
17
|
+
import { orchestrateAutoView } from "./orchestrate/orchestrateAutoView";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Standalone entry point for the AutoView agent.
|
|
21
|
+
*
|
|
22
|
+
* Generates a runnable Next.js + shadcn/ui frontend from any
|
|
23
|
+
* {@link OpenApi.IDocument} — typically produced by passing a third party
|
|
24
|
+
* `swagger.json` through `@autobe/utils.invertOpenApiDocument`. Does not
|
|
25
|
+
* require `@autobe/agent`; the only requirement is an {@link IAgenticaVendor}
|
|
26
|
+
* (model + API key) and a compiler instance for SDK writing.
|
|
27
|
+
*
|
|
28
|
+
* Backward compat: `@autobe/agent`'s `AutoBeAgent.runAutoView()` still works as
|
|
29
|
+
* before — it bypasses this class and hands its own context to the same
|
|
30
|
+
* `orchestrateAutoView` directly.
|
|
31
|
+
*/
|
|
32
|
+
export interface IAutoViewAgentProps {
|
|
33
|
+
/**
|
|
34
|
+
* The OpenAPI document the frontend will be generated against. For a
|
|
35
|
+
* third-party Swagger payload, run it through `invertOpenApiDocument` from
|
|
36
|
+
* `@autobe/utils` first.
|
|
37
|
+
*/
|
|
38
|
+
document: OpenApi.IDocument;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Agentica vendor — model + API key + optional semaphore. Same shape
|
|
42
|
+
* `@autobe/agent` accepts; the agent's quota-tracker / mistral-shim helpers
|
|
43
|
+
* are not wired here because they are AutoBE-specific.
|
|
44
|
+
*/
|
|
45
|
+
vendor: IAgenticaVendor;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Optional design theme passed through to the Product Plan + Render phases.
|
|
49
|
+
* Empty / undefined means "prototype-first, content-first defaults".
|
|
50
|
+
*/
|
|
51
|
+
designTheme?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compiler instance used to write the SDK files. Falls back to a fresh
|
|
55
|
+
* `AutoBeCompiler` when not provided. Pass your own instance if you want to
|
|
56
|
+
* share one across multiple AutoViewAgent runs in the same process.
|
|
57
|
+
*/
|
|
58
|
+
compiler?: IAutoBeCompiler;
|
|
59
|
+
|
|
60
|
+
/** Concurrency cap for the per-screen render batch. Defaults to 8. */
|
|
61
|
+
semaphore?: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Live backend the generated frontend should connect to. When given, the
|
|
65
|
+
* scaffold wires `connection.ts` with `simulate: false` so the dev server
|
|
66
|
+
* renders real data on first boot — the standalone CLI's reason to exist.
|
|
67
|
+
*
|
|
68
|
+
* Omit to fall back to simulator mode (only useful for swaggers that
|
|
69
|
+
* genuinely have no backend). `extractBackendFromSwagger(payload)` in this
|
|
70
|
+
* package is a small helper for pulling the URL out of a swagger's
|
|
71
|
+
* `servers[0].url` field automatically.
|
|
72
|
+
*/
|
|
73
|
+
backend?: { host: string } | null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Opt into Phase 6 runtime audit: after typecheck, boot `next dev`, walk
|
|
77
|
+
* every page with Playwright, and re-render any page that console-errored /
|
|
78
|
+
* crashed / failed to navigate. Catches the class of bugs `tsc --noEmit`
|
|
79
|
+
* cannot see (`obj?.array.method(...)` undefined-access, react render crashes
|
|
80
|
+
* on simulator data).
|
|
81
|
+
*
|
|
82
|
+
* Defaults to `false` because it installs Playwright + Chromium (~150MB) and
|
|
83
|
+
* boots `next dev` twice per agent run, adding ~10–20 minutes.
|
|
84
|
+
*/
|
|
85
|
+
runtimeAudit?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opt into workflow verification: after typecheck, boot the generated app in
|
|
89
|
+
* a real headless browser and walk the derived user workflows, writing
|
|
90
|
+
* `wiki/verification.md`. Proves the app works for a user, not just compiles.
|
|
91
|
+
* Heavy (Chromium + `next dev`); defaults to `false`.
|
|
92
|
+
*/
|
|
93
|
+
verify?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Path globs to KEEP — only endpoints whose path matches become screens.
|
|
97
|
+
* Slices a large swagger to one actor surface, e.g.
|
|
98
|
+
* `["shoppings/customers/**"]`. Empty/absent means "all".
|
|
99
|
+
*/
|
|
100
|
+
include?: string[];
|
|
101
|
+
|
|
102
|
+
/** Path globs to DROP after include, e.g. `["**/monitors/**"]`. */
|
|
103
|
+
exclude?: string[];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Optional callback for every event the orchestrators dispatch
|
|
107
|
+
* (`autoViewStart`, `autoViewSdkStudy`, `autoViewProductPlan`,
|
|
108
|
+
* `autoViewScaffold`, per-page `autoViewRenderPage`, `autoViewReview`,
|
|
109
|
+
* `autoViewComplete`). Useful for progress bars, structured logging, etc.
|
|
110
|
+
*/
|
|
111
|
+
onEvent?: (event: AutoViewDispatchEvent) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Public result of {@link AutoViewAgent.run}. */
|
|
115
|
+
export interface IAutoViewAgentResult {
|
|
116
|
+
/** Path → content map of the generated frontend project. */
|
|
117
|
+
files: Record<string, string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class AutoViewAgent {
|
|
121
|
+
public constructor(private readonly props: IAutoViewAgentProps) {}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Drive every AutoView phase end-to-end and return the assembled frontend
|
|
125
|
+
* project as a flat `path → content` map.
|
|
126
|
+
*
|
|
127
|
+
* Mutates nothing on disk; serialize the map to ZIP, push it to a Sandbox, or
|
|
128
|
+
* write it out directly with `@autobe/filesystem` — your choice. The CLI
|
|
129
|
+
* binary (`bin/autoview`) does the disk-write step.
|
|
130
|
+
*/
|
|
131
|
+
public async run(): Promise<IAutoViewAgentResult> {
|
|
132
|
+
const compiler: IAutoBeCompiler = this.props.compiler ?? {
|
|
133
|
+
interface: new AutoViewInterfaceCompiler(),
|
|
134
|
+
};
|
|
135
|
+
let captured: Record<string, string> = {};
|
|
136
|
+
const ctx: IAutoViewAgentContext = {
|
|
137
|
+
state: () => ({
|
|
138
|
+
interface: { document: this.props.document, step: 0 },
|
|
139
|
+
}),
|
|
140
|
+
compiler: async () => compiler,
|
|
141
|
+
vendor: { semaphore: this.props.semaphore ?? 8 },
|
|
142
|
+
dispatch: (event: AutoViewDispatchEvent) => {
|
|
143
|
+
if (event.type === "autoViewComplete") {
|
|
144
|
+
captured = event.files;
|
|
145
|
+
}
|
|
146
|
+
this.props.onEvent?.(event);
|
|
147
|
+
return event;
|
|
148
|
+
},
|
|
149
|
+
conversate: (cprops) => this.conversate(cprops),
|
|
150
|
+
};
|
|
151
|
+
await orchestrateAutoView(ctx, {
|
|
152
|
+
source: "interface",
|
|
153
|
+
designTheme: this.props.designTheme,
|
|
154
|
+
backend: this.props.backend ?? null,
|
|
155
|
+
runtimeAudit: this.props.runtimeAudit ?? false,
|
|
156
|
+
verify: this.props.verify ?? false,
|
|
157
|
+
include: this.props.include,
|
|
158
|
+
exclude: this.props.exclude,
|
|
159
|
+
});
|
|
160
|
+
return { files: captured };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async conversate(
|
|
164
|
+
cprops: IAutoViewConversateProps,
|
|
165
|
+
): Promise<IAutoViewConversateResult> {
|
|
166
|
+
// Minimal agentica wiring — no quota tracker, no vendor-specific
|
|
167
|
+
// shims, no compaction. The full AutoBE agent layers those on for
|
|
168
|
+
// its own concerns; the standalone path keeps things thin so it
|
|
169
|
+
// works against any OpenAI-compatible endpoint that supports tool
|
|
170
|
+
// calling.
|
|
171
|
+
const agent = new MicroAgentica({
|
|
172
|
+
vendor: this.props.vendor,
|
|
173
|
+
config: {
|
|
174
|
+
executor: { describe: false },
|
|
175
|
+
stream: cprops.enforceFunctionCall === false,
|
|
176
|
+
},
|
|
177
|
+
histories: cprops.histories,
|
|
178
|
+
controllers: [cprops.controller],
|
|
179
|
+
});
|
|
180
|
+
let tokenUsage: IAutoBeTokenUsageJson.IComponent = zeroTokenUsage();
|
|
181
|
+
const metric: AutoBeFunctionCallingMetric = {
|
|
182
|
+
attempt: 0,
|
|
183
|
+
success: 0,
|
|
184
|
+
invalidJson: 0,
|
|
185
|
+
validationFailure: 0,
|
|
186
|
+
consent: 0,
|
|
187
|
+
};
|
|
188
|
+
agent.on("call", () => {
|
|
189
|
+
metric.attempt++;
|
|
190
|
+
});
|
|
191
|
+
agent.on("execute", () => {
|
|
192
|
+
metric.success++;
|
|
193
|
+
});
|
|
194
|
+
agent.on("jsonParseError", () => {
|
|
195
|
+
metric.invalidJson++;
|
|
196
|
+
});
|
|
197
|
+
agent.on("validate", () => {
|
|
198
|
+
metric.validationFailure++;
|
|
199
|
+
});
|
|
200
|
+
await agent.conversate(cprops.userMessage);
|
|
201
|
+
const usage = agent.getTokenUsage().aggregate;
|
|
202
|
+
tokenUsage = {
|
|
203
|
+
total: usage.total,
|
|
204
|
+
input: usage.input,
|
|
205
|
+
output: usage.output,
|
|
206
|
+
};
|
|
207
|
+
return { metric, tokenUsage };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function zeroTokenUsage(): IAutoBeTokenUsageJson.IComponent {
|
|
212
|
+
return {
|
|
213
|
+
total: 0,
|
|
214
|
+
input: { total: 0, cached: 0 },
|
|
215
|
+
output: {
|
|
216
|
+
total: 0,
|
|
217
|
+
reasoning: 0,
|
|
218
|
+
accepted_prediction: 0,
|
|
219
|
+
rejected_prediction: 0,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8
|
+
import { OpenApiConverter } from "@typia/utils";
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
10
|
+
|
|
11
|
+
import { emitMcpServer } from "./emitMcpServer";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* End-to-end proof that the emitted MCP server speaks the real MCP protocol:
|
|
15
|
+
* spawn it over stdio, drive it with an actual MCP `Client` (listTools +
|
|
16
|
+
* callTool), and exercise the producer→consumer chain (list → take an id from
|
|
17
|
+
* the response → detail) against a LOCAL mock backend (deterministic, no
|
|
18
|
+
* network). Distinct from the string-shape unit tests in emitMcpServer.test.ts.
|
|
19
|
+
*/
|
|
20
|
+
function doc(paths: Record<string, unknown>, schemas: Record<string, unknown> = {}) {
|
|
21
|
+
return OpenApiConverter.upgradeDocument({
|
|
22
|
+
openapi: "3.0.0", info: { title: "Shop", version: "1.0.0" }, paths, components: { schemas },
|
|
23
|
+
} as never);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const param = (n: string) => ({ name: n, in: "path", required: true, schema: { type: "string" } });
|
|
27
|
+
const fixture = doc(
|
|
28
|
+
{
|
|
29
|
+
"/sales": { get: { operationId: "index", summary: "List sales.", responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Sale" } } } } } } } },
|
|
30
|
+
"/sales/{saleId}": { get: { operationId: "at", summary: "Get a sale.", parameters: [param("saleId")], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Sale" } } } } } } },
|
|
31
|
+
// a nested resource — its id is produced by the questions list, which itself
|
|
32
|
+
// needs the saleId produced by the sales list (a genuine 2-hop chain).
|
|
33
|
+
"/sales/{saleId}/questions": { get: { operationId: "qIndex", summary: "List questions on a sale.", parameters: [param("saleId")], responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Question" } } } } } } } },
|
|
34
|
+
"/sales/{saleId}/questions/{questionId}": { get: { operationId: "qAt", summary: "Get one question.", parameters: [param("saleId"), param("questionId")], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Question" } } } } } } },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
Sale: { type: "object", properties: { id: { type: "string" }, title: { type: "string" } }, required: ["id"] },
|
|
38
|
+
Question: { type: "object", properties: { id: { type: "string" }, body: { type: "string" } }, required: ["id"] },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const SALES = [{ id: "sale_1", title: "MacBook" }, { id: "sale_2", title: "iPhone" }];
|
|
43
|
+
const QUESTIONS: Record<string, Array<{ id: string; body: string }>> = {
|
|
44
|
+
sale_2: [{ id: "q_9", body: "Is the iPhone unlocked?" }],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe("emitMcpServer — real MCP protocol round-trip", () => {
|
|
48
|
+
let backend: http.Server;
|
|
49
|
+
let base = "";
|
|
50
|
+
let client: Client;
|
|
51
|
+
let transport: StdioClientTransport;
|
|
52
|
+
const tmpDirs: string[] = [];
|
|
53
|
+
|
|
54
|
+
// Temp dirs live UNDER the repo so the spawned ESM server resolves this repo's
|
|
55
|
+
// @modelcontextprotocol/sdk by walking up to node_modules (ESM ignores NODE_PATH).
|
|
56
|
+
const mkTmp = (prefix: string): string => {
|
|
57
|
+
const dir = mkdtempSync(join(process.cwd(), prefix));
|
|
58
|
+
tmpDirs.push(dir);
|
|
59
|
+
return dir;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
// 1. local mock backend
|
|
64
|
+
backend = http.createServer((req, res) => {
|
|
65
|
+
const u = (req.url ?? "").split("?")[0];
|
|
66
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
67
|
+
if (u === "/sales") return res.end(JSON.stringify(SALES));
|
|
68
|
+
const qOne = u!.match(/^\/sales\/([^/]+)\/questions\/([^/]+)$/);
|
|
69
|
+
if (qOne) return res.end(JSON.stringify((QUESTIONS[qOne[1]!] ?? []).find((q) => q.id === qOne[2]) ?? {}));
|
|
70
|
+
const qList = u!.match(/^\/sales\/([^/]+)\/questions$/);
|
|
71
|
+
if (qList) return res.end(JSON.stringify(QUESTIONS[qList[1]!] ?? []));
|
|
72
|
+
const one = u!.match(/^\/sales\/([^/]+)$/);
|
|
73
|
+
return res.end(JSON.stringify(one ? SALES.find((s) => s.id === one[1]) ?? {} : {}));
|
|
74
|
+
});
|
|
75
|
+
await new Promise<void>((ok) => backend.listen(0, "127.0.0.1", () => ok()));
|
|
76
|
+
base = `http://127.0.0.1:${(backend.address() as { port: number }).port}`;
|
|
77
|
+
|
|
78
|
+
// 2. write the emitted server to a temp dir under the repo
|
|
79
|
+
const dir = mkTmp(".tmp-mcp-");
|
|
80
|
+
const files = emitMcpServer(fixture, { backend: base, title: "Shop" });
|
|
81
|
+
for (const [name, content] of Object.entries(files)) writeFileSync(join(dir, name), content);
|
|
82
|
+
|
|
83
|
+
// 3. spawn it as a real MCP server over stdio and connect a real MCP client.
|
|
84
|
+
transport = new StdioClientTransport({
|
|
85
|
+
command: process.execPath,
|
|
86
|
+
args: [join(dir, "server.mjs")],
|
|
87
|
+
env: { ...process.env, API_HOST: base },
|
|
88
|
+
});
|
|
89
|
+
client = new Client({ name: "test", version: "1.0.0" }, { capabilities: {} });
|
|
90
|
+
await client.connect(transport);
|
|
91
|
+
}, 30000);
|
|
92
|
+
|
|
93
|
+
afterAll(async () => {
|
|
94
|
+
await client?.close();
|
|
95
|
+
backend?.close();
|
|
96
|
+
for (const d of tmpDirs) rmSync(d, { recursive: true, force: true });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("listTools returns the surface with read/write annotations over MCP", async () => {
|
|
100
|
+
const { tools } = await client.listTools();
|
|
101
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
102
|
+
const list = tools.find((t) => t.name === "sales.get");
|
|
103
|
+
expect(list).toBeDefined();
|
|
104
|
+
expect(list!.annotations?.readOnlyHint).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("callTool executes the live API and drives a producer→consumer chain", async () => {
|
|
108
|
+
// list
|
|
109
|
+
const listRes = (await client.callTool({ name: "sales.get", arguments: {} })) as {
|
|
110
|
+
content: Array<{ text: string }>;
|
|
111
|
+
isError?: boolean;
|
|
112
|
+
};
|
|
113
|
+
expect(listRes.isError ?? false).toBe(false);
|
|
114
|
+
const sales = JSON.parse(listRes.content[0]!.text) as Array<{ id: string; title: string }>;
|
|
115
|
+
expect(sales.length).toBe(2);
|
|
116
|
+
|
|
117
|
+
// take an id from the list response, call detail with it
|
|
118
|
+
const detailRes = (await client.callTool({
|
|
119
|
+
name: "sales.getBySaleid",
|
|
120
|
+
arguments: { saleId: sales[1]!.id },
|
|
121
|
+
})) as { content: Array<{ text: string }>; isError?: boolean };
|
|
122
|
+
expect(detailRes.isError ?? false).toBe(false);
|
|
123
|
+
const sale = JSON.parse(detailRes.content[0]!.text) as { title: string };
|
|
124
|
+
expect(sale.title).toBe("iPhone");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("drives a 2-hop nested chain, threading two path params from prior responses", async () => {
|
|
128
|
+
const call = async (name: string, args: Record<string, unknown>) =>
|
|
129
|
+
(await client.callTool({ name, arguments: args })) as {
|
|
130
|
+
content: Array<{ text: string }>;
|
|
131
|
+
isError?: boolean;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// hop 0: list sales → saleId
|
|
135
|
+
const sales = JSON.parse((await call("sales.get", {})).content[0]!.text) as Array<{ id: string }>;
|
|
136
|
+
const saleId = sales[1]!.id; // sale_2
|
|
137
|
+
|
|
138
|
+
// hop 1: list that sale's questions (needs saleId) → questionId
|
|
139
|
+
const qList = await call("sales.questions.getBySaleid", { saleId });
|
|
140
|
+
expect(qList.isError ?? false).toBe(false);
|
|
141
|
+
const questions = JSON.parse(qList.content[0]!.text) as Array<{ id: string }>;
|
|
142
|
+
expect(questions.length).toBe(1);
|
|
143
|
+
const questionId = questions[0]!.id; // q_9
|
|
144
|
+
|
|
145
|
+
// hop 2: get the question detail (needs BOTH saleId and questionId)
|
|
146
|
+
const qDetail = await call("sales.questions.getBySaleidAndQuestionid", { saleId, questionId });
|
|
147
|
+
expect(qDetail.isError ?? false).toBe(false);
|
|
148
|
+
const question = JSON.parse(qDetail.content[0]!.text) as { body: string };
|
|
149
|
+
expect(question.body).toContain("iPhone");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("declares the multi-hop producer chain on the nested tool", async () => {
|
|
153
|
+
const { tools } = await client.listTools();
|
|
154
|
+
const nested = tools.find((t) => t.name === "sales.questions.getBySaleidAndQuestionid");
|
|
155
|
+
expect(nested).toBeDefined();
|
|
156
|
+
// both path params are required inputs
|
|
157
|
+
expect(nested!.inputSchema).toMatchObject({ required: expect.arrayContaining(["saleId", "questionId"]) });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("the generated server.mjs is valid JS (node --check)", () => {
|
|
161
|
+
const dir = mkTmp(".tmp-mcp-chk-");
|
|
162
|
+
const files = emitMcpServer(fixture, { backend: base, title: "Shop" });
|
|
163
|
+
const path = join(dir, "server.mjs");
|
|
164
|
+
writeFileSync(path, files["server.mjs"]!);
|
|
165
|
+
const r = spawnSync(process.execPath, ["--check", path], { encoding: "utf8" });
|
|
166
|
+
expect(r.status).toBe(0);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OpenApiConverter } from "@typia/utils";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { emitMcpServer } from "./emitMcpServer";
|
|
5
|
+
|
|
6
|
+
function doc(paths: Record<string, unknown>, schemas: Record<string, unknown> = {}) {
|
|
7
|
+
return OpenApiConverter.upgradeDocument({
|
|
8
|
+
openapi: "3.0.0",
|
|
9
|
+
info: { title: "Shop", version: "1.0.0" },
|
|
10
|
+
paths,
|
|
11
|
+
components: { schemas },
|
|
12
|
+
} as never);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const fixture = doc(
|
|
16
|
+
{
|
|
17
|
+
"/sales": {
|
|
18
|
+
get: { operationId: "index", summary: "List sales.", responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Sale" } } } } } } },
|
|
19
|
+
},
|
|
20
|
+
"/sales/{saleId}": {
|
|
21
|
+
get: { operationId: "at", summary: "Get a sale.", parameters: [{ name: "saleId", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Sale" } } } } } },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{ Sale: { type: "object", properties: { id: { type: "string" }, title: { type: "string" } }, required: ["id"] } },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe("emitMcpServer", () => {
|
|
28
|
+
const files = emitMcpServer(fixture, { backend: "http://127.0.0.1:37001", title: "Shop" });
|
|
29
|
+
|
|
30
|
+
it("emits a runnable MCP project", () => {
|
|
31
|
+
expect(Object.keys(files).sort()).toEqual(["README.md", "package.json", "server.mjs"]);
|
|
32
|
+
const pkg = JSON.parse(files["package.json"]!);
|
|
33
|
+
expect(pkg.dependencies["@modelcontextprotocol/sdk"]).toBeDefined();
|
|
34
|
+
expect(pkg.type).toBe("module");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("server wires the MCP request handlers and bakes the tools + backend", () => {
|
|
38
|
+
const server = files["server.mjs"]!;
|
|
39
|
+
expect(server).toContain("ListToolsRequestSchema");
|
|
40
|
+
expect(server).toContain("CallToolRequestSchema");
|
|
41
|
+
expect(server).toContain("StdioServerTransport");
|
|
42
|
+
// the tool surface is baked in, with the default backend and an HTTP executor
|
|
43
|
+
expect(server).toContain('"sales.get"');
|
|
44
|
+
expect(server).toContain("http://127.0.0.1:37001");
|
|
45
|
+
expect(server).toContain("await fetch(url");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("is deterministic", () => {
|
|
49
|
+
expect(emitMcpServer(fixture, { backend: null })).toEqual(emitMcpServer(fixture, { backend: null }));
|
|
50
|
+
});
|
|
51
|
+
});
|