Compare commits

..
89 changed files with 110 additions and 13985 deletions

View file

@ -1,11 +1,12 @@
name: Publish Any Commit
on:
pull_request:
push:
branches:
- '**'
tags:
- '!**'
# Temporarily disabled - uncomment to re-enable
# pull_request:
# push:
# branches:
# - '**'
# tags:
# - '!**'
jobs:
build:

1
.gitignore vendored
View file

@ -5,4 +5,3 @@ dist
.aider*
**/.claude/settings.local.json
/public

View file

@ -1 +0,0 @@
./scripts/pre-commit

View file

@ -1,17 +1,12 @@
# use-mcp Project Guidelines
## Build, Lint, and Test Commands
- `pnpm dev`: Run development build with watch mode and start all examples/servers
- Chat UI: http://localhost:5002
- Inspector: http://localhost:5001
- Hono MCP Server: http://localhost:5101
- CF Agents MCP Server: http://localhost:5102
- `pnpm dev`: Run development build with watch mode
- `pnpm build`: Build the project
- `pnpm check`: Run prettier checks and TypeScript type checking
### Integration Tests (in /test directory)
- `cd test && pnpm test`: Run integration tests headlessly (default)
- `cd test && pnpm test:headed`: Run integration tests with visible browser
- `cd test && pnpm test`: Run integration tests with visible browser
- `cd test && pnpm test:headless`: Run integration tests headlessly
- `cd test && pnpm test:watch`: Run integration tests in watch mode
- `cd test && pnpm test:ui`: Run integration tests with interactive UI

View file

@ -8,7 +8,7 @@
A lightweight React hook for connecting to [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol) servers. Simplifies authentication and tool calling for AI systems implementing the MCP standard.
Try it out: [Chat Demo](https://chat.use-mcp.dev) | [MCP Inspector](https://inspector.use-mcp.dev) | [Cloudflare Workers AI Playground](https://playground.ai.cloudflare.com/)
Try it out: [MCP Inspector](https://inspector.use-mcp.dev) | [Cloudflare Workers AI Playground](https://playground.ai.cloudflare.com/)
## Installation
@ -20,39 +20,11 @@ pnpm add use-mcp
yarn add use-mcp
```
## Development
To run the development environment with all examples and servers:
```bash
pnpm dev
```
This starts:
- **Inspector**: http://localhost:5001 - MCP server debugging tool
- **Chat UI**: http://localhost:5002 - Example chat interface
- **Hono MCP Server**: http://localhost:5101 - Example MCP server
- **CF Agents MCP Server**: http://localhost:5102 - Cloudflare Workers AI MCP server
### Testing
Integration tests are located in the `test/` directory and run headlessly by default:
```bash
cd test && pnpm test # Run tests headlessly (default)
cd test && pnpm test:headed # Run tests with visible browser
cd test && pnpm test:watch # Run tests in watch mode
cd test && pnpm test:ui # Run tests with interactive UI
```
## Features
- 🔄 Automatic connection management with reconnection and retries
- 🔐 OAuth authentication flow handling with popup and fallback support
- 📦 Simple React hook interface for MCP integration
- 🧰 Full support for MCP tools, resources, and prompts
- 📄 Access server resources and read their contents
- 💬 Use server-provided prompt templates
- 🧰 TypeScript types for editor assistance and type checking
- 📝 Comprehensive logging for debugging
- 🌐 Works with both HTTP and SSE (Server-Sent Events) transports
@ -64,14 +36,10 @@ import { useMcp } from 'use-mcp/react'
function MyAIComponent() {
const {
state, // Connection state: 'discovering' | 'pending_auth' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'
state, // Connection state: 'discovering' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'
tools, // Available tools from MCP server
resources, // Available resources from MCP server
prompts, // Available prompts from MCP server
error, // Error message if connection failed
callTool, // Function to call tools on the MCP server
readResource, // Function to read resource contents
getPrompt, // Function to get prompt messages
retry, // Retry connection manually
authenticate, // Manually trigger authentication
clearStorage, // Clear stored tokens and credentials
@ -115,32 +83,6 @@ function MyAIComponent() {
))}
</ul>
<button onClick={handleSearch}>Search</button>
{/* Example: Display and read resources */}
{resources.length > 0 && (
<div>
<h3>Resources: {resources.length}</h3>
<button onClick={async () => {
const content = await readResource(resources[0].uri)
console.log('Resource content:', content)
}}>
Read First Resource
</button>
</div>
)}
{/* Example: Use prompts */}
{prompts.length > 0 && (
<div>
<h3>Prompts: {prompts.length}</h3>
<button onClick={async () => {
const result = await getPrompt(prompts[0].name)
console.log('Prompt messages:', result.messages)
}}>
Get First Prompt
</button>
</div>
)}
</div>
)
}
@ -226,26 +168,17 @@ function useMcp(options: UseMcpOptions): UseMcpResult
| `autoRetry` | `boolean \| number` | Auto retry connection if initial connection fails, with delay in ms |
| `autoReconnect` | `boolean \| number` | Auto reconnect if an established connection is lost, with delay in ms (default: 3000) |
| `transportType` | `'auto' \| 'http' \| 'sse'` | Transport type preference: 'auto' (HTTP with SSE fallback), 'http' (HTTP only), 'sse' (SSE only) (default: 'auto') |
| `preventAutoAuth` | `boolean` | Prevent automatic authentication popup on initial connection (default: false) |
| `onPopupWindow` | `(url: string, features: string, window: Window \| null) => void` | Callback invoked just after the authentication popup window is opened |
#### Return Value
| Property | Type | Description |
|----------|------|-------------|
| `state` | `string` | Current connection state: 'discovering', 'pending_auth', 'authenticating', 'connecting', 'loading', 'ready', 'failed' |
| `state` | `string` | Current connection state: 'discovering', 'authenticating', 'connecting', 'loading', 'ready', 'failed' |
| `tools` | `Tool[]` | Available tools from the MCP server |
| `resources` | `Resource[]` | Available resources from the MCP server |
| `resourceTemplates` | `ResourceTemplate[]` | Available resource templates from the MCP server |
| `prompts` | `Prompt[]` | Available prompts from the MCP server |
| `error` | `string \| undefined` | Error message if connection failed |
| `authUrl` | `string \| undefined` | Manual authentication URL if popup is blocked |
| `log` | `{ level: 'debug' \| 'info' \| 'warn' \| 'error'; message: string; timestamp: number }[]` | Array of log messages |
| `callTool` | `(name: string, args?: Record<string, unknown>) => Promise<any>` | Function to call a tool on the MCP server |
| `listResources` | `() => Promise<void>` | Refresh the list of available resources |
| `readResource` | `(uri: string) => Promise<{ contents: Array<...> }>` | Read the contents of a specific resource |
| `listPrompts` | `() => Promise<void>` | Refresh the list of available prompts |
| `getPrompt` | `(name: string, args?: Record<string, string>) => Promise<{ messages: Array<...> }>` | Get a specific prompt with optional arguments |
| `retry` | `() => void` | Manually attempt to reconnect |
| `disconnect` | `() => void` | Disconnect from the MCP server |
| `authenticate` | `() => void` | Manually trigger authentication |

View file

@ -1,3 +0,0 @@
# OpenRouter OAuth Configuration
# Note: OpenRouter OAuth doesn't require client ID, only callback URL configuration
VITE_OPENROUTER_REDIRECT_URI=http://localhost:5002/oauth/openrouter/callback

View file

@ -1,30 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.wrangler
# Playwright test results
test-results/
playwright-report/

View file

@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 140
}

View file

@ -1,73 +0,0 @@
# AI Chat Template - Development Guide
## Commands
- **Dev server**: `pnpm dev`
- **Build**: `pnpm build` (runs TypeScript compilation then Vite build)
- **Lint**: `pnpm lint` (ESLint)
- **Deploy**: `pnpm deploy` (builds and deploys with Wrangler)
- **Test**: `pnpm test` (Playwright E2E tests)
- **Test UI**: `pnpm test:ui` (Playwright test runner with UI)
- **Test headed**: `pnpm test:headed` (Run tests in visible browser)
## Code Style
- **Formatting**: Prettier with 2-space tabs, single quotes, no semicolons, 140 char line width
- **Imports**: Use `.tsx`/`.ts` extensions in imports, group by external/internal
- **Components**: React FC with explicit typing, PascalCase names
- **Hooks**: Custom hooks start with `use`, camelCase
- **Types**: Define interfaces in `src/types/index.ts`, use `type` for unions
- **Files**: Use PascalCase for components, camelCase for hooks/utilities
- **State**: Use proper TypeScript typing for all state variables
- **Error handling**: Use try/catch blocks with proper error propagation
- **Database**: IndexedDB with typed interfaces, async/await pattern
- **Styling**: Tailwind CSS classes, responsive design patterns
## Tech Stack
React 19, TypeScript, Vite, Tailwind CSS, Hono API, Cloudflare Workers, IndexedDB, React Router, use-mcp
## Chat UI Specific Guidelines
### Background Animation
- Use CSS `hue-rotate` filter for color cycling (more performant than gradient animation)
- Isolate animations to `::before` pseudo-elements with `z-index: -1` to prevent inheritance
- Use `background-size: 100% 100vh` and `background-repeat: repeat-y` for repeating patterns
- Initialize with random animation delay using CSS custom properties
### Modal Patterns
- Use `rgba(0,0,0,0.8)` for modal backdrops (avoid gradients that cause banding)
- Implement click-outside-to-dismiss with `onClick` on backdrop and `stopPropagation` on content
- Add `cursor-pointer` to all clickable elements including close buttons
- Use `document.body.style.overflow = 'hidden'` to prevent background scrolling
### MCP Server Management
- Store multiple servers in localStorage as JSON array (`mcpServers`)
- Track tool counts separately (`mcpServerToolCounts`) for disabled servers
- Use server-specific IDs for state management
- Each MCP server gets unique `useMcp` hook instance for proper isolation
- OAuth state parameter automatically handles multiple server differentiation
### State Persistence
- Use localStorage for user preferences (model selection, server configurations)
- Use sessionStorage for temporary state (single server URL in legacy components)
- Clear related data when removing servers or models
### UI Indicators
- Use emoji-based indicators (🧠 for models, 🔌 for MCP servers)
- Format counts as `enabled/total` (e.g., "1/2 servers, 3/5 tools")
- Place model selector on left, MCP servers on right for balance
- Show "none" instead of red symbols for cleaner unconfigured states
### Component Organization
- Keep disabled components in React tree but hidden with CSS (`{false && ...}`)
- This prevents MCP connections from being destroyed when modals close
- Use conditional rendering sparingly, prefer CSS visibility for stateful components
### Routing and OAuth
- Use React Router for OAuth callback routes (`/oauth/callback`)
- OAuth callback should match main app styling with loading indicators
- use-mcp library handles multiple server OAuth flows automatically via state parameters
### Performance Considerations
- Avoid animating gradients directly (causes repainting)
- Use transform and filter animations (hardware accelerated)
- Aggregate tools from multiple sources efficiently
- Minimize localStorage reads in render loops

View file

@ -1,92 +0,0 @@
# Model Selector Guide
This guide explains how to use the new favorites-based model selector system.
## Overview
The chat UI now supports:
- **39 models** from 3 providers (Anthropic, Groq, OpenRouter)
- **Favorites system** - star your preferred models
- **Search functionality** - find models by name or provider
- **Tool filtering** - show only models that support tool calling
- **OAuth authentication** - seamless OpenRouter integration
## Features
### Model Selection
- Click the model selector to view all available models
- Models are loaded from the live models.dev API
- Each model shows provider logo, name, and capabilities
### Favorites System
- ⭐ Star models to add them to your favorites
- Starred models are saved to local storage
- Use the "Favorites" filter to show only starred models
### Search & Filtering
- 🔍 Search box to find models by name or provider
- 🔧 "Tools Only" filter (appears when MCP tools are available)
- ⭐ "Favorites" filter to show only starred models
### Authentication
- **API Keys**: Enter manually for Anthropic and Groq
- **OAuth**: One-click authentication for OpenRouter
- Authentication status shown with icons (✓ or ⚠️)
## Provider Support
### Anthropic
- **Models**: 9 models including Claude 4 Sonnet
- **Auth**: API key (requires manual entry)
- **Tools**: All models support tool calling
### Groq
- **Models**: 13 models including Llama and Qwen variants
- **Auth**: API key (requires manual entry)
- **Tools**: 11 models support tool calling
### OpenRouter
- **Models**: 17 models from various providers
- **Auth**: OAuth PKCE flow (one-click authentication)
- **Tools**: All models support tool calling
## Setting Up OpenRouter OAuth
1. Create an OpenRouter account at https://openrouter.ai
2. Go to your dashboard and create an OAuth app
3. Set the redirect URI to: `http://localhost:5002/oauth/openrouter/callback`
4. Copy your client ID to your `.env` file:
```
VITE_OPENROUTER_CLIENT_ID=your_client_id_here
```
## Usage Tips
1. **Star your favorites** - This makes model selection much faster
2. **Use search** - With 39 models, search helps find what you need
3. **Filter by tools** - When using MCP tools, enable "Tools Only" filter
4. **Try different providers** - Each has unique models with different strengths
## Model Data Updates
Model data is fetched from models.dev API and can be updated with:
```bash
pnpm update-models
```
This will refresh the model list with the latest information from the API.
## Local Storage
The following preferences are saved locally:
- **Favorites**: `aiChatTemplate_favorites_v1`
- **Tokens**: `aiChatTemplate_token_[provider]`
- **Selected Model**: `aiChatTemplate_selectedModel`
## Troubleshooting
- **OAuth popup blocked**: Allow popups for this site
- **Authentication failed**: Check your API keys or re-authenticate
- **Model not working**: Verify the model supports the features you're using
- **Tools not showing**: Enable "Tools Only" filter when MCP tools are configured

View file

@ -1,36 +0,0 @@
# AI Chat with MCP
A React-based AI chat application demonstrating [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) integration with multiple AI providers.
This static web application showcases how to use the [`use-mcp`](../../) library to connect to MCP servers, providing extensible AI capabilities through external tools and services. The app supports multiple AI models, stores conversations locally in IndexedDB, and includes OAuth authentication for MCP server connections.
**Live demo**: [chat.use-mcp.dev](https://chat.use-mcp.dev)
## Features
- **MCP Integration**: Connect to MCP servers with OAuth authentication support
- **Multi-model Support**: Anthropic (Claude) and Groq (Llama) models with API key authentication
- **Local Storage**: Conversations stored in browser's IndexedDB
- **Static Deployment**: Builds to static assets for deployment anywhere
- **Modern Stack**: React 19, TypeScript, Tailwind CSS, Vite
## Get started
```sh
pnpm install
pnpm dev
```
Build and deploy:
```sh
pnpm build
pnpm run deploy # deploys to Cloudflare Pages
```
## Development
- **Dev server**: `pnpm dev` (runs on port 5002)
- **Build**: `pnpm build`
- **Lint**: `pnpm lint`
- **Test**: `pnpm test` (Playwright E2E tests)

View file

@ -1,118 +0,0 @@
import type { LanguageModelV1StreamPart } from 'ai'
import { streamText, extractReasoningMiddleware, wrapLanguageModel } from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
import { Hono } from 'hono'
interface Env {
ASSETS: Fetcher
AI: Ai
}
type message = {
role: 'system' | 'user' | 'assistant' | 'data'
content: string
}
const app = new Hono<{ Bindings: Env }>()
// Handle the /api/chat endpoint
app.post('/api/chat', async (c) => {
try {
const { messages, reasoning }: { messages: message[]; reasoning: boolean } = await c.req.json()
const workersai = createWorkersAI({ binding: c.env.AI })
// Choose model based on reasoning preference
const model = reasoning
? wrapLanguageModel({
model: workersai('@cf/deepseek-ai/deepseek-r1-distill-qwen-32b'),
middleware: [
extractReasoningMiddleware({ tagName: 'think' }),
//custom middleware to inject <think> tag at the beginning of a reasoning if it is missing
{
wrapGenerate: async ({ doGenerate }) => {
const result = await doGenerate()
if (!result.text?.includes('<think>')) {
result.text = `<think>${result.text}`
}
return result
},
wrapStream: async ({ doStream }) => {
const { stream, ...rest } = await doStream()
let generatedText = ''
const transformStream = new TransformStream<LanguageModelV1StreamPart, LanguageModelV1StreamPart>({
transform(chunk, controller) {
//we are manually adding the <think> tag because some times, distills of reasoning models omit it
if (chunk.type === 'text-delta') {
if (!generatedText.includes('<think>')) {
generatedText += '<think>'
controller.enqueue({
type: 'text-delta',
textDelta: '<think>',
})
}
generatedText += chunk.textDelta
}
controller.enqueue(chunk)
},
})
return {
stream: stream.pipeThrough(transformStream),
...rest,
}
},
},
],
})
: workersai('@cf/meta/llama-3.3-70b-instruct-fp8-fast')
const systemPrompt: message = {
role: 'system',
content: `
- Do not wrap your responses in html tags.
- Do not apply any formatting to your responses.
- You are an expert conversational chatbot. Your objective is to be as helpful as possible.
- You must keep your responses relevant to the user's prompt.
- You must respond with a maximum of 512 tokens (300 words).
- You must respond clearly and concisely, and explain your logic if required.
- You must not provide any personal information.
- Do not respond with your own personal opinions, and avoid topics unrelated to the user's prompt.
${
messages.length <= 1 &&
`- Important REMINDER: You MUST provide a 5 word title at the END of your response using <chat-title> </chat-title> tags.
If you do not do this, this session will error.
For example, <chat-title>Hello and Welcome</chat-title> Hi, how can I help you today?
`
}
`,
}
const text = await streamText({
model,
messages: [systemPrompt, ...messages],
maxTokens: 2048,
maxRetries: 3,
})
return text.toDataStreamResponse({
sendReasoning: true,
})
} catch (error) {
return c.json({ error: `Chat completion failed. ${(error as Error)?.message}` }, 500)
}
})
// Handle static assets and fallback routes
app.all('*', async (c) => {
if (c.env.ASSETS) {
return c.env.ASSETS.fetch(c.req.raw)
}
return c.text('Not found', 404)
})
export default app

View file

@ -1,45 +0,0 @@
import { test, expect } from '@playwright/test'
import { exec } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
interface ExecError extends Error {
stdout?: string
stderr?: string
}
test.describe('Build Tests', () => {
test('should build without type errors', async () => {
try {
const { stderr } = await execAsync('pnpm build', {
cwd: process.cwd(),
timeout: 60000,
})
// Check for TypeScript compilation errors
expect(stderr).not.toContain('error TS')
expect(stderr).not.toContain('Type error')
} catch (error) {
const execError = error as ExecError
console.error('Build failed:', execError.stdout, execError.stderr)
throw new Error(`Build failed: ${execError.message}`)
}
})
test('should lint without errors', async () => {
try {
const { stderr } = await execAsync('pnpm lint', {
cwd: process.cwd(),
timeout: 30000,
})
// ESLint should pass without errors
expect(stderr).not.toContain('error')
} catch (error) {
const execError = error as ExecError
console.error('Lint failed:', execError.stdout, execError.stderr)
throw new Error(`Lint failed: ${execError.message}`)
}
})
})

View file

@ -1,178 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Chat Functionality', () => {
test.beforeEach(async ({ page }) => {
// Mock the external AI API calls
await page.route('https://api.groq.com/**', async (route) => {
// Mock streaming response for Groq
const mockResponse = 'Hello! This is a mocked response from the AI assistant.'
route.fulfill({
status: 200,
headers: {
'Content-Type': 'text/plain',
},
body: mockResponse,
})
})
await page.route('https://api.anthropic.com/**', async (route) => {
// Mock streaming response for Anthropic
const mockResponse = 'Hello! This is a mocked response from Claude.'
route.fulfill({
status: 200,
headers: {
'Content-Type': 'text/plain',
},
body: mockResponse,
})
})
// Mock the local API endpoint
await page.route('/api/chat', async (route) => {
const mockResponse = {
choices: [
{
delta: {
content: 'Hello! This is a mocked response from the local API.',
},
},
],
}
route.fulfill({
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(mockResponse),
})
})
})
test('should load the chat interface', async ({ page }) => {
await page.goto('/')
// Check if the main elements are present
await expect(page.locator('body')).toBeVisible()
// Look for chat-related elements (adjust selectors based on your UI)
const messageInput = page.locator('textarea, input[type="text"]').first()
await expect(messageInput).toBeVisible()
// Check for a send button or similar
const sendButton = page
.locator('button')
.filter({ hasText: /send|submit/i })
.first()
if (await sendButton.isVisible()) {
await expect(sendButton).toBeVisible()
}
})
test('should send a message and receive a response', async ({ page }) => {
await page.goto('/')
// Wait for the page to load
await page.waitForLoadState('networkidle')
// Find the message input field
const messageInput = page.locator('textarea, input[type="text"]').first()
await expect(messageInput).toBeVisible()
// Type a test message
const testMessage = 'Hello, can you help me?'
await messageInput.fill(testMessage)
await expect(messageInput).toHaveValue(testMessage)
// Send the message by pressing Enter (most common method)
await messageInput.press('Enter')
// Wait a moment for the form submission to process
await page.waitForTimeout(1000)
// Check that a conversation has started - look for any text content that might be a message
const pageContent = await page.textContent('body')
expect(pageContent).toContain(testMessage)
// The input may or may not clear depending on implementation details
// This is less critical than the message appearing
})
test('should handle API errors gracefully', async ({ page }) => {
// Override mocks to simulate API errors
await page.route('https://api.groq.com/**', async (route) => {
route.fulfill({
status: 500,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ error: 'Internal server error' }),
})
})
await page.route('https://api.anthropic.com/**', async (route) => {
route.fulfill({
status: 401,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ error: 'Unauthorized' }),
})
})
await page.goto('/')
// Try to send a message
const messageInput = page.locator('textarea, input[type="text"]').first()
await messageInput.fill('Test message')
const sendButton = page
.locator('button')
.filter({ hasText: /send|submit/i })
.first()
if (await sendButton.isVisible()) {
await sendButton.click()
} else {
await messageInput.press('Enter')
}
// Should handle the error without crashing
await page.waitForTimeout(2000)
// The app should still be functional (not showing a white screen or error page)
await expect(page.locator('body')).toBeVisible()
})
test('should persist conversations', async ({ page }) => {
await page.goto('/')
// Send a message
const messageInput = page.locator('textarea, input[type="text"]').first()
await messageInput.fill('Test message for persistence')
const sendButton = page
.locator('button')
.filter({ hasText: /send|submit/i })
.first()
if (await sendButton.isVisible()) {
await sendButton.click()
} else {
await messageInput.press('Enter')
}
await page.waitForTimeout(2000)
// Refresh the page
await page.reload()
await page.waitForLoadState('networkidle')
// Check if the conversation persisted (this depends on your IndexedDB implementation)
// We'll just verify the page loads correctly after refresh
await expect(page.locator('body')).toBeVisible()
const inputAfterReload = page.locator('textarea, input[type="text"]').first()
await expect(inputAfterReload).toBeVisible()
})
})

View file

@ -1,25 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
}
)

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>use-mcp chat</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -1,59 +0,0 @@
{
"name": "ai-chat-template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --port=5002",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"deploy": "npm run build && npm exec wrangler deploy",
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"test:html": "playwright test --reporter=html",
"update-models": "tsx scripts/update-models.ts"
},
"dependencies": {
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/groq": "^1.2.9",
"@ai-sdk/openai": "^1.3.23",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.0.14",
"ai": "^4.1.61",
"hono": "^4.7.9",
"idb": "^8.0.2",
"lucide-react": "^0.482.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.6.2",
"rehype-highlight": "^7.0.2",
"remark-gfm": "^4.0.1",
"tailwindcss": "^4.0.14",
"workers-ai-provider": "^0.1.3"
},
"devDependencies": {
"@ai-sdk/ui-utils": "^1.2.11",
"@cloudflare/vite-plugin": "^0.1.12",
"@cloudflare/workers-types": "^4.20250317.0",
"@eslint/js": "^9.21.0",
"@playwright/test": "^1.52.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"prettier": "^3.5.3",
"tsx": "^4.20.3",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"use-mcp": "link:../..",
"vite": "^6.2.0",
"wrangler": "^4.1.0"
},
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
}

View file

@ -1,41 +0,0 @@
import { defineConfig, devices } from '@playwright/test'
/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
})

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show more