Skip to content

Commit 951d3c3

Browse files
committed
merging
2 parents 50c535b + 6bc0889 commit 951d3c3

11 files changed

Lines changed: 613 additions & 39 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ AutoDeploy/
4545
│ └── github-oauth.js # helper functions for GitHub API
4646
├── routes/
4747
│ │ └── auth.github.js # all GitHub OAuth + /me routes
48+
└── usersRoutes
4849
│ ├── server.js # Express bootstrap & route mounting
4950
│ ├── db.js # pg Pool + query() + healthCheck()
5051

server/agent/wizardAgent.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
3+
import OpenAI from "openai";
4+
import dotenv from "dotenv";
5+
import fetch from "node-fetch";
6+
7+
dotenv.config();
8+
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
9+
10+
// Helper: call MCP routes dynamically
11+
async function callMCPTool(tool, input) {
12+
const response = await fetch(`http://localhost:4000/mcp/v1/${tool}`, {
13+
method: "POST",
14+
headers: { "Content-Type": "application/json" },
15+
body: JSON.stringify(input),
16+
});
17+
return await response.json();
18+
}
19+
20+
// Wizard Agent Core
21+
export async function runWizardAgent(userPrompt) {
22+
const systemPrompt = `
23+
You are the MCP Wizard Agent.
24+
You have access to these tools:
25+
- repo_reader: lists repos and branches
26+
- pipeline_generator: builds a CI/CD pipeline YAML
27+
- oidc_adapter: lists AWS roles or Jenkins jobs
28+
Decide which tool to call based on user intent and return results in plain text.
29+
`;
30+
31+
const completion = await client.chat.completions.create({
32+
model: "gpt-4o-mini",
33+
messages: [
34+
{ role: "system", content: systemPrompt },
35+
{ role: "user", content: userPrompt },
36+
],
37+
});
38+
39+
const decision = completion.choices[0].message.content;
40+
console.log("\n🤖 Agent decided:", decision);
41+
42+
// Basic keyword trigger (mock reasoning)
43+
if (decision.toLowerCase().includes("repo")) return await callMCPTool("repo_reader", {});
44+
if (decision.toLowerCase().includes("pipeline"))
45+
return await callMCPTool("pipeline_generator", {
46+
repo: "askmyrepo",
47+
provider: "aws",
48+
template: "node_app",
49+
});
50+
if (decision.toLowerCase().includes("role") || decision.toLowerCase().includes("jenkins"))
51+
return await callMCPTool("oidc_adapter", { provider: "aws" });
52+
53+
return { message: "No matching tool found." };
54+
}
55+
56+
// Example local test (can comment out for production)
57+
if (process.argv[2]) {
58+
const input = process.argv.slice(2).join(" ");
59+
runWizardAgent(input)
60+
.then((res) => {
61+
console.log("\n📦 Tool Output:\n", res);
62+
})
63+
.catch(console.error);
64+
}

server/routes/mcp.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import express from "express";
2+
import { MCP_TOOLS } from "../tools/index.js";
3+
4+
const router = express.Router();
5+
6+
// Utility logger
7+
const logRequest = (req, route) => {
8+
console.log(`[MCP] ${new Date().toISOString()} | user=${req.headers["x-user-id"] || "anonymous"} | route=${route}`);
9+
};
10+
11+
// --- MCP Status Route ---
12+
router.get("/status", (req, res) => {
13+
logRequest(req, "/mcp/v1/status");
14+
15+
res.json({
16+
status: "ok",
17+
version: "v1.0.0",
18+
tools_registered: Object.keys(MCP_TOOLS),
19+
timestamp: new Date().toISOString(),
20+
});
21+
});
22+
23+
// Dynamic route: handles any tool in registry
24+
router.all("/:tool_name", async (req, res) => {
25+
const { tool_name } = req.params;
26+
const tool = MCP_TOOLS[tool_name];
27+
logRequest(req, `/mcp/v1/${tool_name}`);
28+
29+
if (!tool) {
30+
return res.status(404).json({ success: false, error: `Tool '${tool_name}' not found.` });
31+
}
32+
33+
try {
34+
// Merge query + body to handle GET or POST
35+
const input = { ...req.query, ...req.body };
36+
const validatedInput = tool.input_schema.parse(input);
37+
const data = await tool.handler(validatedInput);
38+
res.json({ success: true, data });
39+
} catch (error) {
40+
console.error(`Error in ${tool_name}:`, error);
41+
res.status(500).json({ success: false, error: error.message });
42+
}
43+
});
44+
45+
export default router;

server/routes/usersRoutes.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Router } from 'express';
2+
import { z } from 'zod';
3+
import { query } from '../db.js';
4+
5+
const router = Router();
6+
7+
/** Users */
8+
const UserBody = z.object({
9+
email: z.string().email(),
10+
github_username: z.string().min(1).optional(),
11+
});
12+
13+
// Create or upsert user by email
14+
router.post('/users', async (req, res) => {
15+
const parse = UserBody.safeParse(req.body);
16+
if (!parse.success)
17+
return res.status(400).json({ error: parse.error.message });
18+
const { email, github_username } = parse.data;
19+
20+
// upsert on email; requires a unique index on users.email
21+
try {
22+
const rows = await query(
23+
`
24+
insert into users (email, github_username)
25+
values ($1, $2)
26+
on conflict (email) do update set github_username = excluded.github_username
27+
returning *;
28+
`,
29+
[email, github_username ?? null]
30+
);
31+
res.status(201).json({ user: rows[0] });
32+
} catch (e) {
33+
res.status(500).json({ error: e.message });
34+
}
35+
});
36+
37+
router.get('/users', async (_req, res) => {
38+
try {
39+
const rows = await query(
40+
`select * from users order by created_at desc limit 100;`
41+
);
42+
res.json({ users: rows });
43+
} catch (e) {
44+
res.status(500).json({ error: e.message });
45+
}
46+
});
47+
48+
export default router;

server/server.js

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import express from 'express';
33
import cors from 'cors';
44
import helmet from 'helmet';
55
import morgan from 'morgan';
6-
import { healthCheck, query } from './db.js';
6+
import { healthCheck } from './db.js';
77
import githubAuthRouter from './routes/auth.github.js';
8-
import { z } from 'zod';
8+
import userRouter from './routes/usersRoutes.js';
9+
import mcpRoutes from './routes/mcp.js';
910

1011
const app = express();
1112
app.use(express.json());
@@ -26,45 +27,31 @@ app.get('/db/ping', async (_req, res) => {
2627
}
2728
});
2829

29-
/** Users */
30-
const UserBody = z.object({
31-
email: z.string().email(),
32-
github_username: z.string().min(1).optional(),
30+
// Mount users route at /users
31+
app.use('/', userRouter);
32+
33+
// --- Request Logging Middleware ---
34+
app.use((req, res, next) => {
35+
const user = req.headers['x-user-id'] || 'anonymous';
36+
console.log(
37+
`[${new Date().toISOString()}] ${req.method} ${
38+
req.originalUrl
39+
} | user=${user}`
40+
);
41+
next();
3342
});
3443

35-
// Create or upsert user by email
36-
app.post('/users', async (req, res) => {
37-
const parse = UserBody.safeParse(req.body);
38-
if (!parse.success)
39-
return res.status(400).json({ error: parse.error.message });
40-
const { email, github_username } = parse.data;
41-
42-
// upsert on email; requires a unique index on users.email
43-
try {
44-
const rows = await query(
45-
`
46-
insert into users (email, github_username)
47-
values ($1, $2)
48-
on conflict (email) do update set github_username = excluded.github_username
49-
returning *;
50-
`,
51-
[email, github_username ?? null]
52-
);
53-
res.status(201).json({ user: rows[0] });
54-
} catch (e) {
55-
res.status(500).json({ error: e.message });
56-
}
57-
});
58-
59-
app.get('/users', async (_req, res) => {
60-
try {
61-
const rows = await query(
62-
`select * from users order by created_at desc limit 100;`
63-
);
64-
res.json({ users: rows });
65-
} catch (e) {
66-
res.status(500).json({ error: e.message });
67-
}
44+
// -- Agent entry point
45+
app.use('/mcp/v1', mcpRoutes);
46+
47+
// --- Global Error Handler ---
48+
app.use((err, req, res, next) => {
49+
console.error('Global Error:', err);
50+
res.status(500).json({
51+
success: false,
52+
error: 'Internal Server Error',
53+
message: err.message,
54+
});
6855
});
6956

7057
// Mount GitHub OAuth routes at /auth/github
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# 🧭 MCP Hybrid AI Wizard – Flow & Module Architecture
2+
3+
This document shows how user-driven input and AI-driven automation combine within the MCP system.
4+
The goal: users still interact with forms, while the AI Wizard orchestrates MCP tools to build pipelines and deploy them automatically.
5+
6+
---
7+
8+
## 🧩 High-Level Flow
9+
10+
```mermaid
11+
flowchart TD
12+
13+
A[User UI / Wizard Form] -->|Form Data| B[AI Wizard Agent]
14+
B -->|Decides Which Tool(s) to Use| C[MCP API Layer]
15+
C -->|REST Call| D[Tool Endpoint]
16+
D -->|Calls Integration Stub| E[Integration Layer]
17+
E -->|Returns Mock/Real Data| C
18+
C -->|Response| B
19+
B -->|Summarized Result / Next Step| A
20+
```
21+
22+
---
23+
24+
## ⚙️ Module Breakdown
25+
26+
### 1️⃣ Frontend (Wizard UI)
27+
- Presents form fields (repo, branch, provider, template).
28+
- Sends collected inputs to the **AI Wizard Agent**.
29+
- Renders conversation steps or results returned by the agent.
30+
31+
**Tech stack:** React + Zustand + shadcn/ui
32+
**Responsibility:** UX and data capture.
33+
34+
---
35+
36+
### 2️⃣ AI Wizard Agent
37+
- Acts as the **intelligent middle layer** between the user and the MCP tools.
38+
- Determines which tool(s) to invoke based on the user’s form data and conversation context.
39+
- Parses MCP responses, summarizes, and sends human-readable updates back to UI.
40+
41+
**Example logic:**
42+
```js
43+
if (user.provider === "aws") useTool("oidc_adapter");
44+
useTool("pipeline_generator", user);
45+
```
46+
47+
**Responsibility:** Reasoning, orchestration, and dynamic flow.
48+
49+
---
50+
51+
### 3️⃣ MCP API Layer
52+
- Express server exposing endpoints under `/mcp/v1`.
53+
- Validates input with Zod.
54+
- Routes requests to internal tool modules (repo_reader, pipeline_generator, oidc_adapter).
55+
56+
**Responsibility:** Standardize all backend access through predictable JSON schemas.
57+
58+
---
59+
60+
### 4️⃣ MCP Tools (under `/server/tools/`)
61+
Each tool = single responsibility function with metadata and schema.
62+
63+
Example structure:
64+
```js
65+
export const pipeline_generator = {
66+
name: "pipeline_generator",
67+
description: "Generate CI/CD YAML for a given repo and provider",
68+
input_schema: z.object({
69+
repo: z.string(),
70+
provider: z.enum(["aws", "jenkins"]),
71+
template: z.string()
72+
}),
73+
handler: async (params) => { ... }
74+
}
75+
```
76+
77+
**Responsibility:** Encapsulate logic for each automation unit.
78+
79+
---
80+
81+
### 5️⃣ Integrations Layer
82+
- Mock or real API wrappers for external systems.
83+
- Organized by provider type.
84+
85+
Structure:
86+
```
87+
server/integrations/
88+
├── github/
89+
│ └── index.js
90+
├── aws/
91+
│ └── index.js
92+
└── jenkins/
93+
└── index.js
94+
```
95+
96+
**Responsibility:** Handle API auth, SDK calls, and return normalized data.
97+
98+
---
99+
100+
### 6️⃣ Database (Supabase / Postgres)
101+
- Stores users, connections, and tokens (encrypted).
102+
- Provides audit logs of which tools ran under which user.
103+
104+
**Responsibility:** Persistence and security layer.
105+
106+
---
107+
108+
## ✅ Summary
109+
110+
| Layer | Role | Example Interaction |
111+
|-------|------|----------------------|
112+
| Frontend | Collects user input | Form submits data |
113+
| AI Wizard | Decides which MCP tool to use | “I’ll generate your AWS pipeline.” |
114+
| MCP Server | Hosts tools and APIs | `/mcp/v1/pipeline_generator` |
115+
| Tools | Do one job well | Return YAML or list of roles |
116+
| Integrations | Communicate externally | AWS IAM, GitHub, Jenkins |
117+
| Database | Persist context | Users, connections, logs |
118+
119+
---
120+
121+
## 🧠 Next Steps for Implementation
122+
1. Create `/server/routes/mcp.js` to modularize endpoints.
123+
2. Move endpoint logic into `/server/tools/`.
124+
3. Register each tool in an MCP registry.
125+
4. Integrate the AI Wizard (OpenAI, LangChain, or custom agent).
126+
5. Have the agent call MCP tools dynamically based on user input.

0 commit comments

Comments
 (0)