
MCP 도구 호출과 LLM 트래픽을 보안, 거버넌스, 관찰하는 오픈소스 게이트웨이로, API 키 인증과 모니터링용 관리자 콘솔을 제공합니다.
Tuskira AI Agent Gateway용 관리 콘솔입니다. React 19 + Vite + TypeScript (strict) + Tailwind CSS 4 + shadcn/ui로 구성되어 있습니다.
Node 26+ 및 npm 11+가 필요합니다 (pnpm 미지원).
npm install
npm run dev
개발 서버는 /api를 http://localhost:8081로 프록시하므로( vite.config.ts 참조), UI와 함께 게이트웨이를 :8081에서 실행하세요. 게이트웨이가 발급한 API 키(gk_…)로 /login에서 로그인하세요.
리포지토리 루트에서 대신 다음을 실행할 수 있습니다:
make ui-install
make ui-dev
npm run build
dist/에 프로덕션 번들을 생성합니다. 프로덕션 환경에서는 게이트웨이 자체가 :8081의 /에서 이 번들을 제공하므로, 앱은 동일 출처(same-origin)로 API와 통신하며 프록시가 필요하지 않습니다.
make ui-build # from the repo root
콘솔은 @tuskira/gateway-web으로도 패키징되어 있으므로, 자체 React 앱 안에 콘솔의 페이지를 마운트할 수 있습니다. 이 패키지는 npm 레지스트리에 등록되어 있지 않습니다. 각 GitHub 릴리스에서 tarball 형태로 제공되며, 버전은 항상 게이트웨이 릴리스 태그와 일치합니다. React, React DOM, React Router(react-router-dom 7) 및 TanStack Query 5는 피어 의존성이며, 그 외의 모든 것은 패키지와 함께 설치됩니다.
npm install https://github.com/Tuskira/ai-agent-gateway/releases/download/v<version>/tuskira-gateway-web-<version>.tgz \
react react-dom react-router-dom @tanstack/react-query
npm은 package-lock.json에 tarball의 무결성 해시를 기록합니다.
import {
AppShell, AuthProvider, RequireAuth, LoginPage, Toaster, TooltipProvider,
configureGateway, gatewayPageRoutes,
} from '@tuskira/gateway-web'
import '@tuskira/gateway-web/style.css' // Tailwind output + design tokens
import '@tuskira/gateway-web/fonts.css' // optional: self-hosted Inter / JetBrains Mono
configureGateway({
apiBase: '/gw/api/v1', // default '/api/v1'
mcpBase: 'https://gw.example.com/mcp', // default: guessed from the page origin
onUnauthenticated: () => router.navigate('/login'), // default: window.location.assign('/login')
})
const router = createBrowserRouter(
[
{ path: '/login', element: <LoginPage /> },
{ element: <RequireAuth />, children: [{ element: <AppShell />, children: gatewayPageRoutes() }] },
],
{ basename: '/gateway' },
)
QueryClientProvider, AuthProvider, TooltipProvider로 라우터를 감싸고, src/App.tsx에서처럼 <Toaster />를 한 번 렌더링하세요. 콘솔이 전체 라우터를 소유하도록 하려면 gatewayRoutes()가 전체 트리(로그인, 문서, 비밀번호 변경, 셸, 그리고 catch-all 리다이렉트)를 반환합니다. @tuskira/gateway-web이 내보내는 것만 지원되며, src/에서 임포트하지 마세요.
extension을 전달하여 콘솔의 셸과 로그인 가드 안에 자체 사이드바 그룹과 페이지를 배치하세요:
gatewayRoutes({ extension: { navGroups: [...], routes: [...] } })
import { Plug } from 'lucide-react'
import { AppShell, gatewayPageRoutes, type ConsoleExtension } from '@tuskira/gateway-web'
const extension: ConsoleExtension = {
navGroups: [
// New group, placed before the core "Observe" group (appended without `before`).
{ label: 'Billing', before: 'Observe', items: [
{ label: 'Invoices', path: '/invoices', icon: Plug, description: 'Invoices and usage.' },
] },
// Same label as a core group: the items are added to that group.
{ label: 'Access', items: [
{ label: 'Audit', path: '/audit', icon: Plug, description: 'Audit trail.' },
] },
],
routes: [
{ path: '/invoices', Component: InvoicesPage },
{ path: '/audit', Component: AuditPage },
],
}
// Own router: pass the extension to both the routes and the shell.
{ element: <AppShell extension={extension} />, children: gatewayPageRoutes(extension) }
gatewayRoutes({ extension })가 두 가지를 모두 처리합니다. 경로에 라우트가 없는 내비게이션 항목은 "coming soon" 플레이스홀더를 표시합니다. 경로가 다른 항목들의 경로 접두사인 항목에는 end: true를 설정하여 해당 항목이 자신의 경로에서만 활성화되도록 하세요.
브라우저는 apiBase로 설정한 경로에서 게이트웨이 API에 접근할 수 있어야 합니다. 호스트 앱이 콘솔을 제공할 때, /api/v1/*(또는 apiBase 경로를 /api/v1로 다시 rewrite한 경로)를 포트 8081의 게이트웨이로 전달하는 리버스 프록시 규칙을 추가하세요. 경로, 모든 요청 및 응답 헤더(Authorization, X-CSRF-Token, X-Gateway-Acting-Tenant)와 쿠키를 변경하지 않고 유지하세요. 그렇지 않으면 로그인과 CSRF 검사가 실패합니다.
npm run build:lib로 패키지를 빌드하면 dist-lib/(index.js, types/, style.css, fonts.css)가 생성됩니다. 그런 다음 npm pack이 tarball을 생성하며, 릴리스 워크플로가 모든 v* 태그에서 이를 GitHub 릴리스에 첨부합니다.
npm run lint # eslint
npm run typecheck # tsc --noEmit (project references)
npm run test # vitest
npm run format:check # prettier --check
npx shadcn@latest add <component>
이 프로젝트의 components.json은 Vite 템플릿, radix 베이스, neutral 기본 색상, 테마용 CSS 변수를 대상으로 하며, @/* 임포트 별칭은 src/*로 해석됩니다. 새 컴포넌트는 src/components/ui/에 작성됩니다.
tokens.json(저장소 루트)은 디자인 시스템(색상, 간격, 반경, 그림자, 폰트)의 단일 진실 공급원입니다. 편집 후 npm run gen:tokens를 실행하여 다음을 재생성하세요:
src/styles/tokens.css — 원시 토큰을 CSS 사용자 정의 속성으로 표현한 것(:root는 라이트 값, .dark는 다크 오버라이드), 디자이너의 토큰 이름(--indigo-9, --sev-high, --r-4, --shadow-1, ...)을 그대로 유지합니다.src/styles/tokens.ts — 동일한 범주형 색상 스케일(colors.indigo, colors.slate, colors.sev, colors.status, colors.brand)과 fonts, radii, shadows를 비CSS 소비자(예: recharts 시리즈 색상)를 위해 제공합니다.src/index.css는 shadcn의 시맨틱 변수(--background, --primary, --sidebar, ...)를 이 토큰들에 매핑하고, Tailwind v4 @theme 항목을 등록하여 bg-indigo-9, text-text-muted, border-border-strong, shadow-1, rounded-r-4, font-mono 같은 유틸리티를 앱 어디에서나 사용할 수 있게 합니다. src/styles/tokens.css / src/styles/tokens.ts를 직접 편집하지 마세요 — 생성기에 의해 덮어쓰여집니다. src/test/tokens.test.ts는 모든 tokens.ts 항목에 대응하는 선언이 tokens.css에 있는지 검증합니다.
web/
├── index.html
├── vite.config.ts # React + Tailwind plugins, /api dev proxy, vitest config
├── components.json # shadcn/ui config
├── tokens.json # design-token source of truth (see Design tokens)
├── scripts/
│ └── gen-tokens.mjs # tokens.json -> src/styles/tokens.{css,ts}
├── src/
│ ├── main.tsx # ReactDOM root
│ ├── App.tsx # QueryClientProvider, AuthProvider, RouterProvider
│ ├── index.css # Tailwind entrypoint + semantic token mapping
│ ├── styles/
│ │ ├── tokens.css # generated — raw design tokens as CSS vars
│ │ └── tokens.ts # generated — same tokens for non-CSS use
│ ├── auth/
│ │ ├── AuthContext.tsx # AuthProvider / useAuth — principal, signIn, signOut
│ │ └── RequireAuth.tsx # route guard for the authenticated shell
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives (generated, don't hand-edit)
│ │ ├── layout/ # AppShell, Sidebar, TopBar
│ │ └── app/ # ComingSoon, StatCard
│ ├── hooks/
│ │ └── use-theme.ts # light/dark toggle, class strategy, localStorage
│ ├── lib/
│ │ ├── api.ts # fetch wrapper: bearer auth, 401 handling, ApiError
│ │ ├── nav.ts # single source of truth for sidebar nav + routes
│ │ ├── queries.ts # react-query hooks (useHealth)
│ │ ├── query-client.ts # QueryClient instance
│ │ ├── types.ts # Principal, HealthResponse, ApiErrorBody
│ │ └── utils.ts # cn() (shadcn)
│ ├── routes/
│ │ ├── router.tsx # createBrowserRouter (data router)
│ │ ├── LoginPage.tsx
│ │ ├── OverviewPage.tsx
│ │ └── DocsPage.tsx
│ └── test/
│ ├── setup.ts
│ ├── LoginPage.test.tsx
│ ├── Sidebar.test.tsx
│ ├── SignOut.test.tsx
│ └── tokens.test.ts