@guuey/create-agentic-app 0.1.1 → 0.1.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/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/templates/claude-agent-sdk/package.json +3 -3
- package/dist/templates/claude-agent-sdk/web/package.json +1 -1
- package/dist/templates/google-adk/.env.example +4 -0
- package/dist/templates/google-adk/.mcp.json +1 -0
- package/dist/templates/google-adk/README.md +74 -0
- package/dist/templates/google-adk/ggui/blueprints/.gitkeep +0 -0
- package/dist/templates/google-adk/ggui/ggui.json +15 -0
- package/dist/templates/google-adk/ggui/themes/.gitkeep +0 -0
- package/dist/templates/google-adk/guuey.json +14 -0
- package/dist/templates/google-adk/mcps/todo/Dockerfile +46 -0
- package/dist/templates/google-adk/mcps/todo/package.json +22 -0
- package/dist/templates/google-adk/mcps/todo/src/server.ts +173 -0
- package/dist/templates/google-adk/mcps/todo/tsconfig.json +15 -0
- package/dist/templates/google-adk/package.json +22 -0
- package/dist/templates/google-adk/pnpm-workspace.yaml +34 -0
- package/dist/templates/google-adk/prompts/system.md +4 -0
- package/dist/templates/google-adk/scripts/dev.mjs +44 -0
- package/dist/templates/google-adk/src/agent.ts +39 -0
- package/dist/templates/google-adk/tsconfig.json +15 -0
- package/dist/templates/google-adk/tsup.config.ts +13 -0
- package/dist/templates/google-adk/web/.env.example +17 -0
- package/dist/templates/google-adk/web/index.html +13 -0
- package/dist/templates/google-adk/web/package.json +25 -0
- package/dist/templates/google-adk/web/sandbox-proxy.ts +239 -0
- package/dist/templates/google-adk/web/src/App.tsx +272 -0
- package/dist/templates/google-adk/web/src/main.tsx +12 -0
- package/dist/templates/google-adk/web/src/useAgentChat.ts +122 -0
- package/dist/templates/google-adk/web/src/vite-env.d.ts +27 -0
- package/dist/templates/google-adk/web/tsconfig.json +20 -0
- package/dist/templates/google-adk/web/vite.config.ts +12 -0
- package/dist/templates/openai-agents-sdk/package.json +3 -3
- package/dist/templates/openai-agents-sdk/web/package.json +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -171,7 +171,7 @@ var packageRoot2 = (0, import_node_path2.join)(here2, "..");
|
|
|
171
171
|
var defaultTemplatesDir2 = (0, import_node_path2.join)(packageRoot2, "dist", "templates");
|
|
172
172
|
|
|
173
173
|
// src/cli.ts
|
|
174
|
-
var FRAMEWORKS = ["claude-agent-sdk", "openai-agents-sdk"];
|
|
174
|
+
var FRAMEWORKS = ["claude-agent-sdk", "openai-agents-sdk", "google-adk"];
|
|
175
175
|
function isFramework(value) {
|
|
176
176
|
return FRAMEWORKS.includes(value);
|
|
177
177
|
}
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { createInterface } from "readline/promises";
|
|
8
8
|
import { stdin, stdout } from "process";
|
|
9
|
-
var FRAMEWORKS = ["claude-agent-sdk", "openai-agents-sdk"];
|
|
9
|
+
var FRAMEWORKS = ["claude-agent-sdk", "openai-agents-sdk", "google-adk"];
|
|
10
10
|
function isFramework(value) {
|
|
11
11
|
return FRAMEWORKS.includes(value);
|
|
12
12
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Frameworks the scaffolder ships templates for. */
|
|
2
|
-
type Framework = 'claude-agent-sdk' | 'openai-agents-sdk';
|
|
2
|
+
type Framework = 'claude-agent-sdk' | 'openai-agents-sdk' | 'google-adk';
|
|
3
3
|
interface ScaffoldOptions {
|
|
4
4
|
/** Absolute or cwd-relative path to create/populate the new project in. */
|
|
5
5
|
targetDir: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Frameworks the scaffolder ships templates for. */
|
|
2
|
-
type Framework = 'claude-agent-sdk' | 'openai-agents-sdk';
|
|
2
|
+
type Framework = 'claude-agent-sdk' | 'openai-agents-sdk' | 'google-adk';
|
|
3
3
|
interface ScaffoldOptions {
|
|
4
4
|
/** Absolute or cwd-relative path to create/populate the new project in. */
|
|
5
5
|
targetDir: string;
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/claude-agent-sdk": "^0.3.199",
|
|
13
|
-
"@guuey/config": "0.
|
|
14
|
-
"@guuey/worker": "0.
|
|
13
|
+
"@guuey/config": "0.1.1",
|
|
14
|
+
"@guuey/worker": "0.1.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@guuey/cli": "0.
|
|
17
|
+
"@guuey/cli": "0.1.2",
|
|
18
18
|
"tsup": "^8.5.0",
|
|
19
19
|
"tsx": "^4.19.0",
|
|
20
20
|
"typescript": "^5.8.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "mcpServers": { "ggui-dev": { "type": "http", "url": "https://mcp.ggui.ai/dev" } } }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Your Google-ADK agent on Guuey
|
|
2
|
+
|
|
3
|
+
You write **pure [Google ADK](https://google.github.io/adk-docs/) code** —
|
|
4
|
+
`src/agent.ts` exports your agent, and Guuey runs it. There is no Guuey SDK
|
|
5
|
+
to learn, no worker loop, no event plumbing: the platform injects the
|
|
6
|
+
harness around your export, locally (`pnpm dev`) and in production
|
|
7
|
+
(`guuey deploy`) identically.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import type { GuueyContext } from "@guuey/config";
|
|
11
|
+
|
|
12
|
+
export default (guuey: GuueyContext<MCPToolset>) =>
|
|
13
|
+
new LlmAgent({
|
|
14
|
+
model: guuey.model,
|
|
15
|
+
instruction: guuey.instruction,
|
|
16
|
+
tools: [myTool, ...guuey.mcpToolsets],
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm install
|
|
24
|
+
cp .env.example .env.local # set GEMINI_API_KEY
|
|
25
|
+
pnpm dev # local stack: your agent + the todo MCP + chat
|
|
26
|
+
guuey deploy # same code, hosted
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## The GuueyContext (everything the platform hands you)
|
|
30
|
+
|
|
31
|
+
Your factory runs **once per turn** and receives:
|
|
32
|
+
|
|
33
|
+
| field | what it is |
|
|
34
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `model` | resolved model id (from `guuey.json`, registry default otherwise) |
|
|
36
|
+
| `instruction` | your system prompt **with the conversation preamble already prepended** — feed it to the agent and it is conversational with zero state code |
|
|
37
|
+
| `mcpToolsets` | ready-to-use ADK `MCPToolset`s for every server in `guuey.json#mcpServers` — credentials, URLs, and auth headers already resolved by the platform |
|
|
38
|
+
| `user` | the end user this turn serves: `{ id, authMode }` — build multi-tenant behavior on `user.id` |
|
|
39
|
+
| `files` | three storage tiers (absolute paths — see the state map below) |
|
|
40
|
+
| `history` / `memory` / `workingState` | the raw conversation state, if you want to render context yourself instead of using `instruction` |
|
|
41
|
+
|
|
42
|
+
## Where state lives (the three-tier map)
|
|
43
|
+
|
|
44
|
+
| you want to… | use | persistence |
|
|
45
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| save/read **files** from agent code or tools | `guuey.files.home` (per-USER durable) · `guuey.files.session` (per-session scratch) · `guuey.files.app` (read-only app assets) | `home` survives across sessions per user |
|
|
47
|
+
| have the agent **remember the conversation** | nothing — `instruction` already carries history + thread memory + working state | automatic (Guuey folds every turn) |
|
|
48
|
+
| store data from your **MCP server's tools** (e.g. the todo list) | `@guuey/state` KV inside the MCP server — scoped per `(user, server)` | **in-memory in the current release** (durable managed KV + console export/delete is on the roadmap) — persist anything you can't lose to `guuey.files.home` for now |
|
|
49
|
+
|
|
50
|
+
Two honest notes:
|
|
51
|
+
|
|
52
|
+
- **ADK-native session state does not persist across turns here.** Guuey
|
|
53
|
+
runs your agent with a fresh `InMemoryRunner` per turn (that's what makes
|
|
54
|
+
turns cheap and stateless); `session.state` / `outputKey` /
|
|
55
|
+
`DatabaseSessionService` won't carry data forward. Use the table above —
|
|
56
|
+
history and thread memory ARE the persistence, and they arrive
|
|
57
|
+
pre-rendered in `instruction`.
|
|
58
|
+
- **Writing `workingState` for the next turn** is a platform feature in
|
|
59
|
+
flight; today the fold carries what the conversation itself establishes.
|
|
60
|
+
|
|
61
|
+
## MCP servers
|
|
62
|
+
|
|
63
|
+
`guuey.json#mcpServers` declares them; the platform connects them and hands
|
|
64
|
+
you `guuey.mcpToolsets`. Locally, `pnpm dev` boots the colocated `mcps/todo`
|
|
65
|
+
server on its `devPort`. Note: ADK speaks **Streamable HTTP** only — an
|
|
66
|
+
`sse` transport server will be rejected with a clear error.
|
|
67
|
+
|
|
68
|
+
## When you outgrow the factory
|
|
69
|
+
|
|
70
|
+
If you need to own the turn loop itself (custom streaming, multiple agents
|
|
71
|
+
per turn, your own protocol), graduate to a full worker: add a
|
|
72
|
+
`guuey.worker.js` build via `@guuey/worker`'s `serveNative()` — see the
|
|
73
|
+
Guuey docs. `agent.entry` and a full worker are mutually exclusive; the
|
|
74
|
+
worker wins if both exist.
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "1",
|
|
3
|
+
"protocol": "draft-2026-06-12",
|
|
4
|
+
"app": {
|
|
5
|
+
"slug": "agentic-app-template",
|
|
6
|
+
"name": "agentic-app-template"
|
|
7
|
+
},
|
|
8
|
+
"generation": {
|
|
9
|
+
"model": "anthropic:claude-haiku-4-5-20251001"
|
|
10
|
+
},
|
|
11
|
+
"theme": {
|
|
12
|
+
"preset": "indigo",
|
|
13
|
+
"mode": "dark"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "1",
|
|
3
|
+
"agent": {
|
|
4
|
+
"mode": "code",
|
|
5
|
+
"framework": "google-adk",
|
|
6
|
+
"model": "gemini-3.5-flash",
|
|
7
|
+
"entry": "agent.js",
|
|
8
|
+
"systemPrompt": { "file": "prompts/system.md" },
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"todo": { "kind": "hosted", "source": "./mcps/todo", "devPort": 6782 }
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"ggui": { "configFile": "./ggui/ggui.json" }
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# todo-mcp — guuey hosted MCP server image.
|
|
2
|
+
#
|
|
3
|
+
# Self-sufficient multi-stage build: `guuey mcp deploy` packs this directory
|
|
4
|
+
# into a tarball and hands it straight to the builder with no external
|
|
5
|
+
# pre-build step and no build args — this Dockerfile installs its own deps
|
|
6
|
+
# and builds itself. (Different from guuey's own first-party MCP servers,
|
|
7
|
+
# which use a "copy-prebuilt" pattern that expects `dist/` + `node_modules/`
|
|
8
|
+
# to already exist before `docker build` runs — that pattern does not apply
|
|
9
|
+
# to a standalone project like this one.)
|
|
10
|
+
#
|
|
11
|
+
# Build + run locally:
|
|
12
|
+
#
|
|
13
|
+
# docker build -t todo-mcp .
|
|
14
|
+
# docker run -p 8080:8080 todo-mcp
|
|
15
|
+
# curl http://localhost:8080/health
|
|
16
|
+
#
|
|
17
|
+
# Deploy to guuey:
|
|
18
|
+
#
|
|
19
|
+
# cd mcps/todo && guuey mcp deploy
|
|
20
|
+
|
|
21
|
+
FROM node:22-slim AS build
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
COPY package.json ./
|
|
24
|
+
RUN npm install
|
|
25
|
+
COPY tsconfig.json ./
|
|
26
|
+
COPY src ./src
|
|
27
|
+
RUN npm run build
|
|
28
|
+
|
|
29
|
+
FROM node:22-slim
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
ENV NODE_ENV=production
|
|
32
|
+
# guuey's hosted-MCP runtime injects PORT=8080 and probes GET /health at that
|
|
33
|
+
# port — this default matches so `docker run` alone also works.
|
|
34
|
+
ENV PORT=8080
|
|
35
|
+
|
|
36
|
+
COPY package.json ./
|
|
37
|
+
RUN npm install --omit=dev
|
|
38
|
+
COPY --from=build /app/dist ./dist
|
|
39
|
+
|
|
40
|
+
USER node
|
|
41
|
+
EXPOSE 8080
|
|
42
|
+
|
|
43
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
44
|
+
CMD node -e "fetch('http://localhost:' + (process.env.PORT || 8080) + '/health').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"
|
|
45
|
+
|
|
46
|
+
CMD ["node", "dist/server.js"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentic-app-template/todo-mcp",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx src/server.ts",
|
|
8
|
+
"build": "tsup src/server.ts --format esm -d dist",
|
|
9
|
+
"start": "node dist/server.js",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
14
|
+
"zod": "^4.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"tsx": "^4.19.0",
|
|
19
|
+
"tsup": "^8.5.0",
|
|
20
|
+
"typescript": "^5.8.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* todo-mcp — the copy-me example MCP server for @agentic-app-template.
|
|
3
|
+
*
|
|
4
|
+
* A minimal, stateless Streamable HTTP MCP server: four tools over an
|
|
5
|
+
* in-memory todo list. Swap the `Map` for a real database when you outgrow
|
|
6
|
+
* a single replica; everything else (transport wiring, tool shape) stays
|
|
7
|
+
* the same.
|
|
8
|
+
*
|
|
9
|
+
* Transport pattern mirrors guuey's canonical hosted-MCP scaffold
|
|
10
|
+
* (`mcp-servers/_template` in the guuey monorepo): a fresh `McpServer` +
|
|
11
|
+
* `StreamableHTTPServerTransport` are created **per request** and torn down
|
|
12
|
+
* when the response closes. Stateless mode — the guuey proxy (and, locally,
|
|
13
|
+
* whatever MCP client you point at this server) handles sessions, so this
|
|
14
|
+
* server never keeps request state around between calls.
|
|
15
|
+
*
|
|
16
|
+
* Runtime contract when deployed via `guuey mcp deploy` (see mcps/todo's
|
|
17
|
+
* Dockerfile): the platform injects `PORT=8080` and probes `GET /health`
|
|
18
|
+
* for both liveness and readiness. Locally, `pnpm dev` runs this on
|
|
19
|
+
* `PORT=6782` (see ../../scripts/dev.mjs).
|
|
20
|
+
*/
|
|
21
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
|
|
27
|
+
interface Todo {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
done: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const todos = new Map<string, Todo>();
|
|
34
|
+
|
|
35
|
+
const todoShape = {
|
|
36
|
+
id: z.string(),
|
|
37
|
+
title: z.string(),
|
|
38
|
+
done: z.boolean(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Wrap a structured result as both `structuredContent` and a text block. */
|
|
42
|
+
function toolResult(data: Record<string, unknown>) {
|
|
43
|
+
return {
|
|
44
|
+
structuredContent: data,
|
|
45
|
+
content: [{ type: "text" as const, text: JSON.stringify(data) }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildMcpServer(): McpServer {
|
|
50
|
+
const server = new McpServer({
|
|
51
|
+
name: "todo-mcp",
|
|
52
|
+
version: "0.0.0",
|
|
53
|
+
description: "Create, list, toggle, and delete todos.",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
server.registerTool(
|
|
57
|
+
"todo_list",
|
|
58
|
+
{
|
|
59
|
+
title: "List todos",
|
|
60
|
+
description: "List every todo, in creation order.",
|
|
61
|
+
inputSchema: {},
|
|
62
|
+
outputSchema: { todos: z.array(z.object(todoShape)) },
|
|
63
|
+
},
|
|
64
|
+
async () => toolResult({ todos: [...todos.values()] }),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
server.registerTool(
|
|
68
|
+
"todo_create",
|
|
69
|
+
{
|
|
70
|
+
title: "Create todo",
|
|
71
|
+
description: "Create a new todo item.",
|
|
72
|
+
inputSchema: { title: z.string().min(1).describe("Todo title.") },
|
|
73
|
+
outputSchema: todoShape,
|
|
74
|
+
},
|
|
75
|
+
async ({ title }) => {
|
|
76
|
+
const todo: Todo = { id: randomUUID(), title, done: false };
|
|
77
|
+
todos.set(todo.id, todo);
|
|
78
|
+
// Spread into a fresh object literal — `toolResult` takes
|
|
79
|
+
// `Record<string, unknown>`, and a named interface like `Todo` isn't
|
|
80
|
+
// structurally assignable to that without an (unwanted) index
|
|
81
|
+
// signature of its own; a fresh literal is.
|
|
82
|
+
return toolResult({ ...todo });
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
server.registerTool(
|
|
87
|
+
"todo_toggle",
|
|
88
|
+
{
|
|
89
|
+
title: "Toggle todo",
|
|
90
|
+
description: "Flip a todo's done state.",
|
|
91
|
+
inputSchema: { id: z.string().describe("Todo id.") },
|
|
92
|
+
outputSchema: todoShape,
|
|
93
|
+
},
|
|
94
|
+
async ({ id }) => {
|
|
95
|
+
const todo = todos.get(id);
|
|
96
|
+
if (!todo) throw new Error(`todo not found: ${id}`);
|
|
97
|
+
todo.done = !todo.done;
|
|
98
|
+
return toolResult({ ...todo }); // fresh literal — see the create() comment above
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
server.registerTool(
|
|
103
|
+
"todo_delete",
|
|
104
|
+
{
|
|
105
|
+
title: "Delete todo",
|
|
106
|
+
description: "Delete a todo by id.",
|
|
107
|
+
inputSchema: { id: z.string().describe("Todo id.") },
|
|
108
|
+
outputSchema: { id: z.string(), deleted: z.boolean() },
|
|
109
|
+
},
|
|
110
|
+
async ({ id }) => {
|
|
111
|
+
const deleted = todos.delete(id);
|
|
112
|
+
return toolResult({ id, deleted });
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return server;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const PORT = Number(process.env.PORT ?? 6782);
|
|
120
|
+
|
|
121
|
+
const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
122
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
123
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
124
|
+
res.end(JSON.stringify({ status: "ok", server: "todo-mcp", version: "0.0.0" }));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (req.url !== "/mcp") {
|
|
129
|
+
res.writeHead(404, { "content-type": "application/json" }).end(
|
|
130
|
+
JSON.stringify({ error: "not found" }),
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Fresh server + transport per request (stateless mode) — see module doc.
|
|
136
|
+
const mcp = buildMcpServer();
|
|
137
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
138
|
+
|
|
139
|
+
res.on("close", () => {
|
|
140
|
+
transport.close().catch(() => undefined);
|
|
141
|
+
mcp.close().catch(() => undefined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await mcp.connect(transport);
|
|
146
|
+
await transport.handleRequest(req, res);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error("mcp_handle_failed", err);
|
|
149
|
+
if (!res.headersSent) {
|
|
150
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
151
|
+
res.end(
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
jsonrpc: "2.0",
|
|
154
|
+
error: { code: -32603, message: "Internal server error" },
|
|
155
|
+
id: null,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
httpServer.listen(PORT, () => {
|
|
163
|
+
console.log(`todo-mcp listening on :${PORT}`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function shutdown(signal: string): void {
|
|
167
|
+
console.log(`${signal} received — shutting down`);
|
|
168
|
+
httpServer.close(() => process.exit(0));
|
|
169
|
+
// Force-exit if connections don't drain in time.
|
|
170
|
+
setTimeout(() => process.exit(1), 10_000).unref();
|
|
171
|
+
}
|
|
172
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
173
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"resolveJsonModule": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentic-app-template",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "node scripts/dev.mjs",
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@google/adk": "^1.3.0",
|
|
13
|
+
"zod": "^4.2.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@guuey/cli": "0.1.2",
|
|
17
|
+
"tsup": "^8.5.0",
|
|
18
|
+
"tsx": "^4.19.0",
|
|
19
|
+
"typescript": "^5.8.0",
|
|
20
|
+
"@guuey/config": "0.1.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
packages:
|
|
2
|
+
- "mcps/*"
|
|
3
|
+
- "web"
|
|
4
|
+
|
|
5
|
+
# pnpm 11 default `strictDepBuilds: true` blocks install scripts for any
|
|
6
|
+
# dependency with a build script (esbuild's native binary fetch, sharp,
|
|
7
|
+
# etc.) unless individually allowlisted, and fails the whole `install`
|
|
8
|
+
# with ERR_PNPM_IGNORED_BUILDS otherwise. Scaffolded apps pull in build-
|
|
9
|
+
# script deps transitively (tsup/vite -> esbuild, mcp/ui deps, etc.) that
|
|
10
|
+
# we can't enumerate up front, so match pnpm 9/10 behavior here.
|
|
11
|
+
strictDepBuilds: false
|
|
12
|
+
|
|
13
|
+
# pnpm 11 default `verifyDepsBeforeRun: 'install'` re-verifies the
|
|
14
|
+
# lockfile against supply-chain policies (including minimumReleaseAge)
|
|
15
|
+
# before every `pnpm run <script>`, hitting the registry even for
|
|
16
|
+
# already-installed deps. That check 404s for any dependency that only
|
|
17
|
+
# exists as a local tarball (not yet published), which is expected
|
|
18
|
+
# during local/dev/CI installs. Restore pnpm 9/10 behavior: only verify
|
|
19
|
+
# on an explicit `pnpm install`.
|
|
20
|
+
verifyDepsBeforeRun: false
|
|
21
|
+
|
|
22
|
+
# See above — same registry-lookback problem hits a plain `pnpm install`
|
|
23
|
+
# too when minimumReleaseAge is nonzero; disable it for scaffolded apps.
|
|
24
|
+
minimumReleaseAge: 0
|
|
25
|
+
|
|
26
|
+
# @google/adk declares five NON-optional @mikro-orm/* database-driver peer
|
|
27
|
+
# dependencies (mariadb/mssql/mysql/postgresql/sqlite). Auto-installing
|
|
28
|
+
# them drags in sqlite3 — a NATIVE module whose install script needs a
|
|
29
|
+
# GitHub prebuild download or a full node-gyp toolchain, neither of which
|
|
30
|
+
# exists in the platform's image build. None of the drivers is needed:
|
|
31
|
+
# they back DatabaseSessionService, and Guuey runs the ADK with
|
|
32
|
+
# InMemoryRunner (conversation state rides Guuey's own history/memory
|
|
33
|
+
# fold). The unmet-peer warnings this produces are expected and harmless.
|
|
34
|
+
autoInstallPeers: false
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pnpm dev — boots the whole local stack. Ctrl-C tears everything down.
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const procs = [];
|
|
7
|
+
function boot(name, command, args, opts = {}) {
|
|
8
|
+
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], ...opts });
|
|
9
|
+
const prefix = `[${name}]`.padEnd(9);
|
|
10
|
+
child.stdout.on("data", (d) => process.stdout.write(String(d).replace(/^/gm, prefix)));
|
|
11
|
+
child.stderr.on("data", (d) => process.stderr.write(String(d).replace(/^/gm, prefix)));
|
|
12
|
+
child.on("exit", (code) => {
|
|
13
|
+
if (code !== 0 && !shuttingDown) shutdown(`${name} exited (${code})`, 1);
|
|
14
|
+
});
|
|
15
|
+
procs.push(child);
|
|
16
|
+
}
|
|
17
|
+
let shuttingDown = false;
|
|
18
|
+
function shutdown(reason, code = 0) {
|
|
19
|
+
if (shuttingDown) return;
|
|
20
|
+
shuttingDown = true;
|
|
21
|
+
console.log(`\n${reason} — shutting down`);
|
|
22
|
+
for (const p of procs) p.kill("SIGTERM");
|
|
23
|
+
setTimeout(() => process.exit(code), 500);
|
|
24
|
+
}
|
|
25
|
+
process.on("SIGINT", () => shutdown("interrupted"));
|
|
26
|
+
process.on("SIGTERM", () => shutdown("terminated"));
|
|
27
|
+
|
|
28
|
+
if (!existsSync(".env.local"))
|
|
29
|
+
console.warn("hint: cp .env.example .env.local and set your LLM key");
|
|
30
|
+
|
|
31
|
+
boot("worker", "pnpm", ["exec", "tsup", "--watch"]); // rebuilds guuey.worker.js on change
|
|
32
|
+
boot("agent", "pnpm", ["exec", "guuey", "dev", "--serve", "--port", "6790"]);
|
|
33
|
+
boot("todo", "pnpm", ["--filter", "@agentic-app-template/todo-mcp", "dev"], {
|
|
34
|
+
env: { ...process.env, PORT: "6782" },
|
|
35
|
+
});
|
|
36
|
+
boot("ggui", "pnpm", ["exec", "ggui", "serve", "--mcp-only", "--dev-allow-all", "--port", "6781"], {
|
|
37
|
+
cwd: "ggui",
|
|
38
|
+
});
|
|
39
|
+
boot("web", "pnpm", ["--filter", "@agentic-app-template/web", "dev"], {
|
|
40
|
+
env: { ...process.env, PORT: "6890" },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log("\n agent http://localhost:6790 todo-mcp http://localhost:6782");
|
|
44
|
+
console.log(" ggui http://localhost:6781 web http://localhost:6890\n");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your agent — pure Google-ADK code. No Guuey SDK, no harness, no event
|
|
3
|
+
* plumbing: export it and the platform runs it (locally via `pnpm dev`,
|
|
4
|
+
* hosted via `guuey deploy`).
|
|
5
|
+
*
|
|
6
|
+
* The default export is a FACTORY receiving everything Guuey resolved for
|
|
7
|
+
* the current turn (`GuueyContext`): the model + instruction from
|
|
8
|
+
* guuey.json, ready-to-use MCP toolsets for every server declared there,
|
|
9
|
+
* the end user's identity, file storage paths, and the conversation state.
|
|
10
|
+
* See the README for the full context tour.
|
|
11
|
+
*/
|
|
12
|
+
import { FunctionTool, LlmAgent, type MCPToolset } from "@google/adk";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import type { GuueyContext } from "@guuey/config";
|
|
15
|
+
|
|
16
|
+
/** A plain ADK tool — everything the ADK offers works unchanged. */
|
|
17
|
+
const rollDice = new FunctionTool({
|
|
18
|
+
name: "roll_dice",
|
|
19
|
+
description: "Roll an N-sided die and return the result.",
|
|
20
|
+
parameters: z.object({
|
|
21
|
+
sides: z.number().int().min(2).describe("number of faces on the die"),
|
|
22
|
+
}),
|
|
23
|
+
execute: async ({ sides }) => ({
|
|
24
|
+
result: 1 + Math.floor(Math.random() * sides),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default (guuey: GuueyContext<MCPToolset>) =>
|
|
29
|
+
new LlmAgent({
|
|
30
|
+
name: "my_agent",
|
|
31
|
+
model: guuey.model,
|
|
32
|
+
// `instruction` already carries your system prompt PLUS the conversation
|
|
33
|
+
// preamble (history, thread memory, working state) — your agent is
|
|
34
|
+
// conversational without writing any state code.
|
|
35
|
+
instruction: guuey.instruction,
|
|
36
|
+
// Your own tools compose with the MCP servers from guuey.json. Drop
|
|
37
|
+
// `...guuey.mcpToolsets` if you want a fully self-contained agent.
|
|
38
|
+
tools: [rollDice, ...guuey.mcpToolsets],
|
|
39
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: { agent: "src/agent.ts" },
|
|
5
|
+
format: ["esm"],
|
|
6
|
+
outDir: ".",
|
|
7
|
+
clean: false,
|
|
8
|
+
// Dependencies stay EXTERNAL (tsup's default): the platform installs them
|
|
9
|
+
// from the lockfile at image-build time and the Guuey host imports your
|
|
10
|
+
// agent.js — with @google/adk resolved from YOUR node_modules (your pinned
|
|
11
|
+
// version wins). Do NOT bundle @google/adk; its dependency tree is not
|
|
12
|
+
// bundler-safe.
|
|
13
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Vite env for the web chat client. Copy to `web/.env.local` and UNCOMMENT
|
|
2
|
+
# the vars you want to override. (Vite reads env files from this directory —
|
|
3
|
+
# not the project root. Keep unused vars commented out: an empty
|
|
4
|
+
# `VITE_X=` declaration loads as "" — not undefined.)
|
|
5
|
+
|
|
6
|
+
# Agent base URL. Defaults to the local `guuey dev` router (:6790).
|
|
7
|
+
# VITE_AGENT_ENDPOINT_URL=http://localhost:6790
|
|
8
|
+
|
|
9
|
+
# Second-origin MCP-Apps sandbox page for @mcp-ui/client's <AppRenderer>.
|
|
10
|
+
# The MCP Apps spec requires UI resources to mount from a DIFFERENT origin
|
|
11
|
+
# than the app itself (double-iframe sandbox). In local dev this is served
|
|
12
|
+
# automatically on http://127.0.0.1:6891/sandbox.html by the
|
|
13
|
+
# sandboxProxyPlugin in vite.config.ts — leave unset. For a DEPLOYED build,
|
|
14
|
+
# host ../sandbox-proxy.ts's sandbox page on its own origin and point this
|
|
15
|
+
# at it; if unset in production, UI resources show a plain-text notice
|
|
16
|
+
# instead of rendering.
|
|
17
|
+
# VITE_SANDBOX_URL=https://sandbox.example.com/sandbox.html
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>agentic-app-template</title>
|
|
7
|
+
<meta name="description" content="Local dev chat client for your guuey agent." />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|