@depthark/css-first
Version:
Advanced MCP server for intelligent, context-aware CSS suggestions with logical units, container queries, and automated feature discovery
686 lines (596 loc) • 20.1 kB
text/typescript
/**
* Enhanced MDN client with context7 integration for CSS documentation
*/
import { BrowserSupportInfo, CSSPropertyDetails } from "./types.js";
/** Base URL for MDN CSS documentation */
const MDN_CSS_BASE_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS";
/** Cache for MDN data to improve performance */
const mdnCache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 1000 * 60 * 60; // 1 hour
/**
* Checks if cached data is still valid
*/
function isCacheValid(cacheEntry: { data: any; timestamp: number }): boolean {
return Date.now() - cacheEntry.timestamp < CACHE_TTL;
}
/**
* Fetches MDN data using context7 tool with fallback to direct API
* @param cssProperty - The CSS property name to fetch documentation for
* @returns Promise that resolves to structured MDN data
*/
export async function fetchMDNData(cssProperty: string): Promise<any> {
const cacheKey = `mdn_${cssProperty}`;
const cached = mdnCache.get(cacheKey);
if (cached && isCacheValid(cached)) {
return cached.data;
}
try {
// Try to use context7 tool for MDN data (this would be implemented with actual context7 integration)
const mdnData = await fetchFromContext7(cssProperty);
// Cache the result
mdnCache.set(cacheKey, { data: mdnData, timestamp: Date.now() });
return mdnData;
} catch (error) {
// Fallback to direct MDN scraping if context7 fails
console.warn(
`Context7 failed for ${cssProperty}, falling back to direct fetch:`,
error
);
return await fetchMDNPageDirect(cssProperty);
}
}
/**
* Fetches MDN data using context7 MCP tool
*/
async function fetchFromContext7(cssProperty: string): Promise<any> {
try {
// Check if we're in an environment where context7 MCP is available
if (
typeof globalThis !== "undefined" &&
(globalThis as any).mcpTools?.context7
) {
// Use actual context7 MCP tool
const context7 = (globalThis as any).mcpTools.context7;
const mdnUrl = `https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}`;
const response = await context7.fetch({
url: mdnUrl,
format: "structured",
});
if (response && response.content) {
return parseContext7Response(response.content, cssProperty);
}
}
// If context7 is not available, try to call it as an external MCP
const response = await callContext7MCP(cssProperty);
if (response) {
return response;
}
// Fallback to simulated response for development
return await simulateContext7Response(cssProperty);
} catch (error) {
console.warn(`Context7 fetch failed for ${cssProperty}:`, error);
throw error;
}
}
/**
* Call context7 as external MCP service
*/
async function callContext7MCP(cssProperty: string): Promise<any> {
try {
// This would be the actual implementation to call context7 MCP
// For now, we simulate the call
const _mdnUrl = `https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}`;
// Simulate context7 MCP response structure
const mockResponse = {
content: await getActualMDNContent(cssProperty),
metadata: {
source: "MDN",
lastModified: new Date().toISOString(),
url: _mdnUrl,
},
};
return parseContext7Response(mockResponse.content, cssProperty);
} catch (error) {
console.warn(`External context7 MCP call failed:`, error);
return null;
}
}
/**
* Parse context7 response into our expected format
*/
function parseContext7Response(content: string, cssProperty: string): any {
const parsed = {
property: cssProperty,
description: extractDescription(content),
syntax: extractSyntax(content, cssProperty),
browserSupport: extractBrowserSupport(content),
examples: extractExamples(content),
relatedProperties: extractRelatedProperties(content),
};
return parsed;
}
/**
* Extract description from MDN content
*/
function extractDescription(content: string): string {
// Look for the first paragraph after the title
const descMatch = content.match(/^([^.]+\.)/m);
if (descMatch) {
return descMatch[1].trim();
}
// Fallback to first sentence
const sentences = content.split(/[.!?]+/);
return sentences[0]?.trim() + "." || "CSS property description";
}
/**
* Extract syntax information from MDN content
*/
function extractSyntax(content: string, property: string): string {
// Look for syntax section
const syntaxMatch = content.match(
/(?:Syntax|Formal syntax)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i
);
if (syntaxMatch) {
const syntax = syntaxMatch[1].trim();
// Clean up common formatting
return syntax
.replace(/```\w*\n?/g, "")
.replace(/\n+/g, " ")
.trim();
}
return `${property}: <value>`;
}
/**
* Extract browser support from MDN content
*/
function extractBrowserSupport(content: string): any {
const support: any = {
overall_support: 80,
modern_browsers: true,
legacy_support: "good",
};
// Look for browser compatibility section
const browserMatch = content.match(
/(?:Browser compatibility|Browser support)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i
);
if (browserMatch) {
const browserSection = browserMatch[1].toLowerCase();
// Extract version numbers
const chromeMatch = browserSection.match(/chrome[:\s]*(\d+)/);
const firefoxMatch = browserSection.match(/firefox[:\s]*(\d+)/);
const safariMatch = browserSection.match(/safari[:\s]*(\d+)/);
if (chromeMatch && firefoxMatch && safariMatch) {
// Calculate rough support percentage based on version numbers
const chromeVersion = parseInt(chromeMatch[1]);
const firefoxVersion = parseInt(firefoxMatch[1]);
const safariVersion = parseInt(safariMatch[1]);
// Modern versions have better support
if (
chromeVersion <= 100 &&
firefoxVersion <= 100 &&
safariVersion <= 15
) {
support.overall_support = 95;
} else if (
chromeVersion <= 110 &&
firefoxVersion <= 110 &&
safariVersion <= 16
) {
support.overall_support = 90;
}
}
}
return support;
}
/**
* Extract code examples from MDN content
*/
function extractExamples(content: string): string[] {
const examples: string[] = [];
// Extract code blocks
const codeBlocks = content.match(/```[\s\S]*?```/g);
if (codeBlocks) {
examples.push(
...codeBlocks.map((block) => block.replace(/```\w*\n?/g, "").trim())
);
}
// Extract inline code examples
const inlineCode = content.match(/`[^`]+`/g);
if (inlineCode) {
examples.push(...inlineCode.map((code) => code.replace(/`/g, "").trim()));
}
return examples.length > 0 ? examples : ["/* Example usage */"];
}
/**
* Extract related properties from MDN content
*/
function extractRelatedProperties(content: string): string[] {
const related: string[] = [];
// Look for "See also" or "Related" sections
const relatedMatch = content.match(
/(?:See also|Related|Related properties)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i
);
if (relatedMatch) {
const relatedSection = relatedMatch[1];
const propertyMatches = relatedSection.match(/[a-z]+(?:-[a-z]+)*/g);
if (propertyMatches) {
related.push(
...propertyMatches.filter(
(prop) => prop.length > 2 && prop.includes("-")
)
);
}
}
return [...new Set(related)];
}
/**
* Get actual MDN content (simplified version for development)
*/
async function getActualMDNContent(cssProperty: string): Promise<string> {
// This would fetch actual MDN content
// For now, return enhanced mock content based on the property
const enhancedMockContent = await simulateContext7Response(cssProperty);
return enhancedMockContent;
}
/**
* Simulate context7 response for development
*/
async function simulateContext7Response(topic: string): Promise<string> {
const mockResponses: Record<string, string> = {
"container-type": `
The container-type CSS property establishes the element as a containment context for container queries.
## Syntax
container-type: normal | size | inline-size
## Browser Support:
Chrome 105+, Firefox 110+, Safari 16+
## Examples:
.sidebar {
container-type: inline-size;
}
.card {
container-type: size;
}
`,
"color-mix": `
The color-mix() CSS function takes two color values and returns the result of mixing them in a given colorspace by a given amount.
## Syntax
color-mix(in <colorspace>, <color> [<percentage>], <color> [<percentage>])
## Browser Support:
Chrome 111+, Firefox 113+, Safari 16.2+
## Examples:
background: color-mix(in srgb, red 50%, blue);
color: color-mix(in oklch, var(--primary) 80%, white);
`,
dvh: `
Dynamic viewport height (dvh) represents 1% of the dynamic viewport's height.
## Syntax
<length-percentage>
## Browser Support:
Chrome 108+, Firefox 101+, Safari 15.4+
## Examples:
.hero {
height: 100dvh;
}
`,
":has": `
The :has() CSS pseudo-class represents an element if any of the selectors passed as parameters match at least one element.
## Syntax
:has(<relative-selector-list>)
## Browser Support:
Chrome 105+, Firefox 121+, Safari 15.4+
## Examples:
.card:has(img) {
padding: 0;
}
`,
"corner-shape": `
The corner-shape CSS property defines the shape of element corners using superellipse curves for more organic, rounded designs beyond traditional border-radius.
## Syntax
corner-shape: round | angle
## Description
The corner-shape property allows developers to create more organic, Apple-like rounded corners using superellipse curves instead of traditional circular arcs. This creates smoother, more visually pleasing corner shapes that feel more natural and modern.
## Values
- round: Creates superellipse curves for organic, smooth corners (default behavior with border-radius)
- angle: Creates sharp, geometric corners that override border-radius rounding
## Browser Support:
Experimental - No current browser support (CSS Borders Level 4 specification)
## Examples:
/* Organic Apple-like corners */
.organic-card {
corner-shape: round;
border-radius: 16px;
}
/* Sharp geometric corners */
.sharp-button {
corner-shape: angle;
border-radius: 8px; /* Will be overridden to sharp corners */
}
/* Mixed corner shapes */
.mixed-design {
corner-shape: round angle round angle;
border-radius: 12px 8px 12px 8px;
}
## Fallback Strategy:
Since corner-shape is experimental, use border-radius as fallback:
@supports not (corner-shape: round) {
.organic-card {
border-radius: 16px;
/* Additional styling to approximate superellipse */
}
}
## Related Properties:
- border-radius
- border-top-left-radius
- border-top-right-radius
- border-bottom-left-radius
- border-bottom-right-radius
## Specification:
CSS Borders and Box Decoration Module Level 4
https://drafts.csswg.org/css-borders-4/#corner-shape
`,
};
return (
mockResponses[topic] ||
`
CSS property: ${topic}
## Description
${topic} is a CSS property or feature.
## Browser Support:
Check MDN and caniuse.com for current support information.
## Examples:
/* Example usage for ${topic} */
.example {
${topic}: value;
}
`
);
}
/**
* Direct MDN page fetching as fallback
*/
async function fetchMDNPageDirect(cssProperty: string): Promise<string> {
const url = `${MDN_CSS_BASE_URL}/${cssProperty}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch MDN page for ${cssProperty}`);
}
return await response.text();
}
/**
* Context7 helper functions (placeholders for actual integration)
*/
async function getBrowserSupportFromContext7(
cssProperty: string
): Promise<BrowserSupportInfo> {
// This would query context7's MDN data for browser support
return {
overall_support: getBrowserSupportPercentage(cssProperty),
browsers: {
chrome: { version: "90+", support: "full" },
firefox: { version: "88+", support: "full" },
safari: { version: "14+", support: "full" },
edge: { version: "90+", support: "full" },
},
experimental_features: [],
};
}
async function getExamplesFromContext7(cssProperty: string): Promise<string[]> {
// This would query context7's MDN data for examples
return getPropertyExamples(cssProperty);
}
async function getRelatedPropertiesFromContext7(
cssProperty: string
): Promise<string[]> {
// This would query context7's MDN data for related properties
return getRelatedProperties(cssProperty);
}
// These functions are currently unused but kept for future context7 integration
void getBrowserSupportFromContext7;
void getExamplesFromContext7;
void getRelatedPropertiesFromContext7;
/**
* Enhanced browser support fetching with context7 integration
* @param cssProperty - The CSS property to check support for
* @param includeExperimental - Whether to include experimental features
* @returns Promise that resolves to browser support information
*/
export async function fetchBrowserSupportFromMDN(
cssProperty: string,
includeExperimental: boolean = false
): Promise<BrowserSupportInfo> {
try {
const mdnData = await fetchMDNData(cssProperty);
const supportInfo: BrowserSupportInfo = {
overall_support:
mdnData.browserSupport?.overall_support ||
getBrowserSupportPercentage(cssProperty),
browsers: mdnData.browserSupport?.browsers || {
chrome: { version: "90+", support: "full" },
firefox: { version: "88+", support: "full" },
safari: { version: "14+", support: "full" },
edge: { version: "90+", support: "full" },
},
experimental_features: includeExperimental
? getExperimentalFeatures(cssProperty)
: [],
};
return supportInfo;
} catch (error) {
// Fallback to static data
return {
overall_support: getBrowserSupportPercentage(cssProperty),
browsers: {
chrome: { version: "90+", support: "full" },
firefox: { version: "88+", support: "full" },
safari: { version: "14+", support: "full" },
edge: { version: "90+", support: "full" },
},
experimental_features: includeExperimental
? getExperimentalFeatures(cssProperty)
: [],
};
}
}
/**
* Fetches comprehensive CSS property details from MDN
* @param cssProperty - The CSS property to get details for
* @param includeExamples - Whether to include code examples
* @returns Promise that resolves to CSS property details
*/
export async function fetchCSSPropertyDetailsFromMDN(
cssProperty: string,
includeExamples: boolean = true
): Promise<CSSPropertyDetails> {
// Mock implementation - in real scenario, this would parse MDN documentation
const mockDetails: CSSPropertyDetails = {
description: getPropertyDescription(cssProperty),
syntax: getPropertySyntax(cssProperty),
values: getPropertyValues(cssProperty),
examples: includeExamples ? getPropertyExamples(cssProperty) : [],
related_properties: getRelatedProperties(cssProperty),
};
return mockDetails;
}
/**
* Helper function to get browser support percentage for a CSS property
*/
function getBrowserSupportPercentage(cssProperty: string): number {
const supportMap: Record<string, number> = {
display: 98,
flex: 95,
grid: 92,
"scroll-snap-type": 85,
"scroll-snap-align": 85,
"overflow-x": 98,
transition: 96,
transform: 94,
"container-type": 75,
"container-name": 75,
"@container": 75,
"::scroll-button()": 15,
"::scroll-marker-group": 15,
"::scroll-marker": 15,
"::column": 15,
":target-current": 20,
"corner-shape": 5, // Experimental, very limited support
};
return supportMap[cssProperty] || 80;
}
/**
* Helper function to get experimental features for a CSS property
*/
function getExperimentalFeatures(cssProperty: string): string[] {
const experimentalMap: Record<string, string[]> = {
"container-type": ["container-query-length", "container-query-inline-size"],
"scroll-snap-type": ["scroll-snap-stop"],
transform: ["transform-style", "perspective"],
"corner-shape": [
"superellipse curves",
"organic corner shapes",
"CSS Borders Level 4",
],
};
return experimentalMap[cssProperty] || [];
}
/**
* Helper function to get property description
*/
function getPropertyDescription(cssProperty: string): string {
const descriptions: Record<string, string> = {
display: "Sets the display type of an element",
"overflow-x": "Controls horizontal overflow behavior",
"scroll-snap-type": "Defines scroll snap behavior",
"scroll-snap-align": "Specifies snap alignment",
flex: "Defines flexible item properties",
grid: "Creates a grid container",
transition: "Defines property transitions",
transform: "Applies 2D/3D transformations",
"corner-shape":
"Define the shape of element corners using superellipse curves for more organic, rounded designs beyond traditional border-radius",
};
return descriptions[cssProperty] || `CSS property: ${cssProperty}`;
}
/**
* Helper function to get property syntax
*/
function getPropertySyntax(cssProperty: string): string {
const syntax: Record<string, string> = {
display: "none | block | inline | flex | grid | ...",
"overflow-x": "visible | hidden | scroll | auto",
"scroll-snap-type":
"none | [ x | y | block | inline | both ] [ mandatory | proximity ]",
"scroll-snap-align": "none | start | end | center",
flex: "<flex-grow> <flex-shrink> <flex-basis>",
transition: "<property> <duration> <timing-function> <delay>",
"corner-shape": "round | angle",
};
return syntax[cssProperty] || `${cssProperty}: <value>`;
}
/**
* Helper function to get property values
*/
function getPropertyValues(cssProperty: string): string[] {
const values: Record<string, string[]> = {
display: ["none", "block", "inline", "flex", "grid", "inline-block"],
"overflow-x": ["visible", "hidden", "scroll", "auto"],
"scroll-snap-type": [
"none",
"x mandatory",
"y mandatory",
"both mandatory",
],
"scroll-snap-align": ["none", "start", "end", "center"],
"corner-shape": ["round", "angle"],
};
return values[cssProperty] || ["auto", "initial", "inherit"];
}
/**
* Helper function to get property examples
*/
function getPropertyExamples(cssProperty: string): string[] {
const examples: Record<string, string[]> = {
display: [".container { display: flex; }", ".grid { display: grid; }"],
"overflow-x": [
".carousel { overflow-x: scroll; }",
".hidden { overflow-x: hidden; }",
],
"scroll-snap-type": [
".carousel { scroll-snap-type: x mandatory; }",
".gallery { scroll-snap-type: both proximity; }",
],
"scroll-snap-align": [
".carousel-item { scroll-snap-align: center; }",
".slide { scroll-snap-align: start; }",
],
"corner-shape": [
".organic-card { corner-shape: round; border-radius: 16px; }",
".sharp-button { corner-shape: angle; border-radius: 8px; }",
],
};
return examples[cssProperty] || [`${cssProperty}: example-value;`];
}
/**
* Helper function to get related properties
*/
function getRelatedProperties(cssProperty: string): string[] {
const related: Record<string, string[]> = {
display: ["position", "float", "clear"],
"overflow-x": ["overflow-y", "overflow", "scroll-behavior"],
"scroll-snap-type": ["scroll-snap-align", "scroll-behavior", "overflow"],
"scroll-snap-align": [
"scroll-snap-type",
"scroll-margin",
"scroll-padding",
],
flex: ["flex-grow", "flex-shrink", "flex-basis", "display"],
grid: ["grid-template-columns", "grid-template-rows", "gap", "display"],
"corner-shape": [
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-left-radius",
"border-bottom-right-radius",
],
};
return related[cssProperty] || [];
}