@@ -14,6 +14,12 @@ import { GUIDE_SECTIONS, type CodeSnippet, type GuideSection } from './sections'
1414export interface AgentGuideOptions {
1515 /** Live daemon URL detected at modal-open time. Defaults to the documented port. */
1616 daemonUrl ?: string ;
17+ /**
18+ * Launch spec returned by `/api/mcp/install-info`. When present, MCP
19+ * snippets use this absolute command/args/env tuple instead of assuming
20+ * an `od` binary exists on PATH.
21+ */
22+ mcpInstallInfo ?: AgentGuideMcpInstallInfo | null ;
1723 /**
1824 * Optional `od` binary path / hint. When provided we mention it in the
1925 * setup checklist so the agent knows whether to run `od …` directly or
@@ -24,10 +30,22 @@ export interface AgentGuideOptions {
2430 versionHint ?: string ;
2531}
2632
33+ export interface AgentGuideMcpInstallInfo {
34+ command : string ;
35+ args : string [ ] ;
36+ env ?: Record < string , string > ;
37+ }
38+
39+ export interface AgentGuideSnippetRenderOptions {
40+ daemonUrl : string | undefined ;
41+ mcpInstallInfo : AgentGuideMcpInstallInfo | null ;
42+ }
43+
2744const DEFAULT_DAEMON_URL = 'http://127.0.0.1:7456' ;
2845
2946export function buildAgentGuideMarkdown ( options : AgentGuideOptions = { } ) : string {
3047 const daemonUrl = ( options . daemonUrl ?? DEFAULT_DAEMON_URL ) . replace ( / \/ $ / , '' ) ;
48+ const installInfo = normalizeMcpInstallInfo ( options . mcpInstallInfo ) ;
3149 const lines : string [ ] = [ ] ;
3250
3351 lines . push ( '# Open Design — agent setup guide' ) ;
@@ -56,12 +74,23 @@ export function buildAgentGuideMarkdown(options: AgentGuideOptions = {}): string
5674 lines . push ( '' ) ;
5775 lines . push ( ' If it 404s or times out, ask the user to run `pnpm tools-dev` (dev) or open the Open Design app (packaged).' ) ;
5876 lines . push ( '' ) ;
59- lines . push ( '2. Detect available agent CLIs and confirm `od` is on PATH:' ) ;
77+ if ( installInfo ) {
78+ lines . push ( '2. Use this daemon-reported MCP server config. Do not replace it with a bare `od` command:' ) ;
79+ lines . push ( '' ) ;
80+ lines . push ( ' ```json' ) ;
81+ lines . push ( indent ( buildMcpServerConfigSnippet ( installInfo ) , ' ' ) ) ;
82+ lines . push ( ' ```' ) ;
83+ lines . push ( '' ) ;
84+ lines . push ( ` This config came from \`${ daemonUrl } /api/mcp/install-info\` and preserves the absolute command, args, and env needed by packaged installs.` ) ;
85+ } else {
86+ lines . push ( '2. Detect available agent CLIs and confirm `od` is on PATH:' ) ;
87+ lines . push ( '' ) ;
88+ lines . push ( ' ```bash' ) ;
89+ lines . push ( ' od doctor' ) ;
90+ lines . push ( ' od status --json' ) ;
91+ lines . push ( ' ```' ) ;
92+ }
6093 lines . push ( '' ) ;
61- lines . push ( ' ```bash' ) ;
62- lines . push ( ' od doctor' ) ;
63- lines . push ( ' od status --json' ) ;
64- lines . push ( ' ```' ) ;
6594 if ( options . cliHint ) {
6695 lines . push ( '' ) ;
6796 lines . push ( ` The user reported \`od\` at: \`${ options . cliHint } \`` ) ;
@@ -88,7 +117,7 @@ export function buildAgentGuideMarkdown(options: AgentGuideOptions = {}): string
88117 lines . push ( '' ) ;
89118
90119 for ( const section of GUIDE_SECTIONS ) {
91- lines . push ( ...renderSection ( section , daemonUrl ) ) ;
120+ lines . push ( ...renderSection ( section , daemonUrl , installInfo ) ) ;
92121 }
93122
94123 lines . push ( '## Reference URLs' ) ;
@@ -109,7 +138,31 @@ export function buildAgentGuideMarkdown(options: AgentGuideOptions = {}): string
109138 return lines . join ( '\n' ) ;
110139}
111140
112- function renderSection ( section : GuideSection , daemonUrl : string ) : string [ ] {
141+ export function renderAgentGuideSnippetBody (
142+ snippet : CodeSnippet ,
143+ options : AgentGuideSnippetRenderOptions ,
144+ ) : string {
145+ const daemonUrl = ( options . daemonUrl ?? DEFAULT_DAEMON_URL ) . replace ( / \/ $ / , '' ) ;
146+ return renderSnippetBody (
147+ snippet ,
148+ daemonUrl ,
149+ normalizeMcpInstallInfo ( options . mcpInstallInfo ) ,
150+ ) ;
151+ }
152+
153+ export function agentGuideSnippetUsesMcpInstallInfo ( snippet : CodeSnippet ) : boolean {
154+ return (
155+ snippet . language === 'json' &&
156+ snippet . body . includes ( '"mcpServers"' ) &&
157+ snippet . body . includes ( '"command": "od"' )
158+ ) ;
159+ }
160+
161+ function renderSection (
162+ section : GuideSection ,
163+ daemonUrl : string ,
164+ installInfo : AgentGuideMcpInstallInfo | null ,
165+ ) : string [ ] {
113166 const lines : string [ ] = [ ] ;
114167 lines . push ( `## ${ substituteDaemonUrl ( section . heading , daemonUrl ) } ` ) ;
115168 lines . push ( '' ) ;
@@ -122,7 +175,7 @@ function renderSection(section: GuideSection, daemonUrl: string): string[] {
122175 lines . push ( '' ) ;
123176 }
124177 for ( const snippet of section . snippets ) {
125- lines . push ( ...renderSnippet ( snippet , daemonUrl ) ) ;
178+ lines . push ( ...renderSnippet ( snippet , daemonUrl , installInfo ) ) ;
126179 }
127180 if ( section . footer ) {
128181 lines . push ( `> ${ substituteDaemonUrl ( section . footer , daemonUrl ) } ` ) ;
@@ -131,17 +184,69 @@ function renderSection(section: GuideSection, daemonUrl: string): string[] {
131184 return lines ;
132185}
133186
134- function renderSnippet ( snippet : CodeSnippet , daemonUrl : string ) : string [ ] {
187+ function renderSnippet (
188+ snippet : CodeSnippet ,
189+ daemonUrl : string ,
190+ installInfo : AgentGuideMcpInstallInfo | null ,
191+ ) : string [ ] {
135192 const lines : string [ ] = [ ] ;
136193 lines . push ( `### ${ substituteDaemonUrl ( snippet . label , daemonUrl ) } ` ) ;
137194 lines . push ( '' ) ;
138195 lines . push ( '```' + snippet . language ) ;
139- lines . push ( substituteDaemonUrl ( snippet . body , daemonUrl ) ) ;
196+ lines . push ( renderSnippetBody ( snippet , daemonUrl , installInfo ) ) ;
140197 lines . push ( '```' ) ;
141198 lines . push ( '' ) ;
142199 return lines ;
143200}
144201
202+ function renderSnippetBody (
203+ snippet : CodeSnippet ,
204+ daemonUrl : string ,
205+ installInfo : AgentGuideMcpInstallInfo | null ,
206+ ) : string {
207+ if ( installInfo && agentGuideSnippetUsesMcpInstallInfo ( snippet ) ) {
208+ return buildMcpServerConfigSnippet ( installInfo ) ;
209+ }
210+ return substituteDaemonUrl ( snippet . body , daemonUrl ) ;
211+ }
212+
145213function substituteDaemonUrl ( body : string , daemonUrl : string ) : string {
146214 return body . replace ( / h t t p : \/ \/ 1 2 7 \. 0 \. 0 \. 1 : 7 4 5 6 / g, daemonUrl ) ;
147215}
216+
217+ function buildMcpServerConfigSnippet ( info : AgentGuideMcpInstallInfo ) : string {
218+ const env = info . env && Object . keys ( info . env ) . length > 0 ? info . env : undefined ;
219+ return JSON . stringify (
220+ {
221+ mcpServers : {
222+ 'open-design' : {
223+ command : info . command ,
224+ args : info . args ,
225+ ...( env ? { env } : { } ) ,
226+ } ,
227+ } ,
228+ } ,
229+ null ,
230+ 2 ,
231+ ) ;
232+ }
233+
234+ function normalizeMcpInstallInfo (
235+ info : AgentGuideMcpInstallInfo | null | undefined ,
236+ ) : AgentGuideMcpInstallInfo | null {
237+ if ( ! info || typeof info . command !== 'string' || info . command . length === 0 ) return null ;
238+ if ( ! Array . isArray ( info . args ) || ! info . args . every ( ( arg ) => typeof arg === 'string' ) ) return null ;
239+ const env : Record < string , string > = { } ;
240+ for ( const [ key , value ] of Object . entries ( info . env ?? { } ) ) {
241+ if ( typeof value === 'string' ) env [ key ] = value ;
242+ }
243+ return {
244+ command : info . command ,
245+ args : info . args ,
246+ ...( Object . keys ( env ) . length > 0 ? { env } : { } ) ,
247+ } ;
248+ }
249+
250+ function indent ( body : string , prefix : string ) : string {
251+ return body . split ( '\n' ) . map ( ( line ) => `${ prefix } ${ line } ` ) . join ( '\n' ) ;
252+ }
0 commit comments