Skip to content

Commit aa42d58

Browse files
Askirclaude
andcommitted
feat(sandbox-mcp): add crayon workflow tools and enriched instructions to sandbox server
Merges the 12 existing crayon MCP tools (listWorkflows, runWorkflow, etc.) into the sandbox server so local Claude Code has full workflow engine access. Expands MCP instructions with project CLAUDE.md, workflow development guide, and scaffold templates. Fixes cwd to /data/app so tools discover workflows correctly over SSH. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b0988a8 commit aa42d58

File tree

2 files changed

+171
-8
lines changed

2 files changed

+171
-8
lines changed

packages/core/src/cli/mcp/sandbox-server.ts

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { stdioServerFactory } from "@tigerdata/mcp-boilerplate";
2+
import { readFileSync, existsSync } from "node:fs";
23
import { version } from "./config.js";
34
import type { ServerContext } from "./types.js";
45
import { getSandboxApiFactories } from "./sandbox-tools/index.js";
@@ -10,31 +11,188 @@ const serverInfo = {
1011

1112
const context: ServerContext = {};
1213

14+
const PROJECT_ROOT = "/data/app";
15+
1316
function buildInstructions(): string {
14-
const lines = [
17+
const sections: string[] = [];
18+
19+
// ── Sandbox environment ──────────────────────────────────────
20+
const envLines = [
21+
"## Sandbox Environment",
22+
"",
1523
"You are connected to a remote cloud sandbox via MCP.",
16-
"All file and bash operations run via this MCP run on the sandbox.",
17-
"The project root is /data/app.",
24+
"All file operations, bash commands, and crayon tools run on the sandbox, not on the user's local machine.",
25+
`Project root: ${PROJECT_ROOT}`,
1826
];
1927

2028
const flyAppName = process.env.FLY_APP_NAME;
2129
if (flyAppName) {
2230
const publicUrl = `https://${flyAppName}.fly.dev`;
23-
lines.push(
24-
`The sandbox's public URL is: ${publicUrl}`,
25-
`The dev UI is at: ${publicUrl}/dev/`,
26-
"When the app's dev server is running, it is accessible at this public URL you can tell the user to look at it.",
31+
envLines.push(
32+
`Public URL: ${publicUrl}`,
33+
`Dev UI: ${publicUrl}/dev/`,
34+
"The app's dev server is accessible at the public URL above — NOT at localhost.",
2735
);
2836
}
37+
sections.push(envLines.join("\n"));
38+
39+
// ── Project CLAUDE.md (if exists) ────────────────────────────
40+
const claudeMdPath = `${PROJECT_ROOT}/CLAUDE.md`;
41+
if (existsSync(claudeMdPath)) {
42+
try {
43+
const content = readFileSync(claudeMdPath, "utf-8").trim();
44+
if (content) {
45+
sections.push(`## Project Instructions (CLAUDE.md)\n\n${content}`);
46+
}
47+
} catch {
48+
// skip if unreadable
49+
}
50+
}
51+
52+
// ── Crayon workflow engine context ───────────────────────────
53+
sections.push(CRAYON_CONTEXT);
2954

30-
return lines.join("\n");
55+
return sections.join("\n\n---\n\n");
3156
}
3257

58+
const CRAYON_CONTEXT = `## Crayon Workflow Engine
59+
60+
You have access to crayon MCP tools for building and running AI-native workflows.
61+
62+
### Available MCP Tools
63+
64+
**Workflow execution:**
65+
- \`list_workflows\` — discover compiled workflows
66+
- \`run_workflow\` — execute a workflow with JSON input
67+
- \`run_node\` — execute a single node (for testing)
68+
69+
**Run history:**
70+
- \`list_runs\` — list past executions
71+
- \`get_run\` — get details of a specific run
72+
- \`get_trace\` — full execution trace with step-by-step output
73+
74+
**Integrations:**
75+
- \`list_integrations\` — list available OAuth integrations (Salesforce, Slack, etc.)
76+
- \`get_connection_info\` — get credentials for a specific integration + node
77+
78+
**Scaffolding:**
79+
- \`create_app\` — scaffold a new crayon project
80+
- \`create_database\` — set up a database
81+
- \`setup_app_schema\` — initialize crayon tables in existing DB
82+
83+
### Workflow Development Pipeline
84+
85+
1. **Design** — create \`src/crayon/workflows/<name>.ts\` with a \`description\` field that captures the flow (task ordering, conditions, loops)
86+
2. **Create stubs** — for each task, create node stubs in \`src/crayon/nodes/<name>.ts\` or agent stubs in \`src/crayon/agents/<name>.ts\` + \`<name>.md\`
87+
3. **Refine** — add typed Zod schemas, tools, implementation details to each node/agent
88+
4. **Compile** — update the workflow's \`run()\` method from embedded descriptions
89+
90+
### Key Patterns
91+
92+
- **Description-driven:** The \`description\` field in workflows and nodes is the source of truth. It drives code generation.
93+
- **Node types:** \`Node.create()\` for deterministic functions, \`Agent.create()\` for AI reasoning (uses Vercel AI SDK)
94+
- **Agent specs:** Each agent has a colocated \`.md\` file with system prompt, guidelines, and output format
95+
- **Integrations:** Declare in \`integrations: ["salesforce", "openai"]\` array. Credentials fetched at runtime via \`ctx.getConnection()\`.
96+
- **Draft first, ask later:** Make your best guess and let the user correct, rather than interrogating upfront.
97+
- **Run it yourself:** When the user wants to test, use \`run_workflow\` / \`run_node\` tools directly.
98+
99+
### File Locations
100+
101+
| Type | Location |
102+
|------|----------|
103+
| Workflows | \`src/crayon/workflows/*.ts\` |
104+
| Nodes | \`src/crayon/nodes/*.ts\` |
105+
| Agents | \`src/crayon/agents/*.ts\` + \`*.md\` (colocated spec) |
106+
| Agent tools | \`src/crayon/tools/*.ts\` |
107+
| Integrations | \`src/crayon/integrations/*.ts\` |
108+
109+
### Workflow Scaffold Template
110+
111+
\`\`\`typescript
112+
import { z } from "zod";
113+
import { Workflow } from "runcrayon";
114+
115+
const InputSchema = z.object({ /* ... */ });
116+
type Input = z.infer<typeof InputSchema>;
117+
const OutputSchema = z.object({ /* ... */ });
118+
type Output = z.infer<typeof OutputSchema>;
119+
120+
export const myWorkflow = Workflow.create({
121+
name: "my-workflow",
122+
version: 1,
123+
description: \\\`
124+
Summary of what the workflow does.
125+
126+
## Tasks
127+
128+
### 1. Task Name
129+
**Node:** \\\\\\\`node-name\\\\\\\` (node|agent)
130+
\\\`,
131+
inputSchema: InputSchema,
132+
outputSchema: OutputSchema,
133+
async run(ctx, inputs: Input): Promise<Output> {
134+
const result = await ctx.run(myNode, { /* inputs */ });
135+
return { /* outputs */ };
136+
},
137+
});
138+
\`\`\`
139+
140+
### Node Stub Template
141+
142+
\`\`\`typescript
143+
import { z } from "zod";
144+
import { Node } from "runcrayon";
145+
146+
export const myNode = Node.create({
147+
name: "my-node",
148+
description: \\\`What this node does.
149+
**Input Description:** what it needs
150+
**Output Description:** what it produces\\\`,
151+
inputSchema: z.object({}),
152+
outputSchema: z.object({}),
153+
async execute(ctx, input) {
154+
return {};
155+
},
156+
});
157+
\`\`\`
158+
159+
### Agent Stub Template
160+
161+
\`\`\`typescript
162+
import { z } from "zod";
163+
import { Agent } from "runcrayon";
164+
import { fileURLToPath } from "url";
165+
import path from "path";
166+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
167+
168+
export const myAgent = Agent.create({
169+
name: "my-agent",
170+
integrations: ["openai"],
171+
description: \\\`What this agent does.
172+
**Input Description:** what it needs
173+
**Output Description:** what it produces\\\`,
174+
inputSchema: z.object({}),
175+
outputSchema: z.object({}),
176+
tools: {},
177+
specPath: path.resolve(__dirname, "./my-agent.md"),
178+
});
179+
\`\`\`
180+
181+
### Integration Gate
182+
183+
Before implementing nodes that use external services, call \`get_connection_info\` to verify the connection exists. If it fails, tell the user to set up the connection in the Dev UI first.`;
184+
33185
/**
34186
* Start the sandbox MCP server in stdio mode.
35187
* Exposes filesystem and bash tools for remote sandbox access.
36188
*/
37189
export async function startSandboxMcpServer(): Promise<void> {
190+
// Ensure cwd is the project root so crayon tools (listWorkflows, runWorkflow, etc.)
191+
// discover workflows and load .env correctly — SSH sessions may start in $HOME instead.
192+
if (existsSync(PROJECT_ROOT)) {
193+
process.chdir(PROJECT_ROOT);
194+
}
195+
38196
const apiFactories = await getSandboxApiFactories();
39197

40198
await stdioServerFactory({

packages/core/src/cli/mcp/sandbox-tools/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ import { writeFileFactory } from "./writeFile.js";
33
import { editFileFactory } from "./editFile.js";
44
import { listDirectoryFactory } from "./listDirectory.js";
55
import { bashFactory } from "./bash.js";
6+
import { getApiFactories } from "../tools/index.js";
67

78
export async function getSandboxApiFactories() {
9+
const crayonTools = await getApiFactories();
810
return [
11+
// Sandbox filesystem + bash tools
912
readFileFactory,
1013
writeFileFactory,
1114
editFileFactory,
1215
listDirectoryFactory,
1316
bashFactory,
17+
// Crayon workflow engine tools
18+
...crayonTools,
1419
] as const;
1520
}

0 commit comments

Comments
 (0)