-
Notifications
You must be signed in to change notification settings - Fork 72
[LG-5666] MCP-UI App Scaffolding #3280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c0583fc
84f98e5
934fdf5
8aeca63
48aa2df
dfe07f9
ef6b4ae
da452a3
4978f15
a602d1b
27c7a07
e452b93
f6f64cd
cdad5a2
274bc7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.js | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| # local env files | ||
| .env*.local | ||
| .env.local | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # MCP UI App | ||
|
|
||
| A Next.js application for the MCP UI, configured for iframe embedding with CORS and CSP headers. | ||
|
|
||
| ## Getting Started | ||
|
|
||
| ### 1. Environment Setup | ||
|
|
||
| Create a `.env.local` file in the app root with the following variables: | ||
|
|
||
| ```bash | ||
| # Base URL for the application | ||
| NEXT_PUBLIC_BASE_URL=http://localhost:3000 | ||
|
|
||
| # Allowed parent origins for iframe embedding (comma-separated) | ||
| ALLOWED_IFRAME_ORIGINS=http://localhost:3000,http://localhost:3001,https://your-app.com | ||
| ``` | ||
|
|
||
| Configuration options: | ||
|
|
||
| - `NEXT_PUBLIC_BASE_URL`: The base URL of your application | ||
| - `ALLOWED_IFRAME_ORIGINS`: Comma-separated list of allowed parent origins for iframe embedding | ||
|
|
||
| ### 2. Install dependencies | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| ### 3. Run the development server | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
|
|
||
| ## Configuration | ||
|
|
||
| ### Base URL | ||
|
|
||
| The base URL is configured via the `NEXT_PUBLIC_BASE_URL` environment variable and can be accessed in your code: | ||
|
|
||
| ```typescript | ||
| import config from '@/config'; | ||
|
|
||
| console.log(config.baseUrl); // http://localhost:3000 | ||
| ``` | ||
|
|
||
| ### CORS and Iframe Embedding | ||
|
|
||
| The app is configured with: | ||
|
|
||
| - **CORS headers**: Allow cross-origin requests from specified origins | ||
| - **CSP headers**: Content Security Policy appropriate for iframe embedding | ||
| - **Frame ancestors**: Controls which domains can embed this app in an iframe | ||
|
|
||
| To allow your app to be embedded in an iframe from specific domains, add them to the `ALLOWED_IFRAME_ORIGINS` environment variable. | ||
|
|
||
| ## Build | ||
|
|
||
| This app is excluded from the default `pnpm build` command in the workspace. To build this app specifically, run: | ||
|
|
||
| ```bash | ||
| cd apps/mcp-ui-app | ||
| pnpm build | ||
| ``` | ||
|
|
||
| Or from the workspace root: | ||
|
|
||
| ```bash | ||
| pnpm build:apps | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /** @type {import('next').NextConfig} */ | ||
| const nextConfig = { | ||
| reactStrictMode: true, | ||
| transpilePackages: [ | ||
| '@lg-mcp-ui/list-databases', | ||
| '@leafygreen-ui/card', | ||
| '@leafygreen-ui/typography', | ||
| '@leafygreen-ui/emotion', | ||
| '@leafygreen-ui/tokens', | ||
| '@leafygreen-ui/lib', | ||
| ], | ||
|
|
||
| async headers() { | ||
| // Get allowed origins from environment variable | ||
| const allowedOrigins = process.env.ALLOWED_IFRAME_ORIGINS?.split(',') || [ | ||
| '*', | ||
| ]; | ||
|
|
||
| return [ | ||
| { | ||
| // Apply to all routes | ||
| source: '/:path*', | ||
| headers: [ | ||
| // CORS headers for iframe embedding | ||
| { | ||
| key: 'Access-Control-Allow-Origin', | ||
| value: allowedOrigins[0] || '*', // Use first origin or wildcard | ||
| }, | ||
| { | ||
| key: 'Access-Control-Allow-Methods', | ||
| value: 'GET, POST, PUT, DELETE, OPTIONS', | ||
| }, | ||
| { | ||
| key: 'Access-Control-Allow-Headers', | ||
| value: 'Content-Type, Authorization', | ||
| }, | ||
| { | ||
| key: 'Access-Control-Allow-Credentials', | ||
| value: 'true', | ||
| }, | ||
| // Content Security Policy for iframe embedding | ||
| { | ||
| key: 'Content-Security-Policy', | ||
| value: [ | ||
| "default-src 'self'", | ||
| "script-src 'self' 'unsafe-eval' 'unsafe-inline'", | ||
TheSonOfThomp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "style-src 'self' 'unsafe-inline'", | ||
| "img-src 'self' data: https:", | ||
| "font-src 'self' data:", | ||
| "connect-src 'self'", | ||
| `frame-ancestors ${allowedOrigins.join(' ')}`, // Allows embedding in iframes from these origins | ||
| ].join('; '), | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
| }, | ||
| }; | ||
|
|
||
| module.exports = nextConfig; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| { | ||
| "name": "@lg-apps/mcp-ui-app", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: This probably doesn't need to be scoped since we're not publishing it to npm |
||
| "version": "0.1.0", | ||
| "private": true, | ||
| "scripts": { | ||
| "dev": "next dev", | ||
| "build": "next build", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turbo is complaining that there's nothing to cache when I run this. Probable need to add a custom turbo config for the app that sets the outputs https://turborepo.com/docs/reference/configuration#outputs |
||
| "start": "next start", | ||
| "lint": "next lint" | ||
| }, | ||
| "dependencies": { | ||
| "@lg-mcp-ui/list-databases": "workspace:*", | ||
| "next": "^14.2.0", | ||
| "react": "^18.2.0", | ||
| "react-dom": "^18.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^20.12.5", | ||
| "@types/react": "18.2.23", | ||
| "@types/react-dom": "18.2.8", | ||
| "typescript": "~5.8.0" | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import React from 'react'; | ||
| import type { Metadata } from 'next'; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: 'MCP UI App', | ||
| description: 'MCP UI Application', | ||
| }; | ||
|
|
||
| export default function RootLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body>{children}</body> | ||
| </html> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 'use client'; | ||
|
|
||
| import React from 'react'; | ||
| import { ListDatabases } from '@lg-mcp-ui/list-databases'; | ||
|
|
||
| export default function ListDatabasesPage() { | ||
| return <ListDatabases databases={[]} />; | ||
| } | ||
|
Comment on lines
+1
to
+8
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Future consideration — this could probably be a |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would think that a benefit of keeping these different components in a monorepo is being able to centralize
.gitignorepatterns in the root.gitignorefile. Is there an explicit reason we need a separate one defined here?