@every-app/mcp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @every-app/mcp
2
+
3
+ MCP (Model Context Protocol) server for Every App coding agents. This server provides AI coding assistants with tools to explore example apps, search code patterns, and access documentation.
4
+
5
+ ## Quick Start
6
+
7
+ Just add the MCP server to your AI tool's configuration. The examples will be automatically downloaded on first run.
8
+
9
+ ### Claude Code
10
+
11
+ Run this command:
12
+
13
+ ```sh
14
+ claude mcp add every-app -- npx -y @every-app/mcp
15
+ ```
16
+
17
+ ### Cursor
18
+
19
+ Add to `.cursor/mcp.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "every-app": {
25
+ "command": "npx",
26
+ "args": ["-y", "@every-app/mcp"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### OpenCode
33
+
34
+ Add to `opencode.json`:
35
+
36
+ ```json
37
+ {
38
+ "mcp": {
39
+ "every-app": {
40
+ "type": "local",
41
+ "command": ["npx", "-y", "@every-app/mcp"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ That's it! The server will automatically clone the Every App examples to `~/.every-app-mcp/examples` on first run.
48
+
49
+ ## Available Tools
50
+
51
+ | Tool | Description |
52
+ |------|-------------|
53
+ | `every_app_mcp_list_examples` | List all available example apps with descriptions |
54
+ | `every_app_mcp_list_directory` | Browse directory structure of example apps |
55
+ | `every_app_mcp_read_file` | Read file contents with line numbers |
56
+ | `every_app_mcp_search_code` | Search for patterns using regex |
57
+ | `every_app_mcp_find_files` | Find files matching a glob pattern |
58
+ | `every_app_mcp_fetch_docs` | Fetch Every App documentation pages |
59
+
60
+ ## Configuration (Optional)
61
+
62
+ You can customize the examples location by setting environment variables:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "every-app": {
68
+ "command": "npx",
69
+ "args": ["-y", "@every-app/mcp"],
70
+ "env": {
71
+ "EVERY_APP_EXAMPLES_DIR": "/custom/path/to/examples"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ | Variable | Description | Default |
79
+ |----------|-------------|---------|
80
+ | `EVERY_APP_EXAMPLES_DIR` | Path to examples | `~/.every-app-mcp/examples` |
81
+ | `EVERY_APP_DOCS_DIR` | Path to local docs | Falls back to GitHub |
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ # Install dependencies
87
+ pnpm install
88
+
89
+ # Build
90
+ pnpm run build
91
+
92
+ # Type check
93
+ pnpm run types:check
94
+
95
+ # Run locally
96
+ node dist/index.js
97
+ ```
98
+
99
+ ## Requirements
100
+
101
+ - Node.js 18+
102
+ - Git (for automatic example cloning)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerAllTools } from "./tools/index.js";
5
+ import { registerAllResources } from "./resources/index.js";
6
+ import { ensureExamplesExist } from "./setup.js";
7
+ // Create server instance
8
+ const server = new McpServer({
9
+ name: "every-app",
10
+ version: "0.0.1",
11
+ });
12
+ // Register all tools and resources
13
+ registerAllTools(server);
14
+ registerAllResources(server);
15
+ // Main function
16
+ async function main() {
17
+ // Ensure examples are available (clone if needed)
18
+ const setupResult = await ensureExamplesExist();
19
+ if (!setupResult.success) {
20
+ console.error(`Warning: ${setupResult.message}`);
21
+ console.error("Some tools may not work correctly without the examples.");
22
+ }
23
+ else {
24
+ console.error(setupResult.message);
25
+ }
26
+ const transport = new StdioServerTransport();
27
+ await server.connect(transport);
28
+ console.error("Every App MCP Server running on stdio");
29
+ }
30
+ main().catch((error) => {
31
+ console.error("Fatal error:", error);
32
+ process.exit(1);
33
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAllResources(server: McpServer): void;
@@ -0,0 +1,4 @@
1
+ import { registerPromptResources } from "./prompts.js";
2
+ export function registerAllResources(server) {
3
+ registerPromptResources(server);
4
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPromptResources(server: McpServer): void;
@@ -0,0 +1,87 @@
1
+ const MOCKUP_FROM_SPEC_PROMPT = `# Task: Build Mockup UI for App
2
+ Below is a description of a Product Spec for my app idea that I want to create a functional mockup for.
3
+
4
+ Please implement all the pages and key interactions described in the product spec. Please use Daisy UI to accomplish this. Reference the context7 tool to read the DaisyUI docs if necessary.
5
+
6
+ Please implement any interactions that seem reasonable. You can mock all data / should not implement any backend logic.
7
+
8
+ ## Goal
9
+ Functional mockup of product spec supporting key flows built using Daisy UI
10
+
11
+ ## Non-Goals
12
+ Implementing any backend functionality
13
+
14
+ ## Coding Guidelines
15
+ Please still try to follow good frontend coding practices like breaking things into smaller components / file so that the code is readable.
16
+
17
+ ## Checklist before implementing
18
+ - Please ask any clarifying questions if the Product Spec is ambigious
19
+ - Asking these are not essential and you should only do so if there are major things which are not clear in the spec.
20
+
21
+ --
22
+ # Product Spec
23
+ [PASTE PRODUCT SPEC HERE]`;
24
+ const REVIEW_CODE_PROMPT = `# Task: Review Code for Every App Best Practices
25
+
26
+ Please review the code changes in this PR/file for the following:
27
+
28
+ ## Security
29
+ - Check for exposed secrets or sensitive data
30
+ - Validate input handling and sanitization
31
+ - Review authentication and authorization patterns
32
+
33
+ ## Schema Design
34
+ - Check Drizzle schema for proper relationships
35
+ - Verify indexes are appropriate
36
+ - Review migration safety
37
+
38
+ ## Simplification
39
+ - Look for opportunities to simplify complex logic
40
+ - Identify duplicate code that could be extracted
41
+ - Check for unused imports or dead code
42
+
43
+ ## Every App Patterns
44
+ - Verify proper use of TanStack hooks
45
+ - Check optimistic mutation patterns
46
+ - Review error handling
47
+
48
+ ## Output
49
+ Provide specific, actionable feedback organized by category.`;
50
+ export function registerPromptResources(server) {
51
+ // Register as prompts (user-driven: user selects from prompt list)
52
+ server.prompt("every-app-mockup-from-spec", "Build a UI mockup from a product spec using DaisyUI", async () => ({
53
+ messages: [
54
+ {
55
+ role: "user",
56
+ content: { type: "text", text: MOCKUP_FROM_SPEC_PROMPT },
57
+ },
58
+ ],
59
+ }));
60
+ server.prompt("every-app-review-code", "Review code for Every App best practices", async () => ({
61
+ messages: [
62
+ {
63
+ role: "user",
64
+ content: { type: "text", text: REVIEW_CODE_PROMPT },
65
+ },
66
+ ],
67
+ }));
68
+ // Register as resources (application-driven: AI can fetch these proactively)
69
+ server.resource("prompts/mockup-from-spec", "every-app://prompts/mockup-from-spec", async (uri) => ({
70
+ contents: [
71
+ {
72
+ uri: uri.href,
73
+ mimeType: "text/markdown",
74
+ text: MOCKUP_FROM_SPEC_PROMPT,
75
+ },
76
+ ],
77
+ }));
78
+ server.resource("prompts/review-code", "every-app://prompts/review-code", async (uri) => ({
79
+ contents: [
80
+ {
81
+ uri: uri.href,
82
+ mimeType: "text/markdown",
83
+ text: REVIEW_CODE_PROMPT,
84
+ },
85
+ ],
86
+ }));
87
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get the examples directory, using environment variable or default location
3
+ */
4
+ export declare function getExamplesDirectory(): string;
5
+ /**
6
+ * Clone or update the examples repository
7
+ * Uses sparse checkout to only get apps/ and templates/ directories
8
+ */
9
+ export declare function ensureExamplesExist(): Promise<{
10
+ success: boolean;
11
+ dir: string;
12
+ message: string;
13
+ }>;
package/dist/setup.js ADDED
@@ -0,0 +1,113 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import { execSync, execFileSync } from "node:child_process";
5
+ // Default location for examples
6
+ const DEFAULT_EXAMPLES_DIR = path.join(os.homedir(), ".every-app-mcp", "examples");
7
+ // Repository info
8
+ const REPO_URL = "https://github.com/every-app/every-app.git";
9
+ const SPARSE_PATHS = ["apps", "templates"];
10
+ /**
11
+ * Get the examples directory, using environment variable or default location
12
+ */
13
+ export function getExamplesDirectory() {
14
+ return process.env.EVERY_APP_EXAMPLES_DIR || DEFAULT_EXAMPLES_DIR;
15
+ }
16
+ /**
17
+ * Check if git is available
18
+ */
19
+ function hasGit() {
20
+ try {
21
+ execSync("git --version", { stdio: "ignore" });
22
+ return true;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ /**
29
+ * Clone or update the examples repository
30
+ * Uses sparse checkout to only get apps/ and templates/ directories
31
+ */
32
+ export async function ensureExamplesExist() {
33
+ const examplesDir = getExamplesDirectory();
34
+ // Check if examples already exist and have content
35
+ if (fs.existsSync(examplesDir)) {
36
+ const hasApps = fs.existsSync(path.join(examplesDir, "apps"));
37
+ const hasTemplates = fs.existsSync(path.join(examplesDir, "templates"));
38
+ if (hasApps || hasTemplates) {
39
+ // Try to update if it's a git repo
40
+ if (fs.existsSync(path.join(examplesDir, ".git"))) {
41
+ try {
42
+ execFileSync("git", ["pull", "--quiet"], {
43
+ cwd: examplesDir,
44
+ stdio: "ignore",
45
+ timeout: 30000,
46
+ });
47
+ return {
48
+ success: true,
49
+ dir: examplesDir,
50
+ message: "Examples updated successfully",
51
+ };
52
+ }
53
+ catch {
54
+ // Pull failed, but we have existing examples so continue
55
+ return {
56
+ success: true,
57
+ dir: examplesDir,
58
+ message: "Using existing examples (update failed)",
59
+ };
60
+ }
61
+ }
62
+ return {
63
+ success: true,
64
+ dir: examplesDir,
65
+ message: "Using existing examples",
66
+ };
67
+ }
68
+ }
69
+ // Need to clone the examples
70
+ if (!hasGit()) {
71
+ return {
72
+ success: false,
73
+ dir: examplesDir,
74
+ message: "Git is not installed. Please install git and try again, or manually clone the examples.",
75
+ };
76
+ }
77
+ // Create parent directory
78
+ const parentDir = path.dirname(examplesDir);
79
+ if (!fs.existsSync(parentDir)) {
80
+ fs.mkdirSync(parentDir, { recursive: true });
81
+ }
82
+ try {
83
+ console.error("Cloning Every App examples (this may take a moment)...");
84
+ // Use sparse checkout to only get apps/ and templates/
85
+ execFileSync("git", ["clone", "--filter=blob:none", "--sparse", REPO_URL, examplesDir], { stdio: "ignore", timeout: 120000 });
86
+ execFileSync("git", ["sparse-checkout", "set", ...SPARSE_PATHS], {
87
+ cwd: examplesDir,
88
+ stdio: "ignore",
89
+ timeout: 30000,
90
+ });
91
+ return {
92
+ success: true,
93
+ dir: examplesDir,
94
+ message: `Examples cloned to ${examplesDir}`,
95
+ };
96
+ }
97
+ catch (error) {
98
+ // Clean up failed clone
99
+ if (fs.existsSync(examplesDir)) {
100
+ try {
101
+ fs.rmSync(examplesDir, { recursive: true });
102
+ }
103
+ catch {
104
+ // Ignore cleanup errors
105
+ }
106
+ }
107
+ return {
108
+ success: false,
109
+ dir: examplesDir,
110
+ message: `Failed to clone examples: ${error instanceof Error ? error.message : "Unknown error"}`,
111
+ };
112
+ }
113
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerFetchDocsTool(server: McpServer): void;
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { errorResponse, textResponse } from "../utils.js";
5
+ const AVAILABLE_DOCS = `Available pages include:
6
+ - introduction
7
+ - tech-stack/overview
8
+ - tech-stack/tanstack-start
9
+ - tech-stack/drizzle
10
+ - tech-stack/cloudflare
11
+ - embedded-sdk/overview
12
+ - embedded-sdk/client
13
+ - embedded-sdk/server
14
+ - build-an-app/start-from-template
15
+ - build-an-app/development-workflow
16
+ - build-an-app/deployment
17
+ - coding-agent/setup`;
18
+ export function registerFetchDocsTool(server) {
19
+ server.tool("every_app_mcp_fetch_docs", "Fetch content from the Every App documentation. Use this to understand concepts, APIs, and best practices.", {
20
+ page: z
21
+ .string()
22
+ .describe('Documentation page path (e.g., "introduction", "tech-stack/drizzle", "embedded-sdk/client")'),
23
+ }, async ({ page }) => {
24
+ // Validate page contains only safe characters (alphanumeric, hyphens, slashes)
25
+ if (!/^[a-zA-Z0-9\-\/]+$/.test(page)) {
26
+ return errorResponse(`Invalid page path: ${page}\n\n${AVAILABLE_DOCS}`);
27
+ }
28
+ // Try to read from local docs directory first
29
+ const docsDir = process.env.EVERY_APP_DOCS_DIR;
30
+ if (docsDir) {
31
+ // Try to read from local docs directory
32
+ const localPath = path.join(docsDir, `${page}.mdx`);
33
+ if (fs.existsSync(localPath)) {
34
+ const content = fs.readFileSync(localPath, "utf-8");
35
+ return textResponse(`# ${page}\n\n${content}`);
36
+ }
37
+ // Try without .mdx extension (might be a directory index)
38
+ const indexPath = path.join(docsDir, page, "index.mdx");
39
+ if (fs.existsSync(indexPath)) {
40
+ const content = fs.readFileSync(indexPath, "utf-8");
41
+ return textResponse(`# ${page}\n\n${content}`);
42
+ }
43
+ }
44
+ // Fallback to fetching from GitHub raw content
45
+ const rawUrl = `https://raw.githubusercontent.com/every-app/every-app/main/landing-page/src/content/docs/docs/${page}.mdx`;
46
+ try {
47
+ const response = await fetch(rawUrl);
48
+ if (!response.ok) {
49
+ return errorResponse(`Documentation page not found: ${page}\n\n${AVAILABLE_DOCS}`);
50
+ }
51
+ const content = await response.text();
52
+ return textResponse(`# ${page}\n\n${content}`);
53
+ }
54
+ catch (error) {
55
+ return errorResponse(`Error fetching docs: ${error instanceof Error ? error.message : "Unknown error"}`);
56
+ }
57
+ });
58
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerFindFilesTool(server: McpServer): void;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import { findFiles, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
4
+ export function registerFindFilesTool(server) {
5
+ server.tool("every_app_mcp_find_files", "Find files matching a glob pattern in the Every App examples.", {
6
+ pattern: z
7
+ .string()
8
+ .describe('Glob pattern to match (e.g., "**/*.tsx", "**/schema.ts")'),
9
+ path: z
10
+ .string()
11
+ .optional()
12
+ .describe("Path to search within (relative to examples root). Defaults to entire examples directory."),
13
+ }, async ({ pattern, path: inputPath }) => {
14
+ const result = getExamplesDir();
15
+ if ("error" in result) {
16
+ return errorResponse(result.error);
17
+ }
18
+ const examplesDir = result.dir;
19
+ // Validate path stays within examples directory
20
+ let searchPath = examplesDir;
21
+ if (inputPath) {
22
+ const pathValidation = validatePathWithinBase(examplesDir, inputPath);
23
+ if (!pathValidation.valid) {
24
+ return errorResponse(pathValidation.error);
25
+ }
26
+ searchPath = pathValidation.resolvedPath;
27
+ }
28
+ if (!fs.existsSync(searchPath)) {
29
+ return errorResponse(`Path not found: ${inputPath || "(root)"}`);
30
+ }
31
+ try {
32
+ const files = findFiles(searchPath, pattern, { maxResults: 100 });
33
+ if (files.length === 0) {
34
+ return textResponse(`No files found matching: ${pattern}`);
35
+ }
36
+ // Prepend the input path to make paths relative to examples root
37
+ const displayFiles = inputPath
38
+ ? files.map((f) => `${inputPath}/${f}`)
39
+ : files;
40
+ const truncated = files.length >= 100;
41
+ let output = displayFiles.join("\n");
42
+ if (truncated) {
43
+ output += "\n\n... (results truncated, refine your pattern)";
44
+ }
45
+ return textResponse(`Found ${files.length} files:\n\n${output}`);
46
+ }
47
+ catch (error) {
48
+ return errorResponse(`Search error: ${error instanceof Error ? error.message : "Unknown error"}`);
49
+ }
50
+ });
51
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAllTools(server: McpServer): void;
@@ -0,0 +1,14 @@
1
+ import { registerListDirectoryTool } from "./list-directory.js";
2
+ import { registerReadFileTool } from "./read-file.js";
3
+ import { registerSearchCodeTool } from "./search-code.js";
4
+ import { registerFindFilesTool } from "./find-files.js";
5
+ import { registerFetchDocsTool } from "./fetch-docs.js";
6
+ import { registerListExamplesTool } from "./list-examples.js";
7
+ export function registerAllTools(server) {
8
+ registerListDirectoryTool(server);
9
+ registerReadFileTool(server);
10
+ registerSearchCodeTool(server);
11
+ registerFindFilesTool(server);
12
+ registerFetchDocsTool(server);
13
+ registerListExamplesTool(server);
14
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerListDirectoryTool(server: McpServer): void;
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { IGNORE_PATTERNS, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
5
+ export function registerListDirectoryTool(server) {
6
+ server.tool("every_app_mcp_list_directory", "List files and directories in the Every App examples. Use this to explore the structure of example apps.", {
7
+ path: z
8
+ .string()
9
+ .optional()
10
+ .describe('Path relative to the Every App examples root (e.g., "apps/todo-app/src"). Defaults to root.'),
11
+ }, async ({ path: inputPath }) => {
12
+ const result = getExamplesDir();
13
+ if ("error" in result) {
14
+ return errorResponse(result.error);
15
+ }
16
+ const examplesDir = result.dir;
17
+ // Validate path stays within examples directory
18
+ let targetPath = examplesDir;
19
+ if (inputPath) {
20
+ const pathValidation = validatePathWithinBase(examplesDir, inputPath);
21
+ if (!pathValidation.valid) {
22
+ return errorResponse(pathValidation.error);
23
+ }
24
+ targetPath = pathValidation.resolvedPath;
25
+ }
26
+ if (!fs.existsSync(targetPath)) {
27
+ return errorResponse(`Directory not found: ${inputPath || "(root)"}`);
28
+ }
29
+ const stat = fs.statSync(targetPath);
30
+ if (!stat.isDirectory()) {
31
+ return errorResponse(`Path is not a directory: ${inputPath}`);
32
+ }
33
+ // Build directory tree
34
+ function buildTree(dir, prefix = "", depth = 0) {
35
+ if (depth > 3)
36
+ return [`${prefix}...`]; // Limit depth
37
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
38
+ const filtered = entries.filter((e) => !IGNORE_PATTERNS.includes(e.name) && !e.name.startsWith("."));
39
+ const lines = [];
40
+ filtered.forEach((entry, idx) => {
41
+ const isLast = idx === filtered.length - 1;
42
+ const connector = isLast ? "└── " : "├── ";
43
+ const childPrefix = isLast ? " " : "│ ";
44
+ lines.push(`${prefix}${connector}${entry.name}${entry.isDirectory() ? "/" : ""}`);
45
+ if (entry.isDirectory()) {
46
+ const subLines = buildTree(path.join(dir, entry.name), prefix + childPrefix, depth + 1);
47
+ lines.push(...subLines);
48
+ }
49
+ });
50
+ return lines;
51
+ }
52
+ const tree = buildTree(targetPath);
53
+ const output = [`${inputPath || "."}/`, ...tree].join("\n");
54
+ return textResponse(output);
55
+ });
56
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerListExamplesTool(server: McpServer): void;
@@ -0,0 +1,39 @@
1
+ import { textResponse } from "../utils.js";
2
+ const EXAMPLE_APPS = [
3
+ {
4
+ name: "apps/todo-app",
5
+ description: "Simple todo application demonstrating basic CRUD operations, routing, and embedded app patterns",
6
+ goodFor: [
7
+ "Basic data relationships",
8
+ "Complex Drag & Drop",
9
+ "Route setup",
10
+ "Embedded provider usage",
11
+ ],
12
+ },
13
+ {
14
+ name: "apps/workout-tracker",
15
+ description: "Workout tracking app with complex data relationships and forms",
16
+ goodFor: [
17
+ "Complex TanstackDB Optimistic Updates",
18
+ "Simple Drag & Drop",
19
+ "Complex data relationships",
20
+ "Form handling",
21
+ "Advanced queries",
22
+ ],
23
+ },
24
+ {
25
+ name: "apps/every-chef",
26
+ description: "Cooking assistant with LLM integration",
27
+ goodFor: [
28
+ "LLM integration patterns",
29
+ "AI-powered features",
30
+ "Streaming responses",
31
+ ],
32
+ },
33
+ ];
34
+ export function registerListExamplesTool(server) {
35
+ server.tool("every_app_mcp_list_examples", "List available Every App example applications and what they demonstrate.", {}, async () => {
36
+ const output = EXAMPLE_APPS.map((ex) => `## ${ex.name}\n${ex.description}\n\n**Good for learning:**\n${ex.goodFor.map((g) => `- ${g}`).join("\n")}`).join("\n\n---\n\n");
37
+ return textResponse(`# Available Every App Examples\n\n${output}\n\n---\n\nUse \`every_app_mcp_list_directory\` to explore the structure of any example, and \`every_app_mcp_read_file\` to view implementation details.`);
38
+ });
39
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerReadFileTool(server: McpServer): void;
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import { getExamplesDir, errorResponse, textResponse, validatePathWithinBase } from "../utils.js";
4
+ export function registerReadFileTool(server) {
5
+ server.tool("every_app_mcp_read_file", "Read the contents of a file from the Every App examples. Use this to see how patterns are implemented.", {
6
+ path: z
7
+ .string()
8
+ .describe('Path relative to the Every App examples root (e.g., "apps/todo-app/src/routes/index.tsx")'),
9
+ startLine: z
10
+ .number()
11
+ .optional()
12
+ .describe("Line number to start reading from (1-based). Defaults to 1."),
13
+ endLine: z
14
+ .number()
15
+ .optional()
16
+ .describe("Line number to stop reading at (inclusive). Defaults to end of file."),
17
+ }, async ({ path: inputPath, startLine, endLine }) => {
18
+ const result = getExamplesDir();
19
+ if ("error" in result) {
20
+ return errorResponse(result.error);
21
+ }
22
+ const examplesDir = result.dir;
23
+ // Validate path stays within examples directory
24
+ const pathValidation = validatePathWithinBase(examplesDir, inputPath);
25
+ if (!pathValidation.valid) {
26
+ return errorResponse(pathValidation.error);
27
+ }
28
+ const filePath = pathValidation.resolvedPath;
29
+ if (!fs.existsSync(filePath)) {
30
+ return errorResponse(`File not found: ${inputPath}`);
31
+ }
32
+ const stat = fs.statSync(filePath);
33
+ if (stat.isDirectory()) {
34
+ return errorResponse(`Path is a directory, not a file: ${inputPath}. Use list_directory instead.`);
35
+ }
36
+ const content = fs.readFileSync(filePath, "utf-8");
37
+ const lines = content.split("\n");
38
+ const start = Math.max(1, startLine || 1);
39
+ const end = Math.min(lines.length, endLine || lines.length);
40
+ const selectedLines = lines.slice(start - 1, end);
41
+ const numberedLines = selectedLines.map((line, idx) => `${(start + idx).toString().padStart(5, " ")}| ${line}`);
42
+ const header = `File: ${inputPath} (lines ${start}-${end} of ${lines.length})`;
43
+ const output = [header, "─".repeat(60), ...numberedLines].join("\n");
44
+ return textResponse(output);
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSearchCodeTool(server: McpServer): void;
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import { searchFiles, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
4
+ export function registerSearchCodeTool(server) {
5
+ server.tool("every_app_mcp_search_code", "Search for patterns in the Every App examples using regex. Use this to find implementations of specific patterns.", {
6
+ pattern: z.string().describe("Regular expression pattern to search for"),
7
+ path: z
8
+ .string()
9
+ .optional()
10
+ .describe("Path to search within (relative to examples root). Defaults to entire examples directory."),
11
+ filePattern: z
12
+ .string()
13
+ .optional()
14
+ .describe('Glob pattern for files to include (e.g., "**/*.tsx", "**/*.ts")'),
15
+ }, async ({ pattern, path: inputPath, filePattern }) => {
16
+ const result = getExamplesDir();
17
+ if ("error" in result) {
18
+ return errorResponse(result.error);
19
+ }
20
+ const examplesDir = result.dir;
21
+ // Validate path stays within examples directory
22
+ if (inputPath) {
23
+ const pathValidation = validatePathWithinBase(examplesDir, inputPath);
24
+ if (!pathValidation.valid) {
25
+ return errorResponse(pathValidation.error);
26
+ }
27
+ if (!fs.existsSync(pathValidation.resolvedPath)) {
28
+ return errorResponse(`Path not found: ${inputPath}`);
29
+ }
30
+ }
31
+ try {
32
+ const matches = searchFiles(examplesDir, pattern, {
33
+ filePattern,
34
+ searchPath: inputPath,
35
+ maxResults: 100,
36
+ });
37
+ if (matches.length === 0) {
38
+ return textResponse(`No matches found for pattern: ${pattern}`);
39
+ }
40
+ // Group matches by file
41
+ const byFile = new Map();
42
+ for (const match of matches) {
43
+ const existing = byFile.get(match.file) || [];
44
+ existing.push(match);
45
+ byFile.set(match.file, existing);
46
+ }
47
+ // Format output
48
+ const lines = [`Found ${matches.length} matches:\n`];
49
+ for (const [file, fileMatches] of byFile) {
50
+ lines.push(file);
51
+ for (const match of fileMatches) {
52
+ lines.push(` ${match.line}: ${match.text}`);
53
+ }
54
+ lines.push("");
55
+ }
56
+ const truncated = matches.length >= 100;
57
+ if (truncated) {
58
+ lines.push("... (results truncated, refine your search)");
59
+ }
60
+ return textResponse(lines.join("\n"));
61
+ }
62
+ catch (error) {
63
+ return errorResponse(`Search error: ${error instanceof Error ? error.message : "Unknown error"}`);
64
+ }
65
+ });
66
+ }
@@ -0,0 +1,38 @@
1
+ export declare const IGNORE_PATTERNS: string[];
2
+ export declare function getExamplesDir(): {
3
+ dir: string;
4
+ } | {
5
+ error: string;
6
+ };
7
+ export declare function errorResponse(text: string): {
8
+ content: {
9
+ type: "text";
10
+ text: string;
11
+ }[];
12
+ };
13
+ export declare function validatePathWithinBase(baseDir: string, inputPath: string): {
14
+ valid: true;
15
+ resolvedPath: string;
16
+ } | {
17
+ valid: false;
18
+ error: string;
19
+ };
20
+ export declare function textResponse(text: string): {
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ };
26
+ export declare function findFiles(baseDir: string, pattern: string, options?: {
27
+ maxResults?: number;
28
+ }): string[];
29
+ export interface SearchMatch {
30
+ file: string;
31
+ line: number;
32
+ text: string;
33
+ }
34
+ export declare function searchFiles(baseDir: string, searchPattern: string, options?: {
35
+ filePattern?: string;
36
+ maxResults?: number;
37
+ searchPath?: string;
38
+ }): SearchMatch[];
package/dist/utils.js ADDED
@@ -0,0 +1,168 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getExamplesDirectory } from "./setup.js";
4
+ // Ignore patterns for directory listings and searches
5
+ export const IGNORE_PATTERNS = [
6
+ "node_modules",
7
+ ".git",
8
+ "dist",
9
+ "build",
10
+ ".next",
11
+ ".turbo",
12
+ ".cache",
13
+ "coverage",
14
+ ".wrangler",
15
+ ".mf",
16
+ ];
17
+ // Binary file extensions to skip when searching
18
+ const BINARY_EXTENSIONS = new Set([
19
+ ".png",
20
+ ".jpg",
21
+ ".jpeg",
22
+ ".gif",
23
+ ".ico",
24
+ ".webp",
25
+ ".svg",
26
+ ".woff",
27
+ ".woff2",
28
+ ".ttf",
29
+ ".eot",
30
+ ".mp3",
31
+ ".mp4",
32
+ ".wav",
33
+ ".pdf",
34
+ ".zip",
35
+ ".tar",
36
+ ".gz",
37
+ ".lock",
38
+ ]);
39
+ // Helper to get examples directory or return error
40
+ export function getExamplesDir() {
41
+ const examplesDir = getExamplesDirectory();
42
+ if (!fs.existsSync(examplesDir)) {
43
+ return {
44
+ error: `Examples directory not found at ${examplesDir}. The server may still be initializing - please try again in a moment.`,
45
+ };
46
+ }
47
+ return { dir: examplesDir };
48
+ }
49
+ // Helper to create error response
50
+ export function errorResponse(text) {
51
+ return {
52
+ content: [{ type: "text", text }],
53
+ };
54
+ }
55
+ // Validate that a resolved path stays within the base directory (prevents path traversal)
56
+ export function validatePathWithinBase(baseDir, inputPath) {
57
+ const resolvedBase = path.resolve(baseDir);
58
+ const resolvedPath = path.resolve(baseDir, inputPath);
59
+ if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
60
+ return {
61
+ valid: false,
62
+ error: "Path traversal detected - access denied",
63
+ };
64
+ }
65
+ return { valid: true, resolvedPath };
66
+ }
67
+ // Helper to create success response
68
+ export function textResponse(text) {
69
+ return {
70
+ content: [{ type: "text", text }],
71
+ };
72
+ }
73
+ // Check if a path should be ignored
74
+ function shouldIgnore(name) {
75
+ return IGNORE_PATTERNS.includes(name) || name.startsWith(".");
76
+ }
77
+ // Check if a file is binary based on extension
78
+ function isBinaryFile(filePath) {
79
+ const ext = path.extname(filePath).toLowerCase();
80
+ return BINARY_EXTENSIONS.has(ext);
81
+ }
82
+ // Recursively find all files matching a glob-like pattern
83
+ export function findFiles(baseDir, pattern, options = {}) {
84
+ const { maxResults = 500 } = options;
85
+ const results = [];
86
+ // Convert glob pattern to regex
87
+ const regexPattern = pattern
88
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
89
+ .replace(/\*/g, "[^/]*")
90
+ .replace(/\?/g, ".")
91
+ .replace(/{{GLOBSTAR}}/g, ".*");
92
+ const regex = new RegExp(`^${regexPattern}$`);
93
+ function walk(dir, relativePath = "") {
94
+ if (results.length >= maxResults)
95
+ return;
96
+ let entries;
97
+ try {
98
+ entries = fs.readdirSync(dir, { withFileTypes: true });
99
+ }
100
+ catch {
101
+ return; // Skip directories we can't read
102
+ }
103
+ for (const entry of entries) {
104
+ if (results.length >= maxResults)
105
+ break;
106
+ if (shouldIgnore(entry.name))
107
+ continue;
108
+ const fullPath = path.join(dir, entry.name);
109
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
110
+ if (entry.isDirectory()) {
111
+ walk(fullPath, relPath);
112
+ }
113
+ else if (entry.isFile()) {
114
+ if (regex.test(relPath)) {
115
+ results.push(relPath);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ walk(baseDir);
121
+ return results;
122
+ }
123
+ export function searchFiles(baseDir, searchPattern, options = {}) {
124
+ const { filePattern, maxResults = 100, searchPath } = options;
125
+ const matches = [];
126
+ const searchDir = searchPath ? path.join(baseDir, searchPath) : baseDir;
127
+ // Get all files to search
128
+ const globPattern = filePattern || "**/*";
129
+ const files = findFiles(searchDir, globPattern, { maxResults: 1000 });
130
+ let regex;
131
+ try {
132
+ regex = new RegExp(searchPattern, "gi");
133
+ }
134
+ catch {
135
+ throw new Error(`Invalid regex pattern: ${searchPattern}`);
136
+ }
137
+ for (const file of files) {
138
+ if (matches.length >= maxResults)
139
+ break;
140
+ const fullPath = path.join(searchDir, file);
141
+ // Skip binary files
142
+ if (isBinaryFile(fullPath))
143
+ continue;
144
+ let content;
145
+ try {
146
+ content = fs.readFileSync(fullPath, "utf-8");
147
+ }
148
+ catch {
149
+ continue; // Skip files we can't read
150
+ }
151
+ const lines = content.split("\n");
152
+ for (let i = 0; i < lines.length; i++) {
153
+ if (matches.length >= maxResults)
154
+ break;
155
+ const line = lines[i];
156
+ regex.lastIndex = 0; // Reset regex state
157
+ if (regex.test(line)) {
158
+ const relativePath = searchPath ? `${searchPath}/${file}` : file;
159
+ matches.push({
160
+ file: relativePath,
161
+ line: i + 1,
162
+ text: line.length > 200 ? line.substring(0, 200) + "..." : line,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ return matches;
168
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@every-app/mcp",
3
+ "version": "0.0.1",
4
+ "description": "MCP server for Every App coding agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "every-app-mcp": "./dist/index.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "types:check": "tsc --noEmit",
16
+ "start": "node dist/index.js",
17
+ "build:prepublish": "pnpm install --ignore-scripts && npm run types:check && npm run build",
18
+ "prepublishOnly": "npm run build:prepublish"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "zod": "^3.24.1"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.10.2",
29
+ "typescript": "^5.7.2"
30
+ }
31
+ }