@every-app/mcp 0.0.1 → 0.0.3

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 CHANGED
@@ -1,10 +1,9 @@
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.
1
+ # Every App MCP
2
+ This server gives your coding agent access to example apps and Every App documentation which can be used for reference when building out your application.
4
3
 
5
4
  ## Quick Start
6
5
 
7
- Just add the MCP server to your AI tool's configuration. The examples will be automatically downloaded on first run.
6
+ This MCP server is compatible with any coding agent tool. Below are some example configs:
8
7
 
9
8
  ### Claude Code
10
9
 
@@ -44,41 +43,16 @@ Add to `opencode.json`:
44
43
  }
45
44
  ```
46
45
 
47
- That's it! The server will automatically clone the Every App examples to `~/.every-app-mcp/examples` on first run.
48
-
49
46
  ## Available Tools
50
47
 
51
48
  | Tool | Description |
52
49
  |------|-------------|
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 |
50
+ | `browse` | List available example apps, internal packages, and documentation |
51
+ | `list_directory` | Browse directory structure of examples and docs |
52
+ | `read_file` | Read file contents with line numbers |
53
+ | `search_code` | Search for patterns using regex |
54
+ | `find_files` | Find files matching a glob pattern |
59
55
 
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
56
 
83
57
  ## Development
84
58
 
@@ -93,10 +67,7 @@ pnpm run build
93
67
  pnpm run types:check
94
68
 
95
69
  # Run locally
96
- node dist/index.js
70
+ Swap command argument with this:
71
+ `bun run ~/your_workspace/every-app/packages/mcp/src/index.ts`
97
72
  ```
98
73
 
99
- ## Requirements
100
-
101
- - Node.js 18+
102
- - Git (for automatic example cloning)
package/dist/setup.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  export declare function getExamplesDirectory(): string;
5
5
  /**
6
6
  * Clone or update the examples repository
7
- * Uses sparse checkout to only get apps/ and templates/ directories
7
+ * Uses a shallow clone for speed
8
8
  */
9
9
  export declare function ensureExamplesExist(): Promise<{
10
10
  success: boolean;
package/dist/setup.js CHANGED
@@ -6,7 +6,6 @@ import { execSync, execFileSync } from "node:child_process";
6
6
  const DEFAULT_EXAMPLES_DIR = path.join(os.homedir(), ".every-app-mcp", "examples");
7
7
  // Repository info
8
8
  const REPO_URL = "https://github.com/every-app/every-app.git";
9
- const SPARSE_PATHS = ["apps", "templates"];
10
9
  /**
11
10
  * Get the examples directory, using environment variable or default location
12
11
  */
@@ -27,42 +26,48 @@ function hasGit() {
27
26
  }
28
27
  /**
29
28
  * Clone or update the examples repository
30
- * Uses sparse checkout to only get apps/ and templates/ directories
29
+ * Uses a shallow clone for speed
31
30
  */
32
31
  export async function ensureExamplesExist() {
33
32
  const examplesDir = getExamplesDirectory();
34
- // Check if examples already exist and have content
33
+ // Check if examples already exist
35
34
  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
- }
35
+ // Try to update if it's a git repo
36
+ if (fs.existsSync(path.join(examplesDir, ".git"))) {
37
+ try {
38
+ execFileSync("git", ["fetch", "--depth=1", "origin", "main"], {
39
+ cwd: examplesDir,
40
+ stdio: "ignore",
41
+ timeout: 30000,
42
+ });
43
+ execFileSync("git", ["reset", "--hard", "origin/main"], {
44
+ cwd: examplesDir,
45
+ stdio: "ignore",
46
+ timeout: 30000,
47
+ });
48
+ return {
49
+ success: true,
50
+ dir: examplesDir,
51
+ message: "Examples updated successfully",
52
+ };
53
+ }
54
+ catch {
55
+ return {
56
+ success: false,
57
+ dir: examplesDir,
58
+ message: "Failed to update examples repository",
59
+ };
61
60
  }
61
+ }
62
+ // Existing directory isn't a git repo; remove and re-clone
63
+ try {
64
+ fs.rmSync(examplesDir, { recursive: true, force: true });
65
+ }
66
+ catch {
62
67
  return {
63
- success: true,
68
+ success: false,
64
69
  dir: examplesDir,
65
- message: "Using existing examples",
70
+ message: "Existing examples directory is not a git repo",
66
71
  };
67
72
  }
68
73
  }
@@ -81,13 +86,7 @@ export async function ensureExamplesExist() {
81
86
  }
82
87
  try {
83
88
  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
- });
89
+ execFileSync("git", ["clone", "--depth=1", REPO_URL, examplesDir], { stdio: "ignore", timeout: 120000 });
91
90
  return {
92
91
  success: true,
93
92
  dir: examplesDir,
@@ -2,57 +2,78 @@ import { z } from "zod";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
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`;
5
+ import { getExamplesDirectory } from "../setup.js";
6
+ /**
7
+ * Get the docs directory path
8
+ */
9
+ function getDocsDirectory() {
10
+ return path.join(getExamplesDirectory(), "landing-page/src/content/docs/docs");
11
+ }
12
+ /**
13
+ * Recursively find all .mdx files in a directory and return their paths relative to the base
14
+ */
15
+ function findMdxFiles(dir, basePath = "") {
16
+ const results = [];
17
+ if (!fs.existsSync(dir)) {
18
+ return results;
19
+ }
20
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(dir, entry.name);
23
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
24
+ if (entry.isDirectory()) {
25
+ results.push(...findMdxFiles(fullPath, relativePath));
26
+ }
27
+ else if (entry.isFile() && entry.name.endsWith(".mdx")) {
28
+ // Remove .mdx extension for the page path
29
+ results.push(relativePath.replace(/\.mdx$/, ""));
30
+ }
31
+ }
32
+ return results;
33
+ }
18
34
  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.", {
35
+ server.tool("fetch_docs", "Browse and read Every App documentation. Call without a page to see available docs, or with a page path to read it.", {
20
36
  page: z
21
37
  .string()
22
- .describe('Documentation page path (e.g., "introduction", "tech-stack/drizzle", "embedded-sdk/client")'),
38
+ .optional()
39
+ .describe('Documentation page path (e.g., "introduction", "walkthrough/ai-chat"). Leave empty to list available pages.'),
23
40
  }, async ({ page }) => {
41
+ const docsDir = getDocsDirectory();
42
+ // If no page specified, list available docs
43
+ if (!page) {
44
+ const docPages = findMdxFiles(docsDir);
45
+ if (docPages.length === 0) {
46
+ return textResponse(`Documentation not found locally. The docs may not have been cloned yet.
47
+
48
+ Try restarting the MCP server to trigger a fresh clone, or check that git is available.`);
49
+ }
50
+ const docsList = docPages.sort().map((p) => `- ${p}`).join("\n");
51
+ return textResponse(`# Available Documentation Pages
52
+
53
+ ${docsList}
54
+
55
+ ---
56
+ Call this tool with a page path to read its content.`);
57
+ }
24
58
  // Validate page contains only safe characters (alphanumeric, hyphens, slashes)
25
59
  if (!/^[a-zA-Z0-9\-\/]+$/.test(page)) {
26
- return errorResponse(`Invalid page path: ${page}\n\n${AVAILABLE_DOCS}`);
60
+ return errorResponse(`Invalid page path: ${page}. Page paths can only contain letters, numbers, hyphens, and slashes.`);
27
61
  }
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();
62
+ // Try to read from local docs directory
63
+ const localPath = path.join(docsDir, `${page}.mdx`);
64
+ if (fs.existsSync(localPath)) {
65
+ const content = fs.readFileSync(localPath, "utf-8");
52
66
  return textResponse(`# ${page}\n\n${content}`);
53
67
  }
54
- catch (error) {
55
- return errorResponse(`Error fetching docs: ${error instanceof Error ? error.message : "Unknown error"}`);
68
+ // Try without .mdx extension (might be a directory index)
69
+ const indexPath = path.join(docsDir, page, "index.mdx");
70
+ if (fs.existsSync(indexPath)) {
71
+ const content = fs.readFileSync(indexPath, "utf-8");
72
+ return textResponse(`# ${page}\n\n${content}`);
56
73
  }
74
+ // Page not found
75
+ return errorResponse(`Documentation page not found: ${page}
76
+
77
+ Call this tool without a page argument to see available pages.`);
57
78
  });
58
79
  }
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import * as fs from "node:fs";
3
3
  import { findFiles, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
4
4
  export function registerFindFilesTool(server) {
5
- server.tool("every_app_mcp_find_files", "Find files matching a glob pattern in the Every App examples.", {
5
+ server.tool("find_files", "Find files matching a glob pattern in the Every App examples.", {
6
6
  pattern: z
7
7
  .string()
8
8
  .describe('Glob pattern to match (e.g., "**/*.tsx", "**/schema.ts")'),
@@ -2,13 +2,11 @@ import { registerListDirectoryTool } from "./list-directory.js";
2
2
  import { registerReadFileTool } from "./read-file.js";
3
3
  import { registerSearchCodeTool } from "./search-code.js";
4
4
  import { registerFindFilesTool } from "./find-files.js";
5
- import { registerFetchDocsTool } from "./fetch-docs.js";
6
- import { registerListExamplesTool } from "./list-examples.js";
5
+ import { registerBrowseTool } from "./list-examples.js";
7
6
  export function registerAllTools(server) {
8
7
  registerListDirectoryTool(server);
9
8
  registerReadFileTool(server);
10
9
  registerSearchCodeTool(server);
11
10
  registerFindFilesTool(server);
12
- registerFetchDocsTool(server);
13
- registerListExamplesTool(server);
11
+ registerBrowseTool(server);
14
12
  }
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { IGNORE_PATTERNS, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
5
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.", {
6
+ server.tool("list_directory", "List files and directories in the Every App examples. Use this to explore the structure of example apps.", {
7
7
  path: z
8
8
  .string()
9
9
  .optional()
@@ -1,2 +1,2 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- export declare function registerListExamplesTool(server: McpServer): void;
2
+ export declare function registerBrowseTool(server: McpServer): void;
@@ -1,13 +1,17 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
1
3
  import { textResponse } from "../utils.js";
4
+ import { getExamplesDirectory } from "../setup.js";
2
5
  const EXAMPLE_APPS = [
3
6
  {
4
7
  name: "apps/todo-app",
5
8
  description: "Simple todo application demonstrating basic CRUD operations, routing, and embedded app patterns",
6
9
  goodFor: [
10
+ "Embedded provider usage",
7
11
  "Basic data relationships",
8
- "Complex Drag & Drop",
12
+ "Complex Drag & Drop and fractional indexing for ordering",
13
+ "Keybindings and keyboard based navigation",
9
14
  "Route setup",
10
- "Embedded provider usage",
11
15
  ],
12
16
  },
13
17
  {
@@ -16,24 +20,131 @@ const EXAMPLE_APPS = [
16
20
  goodFor: [
17
21
  "Complex TanstackDB Optimistic Updates",
18
22
  "Simple Drag & Drop",
19
- "Complex data relationships",
23
+ "Slide animations for mobile navigation",
24
+ "Complex data relationships and drizzle schema",
20
25
  "Form handling",
21
26
  "Advanced queries",
22
27
  ],
23
28
  },
24
29
  {
25
- name: "apps/every-chef",
26
- description: "Cooking assistant with LLM integration",
30
+ name: "apps/chef",
31
+ description: "AI-powered cooking assistant with image analysis and recipe management",
32
+ goodFor: [
33
+ "Authenticated image upload and access (R2 storage with auth-gated retrieval)",
34
+ "AI Chat with image upload, mobile camera access, and optimistic UI updates",
35
+ "Streaming AI responses with database persistence",
36
+ "Human-in-loop AI tools (AI suggests, user approves)",
37
+ "Multi-part messages (text, images, tool invocations)",
38
+ "Drawer based mobile navigation instead of TabBar due to wanting to show list of chats nicely. Helpful for any UI which needs dynamic navigation instead of 4 or 5 TabBar items.",
39
+ ],
40
+ },
41
+ ];
42
+ const INTERNAL_PACKAGES = [
43
+ {
44
+ name: "apps/every-app-gateway",
45
+ description: "The Every App Gateway - central authentication hub that manages user accounts and hosts embedded apps",
46
+ goodFor: [
47
+ "Understanding how authentication flows work",
48
+ "How embedded apps are loaded and displayed",
49
+ "JWT token generation and validation",
50
+ "User session management",
51
+ "How the Gateway communicates with embedded apps via postMessage",
52
+ ],
53
+ },
54
+ {
55
+ name: "packages/sdk",
56
+ description: "The @every-app/sdk package - client and server utilities for building Every Apps",
27
57
  goodFor: [
28
- "LLM integration patterns",
29
- "AI-powered features",
30
- "Streaming responses",
58
+ "EmbeddedAppProvider implementation",
59
+ "Session management and authentication helpers",
60
+ "Server-side request authentication",
61
+ "Understanding how apps communicate with the Gateway",
62
+ ],
63
+ },
64
+ {
65
+ name: "packages/cli",
66
+ description: "The @every-app/cli package - command-line tool for creating and deploying Every Apps",
67
+ goodFor: [
68
+ "How app creation works (templates, scaffolding)",
69
+ "Deployment flow to Cloudflare",
70
+ "Gateway deployment and configuration",
71
+ "Database migrations and secret management",
72
+ ],
73
+ },
74
+ {
75
+ name: "packages/mcp",
76
+ description: "The @every-app/mcp package - MCP server that provides access to examples and documentation",
77
+ goodFor: [
78
+ "How this MCP server is built",
79
+ "Example of building MCP tools",
80
+ "Sparse git checkout patterns",
31
81
  ],
32
82
  },
33
83
  ];
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.`);
84
+ /**
85
+ * Recursively find all .mdx files in a directory and return their paths relative to the base
86
+ */
87
+ function findMdxFiles(dir, basePath = "") {
88
+ const results = [];
89
+ if (!fs.existsSync(dir)) {
90
+ return results;
91
+ }
92
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
93
+ for (const entry of entries) {
94
+ const fullPath = path.join(dir, entry.name);
95
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
96
+ if (entry.isDirectory()) {
97
+ results.push(...findMdxFiles(fullPath, relativePath));
98
+ }
99
+ else if (entry.isFile() && entry.name.endsWith(".mdx")) {
100
+ // Remove .mdx extension for display
101
+ results.push(relativePath.replace(/\.mdx$/, ""));
102
+ }
103
+ }
104
+ return results;
105
+ }
106
+ /**
107
+ * Get the docs directory path
108
+ */
109
+ function getDocsDirectory() {
110
+ return path.join(getExamplesDirectory(), "landing-page/src/content/docs/docs");
111
+ }
112
+ function formatEntries(entries) {
113
+ return entries
114
+ .map((entry) => `## ${entry.name}\n${entry.description}\n\n**Good for learning:**\n${entry.goodFor.map((g) => `- ${g}`).join("\n")}`)
115
+ .join("\n\n---\n\n");
116
+ }
117
+ export function registerBrowseTool(server) {
118
+ server.tool("browse", "Browse available Every App resources: example apps, internal packages, and documentation. Start here to discover what's available.", {}, async () => {
119
+ const examplesOutput = formatEntries(EXAMPLE_APPS);
120
+ const internalsOutput = formatEntries(INTERNAL_PACKAGES);
121
+ // Dynamically discover docs
122
+ const docsDir = getDocsDirectory();
123
+ const docPages = findMdxFiles(docsDir).sort();
124
+ const docsOutput = docPages.length > 0
125
+ ? `Use \`read_file\` with path \`landing-page/src/content/docs/docs/<page>.mdx\` to read any of these:\n\n${docPages.map((p) => `- ${p}`).join("\n")}`
126
+ : "Documentation not available. Try reconnecting the MCP server to trigger a fresh clone.";
127
+ return textResponse(`# Every App Resources
128
+
129
+ **Note:** Code examples are from the latest version on GitHub. The user may be on an older version of the SDK, CLI, or Gateway. If something doesn't match what they're seeing, check their package versions.
130
+
131
+ ---
132
+
133
+ # Documentation
134
+ ${docsOutput}
135
+
136
+ ---
137
+
138
+ # Example Applications
139
+ Complete example apps you can learn from. Use \`list_directory\` and \`read_file\` to explore:
140
+
141
+ ${examplesOutput}
142
+
143
+ ---
144
+
145
+ # Internal Packages
146
+ Core Every App packages - useful for understanding how Every App works:
147
+
148
+ ${internalsOutput}`);
38
149
  });
39
150
  }
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import * as fs from "node:fs";
3
3
  import { getExamplesDir, errorResponse, textResponse, validatePathWithinBase } from "../utils.js";
4
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.", {
5
+ server.tool("read_file", "Read the contents of a file from the Every App examples. Use this to see how patterns are implemented.", {
6
6
  path: z
7
7
  .string()
8
8
  .describe('Path relative to the Every App examples root (e.g., "apps/todo-app/src/routes/index.tsx")'),
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import * as fs from "node:fs";
3
3
  import { searchFiles, getExamplesDir, errorResponse, textResponse, validatePathWithinBase, } from "../utils.js";
4
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.", {
5
+ server.tool("search_code", "Search for patterns in the Every App examples using regex. Use this to find implementations of specific patterns.", {
6
6
  pattern: z.string().describe("Regular expression pattern to search for"),
7
7
  path: z
8
8
  .string()
package/dist/utils.js CHANGED
@@ -54,14 +54,32 @@ export function errorResponse(text) {
54
54
  }
55
55
  // Validate that a resolved path stays within the base directory (prevents path traversal)
56
56
  export function validatePathWithinBase(baseDir, inputPath) {
57
- const resolvedBase = path.resolve(baseDir);
58
- const resolvedPath = path.resolve(baseDir, inputPath);
57
+ let resolvedBase;
58
+ try {
59
+ resolvedBase = fs.realpathSync(baseDir);
60
+ }
61
+ catch {
62
+ return { valid: false, error: "Base directory not accessible" };
63
+ }
64
+ const resolvedPath = path.resolve(resolvedBase, inputPath);
59
65
  if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
60
66
  return {
61
67
  valid: false,
62
68
  error: "Path traversal detected - access denied",
63
69
  };
64
70
  }
71
+ if (fs.existsSync(resolvedPath)) {
72
+ let realPath;
73
+ try {
74
+ realPath = fs.realpathSync(resolvedPath);
75
+ }
76
+ catch {
77
+ return { valid: false, error: "Path not accessible" };
78
+ }
79
+ if (!realPath.startsWith(resolvedBase + path.sep) && realPath !== resolvedBase) {
80
+ return { valid: false, error: "Path resolves outside base directory" };
81
+ }
82
+ }
65
83
  return { valid: true, resolvedPath };
66
84
  }
67
85
  // Helper to create success response
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@every-app/mcp",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "MCP server for Every App coding agents",
5
5
  "type": "module",
6
6
  "bin": {