@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 +102 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +33 -0
- package/dist/resources/index.d.ts +2 -0
- package/dist/resources/index.js +4 -0
- package/dist/resources/prompts.d.ts +2 -0
- package/dist/resources/prompts.js +87 -0
- package/dist/setup.d.ts +13 -0
- package/dist/setup.js +113 -0
- package/dist/tools/fetch-docs.d.ts +2 -0
- package/dist/tools/fetch-docs.js +58 -0
- package/dist/tools/find-files.d.ts +2 -0
- package/dist/tools/find-files.js +51 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/list-directory.d.ts +2 -0
- package/dist/tools/list-directory.js +56 -0
- package/dist/tools/list-examples.d.ts +2 -0
- package/dist/tools/list-examples.js +39 -0
- package/dist/tools/read-file.d.ts +2 -0
- package/dist/tools/read-file.js +46 -0
- package/dist/tools/search-code.d.ts +2 -0
- package/dist/tools/search-code.js +66 -0
- package/dist/utils.d.ts +38 -0
- package/dist/utils.js +168 -0
- package/package.json +31 -0
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)
|
package/dist/index.d.ts
ADDED
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,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
|
+
}
|
package/dist/setup.d.ts
ADDED
|
@@ -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,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,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,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,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,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,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,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
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -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
|
+
}
|