@dypai-ai/mcp 1.2.0 → 1.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dypai-ai/mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "DYPAI MCP Server — AI agent toolkit for building and deploying full-stack apps",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -80,27 +80,16 @@ const LOCAL_TOOLS = [
80
80
 
81
81
  const localToolMap = new Map(LOCAL_TOOLS.map(t => [t.name, t]))
82
82
 
83
- // ── Remote tools (loaded from MCP server at startup) ────────────────────────
84
-
85
- // Fallback definitions in case remote MCP is unreachable
86
- const FALLBACK_REMOTE_TOOLS = [
87
- { name: "list_projects", description: "List all your projects.", inputSchema: { type: "object", properties: {}, required: [] } },
88
- { name: "get_project", description: "Get project details.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: ["project_id"] } },
89
- { name: "create_project", description: "Create a new project (free plan).", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } },
90
- { name: "execute_sql", description: "Run SQL on the project's database.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, sql: { type: "string" } }, required: ["sql"] } },
91
- { name: "create_endpoint", description: "Create an API endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" }, method: { type: "string" }, workflow_code: { type: "object" } }, required: ["name", "method", "workflow_code"] } },
92
- { name: "update_endpoint", description: "Update an endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" } }, required: ["name"] } },
93
- { name: "delete_endpoint", description: "Delete an endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" } }, required: ["name"] } },
94
- { name: "manage_users", description: "Manage auth users.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string" } }, required: ["operation"] } },
95
- { name: "manage_roles", description: "Manage roles.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string" } }, required: ["operation"] } },
96
- { name: "list_buckets", description: "List storage buckets.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
97
- { name: "search_docs", description: "Search documentation.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
98
- { name: "search_workflow_templates", description: "Search workflow templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
99
- { name: "search_project_templates", description: "Search project starter templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
100
- { name: "search_nodes", description: "Search workflow nodes.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
101
- ]
102
-
103
- // Descriptions match the remote MCP server (dypai-mcp) exactly
83
+ // ── Remote tools (proxied to the remote MCP server) ────────────────────────
84
+ //
85
+ // The catalog below is the authoritative agent-facing contract. It doesn't
86
+ // auto-sync from the remote — we define it here so tool descriptions stay
87
+ // stable across remote deploys and so the git-first flow is discoverable
88
+ // even when the remote's own tool descriptions drift.
89
+ //
90
+ // If the remote is unreachable, tool CALLS will fail at invoke time (the
91
+ // proxy surfaces the error); the tool LIST stays the same so the agent
92
+ // still sees a coherent catalog.
104
93
  const REMOTE_TOOLS = [
105
94
  // ── Project ───────────────────────────────────────────────────────────────
106
95
  { name: "list_projects", description: "Lists all projects you have access to across your organizations. Returns project id, name, description, organization, subscription plan, and status. Use this as the first step to discover which projects are available, then pass project_id to other tools.", inputSchema: { type: "object", properties: { organization_id: { type: "string", description: "Optional. Filter projects by organization UUID." } }, required: [] } },
@@ -108,7 +108,19 @@ Or use "blank" for an empty starter project.`,
108
108
  template: template || "blank",
109
109
  engine_url: engineUrl,
110
110
  sdk_client: "src/lib/dypai.ts",
111
- message: `Project created at ${directory} with ${created} files.${installed ? " Dependencies installed." : " Run 'npm install' to install dependencies."} SDK client ready at src/lib/dypai.ts — import { dypai } from './lib/dypai' to use.`,
111
+ // Critical: dypai_pull without an absolute out_dir can land in $HOME
112
+ // when the MCP process cwd isn't the workspace. Tell the agent exactly
113
+ // what to pass next so the backend materializes INSIDE this project.
114
+ next_step: {
115
+ action: "materialize_backend",
116
+ tool: "dypai_pull",
117
+ args: {
118
+ project_id,
119
+ out_dir: join(directory, "dypai"),
120
+ },
121
+ why: "Use the ABSOLUTE dypai/ path inside the project you just created. A relative './dypai' can resolve to the wrong folder (e.g. $HOME) when the MCP isn't running from your workspace.",
122
+ },
123
+ message: `Project created at ${directory} with ${created} files.${installed ? " Dependencies installed." : " Run 'npm install' to install dependencies."} SDK client ready at src/lib/dypai.ts — import { dypai } from './lib/dypai' to use. Next: call dypai_pull with out_dir="${join(directory, "dypai")}" to materialize the backend inside this project.`,
112
124
  }
113
125
  },
114
126
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Workspace-aware path resolution for dypai/ folder.
3
+ *
4
+ * The MCP often runs from $HOME (default cwd when an IDE spawns it over stdio).
5
+ * A naive `resolvePath(cwd, "./dypai")` lands in `~/dypai/` — silently writing
6
+ * files where the user will never find them, OR silently reading from an empty
7
+ * folder so push reports "no_changes" and the agent thinks everything is fine.
8
+ *
9
+ * This helper centralizes the resolution + suspicious-path detection so pull
10
+ * AND push share the exact same logic. Hard-stop is the caller's responsibility.
11
+ */
12
+
13
+ import { existsSync } from "fs"
14
+ import { isAbsolute, dirname, join, resolve as resolvePath, sep, delimiter } from "path"
15
+ import { homedir } from "os"
16
+
17
+ /**
18
+ * Resolve a (possibly relative) dypai/ path. Walks several signals in order:
19
+ * 1. absolute → use as-is
20
+ * 2. env vars set by IDEs (CLAUDE_PROJECT_DIR, WORKSPACE_FOLDER_PATHS, etc.)
21
+ * 3. walk UP from cwd looking for project markers (.git, package.json, dypai/)
22
+ * 4. fall back to cwd (often $HOME — flagged as suspicious by the warning fn)
23
+ *
24
+ * Returns { path, source } so the caller can render a helpful trace.
25
+ */
26
+ export function resolveOutDir(outDir) {
27
+ if (isAbsolute(outDir)) return { path: outDir, source: "absolute" }
28
+
29
+ const envCandidates = [
30
+ ["CLAUDE_PROJECT_DIR", process.env.CLAUDE_PROJECT_DIR],
31
+ ["DYPAI_WORKSPACE_ROOT", process.env.DYPAI_WORKSPACE_ROOT],
32
+ ["WORKSPACE_FOLDER_PATHS", process.env.WORKSPACE_FOLDER_PATHS?.split(delimiter)[0]],
33
+ ["PROJECT_ROOT", process.env.PROJECT_ROOT],
34
+ ]
35
+ for (const [name, val] of envCandidates) {
36
+ if (val && isAbsolute(val)) return { path: resolvePath(val, outDir), source: `env:${name}` }
37
+ }
38
+
39
+ let cursor = process.cwd()
40
+ for (let i = 0; i < 6; i++) {
41
+ if (existsSync(join(cursor, ".git")) ||
42
+ existsSync(join(cursor, "package.json")) ||
43
+ existsSync(join(cursor, "dypai"))) {
44
+ return { path: resolvePath(cursor, outDir), source: "project_marker" }
45
+ }
46
+ const parent = dirname(cursor)
47
+ if (parent === cursor) break
48
+ cursor = parent
49
+ }
50
+
51
+ return { path: resolvePath(process.cwd(), outDir), source: "cwd_fallback" }
52
+ }
53
+
54
+ /**
55
+ * Return a warning string when the resolved path looks suspicious.
56
+ * Specifically: cwd_fallback that landed inside $HOME at depth ≤ 2.
57
+ * Returns null when the path looks fine.
58
+ */
59
+ export function suspiciousPathWarning(resolvedPath, source) {
60
+ if (source === "absolute") return null
61
+ const home = homedir()
62
+ if (source === "cwd_fallback" && resolvedPath.startsWith(home + sep)) {
63
+ const rel = resolvedPath.slice(home.length + 1)
64
+ if (!rel.includes(sep) || rel.split(sep).length <= 2) {
65
+ return (
66
+ `Output landed at ${resolvedPath}. The MCP process cwd is your home dir, not your project. ` +
67
+ `Re-run with an ABSOLUTE path (e.g. ${home}/path/to/your-project/dypai) ` +
68
+ `or set the CLAUDE_PROJECT_DIR env var on the MCP entry.`
69
+ )
70
+ }
71
+ }
72
+ return null
73
+ }
74
+
75
+ /**
76
+ * Convenience wrapper: resolve + suspicious check + structured error builder.
77
+ * Returns { ok: true, path, source } on success, or { ok: false, error } when
78
+ * the resolved path is suspicious. Pull/push pass the error response straight back.
79
+ */
80
+ export function resolveAndGuard(outDir, { project_id, tool, arg_name = "out_dir" } = {}) {
81
+ const { path, source } = resolveOutDir(outDir)
82
+ const warning = suspiciousPathWarning(path, source)
83
+ if (!warning) return { ok: true, path, source }
84
+
85
+ return {
86
+ ok: false,
87
+ error: {
88
+ success: false,
89
+ error: "Resolved path looks wrong — aborting before reading/writing any files.",
90
+ resolved_to: path,
91
+ resolved_via: source,
92
+ explanation: warning,
93
+ fix:
94
+ `Call ${tool || "this tool"} again with an ABSOLUTE ${arg_name} pointing inside your project, e.g.\n` +
95
+ ` ${tool || "<tool>"}({ ${project_id ? `project_id: "${project_id}", ` : ""}${arg_name}: "/absolute/path/to/your-project/dypai" })\n` +
96
+ `If you just ran download_template, reuse the "directory" it returned and append "/dypai".`,
97
+ },
98
+ }
99
+ }
@@ -358,6 +358,13 @@ export const dypaiPushTool = {
358
358
  errors.push({ op: "realtime", error: e.message })
359
359
  }
360
360
 
361
+ // Names of endpoints that actually changed this run — drives the follow-up
362
+ // suggestion so the agent knows what to test next.
363
+ const changedNames = [
364
+ ...applied.created,
365
+ ...applied.updated.map(u => u.name),
366
+ ]
367
+
361
368
  return {
362
369
  success: errors.length === 0,
363
370
  applied: true,
@@ -375,7 +382,7 @@ export const dypaiPushTool = {
375
382
  next_step: errors.length
376
383
  ? "Fix the offending YAMLs and push again."
377
384
  : changedNames.length
378
- ? `Test changed endpoint(s) with test_workflow: ${changedNames.slice(0, 3).join(", ")}${changedNames.length > 3 ? "…" : ""}`
385
+ ? `Test changed endpoint(s) with dypai_test_endpoint: ${changedNames.slice(0, 3).join(", ")}${changedNames.length > 3 ? "…" : ""}`
379
386
  : undefined,
380
387
  hint: errors.some(e => /credential/i.test(e.error))
381
388
  ? "A referenced credential doesn't exist remotely. Create it in the dashboard (same name), then retry."