# Directory Structure ``` ├── .eslintrc.json ├── .gitignore ├── app │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── Base.tsx │ └── ui │ ├── accordion.tsx │ └── button.tsx ├── components.json ├── functions │ └── get_geo.ts ├── lib │ └── utils.ts ├── LICENSE ├── mcp-server │ ├── .gitignore │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ └── next.svg ├── README_zh-CN.md ├── README.md ├── tailwind.config.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | ``` -------------------------------------------------------------------------------- /mcp-server/.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.* 11 | 12 | # NPM 13 | .npmrc 14 | .npmrc.local 15 | .npmrc.* 16 | .npm 17 | 18 | # IDE 19 | .idea/ 20 | .vscode/ 21 | *.sublime-* 22 | 23 | # Logs 24 | *.log 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # System files 30 | .DS_Store 31 | Thumbs.db 32 | 33 | test.ts ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # Tencent Cloud Edge Functions 39 | .env 40 | .edgeone 41 | edgeone.json 42 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # EdgeOne Pages MCP: Geo Location Service 2 | 3 | This project demonstrates how to use EdgeOne Pages Functions to retrieve user geolocation information and integrate it with large language models through MCP (Model Context Protocol). 4 | 5 | ## Demo 6 | 7 |  8 | 9 | ## Deploy 10 | 11 | [](https://edgeone.ai/pages/new?template=mcp-geo) 12 | 13 | More Templates: [EdgeOne Pages](https://edgeone.ai/pages/templates) 14 | 15 | ## Components 16 | 17 | ### 1. EdgeOne Pages Functions: Geolocation 18 | 19 | The project includes an EdgeOne Pages Function that retrieves user geolocation information: 20 | 21 | * Uses the EdgeOne request context to access geolocation data 22 | * Returns location information in a JSON format 23 | * Located in `functions/get_geo.ts` 24 | 25 | ### 2. MCP Server Integration 26 | 27 | The MCP server component provides an interface for large language models to access geolocation data: 28 | 29 | * Implements the Model Context Protocol (MCP) 30 | * Exposes a `get_geolocation` tool that can be used by AI models 31 | * Uses the EdgeOne Pages Function to fetch geolocation data 32 | * Located in `mcp-server/index.ts` 33 | 34 | ## MCP Configuration 35 | 36 | To use the MCP server with large language models, add the following configuration: 37 | 38 | ```json 39 | { 40 | "mcpServers": { 41 | "edgeone-geo-mcp-server": { 42 | "command": "tsx", 43 | "args": ["path/to/mcp-server/index.ts"] 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ## Learn More 50 | 51 | * [EdgeOne Pages](https://edgeone.ai/products/pages) 52 | * [EdgeOne Pages Functions documentation](https://edgeone.ai/document/162227908259442688) 53 | * [Model Context Protocol (MCP)](https://modelcontextprotocol.github.io) - Learn about integrating AI models with external tools and services 54 | ``` -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- ``` 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'export', 4 | }; 5 | 6 | export default nextConfig; 7 | ``` -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Base } from '@/components/Base'; 2 | 3 | export default function Home() { 4 | return ( 5 | <main> 6 | <Base /> 7 | </main> 8 | ); 9 | } 10 | ``` -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- ``` 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | ``` -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | ``` -------------------------------------------------------------------------------- /functions/get_geo.ts: -------------------------------------------------------------------------------- ```typescript 1 | export function onRequest({ request }: { request: any }) { 2 | const geo = request.eo.geo; 3 | const res = JSON.stringify({ 4 | geo: geo, 5 | }); 6 | 7 | return new Response(res, { 8 | headers: { 9 | 'content-type': 'application/json; charset=UTF-8', 10 | 'Access-Control-Allow-Origin': '*', 11 | }, 12 | }); 13 | } 14 | ``` -------------------------------------------------------------------------------- /mcp-server/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-geo-server", 3 | "version": "0.0.1", 4 | "description": "A tool to get user geolocation information", 5 | "main": "index.ts", 6 | "type": "module", 7 | "scripts": {}, 8 | "keywords": [], 9 | "author": "EdgeOne Pages", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.7.0", 13 | "zod": "^3.24.2" 14 | } 15 | } 16 | ``` -------------------------------------------------------------------------------- /mcp-server/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "outDir": "./dist", 7 | "rootDir": "./", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["index.ts"], 14 | "exclude": ["node_modules", "dist"] 15 | } ``` -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } ``` -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "EdgeOne Pages MCP: Geo Location Service", 9 | description: "A demo project showcasing EdgeOne Pages Functions for geolocation information and MCP integration", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | <html lang="en"> 19 | <body className={inter.className}>{children}</body> 20 | </html> 21 | ); 22 | } 23 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules", "mcp-server"] 26 | } 27 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "functions-geo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "cd mcp-server && npm install" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-accordion": "^1.2.0", 14 | "@radix-ui/react-icons": "^1.3.0", 15 | "@radix-ui/react-slot": "^1.1.0", 16 | "class-variance-authority": "^0.7.0", 17 | "clsx": "^2.1.1", 18 | "lucide-react": "^0.438.0", 19 | "next": "14.2.5", 20 | "react": "^18", 21 | "react-dom": "^18", 22 | "tailwind-merge": "^2.5.2", 23 | "tailwindcss-animate": "^1.0.7", 24 | "@jridgewell/gen-mapping": "0.3.5" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20", 28 | "@types/react": "^18", 29 | "@types/react-dom": "^18", 30 | "eslint": "^8", 31 | "eslint-config-next": "14.2.5", 32 | "postcss": "^8", 33 | "tailwindcss": "^3.4.1", 34 | "typescript": "^5" 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- ``` 1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg> ``` -------------------------------------------------------------------------------- /mcp-server/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 5 | 6 | const server = new McpServer({ 7 | name: 'edgeone-geo-mcp-server', 8 | version: '1.0.0', 9 | description: 'An MCP service for getting user geolocation information', 10 | }); 11 | 12 | const handleApiError = (error: any) => { 13 | console.error('API Error:', error); 14 | const errorMessage = error.message || 'Unknown error occurred'; 15 | return { 16 | content: [ 17 | { 18 | type: 'text' as const, 19 | text: `Error: ${errorMessage}`, 20 | }, 21 | ], 22 | isError: true, 23 | }; 24 | }; 25 | 26 | export async function getGeoLocation(): Promise<any> { 27 | try { 28 | const res = await fetch('https://mcp-geo.edgeone.app/get_geo'); 29 | if (!res.ok) { 30 | throw new Error(`HTTP error: ${res.status} ${res.statusText}`); 31 | } 32 | return await res.json(); 33 | } catch (error) { 34 | console.error('Failed to get geolocation:', error); 35 | throw error; 36 | } 37 | } 38 | 39 | server.tool( 40 | 'get_geolocation', 41 | "Get the user's geolocation information", 42 | {}, 43 | async () => { 44 | try { 45 | const geoData = await getGeoLocation(); 46 | 47 | return { 48 | content: [ 49 | { 50 | type: 'text' as const, 51 | text: JSON.stringify(geoData, null, 2), 52 | }, 53 | ], 54 | }; 55 | } catch (e) { 56 | return handleApiError(e); 57 | } 58 | } 59 | ); 60 | 61 | console.log('Starting edgeone-geo-mcp-server...'); 62 | const transport = new StdioServerTransport(); 63 | await server.connect(transport); 64 | console.log('edgeone-geo-mcp-server started successfully!'); 65 | ``` -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes<HTMLButtonElement>, 39 | VariantProps<typeof buttonVariants> { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | <Comp 48 | className={cn(buttonVariants({ variant, size, className }))} 49 | ref={ref} 50 | {...props} 51 | /> 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | ``` -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDownIcon } from "@radix-ui/react-icons" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef<typeof AccordionPrimitive.Item>, 13 | React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> 14 | >(({ className, ...props }, ref) => ( 15 | <AccordionPrimitive.Item 16 | ref={ref} 17 | className={cn("border-b", className)} 18 | {...props} 19 | /> 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef<typeof AccordionPrimitive.Trigger>, 25 | React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> 26 | >(({ className, children, ...props }, ref) => ( 27 | <AccordionPrimitive.Header className="flex"> 28 | <AccordionPrimitive.Trigger 29 | ref={ref} 30 | className={cn( 31 | "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | <ChevronDownIcon className="w-4 h-4 transition-transform duration-200 shrink-0 text-muted-foreground" /> 38 | </AccordionPrimitive.Trigger> 39 | </AccordionPrimitive.Header> 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef<typeof AccordionPrimitive.Content>, 45 | React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> 46 | >(({ className, children, ...props }, ref) => ( 47 | <AccordionPrimitive.Content 48 | ref={ref} 49 | className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" 50 | {...props} 51 | > 52 | <div className={cn("pb-4 pt-0", className)}>{children}</div> 53 | </AccordionPrimitive.Content> 54 | )) 55 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 58 | ``` -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- ```css 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | @layer utilities { 20 | .text-balance { 21 | text-wrap: balance; 22 | } 23 | } 24 | 25 | @layer base { 26 | :root { 27 | --background: 0 0% 100%; 28 | --foreground: 0 0% 3.9%; 29 | --card: 0 0% 100%; 30 | --card-foreground: 0 0% 3.9%; 31 | --popover: 0 0% 100%; 32 | --popover-foreground: 0 0% 3.9%; 33 | --primary: 0 0% 9%; 34 | --primary-foreground: 0 0% 98%; 35 | --secondary: 0 0% 96.1%; 36 | --secondary-foreground: 0 0% 9%; 37 | --muted: 0 0% 96.1%; 38 | --muted-foreground: 0 0% 45.1%; 39 | --accent: 0 0% 96.1%; 40 | --accent-foreground: 0 0% 9%; 41 | --destructive: 0 84.2% 60.2%; 42 | --destructive-foreground: 0 0% 98%; 43 | --border: 0 0% 89.8%; 44 | --input: 0 0% 89.8%; 45 | --ring: 0 0% 3.9%; 46 | --chart-1: 12 76% 61%; 47 | --chart-2: 173 58% 39%; 48 | --chart-3: 197 37% 24%; 49 | --chart-4: 43 74% 66%; 50 | --chart-5: 27 87% 67%; 51 | --radius: 0.5rem; 52 | } 53 | .dark { 54 | --background: 0 0% 3.9%; 55 | --foreground: 0 0% 98%; 56 | --card: 0 0% 3.9%; 57 | --card-foreground: 0 0% 98%; 58 | --popover: 0 0% 3.9%; 59 | --popover-foreground: 0 0% 98%; 60 | --primary: 0 0% 98%; 61 | --primary-foreground: 0 0% 9%; 62 | --secondary: 0 0% 14.9%; 63 | --secondary-foreground: 0 0% 98%; 64 | --muted: 0 0% 14.9%; 65 | --muted-foreground: 0 0% 63.9%; 66 | --accent: 0 0% 14.9%; 67 | --accent-foreground: 0 0% 98%; 68 | --destructive: 0 62.8% 30.6%; 69 | --destructive-foreground: 0 0% 98%; 70 | --border: 0 0% 14.9%; 71 | --input: 0 0% 14.9%; 72 | --ring: 0 0% 83.1%; 73 | --chart-1: 220 70% 50%; 74 | --chart-2: 160 60% 45%; 75 | --chart-3: 30 80% 55%; 76 | --chart-4: 280 65% 60%; 77 | --chart-5: 340 75% 55%; 78 | } 79 | } 80 | 81 | @layer base { 82 | * { 83 | @apply border-border; 84 | } 85 | body { 86 | @apply bg-background text-foreground; 87 | } 88 | } 89 | ``` -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | backgroundImage: { 13 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 14 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' 15 | }, 16 | borderRadius: { 17 | lg: 'var(--radius)', 18 | md: 'calc(var(--radius) - 2px)', 19 | sm: 'calc(var(--radius) - 4px)' 20 | }, 21 | colors: { 22 | background: 'hsl(var(--background))', 23 | foreground: 'hsl(var(--foreground))', 24 | card: { 25 | DEFAULT: 'hsl(var(--card))', 26 | foreground: 'hsl(var(--card-foreground))' 27 | }, 28 | popover: { 29 | DEFAULT: 'hsl(var(--popover))', 30 | foreground: 'hsl(var(--popover-foreground))' 31 | }, 32 | primary: { 33 | DEFAULT: 'hsl(var(--primary))', 34 | foreground: 'hsl(var(--primary-foreground))' 35 | }, 36 | secondary: { 37 | DEFAULT: 'hsl(var(--secondary))', 38 | foreground: 'hsl(var(--secondary-foreground))' 39 | }, 40 | muted: { 41 | DEFAULT: 'hsl(var(--muted))', 42 | foreground: 'hsl(var(--muted-foreground))' 43 | }, 44 | accent: { 45 | DEFAULT: 'hsl(var(--accent))', 46 | foreground: 'hsl(var(--accent-foreground))' 47 | }, 48 | destructive: { 49 | DEFAULT: 'hsl(var(--destructive))', 50 | foreground: 'hsl(var(--destructive-foreground))' 51 | }, 52 | border: 'hsl(var(--border))', 53 | input: 'hsl(var(--input))', 54 | ring: 'hsl(var(--ring))', 55 | chart: { 56 | '1': 'hsl(var(--chart-1))', 57 | '2': 'hsl(var(--chart-2))', 58 | '3': 'hsl(var(--chart-3))', 59 | '4': 'hsl(var(--chart-4))', 60 | '5': 'hsl(var(--chart-5))' 61 | } 62 | }, 63 | keyframes: { 64 | 'accordion-down': { 65 | from: { 66 | height: '0' 67 | }, 68 | to: { 69 | height: 'var(--radix-accordion-content-height)' 70 | } 71 | }, 72 | 'accordion-up': { 73 | from: { 74 | height: 'var(--radix-accordion-content-height)' 75 | }, 76 | to: { 77 | height: '0' 78 | } 79 | } 80 | }, 81 | animation: { 82 | 'accordion-down': 'accordion-down 0.2s ease-out', 83 | 'accordion-up': 'accordion-up 0.2s ease-out' 84 | } 85 | } 86 | }, 87 | plugins: [require("tailwindcss-animate")], 88 | }; 89 | export default config; 90 | ``` -------------------------------------------------------------------------------- /components/Base.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react'; 2 | 3 | export const Base = () => { 4 | return ( 5 | <div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 py-12 px-4 sm:px-6 lg:px-8"> 6 | <div className="max-w-4xl mx-auto"> 7 | <div className="text-center"> 8 | <h1 className="text-4xl font-bold text-gray-900 mb-4"> 9 | EdgeOne Pages MCP: Geo Location Service 10 | </h1> 11 | <p className="text-xl text-gray-600 mb-8"> 12 | A geolocation service example demonstrating how to obtain user 13 | location information in EdgeOne Pages and interact with AI models 14 | through the MCP protocol. 15 | </p> 16 | </div> 17 | 18 | {/* Project Overview */} 19 | <div className="bg-white rounded-lg shadow-lg p-6 mb-8"> 20 | <h2 className="text-2xl font-semibold text-gray-900 mb-4"> 21 | Project Overview 22 | </h2> 23 | <p className="text-gray-600 mb-4"> 24 | In modern web applications, obtaining user geolocation information 25 | is a common requirement. This project demonstrates how to: 26 | </p> 27 | <ul className="list-disc list-inside text-gray-600 space-y-2 mb-4"> 28 | <li> 29 | Securely obtain user geolocation using EdgeOne Pages Functions 30 | </li> 31 | <li> 32 | Enable AI models to access location data through the MCP protocol 33 | </li> 34 | <li>Elegantly display location information in the frontend</li> 35 | </ul> 36 | </div> 37 | 38 | {/* Core Features */} 39 | <div className="bg-white rounded-lg shadow-lg p-6 mb-8"> 40 | <h2 className="text-2xl font-semibold text-gray-900 mb-4"> 41 | Core Features 42 | </h2> 43 | <div className="grid md:grid-cols-2 gap-6"> 44 | <div className="border border-gray-200 rounded-lg p-4"> 45 | <h3 className="text-lg font-medium text-gray-900 mb-2"> 46 | EdgeOne Pages Functions 47 | </h3> 48 | <ul className="text-gray-600 space-y-2"> 49 | <li> 50 | • Automatically retrieve visitor's geolocation information 51 | </li> 52 | <li> 53 | • Includes detailed data about country, region, city, etc. 54 | </li> 55 | <li> 56 | • Precise positioning through EdgeOne's global node network 57 | </li> 58 | <li>• Returns standardized JSON format data</li> 59 | </ul> 60 | </div> 61 | <div className="border border-gray-200 rounded-lg p-4"> 62 | <h3 className="text-lg font-medium text-gray-900 mb-2"> 63 | MCP Protocol Integration 64 | </h3> 65 | <ul className="text-gray-600 space-y-2"> 66 | <li>• Implements Model Context Protocol standard</li> 67 | <li>• Provides get_geolocation tool interface</li> 68 | <li> 69 | • Supports real-time AI model calls to geolocation service 70 | </li> 71 | <li>• Convenient server-side integration solution</li> 72 | </ul> 73 | </div> 74 | </div> 75 | </div> 76 | 77 | {/* Quick Start */} 78 | <div className="text-center space-y-4"> 79 | <a 80 | href="https://edgeone.ai/pages/new?template=mcp-geo" 81 | target="_blank" 82 | rel="noopener noreferrer" 83 | aria-label="Deploy your own instance of the MCP Geo Location Service" 84 | className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 mr-4" 85 | > 86 | Deploy Now 87 | </a> 88 | <a 89 | href="https://edgeone.ai/pages/templates" 90 | target="_blank" 91 | rel="noopener noreferrer" 92 | aria-label="View more EdgeOne Pages templates" 93 | className="inline-flex items-center px-6 py-3 border border-gray-300 text-base font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" 94 | > 95 | View More Templates 96 | </a> 97 | </div> 98 | </div> 99 | </div> 100 | ); 101 | }; 102 | ```