@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 +10 -39
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +36 -37
- package/dist/tools/fetch-docs.js +63 -42
- package/dist/tools/find-files.js +1 -1
- package/dist/tools/index.js +2 -4
- package/dist/tools/list-directory.js +1 -1
- package/dist/tools/list-examples.d.ts +1 -1
- package/dist/tools/list-examples.js +123 -12
- package/dist/tools/read-file.js +1 -1
- package/dist/tools/search-code.js +1 -1
- package/dist/utils.js +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
33
|
+
// Check if examples already exist
|
|
35
34
|
if (fs.existsSync(examplesDir)) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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:
|
|
68
|
+
success: false,
|
|
64
69
|
dir: examplesDir,
|
|
65
|
-
message: "
|
|
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
|
-
|
|
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,
|
package/dist/tools/fetch-docs.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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("
|
|
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
|
-
.
|
|
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}
|
|
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
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
}
|
package/dist/tools/find-files.js
CHANGED
|
@@ -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("
|
|
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")'),
|
package/dist/tools/index.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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("
|
|
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
|
|
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
|
-
"
|
|
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/
|
|
26
|
-
description: "
|
|
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
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
}
|
package/dist/tools/read-file.js
CHANGED
|
@@ -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("
|
|
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("
|
|
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
|
-
|
|
58
|
-
|
|
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
|