@rtrentjones/greenlight 0.2.4
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/LICENSE +21 -0
- package/assets/skills/deploy-verify-promote/SKILL.md +53 -0
- package/assets/skills/provider-cloudflare/SKILL.md +42 -0
- package/assets/skills/provider-github/SKILL.md +35 -0
- package/assets/skills/provider-hcp/SKILL.md +46 -0
- package/assets/skills/provider-oci/SKILL.md +58 -0
- package/assets/skills/provider-supabase/SKILL.md +40 -0
- package/assets/skills/provider-vercel/SKILL.md +39 -0
- package/dist/agent-web-I4LXW4SR.js +7 -0
- package/dist/bin.js +1951 -0
- package/dist/chunk-6N7MD6FR.js +75 -0
- package/dist/chunk-KFKYLGFX.js +271 -0
- package/dist/chunk-KP3Y6WRU.js +45 -0
- package/dist/chunk-QFKE5JKC.js +12 -0
- package/dist/chunk-UXHHLEYO.js +231 -0
- package/dist/chunk-WFZTRXBF.js +61 -0
- package/dist/chunk-XBDQJVAX.js +94 -0
- package/dist/eval-LLQPOEQX.js +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/mcp-KU7WKB5K.js +7 -0
- package/dist/playwright-CGTTHGIL.js +7 -0
- package/dist/test-7GMOU7I5.js +7 -0
- package/package.json +51 -0
- package/templates/_template-astro/README.md +18 -0
- package/templates/_template-astro/astro.config.mjs +9 -0
- package/templates/_template-astro/package.json +18 -0
- package/templates/_template-astro/src/pages/index.astro +18 -0
- package/templates/_template-astro/tsconfig.json +5 -0
- package/templates/_template-astro/wrangler.jsonc +12 -0
- package/templates/_template-mcp/README.md +28 -0
- package/templates/_template-mcp/oci/Dockerfile +11 -0
- package/templates/_template-mcp/oci/package.json +12 -0
- package/templates/_template-mcp/oci/server.ts +80 -0
- package/templates/_template-mcp/workers/README.md +32 -0
- package/templates/_template-next/README.md +5 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
msg,
|
|
3
|
+
report
|
|
4
|
+
} from "./chunk-QFKE5JKC.js";
|
|
5
|
+
|
|
6
|
+
// ../packages/verify/src/mcp.ts
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
|
+
async function verifyMcp(baseUrl, spec) {
|
|
10
|
+
const checks = [];
|
|
11
|
+
const client = new Client({ name: "greenlight-verify", version: "0.0.0" });
|
|
12
|
+
const transport = new StreamableHTTPClientTransport(new URL(baseUrl));
|
|
13
|
+
try {
|
|
14
|
+
await client.connect(transport);
|
|
15
|
+
checks.push({ name: "initialize handshake", pass: true });
|
|
16
|
+
} catch (e) {
|
|
17
|
+
checks.push({ name: "initialize handshake", pass: false, detail: msg(e) });
|
|
18
|
+
return report("mcp", baseUrl, checks);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const { tools } = await client.listTools();
|
|
22
|
+
const names = tools.map((t) => t.name);
|
|
23
|
+
checks.push({ name: `tools/list responded (${names.length} tools)`, pass: true });
|
|
24
|
+
for (const t of spec.expectTools) {
|
|
25
|
+
const has = names.includes(t);
|
|
26
|
+
checks.push({
|
|
27
|
+
name: `tools/list includes "${t}"`,
|
|
28
|
+
pass: has,
|
|
29
|
+
detail: has ? void 0 : `got [${names.join(", ")}]`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
checks.push({ name: "tools/list", pass: false, detail: msg(e) });
|
|
34
|
+
}
|
|
35
|
+
if (spec.call) {
|
|
36
|
+
const label = `tools/call ${spec.call.name}`;
|
|
37
|
+
try {
|
|
38
|
+
const res = await client.callTool({
|
|
39
|
+
name: spec.call.name,
|
|
40
|
+
arguments: spec.call.args ?? {}
|
|
41
|
+
});
|
|
42
|
+
const reasons = [];
|
|
43
|
+
if (res.isError) reasons.push("result.isError = true");
|
|
44
|
+
for (const k of spec.call.expectKeys ?? []) {
|
|
45
|
+
if (!res.structuredContent || !(k in res.structuredContent)) {
|
|
46
|
+
reasons.push(`structuredContent missing "${k}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
checks.push({
|
|
50
|
+
name: label,
|
|
51
|
+
pass: reasons.length === 0,
|
|
52
|
+
detail: reasons.join("; ") || void 0
|
|
53
|
+
});
|
|
54
|
+
} catch (e) {
|
|
55
|
+
checks.push({ name: label, pass: false, detail: msg(e) });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await client.close();
|
|
59
|
+
if (spec.requireAuthRejection) checks.push(await checkAuthRejection(baseUrl));
|
|
60
|
+
return report("mcp", baseUrl, checks);
|
|
61
|
+
}
|
|
62
|
+
async function checkAuthRejection(baseUrl) {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(baseUrl, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"content-type": "application/json",
|
|
68
|
+
accept: "application/json, text/event-stream"
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
jsonrpc: "2.0",
|
|
72
|
+
id: 1,
|
|
73
|
+
method: "initialize",
|
|
74
|
+
params: {
|
|
75
|
+
protocolVersion: "2025-06-18",
|
|
76
|
+
capabilities: {},
|
|
77
|
+
clientInfo: { name: "greenlight-verify-probe", version: "0.0.0" }
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
const rejected = res.status === 401 || res.status === 403;
|
|
82
|
+
return {
|
|
83
|
+
name: "unauthenticated request rejected",
|
|
84
|
+
pass: rejected,
|
|
85
|
+
detail: rejected ? void 0 : `expected 401/403, got ${res.status}`
|
|
86
|
+
};
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { name: "unauthenticated request rejected", pass: false, detail: msg(e) };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
verifyMcp
|
|
94
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineConfig,
|
|
3
|
+
defineVerify,
|
|
4
|
+
loadConfig
|
|
5
|
+
} from "./chunk-KFKYLGFX.js";
|
|
6
|
+
import "./chunk-XBDQJVAX.js";
|
|
7
|
+
import "./chunk-WFZTRXBF.js";
|
|
8
|
+
import "./chunk-KP3Y6WRU.js";
|
|
9
|
+
import "./chunk-UXHHLEYO.js";
|
|
10
|
+
import "./chunk-6N7MD6FR.js";
|
|
11
|
+
import "./chunk-QFKE5JKC.js";
|
|
12
|
+
export {
|
|
13
|
+
defineConfig,
|
|
14
|
+
defineVerify,
|
|
15
|
+
loadConfig
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rtrentjones/greenlight",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "Greenlight CLI — setup and lifecycle for the harness.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/RTrentJones/greenlight.git",
|
|
9
|
+
"directory": "cli"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"bin": {
|
|
14
|
+
"greenlight": "./dist/bin.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"templates",
|
|
19
|
+
"assets"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
26
|
+
"jiti": "^2.4.2",
|
|
27
|
+
"zod": "^3.24.1"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"playwright": "^1.49.0",
|
|
31
|
+
"@anthropic-ai/sdk": "^0.69.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@rtrentjones/greenlight-adapters": "0.2.4",
|
|
35
|
+
"@rtrentjones/greenlight-loop": "0.2.4",
|
|
36
|
+
"@rtrentjones/greenlight-shared": "0.2.4",
|
|
37
|
+
"@rtrentjones/greenlight-verify": "0.2.4"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "node scripts/copy-assets.mjs && tsup",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
42
|
+
"dev": "tsx src/bin.ts"
|
|
43
|
+
},
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./dist/index.d.ts",
|
|
48
|
+
"import": "./dist/index.js"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# `_template-astro`
|
|
2
|
+
|
|
3
|
+
Lane template for **Astro on Cloudflare Workers** (Static Assets) — verify mode `api`
|
|
4
|
+
(+ light `playwright`). Materialized into a tool by `greenlight add <name> --lane astro --target workers`.
|
|
5
|
+
|
|
6
|
+
A complete, copy-ready minimal site: a homepage, `@astrojs/sitemap`, a `wrangler.jsonc`
|
|
7
|
+
for Workers Static Assets, and `astro/tsconfigs/strict`. `add` rewrites `package.json`'s
|
|
8
|
+
`name` to your tool name. `site` comes from `SITE_URL` (default `example.dev`).
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
greenlight add marketing --lane astro --target workers
|
|
12
|
+
pnpm --filter marketing build && pnpm --filter marketing preview
|
|
13
|
+
greenlight verify marketing --url http://localhost:4321
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The default astro verify spec is a generic web smoke (homepage 200 + no broken internal
|
|
17
|
+
links). For a content site that also has a feed/sitemap (like the blog), add a
|
|
18
|
+
`verify.config.ts` asserting `rssValid` / `sitemapValid` — see `apps/blog/verify.config.ts`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import sitemap from '@astrojs/sitemap';
|
|
2
|
+
import { defineConfig } from 'astro/config';
|
|
3
|
+
|
|
4
|
+
// `site` is injected from the manifest domain at build time (SITE_URL); the default
|
|
5
|
+
// keeps the template generic — no real domain (seam rule 15.2.1).
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
site: process.env.SITE_URL ?? 'https://example.dev',
|
|
8
|
+
integrations: [sitemap()],
|
|
9
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "greenlight-template-astro",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "astro dev",
|
|
8
|
+
"build": "astro build",
|
|
9
|
+
"preview": "astro preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@astrojs/sitemap": "^3.2.1",
|
|
13
|
+
"astro": "^5.7.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"wrangler": "^4.20.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
const title = 'New Astro tool';
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!doctype html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10
|
+
<title>{title}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<main>
|
|
14
|
+
<h1>{title}</h1>
|
|
15
|
+
<p>Scaffolded by <code>greenlight add <name> --lane astro --target workers</code>. Edit <code>src/pages/index.astro</code> and ship it through the loop.</p>
|
|
16
|
+
</main>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Astro static site on Cloudflare Workers Static Assets. `greenlight add` rewrites
|
|
3
|
+
// package.json name; set the worker name to match your tool. Routes/custom domains
|
|
4
|
+
// are managed by Terraform (Phase 5) — names stay generic (seam rule 15.2.1).
|
|
5
|
+
"name": "astro-tool",
|
|
6
|
+
"compatibility_date": "2025-06-01",
|
|
7
|
+
"assets": { "directory": "./dist" },
|
|
8
|
+
"env": {
|
|
9
|
+
"beta": { "name": "astro-tool-beta" },
|
|
10
|
+
"prod": { "name": "astro-tool" }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# `_template-mcp`
|
|
2
|
+
|
|
3
|
+
Lane template for **MCP servers** — verify mode `mcp` (initialize → `tools/list` → call → auth assertion). Materialized into a tool by `greenlight add`. Two target shapes:
|
|
4
|
+
|
|
5
|
+
## `oci` — Node streamable-HTTP server (recommended; the BAMCP shape)
|
|
6
|
+
|
|
7
|
+
A plain Node HTTP server using `@modelcontextprotocol/sdk` (`StreamableHTTPServerTransport`), containerized and run behind a Cloudflare Tunnel in prod. Best for stateful servers or ones needing local binaries/filesystem (e.g. samtools). See [oci/server.ts](oci/server.ts) + [oci/Dockerfile](oci/Dockerfile). This is the reference implementation — `tools/ping-mcp` is an instance of it.
|
|
8
|
+
|
|
9
|
+
Local dev / loop proof (no cloud):
|
|
10
|
+
```
|
|
11
|
+
PORT=8787 node oci/server.ts # or `pnpm --filter <pkg> start`
|
|
12
|
+
greenlight verify <name> --url http://127.0.0.1:8787/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## `workers` — remote MCP on the edge (optional)
|
|
16
|
+
|
|
17
|
+
Cloudflare's `agents` package (`McpAgent` / `createMcpHandler`) can host a remote MCP on Workers. **Caveat:** `agents` pulls heavy transitive deps (`ai`, `react`) and currently needs an `ai` alias in `wrangler` config to bundle. For a simple server, prefer the `oci`/Node shape above; reach for `workers` only when you specifically want edge hosting + Durable-Object session state.
|
|
18
|
+
|
|
19
|
+
## Verify spec
|
|
20
|
+
|
|
21
|
+
Ship a `verify.config.ts` (default export) so `greenlight verify` asserts the real contract:
|
|
22
|
+
```ts
|
|
23
|
+
export default { mode: 'mcp', expectTools: ['<tool>'], call: { name: '<tool>' } };
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Auth
|
|
27
|
+
|
|
28
|
+
`auth: none` only for public read-only servers. Mutating/private servers default to `bearer`/`oauth` (greenlight-v1.md §6/§14).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# MCP server (oci lane). Node 24 strips TypeScript types natively, so server.ts runs directly.
|
|
2
|
+
FROM node:24-slim
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json ./
|
|
6
|
+
RUN npm install --omit=dev
|
|
7
|
+
|
|
8
|
+
COPY . .
|
|
9
|
+
ENV PORT=8787
|
|
10
|
+
EXPOSE 8787
|
|
11
|
+
CMD ["node", "src/server.ts"]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
|
|
7
|
+
// MCP server template (oci lane): Node streamable-HTTP, containerized, run behind a
|
|
8
|
+
// Cloudflare Tunnel in prod. Rename `my-mcp` and add your tools.
|
|
9
|
+
const PORT = Number(process.env.PORT ?? 8787);
|
|
10
|
+
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
|
11
|
+
|
|
12
|
+
function buildMcpServer(): McpServer {
|
|
13
|
+
const server = new McpServer({ name: 'my-mcp', version: '0.0.0' });
|
|
14
|
+
server.registerTool(
|
|
15
|
+
'echo',
|
|
16
|
+
{ description: 'Echo the input text.', inputSchema: {} },
|
|
17
|
+
async () => ({ content: [{ type: 'text', text: 'hello from my-mcp' }] }),
|
|
18
|
+
);
|
|
19
|
+
return server;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readJson(req: http.IncomingMessage): Promise<unknown> {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
let data = '';
|
|
25
|
+
req.on('data', (c) => {
|
|
26
|
+
data += c;
|
|
27
|
+
});
|
|
28
|
+
req.on('end', () => {
|
|
29
|
+
try {
|
|
30
|
+
resolve(data ? JSON.parse(data) : undefined);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
reject(e);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
req.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
40
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
41
|
+
if (url.pathname !== '/mcp') {
|
|
42
|
+
res.writeHead(404).end('connect at /mcp');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
46
|
+
if (req.method === 'POST') {
|
|
47
|
+
const body = await readJson(req);
|
|
48
|
+
let transport = sessionId ? transports[sessionId] : undefined;
|
|
49
|
+
if (!transport && isInitializeRequest(body)) {
|
|
50
|
+
transport = new StreamableHTTPServerTransport({
|
|
51
|
+
sessionIdGenerator: () => randomUUID(),
|
|
52
|
+
onsessioninitialized: (sid) => {
|
|
53
|
+
if (transport) transports[sid] = transport;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
await buildMcpServer().connect(transport);
|
|
57
|
+
}
|
|
58
|
+
if (!transport) {
|
|
59
|
+
res.writeHead(400).end('no session');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await transport.handleRequest(req, res, body);
|
|
63
|
+
} else if (
|
|
64
|
+
(req.method === 'GET' || req.method === 'DELETE') &&
|
|
65
|
+
sessionId &&
|
|
66
|
+
transports[sessionId]
|
|
67
|
+
) {
|
|
68
|
+
await transports[sessionId].handleRequest(req, res);
|
|
69
|
+
} else {
|
|
70
|
+
res.writeHead(405).end();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
http
|
|
75
|
+
.createServer((req, res) => {
|
|
76
|
+
void handle(req, res);
|
|
77
|
+
})
|
|
78
|
+
.listen(PORT, () => {
|
|
79
|
+
console.log(`my-mcp listening on :${PORT}/mcp`);
|
|
80
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `_template-mcp/workers` (optional shape)
|
|
2
|
+
|
|
3
|
+
Remote MCP on Cloudflare Workers via Cloudflare's [`agents`](https://www.npmjs.com/package/agents) package (`McpAgent` for Durable-Object session state, or `createMcpHandler` for a stateless fetch handler).
|
|
4
|
+
|
|
5
|
+
**Status / caveat (Phase 4):** `agents` pulls heavy transitive deps (`ai`, `react`) and its bundle does a dynamic `import("ai")` that `wrangler` fails to resolve without an `alias` entry — e.g. in `wrangler.jsonc`:
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
{ "alias": { "ai": "./src/ai-stub.ts" } }
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For a simple server this overhead isn't worth it — use the [`../oci`](../oci) Node shape, which proves the same protocol loop locally (`greenlight verify <name> --url http://127.0.0.1:8787/mcp`) with no exotic deps. Reach for `workers` only when you specifically need edge hosting + DO-backed sessions.
|
|
12
|
+
|
|
13
|
+
Sketch (stateless handler):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
17
|
+
import { createMcpHandler } from 'agents/mcp';
|
|
18
|
+
|
|
19
|
+
const server = new McpServer({ name: 'my-mcp', version: '0.0.0' });
|
|
20
|
+
server.registerTool('ping', { description: 'ping', inputSchema: {} }, async () => ({
|
|
21
|
+
content: [{ type: 'text', text: 'pong' }],
|
|
22
|
+
}));
|
|
23
|
+
const mcp = createMcpHandler(server);
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
fetch(request: Request, env: unknown, ctx: ExecutionContext) {
|
|
27
|
+
return new URL(request.url).pathname === '/mcp'
|
|
28
|
+
? mcp(request, env, ctx)
|
|
29
|
+
: new Response('connect at /mcp', { status: 404 });
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# `_template-next` (placeholder)
|
|
2
|
+
|
|
3
|
+
Lane template for **Next.js on Vercel** with Supabase — verify mode `api + playwright`.
|
|
4
|
+
|
|
5
|
+
> **Phase 0:** placeholder only. Real template content arrives when the `next` lane is exercised (HeistMind migration, **Phase 9** — greenlight-v1.md §16). Materialized into a tool by `greenlight add` / `greenlight adopt`.
|