phewsh-mcp-server 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +161 -0
  2. package/package.json +45 -0
  3. package/src/index.js +1029 -0
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # phewsh-mcp-server
2
+
3
+ **Your projects have intelligence. Now your agents can use it.**
4
+
5
+ PHEWSH MCP gives any AI agent — Claude Code, Cursor, custom bots — instant access to your project's vision, plan, constraints, and task queue. The agent starts a session, gets a structured task with verification criteria, executes it, reports back, and automatically gets the next one.
6
+
7
+ You shape the intent. Agents do the work. PHEWSH keeps the thread.
8
+
9
+ ## The experience
10
+
11
+ **If you've never used an MCP server before:** this lets your AI assistant know what you're building, what matters, and what to do next — without you re-explaining it every time. Set it up once, and every conversation starts with full context.
12
+
13
+ **If you build with agents:** this is the coordination protocol. Structured dispatch packets (not chat prompts), smart task ordering, verification gates, blocker tracking, session history across agents, and auto-chaining. Your agents share a brain.
14
+
15
+ ## Quick start
16
+
17
+ ### 1. Install
18
+
19
+ ```bash
20
+ npm install -g phewsh-mcp-server
21
+ ```
22
+
23
+ Or if you have the PHEWSH repo:
24
+
25
+ ```bash
26
+ cd mcp && npm install
27
+ ```
28
+
29
+ ### 2. Configure Claude Code
30
+
31
+ Add to `~/.claude/settings.json`:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "phewsh": {
37
+ "command": "npx",
38
+ "args": ["-y", "phewsh-mcp-server"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Or with a local install:
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "phewsh": {
50
+ "command": "node",
51
+ "args": ["/path/to/phewsh/mcp/src/index.js"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### 3. Sync your projects
58
+
59
+ If you use the PHEWSH CLI:
60
+
61
+ ```bash
62
+ phewsh mcp sync
63
+ ```
64
+
65
+ If you use the web app at phewsh.com/intent, export your projects from the settings menu.
66
+
67
+ The server also automatically reads any `.intent/` directory in your current working directory.
68
+
69
+ ### 4. Start working
70
+
71
+ Open Claude Code in your project and say:
72
+
73
+ > "Check phewsh for what I should work on next"
74
+
75
+ Claude will call `phewsh_start`, get your full project briefing and first task, and begin executing. When it finishes, it reports back and gets the next task automatically.
76
+
77
+ ## How it works
78
+
79
+ ```
80
+ You shape intent PHEWSH coordinates Agents execute
81
+ (web app or CLI) --> (MCP server) --> (Claude, Cursor, etc.)
82
+ ^ |
83
+ |______ results _____________|
84
+ ```
85
+
86
+ 1. You define what you're building in PHEWSH (vision, plan, constraints, tasks)
87
+ 2. The MCP server exposes this as structured dispatch packets
88
+ 3. Any agent connects, pulls a task, and gets: objective, constraints, verification criteria, rollback plan
89
+ 4. Agent executes, reports result — server auto-chains to the next task
90
+ 5. Session history tracks what happened across agents and sessions
91
+
92
+ The dispatch packet is NOT a chat prompt. It's an execution contract:
93
+
94
+ ```
95
+ Objective "Deploy the landing page to production"
96
+ Success "Live and accessible at target URL"
97
+ Constraints Budget: $0 | Urgency: urgent | Autonomy: guided
98
+ Verification [ ] Accessible at URL [ ] No error responses
99
+ Rollback Reversible — redeploy previous version
100
+ After Report back, then: "Set up analytics tracking"
101
+ ```
102
+
103
+ ## Tools
104
+
105
+ | Tool | Purpose |
106
+ |------|---------|
107
+ | `phewsh_start` | **Start here.** Full project briefing + first task in one call. |
108
+ | `phewsh_next_task` | Get next highest-priority task as a dispatch packet. |
109
+ | `phewsh_complete_task` | Report results. Auto-chains to the next task. |
110
+ | `phewsh_check_verification` | Confirm what needs to be true before marking done. |
111
+ | `phewsh_flag_blocker` | Flag a blocked task. Suggests alternative work. |
112
+ | `phewsh_list_projects` | List all projects with progress and constraints. |
113
+ | `phewsh_get_context` | Deep project briefing — vision, plan, gate, state. |
114
+ | `phewsh_session_history` | What agents have done — completions, blockers, sessions. |
115
+
116
+ ## Prompts
117
+
118
+ Pre-built workflows agents can invoke:
119
+
120
+ | Prompt | What it does |
121
+ |--------|-------------|
122
+ | `work_session` | Full autonomous session: get briefed, execute up to N tasks, report results. |
123
+ | `status_check` | Quick status: what's done, what's pending, any blockers. No execution. |
124
+ | `review_results` | Human oversight: review what agents did, what failed, what needs attention. |
125
+
126
+ ## Resources
127
+
128
+ Project artifacts are readable as MCP resources:
129
+
130
+ - `phewsh://projects/{id}/briefing` — full project context
131
+ - `phewsh://projects/{id}/artifacts/vision` — vision artifact
132
+ - `phewsh://projects/{id}/artifacts/plan` — plan artifact
133
+
134
+ ## What makes this different
135
+
136
+ **Smart ordering.** Tasks aren't first-in-first-out. Priority considers urgency, execution type, category freshness, and age. Agents get the highest-impact work first.
137
+
138
+ **Auto-chaining.** Complete a task, get the next one in the response. No extra calls. Agents can run through an entire work queue in a single session.
139
+
140
+ **Blocker awareness.** Can't do a task? Flag it and get an alternative. The human gets notified. No one wastes time.
141
+
142
+ **Agent identity.** Every session start, completion, and blocker is tagged with who did it. Multiple agents work on the same project without confusion.
143
+
144
+ **Verification gates.** Every task has concrete "done when" criteria. Agents know exactly what to prove before reporting complete.
145
+
146
+ **Constraint-aware.** Budget, time, skill level, urgency, autonomy — all flow into dispatch packets. An agent won't suggest a $500 service on a $0 budget.
147
+
148
+ ## Project sources
149
+
150
+ The server loads projects from:
151
+
152
+ 1. **`.intent/` in cwd** — automatically loaded as the "local" project
153
+ 2. **`~/.phewsh/projects.json`** — synced via `phewsh mcp sync` or web app export
154
+
155
+ ## Philosophy
156
+
157
+ PHEWSH is the coordination layer, not another agent. It doesn't execute — it remembers, reasons about constraints, orders work, and verifies results. The agent does the work. PHEWSH makes sure it's the right work, done right.
158
+
159
+ ---
160
+
161
+ *Built by [PHEWSH](https://phewsh.com) — software that adapts to you.*
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "phewsh-mcp-server",
3
+ "version": "0.2.0",
4
+ "description": "Connect AI agents to your project intelligence. One call to start. One call to finish. Everything in between is execution.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "phewsh-mcp": "src/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "dev": "node --watch src/index.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "phewsh",
18
+ "intent",
19
+ "dispatch",
20
+ "agent",
21
+ "coordination",
22
+ "ai-agents",
23
+ "claude-code",
24
+ "cursor",
25
+ "project-management"
26
+ ],
27
+ "author": "Phewsh <hello@phewsh.com>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/cleverIdeaz/phewsh",
32
+ "directory": "mcp"
33
+ },
34
+ "homepage": "https://phewsh.com/intent",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.0.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "files": [
42
+ "src/",
43
+ "README.md"
44
+ ]
45
+ }
package/src/index.js ADDED
@@ -0,0 +1,1029 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PHEWSH MCP Server v0.2.0
5
+ *
6
+ * The coordination layer between humans and AI agents.
7
+ *
8
+ * Any MCP-capable agent connects here and immediately knows:
9
+ * - What project they're working on
10
+ * - What needs to happen next (structured, not vague)
11
+ * - What constraints to respect
12
+ * - How to prove they did it right
13
+ * - What to do after they finish
14
+ *
15
+ * One call to start. One call to finish. Everything in between is execution.
16
+ */
17
+
18
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
19
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
+ import {
21
+ CallToolRequestSchema,
22
+ ListToolsRequestSchema,
23
+ ListResourcesRequestSchema,
24
+ ReadResourceRequestSchema,
25
+ ListPromptsRequestSchema,
26
+ GetPromptRequestSchema,
27
+ } from "@modelcontextprotocol/sdk/types.js";
28
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "fs";
29
+ import { join, resolve } from "path";
30
+ import { homedir } from "os";
31
+
32
+ // ─── Config ─────────────────────────────────────────────────────────────────
33
+
34
+ const PHEWSH_DIR = join(homedir(), ".phewsh");
35
+ const PROJECTS_FILE = join(PHEWSH_DIR, "projects.json");
36
+ const RESULTS_DIR = join(PHEWSH_DIR, "results");
37
+ const SESSIONS_DIR = join(PHEWSH_DIR, "sessions");
38
+
39
+ // Ensure directories exist
40
+ [PHEWSH_DIR, RESULTS_DIR, SESSIONS_DIR].forEach(dir => {
41
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
42
+ });
43
+
44
+ // ─── Project Loading ────────────────────────────────────────────────────────
45
+
46
+ function loadProjects() {
47
+ const projects = [];
48
+
49
+ // 1. Check for local .intent/ directory
50
+ const localIntent = join(process.cwd(), ".intent");
51
+ if (existsSync(localIntent)) {
52
+ const local = loadLocalIntentProject(localIntent);
53
+ if (local) projects.push(local);
54
+ }
55
+
56
+ // 2. Check for synced projects from web app
57
+ if (existsSync(PROJECTS_FILE)) {
58
+ try {
59
+ const data = JSON.parse(readFileSync(PROJECTS_FILE, "utf-8"));
60
+ if (Array.isArray(data)) {
61
+ projects.push(...data);
62
+ }
63
+ } catch { /* ignore parse errors */ }
64
+ }
65
+
66
+ return projects;
67
+ }
68
+
69
+ function loadLocalIntentProject(dir) {
70
+ const project = {
71
+ id: "local",
72
+ name: "Current Project",
73
+ source: "local",
74
+ artifacts: {},
75
+ actions: [],
76
+ decisionGate: null,
77
+ };
78
+
79
+ // Read standard .intent/ files
80
+ const files = ["vision.md", "plan.md", "next.md", "status.md", "narrative.md"];
81
+ for (const file of files) {
82
+ const path = join(dir, file);
83
+ if (existsSync(path)) {
84
+ const kind = file.replace(".md", "");
85
+ project.artifacts[kind] = {
86
+ kind,
87
+ content: readFileSync(path, "utf-8"),
88
+ };
89
+ if (kind === "vision") {
90
+ const firstLine = project.artifacts[kind].content.split("\n").find(l => l.trim().length > 5);
91
+ if (firstLine) project.name = firstLine.replace(/^#+\s*/, "").trim().slice(0, 60);
92
+ }
93
+ }
94
+ }
95
+
96
+ // Read project.json if it exists (has constraints, gate, actions, etc.)
97
+ const metaPath = join(dir, "project.json");
98
+ if (existsSync(metaPath)) {
99
+ try {
100
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
101
+ Object.assign(project, meta);
102
+ } catch { /* ignore */ }
103
+ }
104
+
105
+ return Object.keys(project.artifacts).length > 0 ? project : null;
106
+ }
107
+
108
+ // ─── Smart Task Ordering ────────────────────────────────────────────────────
109
+
110
+ const URGENCY_WEIGHT = { critical: 4, urgent: 3, moderate: 2, relaxed: 1 };
111
+ const TYPE_PRIORITY = { agent: 3, ai: 2, human: 1 }; // agents should grab agent tasks first
112
+
113
+ function scoreTask(action, project) {
114
+ let score = 0;
115
+ const gate = project.decisionGate;
116
+
117
+ // Urgency from gate constraints
118
+ if (gate?.constraints?.urgency) {
119
+ score += (URGENCY_WEIGHT[gate.constraints.urgency] || 2) * 10;
120
+ }
121
+
122
+ // Execution type match — prefer agent-executable tasks
123
+ const execType = action.executionType || classifyExecutionType(action.intent);
124
+ score += (TYPE_PRIORITY[execType] || 1) * 5;
125
+
126
+ // Category clustering — if previous completed actions share category, boost same-category
127
+ const completedCategories = (project.actions || [])
128
+ .filter(a => a.state === "reconciled" && a.reconciliation?.decision === "accept")
129
+ .slice(-3)
130
+ .map(a => a.category);
131
+ if (action.category && !completedCategories.includes(action.category)) {
132
+ score += 3; // Fresh category = higher priority (avoids tunnel vision)
133
+ }
134
+
135
+ // Recency penalty — older tasks get slight boost
136
+ if (action.createdAt) {
137
+ const age = Date.now() - new Date(action.createdAt).getTime();
138
+ score += Math.min(5, Math.floor(age / (1000 * 60 * 60 * 24))); // +1 per day, max 5
139
+ }
140
+
141
+ return score;
142
+ }
143
+
144
+ function getOrderedTasks(project, filter = "any") {
145
+ const pending = (project.actions || []).filter(a => {
146
+ if (a.state !== "intended") return false;
147
+ if (filter === "any") return true;
148
+ const aType = a.executionType || classifyExecutionType(a.intent);
149
+ return aType === filter;
150
+ });
151
+
152
+ return pending
153
+ .map(a => ({ action: a, score: scoreTask(a, project) }))
154
+ .sort((a, b) => b.score - a.score)
155
+ .map(s => s.action);
156
+ }
157
+
158
+ // ─── Agent Session Tracking ─────────────────────────────────────────────────
159
+
160
+ function recordSession(agentId, projectId, event, data = {}) {
161
+ const session = {
162
+ agentId: agentId || "anonymous",
163
+ projectId,
164
+ event,
165
+ timestamp: new Date().toISOString(),
166
+ ...data,
167
+ };
168
+
169
+ const file = join(SESSIONS_DIR, `${projectId}_sessions.json`);
170
+ let sessions = [];
171
+ if (existsSync(file)) {
172
+ try { sessions = JSON.parse(readFileSync(file, "utf-8")); } catch { sessions = []; }
173
+ }
174
+ sessions.push(session);
175
+ // Keep last 100 sessions per project
176
+ if (sessions.length > 100) sessions = sessions.slice(-100);
177
+ writeFileSync(file, JSON.stringify(sessions, null, 2));
178
+ return session;
179
+ }
180
+
181
+ function getRecentSessions(projectId, limit = 10) {
182
+ const file = join(SESSIONS_DIR, `${projectId}_sessions.json`);
183
+ if (!existsSync(file)) return [];
184
+ try {
185
+ const sessions = JSON.parse(readFileSync(file, "utf-8"));
186
+ return sessions.slice(-limit);
187
+ } catch { return []; }
188
+ }
189
+
190
+ // ─── Execution Type Classification ─────────────────────────────────────────
191
+
192
+ function classifyExecutionType(intent) {
193
+ const lower = intent.toLowerCase();
194
+ if (/\b(meet|call|email|talk|discuss|decide|choose|buy|purchase|sign up|register|hire|interview|negotiate|pitch|present|attend|schedule|book|contact|reach out|approve)\b/.test(lower)) return "human";
195
+ if (/\b(deploy|publish|push|install|run|execute|build|compile|test|debug|browse|navigate|click|fill|submit|upload|download|configure|ssh|terminal|git push|npm|docker|automate|scrape|monitor)\b/.test(lower)) return "agent";
196
+ return "ai";
197
+ }
198
+
199
+ // ─── Dispatch Packet Generation ─────────────────────────────────────────────
200
+
201
+ function generatePacket(action, project) {
202
+ const constraints = project.decisionGate?.constraints || {};
203
+
204
+ return {
205
+ version: "1.0",
206
+ id: action.id || `task_${Date.now()}`,
207
+ createdAt: new Date().toISOString(),
208
+
209
+ objective: {
210
+ task: action.intent,
211
+ category: action.category,
212
+ successState: deriveSuccessState(action.intent),
213
+ },
214
+
215
+ constraints: {
216
+ budget: constraints.budget ? `$${constraints.budget}` : undefined,
217
+ time: constraints.timeHoursPerWeek ? `${constraints.timeHoursPerWeek}h/week available` : undefined,
218
+ skill: constraints.skillLevel,
219
+ urgency: constraints.urgency,
220
+ boundaries: deriveBoundaries(constraints),
221
+ },
222
+
223
+ context: {
224
+ project: `${project.name}${project.tldr ? ` — ${project.tldr}` : ""}`,
225
+ vision: project.artifacts?.vision?.content?.slice(0, 600),
226
+ plan: findRelevantPlan(action.intent, project.artifacts?.plan?.content),
227
+ priorWork: getPriorWork(action, project),
228
+ },
229
+
230
+ verification: deriveVerification(action.intent),
231
+ rollback: assessReversibility(action.intent),
232
+
233
+ continuation: {
234
+ reportBack: "When done, call phewsh_complete_task with your result. Include: what you did, what worked, any issues.",
235
+ nextActions: getNextActions(action, project),
236
+ },
237
+
238
+ runtime: inferRuntime(action.intent),
239
+ };
240
+ }
241
+
242
+ function deriveSuccessState(intent) {
243
+ const lower = intent.toLowerCase();
244
+ if (/deploy|publish|push|ship/.test(lower)) return "Live and accessible at target URL";
245
+ if (/set up|configure|install/.test(lower)) return "Service running and verified";
246
+ if (/write|draft|create/.test(lower)) return "Document complete and saved";
247
+ if (/research|investigate|analyze/.test(lower)) return "Findings documented with recommendations";
248
+ if (/fix|debug|resolve/.test(lower)) return "Issue no longer reproducible";
249
+ if (/build|implement|code/.test(lower)) return "Feature working and integrated";
250
+ if (/test|verify/.test(lower)) return "All checks pass";
251
+ return `"${intent}" completed and verified`;
252
+ }
253
+
254
+ function deriveBoundaries(constraints) {
255
+ const b = [];
256
+ if (constraints.budget && constraints.budget < 100) b.push("Do not use paid services without approval");
257
+ if (constraints.urgency === "critical") b.push("Do not spend time on non-essential polish");
258
+ if (constraints.autonomy === "hands-on") b.push("Do not make irreversible decisions without presenting options");
259
+ return b.length > 0 ? b : undefined;
260
+ }
261
+
262
+ function findRelevantPlan(intent, planContent) {
263
+ if (!planContent) return undefined;
264
+ const lines = planContent.split("\n");
265
+ const words = intent.toLowerCase().split(/\s+/).filter(w => w.length > 4);
266
+ let bestIdx = -1, bestScore = 0;
267
+ for (let i = 0; i < lines.length; i++) {
268
+ const score = words.filter(w => lines[i].toLowerCase().includes(w)).length;
269
+ if (score > bestScore) { bestScore = score; bestIdx = i; }
270
+ }
271
+ if (bestIdx >= 0 && bestScore >= 2) {
272
+ return lines.slice(Math.max(0, bestIdx - 2), bestIdx + 8).join("\n");
273
+ }
274
+ return undefined;
275
+ }
276
+
277
+ function getPriorWork(action, project) {
278
+ const done = (project.actions || [])
279
+ .filter(a => a.id !== action.id && a.state === "reconciled" && a.reconciliation?.decision === "accept")
280
+ .slice(-5)
281
+ .map(a => a.intent);
282
+ return done.length > 0 ? done : undefined;
283
+ }
284
+
285
+ function getNextActions(action, project) {
286
+ const next = (project.actions || [])
287
+ .filter(a => a.id !== action.id && a.state === "intended" && a.category === action.category)
288
+ .slice(0, 2)
289
+ .map(a => a.intent);
290
+ return next.length > 0 ? next : undefined;
291
+ }
292
+
293
+ function deriveVerification(intent) {
294
+ const lower = intent.toLowerCase();
295
+ const criteria = [];
296
+ if (/deploy|publish/.test(lower)) { criteria.push("Accessible at target URL", "No error responses"); }
297
+ else if (/build|implement|code/.test(lower)) { criteria.push("Compiles without errors", "Feature works as described"); }
298
+ else if (/write|draft/.test(lower)) { criteria.push("Content complete and coherent", "Saved to expected location"); }
299
+ else if (/set up|configure/.test(lower)) { criteria.push("Responds to test interaction", "Persists across restart"); }
300
+ else { criteria.push("Objective achieved as described", "No unintended side effects"); }
301
+ return { criteria };
302
+ }
303
+
304
+ function assessReversibility(intent) {
305
+ const lower = intent.toLowerCase();
306
+ if (/delete|remove|send email|publish|announce/.test(lower)) return { canRevert: false, warning: "Confirm before executing" };
307
+ if (/deploy|push|migrate/.test(lower)) return { canRevert: true, how: "Redeploy previous version" };
308
+ return { canRevert: true };
309
+ }
310
+
311
+ function inferRuntime(intent) {
312
+ const lower = intent.toLowerCase();
313
+ if (/deploy|push|git|npm|build|compile|test|ssh|terminal/.test(lower)) return { preferred: "claude-code", needs: ["filesystem", "terminal"] };
314
+ if (/browse|navigate|click|fill|submit|sign up|scrape/.test(lower)) return { preferred: "browser-agent", needs: ["browser", "network"] };
315
+ if (/meet|call|email|discuss|decide|buy|hire/.test(lower)) return { preferred: "human", needs: ["judgment", "social"] };
316
+ return { preferred: "claude-code", needs: ["text-generation"] };
317
+ }
318
+
319
+ // ─── Project Summary Helpers ────────────────────────────────────────────────
320
+
321
+ function summarizeProject(p) {
322
+ const actions = p.actions || [];
323
+ const total = actions.length;
324
+ const done = actions.filter(a => a.state === "reconciled" && a.reconciliation?.decision === "accept").length;
325
+ const pending = actions.filter(a => a.state === "intended").length;
326
+ const dispatched = actions.filter(a => a.state === "dispatched").length;
327
+ const gate = p.decisionGate;
328
+
329
+ return {
330
+ id: p.id,
331
+ name: p.name,
332
+ tldr: p.tldr,
333
+ source: p.source || "synced",
334
+ progress: total > 0 ? { done, total, percent: Math.round(done / total * 100) } : null,
335
+ pending,
336
+ dispatched,
337
+ feasibility: gate?.feasibility,
338
+ constraints: gate?.constraints ? {
339
+ budget: gate.constraints.budget || null,
340
+ timePerWeek: gate.constraints.timeHoursPerWeek || null,
341
+ urgency: gate.constraints.urgency,
342
+ autonomy: gate.constraints.autonomy,
343
+ } : null,
344
+ };
345
+ }
346
+
347
+ function buildFullBriefing(project) {
348
+ const sections = [];
349
+ sections.push(`# ${project.name}`);
350
+ if (project.tldr) sections.push(`> ${project.tldr}`);
351
+ sections.push("");
352
+
353
+ // Gate + Constraints
354
+ const gate = project.decisionGate;
355
+ if (gate) {
356
+ sections.push("## Your Operating Reality");
357
+ if (gate.goal) sections.push(`**Goal:** ${gate.goal}`);
358
+ if (gate.feasibility) sections.push(`**Feasibility:** ${gate.feasibility}`);
359
+ const c = gate.constraints || {};
360
+ const constraintLines = [];
361
+ if (c.budget) constraintLines.push(`Budget: $${c.budget}`);
362
+ if (c.timeHoursPerWeek) constraintLines.push(`Time: ${c.timeHoursPerWeek}h/week`);
363
+ if (c.skillLevel) constraintLines.push(`Skill: ${c.skillLevel}`);
364
+ if (c.urgency) constraintLines.push(`Urgency: ${c.urgency}`);
365
+ if (c.autonomy) constraintLines.push(`Autonomy: ${c.autonomy}`);
366
+ if (constraintLines.length > 0) {
367
+ sections.push("");
368
+ constraintLines.forEach(l => sections.push(`- ${l}`));
369
+ }
370
+ if (gate.successCriteria?.length > 0) {
371
+ sections.push("");
372
+ sections.push("**Success looks like:**");
373
+ gate.successCriteria.forEach(c => sections.push(`- ${c}`));
374
+ }
375
+ sections.push("");
376
+ }
377
+
378
+ // Vision (condensed)
379
+ if (project.artifacts?.vision?.content) {
380
+ sections.push("## Vision");
381
+ sections.push(project.artifacts.vision.content.slice(0, 800));
382
+ sections.push("");
383
+ }
384
+
385
+ // Plan (condensed)
386
+ if (project.artifacts?.plan?.content) {
387
+ sections.push("## Plan");
388
+ sections.push(project.artifacts.plan.content.slice(0, 1200));
389
+ sections.push("");
390
+ }
391
+
392
+ // Execution state
393
+ const actions = project.actions || [];
394
+ if (actions.length > 0) {
395
+ const done = actions.filter(a => a.state === "reconciled" && a.reconciliation?.decision === "accept");
396
+ const pending = actions.filter(a => a.state === "intended");
397
+ const dispatched = actions.filter(a => a.state === "dispatched");
398
+
399
+ sections.push("## Execution State");
400
+ sections.push(`Progress: ${done.length}/${actions.length} complete (${Math.round(done.length / actions.length * 100)}%)`);
401
+
402
+ if (dispatched.length > 0) {
403
+ sections.push("");
404
+ sections.push("**In progress:**");
405
+ dispatched.forEach(a => sections.push(`- ${a.intent}`));
406
+ }
407
+
408
+ if (done.length > 0) {
409
+ sections.push("");
410
+ sections.push("**Recently completed:**");
411
+ done.slice(-5).forEach(a => sections.push(`- ${a.intent}`));
412
+ }
413
+
414
+ if (pending.length > 0) {
415
+ sections.push("");
416
+ sections.push("**Ready for you:**");
417
+ const ordered = getOrderedTasks(project, "any").slice(0, 8);
418
+ ordered.forEach(a => {
419
+ const type = a.executionType || classifyExecutionType(a.intent);
420
+ sections.push(`- [${type}] ${a.intent}`);
421
+ });
422
+ if (pending.length > 8) sections.push(` ...and ${pending.length - 8} more`);
423
+ }
424
+ sections.push("");
425
+ }
426
+
427
+ return sections.join("\n");
428
+ }
429
+
430
+ // ─── MCP Server ─────────────────────────────────────────────────────────────
431
+
432
+ const server = new Server(
433
+ { name: "phewsh", version: "0.2.0" },
434
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
435
+ );
436
+
437
+ // ── Tools ──
438
+
439
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
440
+ tools: [
441
+ {
442
+ name: "phewsh_start",
443
+ description: "Start a work session. Returns everything you need: project context, constraints, what changed, and your next task — all in one call. Call this first.",
444
+ inputSchema: {
445
+ type: "object",
446
+ properties: {
447
+ project_id: { type: "string", description: "Project ID (use 'local' for .intent/ project in cwd). Omit to auto-detect." },
448
+ agent_id: { type: "string", description: "Your agent identifier (e.g. 'claude-code', 'cursor', 'custom-agent')" },
449
+ },
450
+ },
451
+ },
452
+ {
453
+ name: "phewsh_next_task",
454
+ description: "Get the next highest-priority task as a structured dispatch packet. Contains objective, constraints, verification criteria, and what to do after. Call this when you're ready for more work.",
455
+ inputSchema: {
456
+ type: "object",
457
+ properties: {
458
+ project_id: { type: "string", description: "Project ID" },
459
+ type: { type: "string", enum: ["agent", "ai", "human", "any"], description: "Filter by who can do it (default: agent)" },
460
+ skip_ids: { type: "array", items: { type: "string" }, description: "Task IDs to skip (already attempted)" },
461
+ },
462
+ required: ["project_id"],
463
+ },
464
+ },
465
+ {
466
+ name: "phewsh_complete_task",
467
+ description: "Report that you finished a task. Include what you did and whether it worked. Returns the next task automatically — keep the momentum going.",
468
+ inputSchema: {
469
+ type: "object",
470
+ properties: {
471
+ project_id: { type: "string", description: "Project ID" },
472
+ task_id: { type: "string", description: "The task ID from the dispatch packet" },
473
+ result: { type: "string", description: "What you accomplished — be specific about outputs and artifacts" },
474
+ success: { type: "boolean", description: "Did it work?" },
475
+ issues: { type: "string", description: "Problems encountered, decisions made, or things the human should know" },
476
+ agent_id: { type: "string", description: "Your agent identifier" },
477
+ },
478
+ required: ["project_id", "task_id", "result", "success"],
479
+ },
480
+ },
481
+ {
482
+ name: "phewsh_list_projects",
483
+ description: "List all available projects with their progress, constraints, and pending task counts.",
484
+ inputSchema: { type: "object", properties: {} },
485
+ },
486
+ {
487
+ name: "phewsh_get_context",
488
+ description: "Get the full project briefing — vision, plan, constraints, execution state. Use when you need deeper context on a specific project.",
489
+ inputSchema: {
490
+ type: "object",
491
+ properties: {
492
+ project_id: { type: "string", description: "Project ID" },
493
+ },
494
+ required: ["project_id"],
495
+ },
496
+ },
497
+ {
498
+ name: "phewsh_check_verification",
499
+ description: "Before marking a task complete, check its verification criteria. Returns what you need to confirm.",
500
+ inputSchema: {
501
+ type: "object",
502
+ properties: {
503
+ task_id: { type: "string", description: "The task ID to check verification for" },
504
+ project_id: { type: "string", description: "Project ID" },
505
+ },
506
+ required: ["task_id", "project_id"],
507
+ },
508
+ },
509
+ {
510
+ name: "phewsh_flag_blocker",
511
+ description: "Flag a task as blocked — something is preventing execution. The human will be notified.",
512
+ inputSchema: {
513
+ type: "object",
514
+ properties: {
515
+ project_id: { type: "string", description: "Project ID" },
516
+ task_id: { type: "string", description: "Task ID that's blocked" },
517
+ reason: { type: "string", description: "What's blocking execution" },
518
+ needs: { type: "string", description: "What you need to unblock (e.g. 'API key', 'human decision', 'access to service')" },
519
+ agent_id: { type: "string", description: "Your agent identifier" },
520
+ },
521
+ required: ["project_id", "task_id", "reason"],
522
+ },
523
+ },
524
+ {
525
+ name: "phewsh_session_history",
526
+ description: "See what agents have done on this project — completions, blockers, session starts. Useful for understanding context across sessions.",
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {
530
+ project_id: { type: "string", description: "Project ID" },
531
+ limit: { type: "number", description: "Number of events to return (default: 20)" },
532
+ },
533
+ required: ["project_id"],
534
+ },
535
+ },
536
+ ],
537
+ }));
538
+
539
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
540
+ const { name, arguments: args } = request.params;
541
+ const projects = loadProjects();
542
+
543
+ switch (name) {
544
+ // ─── START SESSION ──────────────────────────────────────────────────────
545
+ case "phewsh_start": {
546
+ // Auto-detect project if none specified
547
+ let projectId = args.project_id;
548
+ if (!projectId) {
549
+ if (projects.length === 1) projectId = projects[0].id;
550
+ else if (projects.find(p => p.id === "local")) projectId = "local";
551
+ else {
552
+ return { content: [{ type: "text", text: `Multiple projects available. Specify one:\n${projects.map(p => `- ${p.id}: ${p.name}`).join("\n")}` }] };
553
+ }
554
+ }
555
+
556
+ const project = projects.find(p => p.id === projectId);
557
+ if (!project) {
558
+ return { content: [{ type: "text", text: `Project "${projectId}" not found. Available: ${projects.map(p => `${p.id} (${p.name})`).join(", ")}` }] };
559
+ }
560
+
561
+ // Record session start
562
+ recordSession(args.agent_id, projectId, "session_start");
563
+
564
+ const sections = [];
565
+
566
+ // Greeting + orientation
567
+ sections.push(`# Session Started: ${project.name}`);
568
+ if (args.agent_id) sections.push(`Agent: ${args.agent_id}`);
569
+ sections.push("");
570
+
571
+ // What changed since last session
572
+ const recentSessions = getRecentSessions(projectId, 20);
573
+ const recentCompletions = recentSessions.filter(s => s.event === "task_complete" && s.timestamp > new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
574
+ const recentBlockers = recentSessions.filter(s => s.event === "blocker_flagged" && !s.resolved);
575
+
576
+ if (recentCompletions.length > 0 || recentBlockers.length > 0) {
577
+ sections.push("## Since Last Session");
578
+ if (recentCompletions.length > 0) {
579
+ sections.push("**Completed:**");
580
+ recentCompletions.slice(-5).forEach(s => sections.push(`- ${s.result?.split("\n")[0] || s.taskId}`));
581
+ }
582
+ if (recentBlockers.length > 0) {
583
+ sections.push("**Blocked:**");
584
+ recentBlockers.forEach(s => sections.push(`- ${s.reason} (needs: ${s.needs || "unknown"})`));
585
+ }
586
+ sections.push("");
587
+ }
588
+
589
+ // Full briefing
590
+ sections.push(buildFullBriefing(project));
591
+
592
+ // Your first task
593
+ const nextTasks = getOrderedTasks(project, "any");
594
+ if (nextTasks.length > 0) {
595
+ const first = nextTasks[0];
596
+ const packet = generatePacket(first, project);
597
+ sections.push("---");
598
+ sections.push("## Your First Task");
599
+ sections.push("");
600
+ sections.push(`**${packet.objective.task}**`);
601
+ sections.push(`Done when: ${packet.objective.successState}`);
602
+ sections.push(`Task ID: \`${packet.id}\``);
603
+ sections.push("");
604
+ if (packet.constraints.boundaries) {
605
+ sections.push("Boundaries:");
606
+ packet.constraints.boundaries.forEach(b => sections.push(`- ${b}`));
607
+ sections.push("");
608
+ }
609
+ sections.push("Verify before completing:");
610
+ packet.verification.criteria.forEach(c => sections.push(`- [ ] ${c}`));
611
+ sections.push("");
612
+ if (!packet.rollback.canRevert) {
613
+ sections.push(`**Warning:** This action is not easily reversible. ${packet.rollback.warning || "Confirm before executing."}`);
614
+ sections.push("");
615
+ }
616
+ if (packet.continuation.nextActions?.length > 0) {
617
+ sections.push("After this, next up:");
618
+ packet.continuation.nextActions.forEach(a => sections.push(`- ${a}`));
619
+ sections.push("");
620
+ }
621
+ sections.push(`When done, call \`phewsh_complete_task\` with task_id: "${packet.id}"`);
622
+ } else {
623
+ sections.push("---");
624
+ sections.push("## No Pending Tasks");
625
+ sections.push("All tasks are complete or dispatched. Check back later or ask the project owner for new work.");
626
+ }
627
+
628
+ return { content: [{ type: "text", text: sections.join("\n") }] };
629
+ }
630
+
631
+ // ─── NEXT TASK ──────────────────────────────────────────────────────────
632
+ case "phewsh_next_task": {
633
+ const project = projects.find(p => p.id === args.project_id);
634
+ if (!project) return { content: [{ type: "text", text: `Project "${args.project_id}" not found.` }] };
635
+
636
+ const typeFilter = args.type || "agent";
637
+ const skipIds = args.skip_ids || [];
638
+ let ordered = getOrderedTasks(project, typeFilter);
639
+ ordered = ordered.filter(a => !skipIds.includes(a.id));
640
+
641
+ if (ordered.length === 0) {
642
+ // Try broader filter
643
+ if (typeFilter !== "any") {
644
+ ordered = getOrderedTasks(project, "any").filter(a => !skipIds.includes(a.id));
645
+ if (ordered.length > 0) {
646
+ return { content: [{ type: "text", text: `No "${typeFilter}" tasks pending. ${ordered.length} tasks available with type filter "any". Call again with type: "any" if you can handle them.` }] };
647
+ }
648
+ }
649
+ return { content: [{ type: "text", text: "All caught up. No pending tasks matching your criteria." }] };
650
+ }
651
+
652
+ const action = ordered[0];
653
+ const packet = generatePacket(action, project);
654
+ const remaining = ordered.length - 1;
655
+
656
+ const output = {
657
+ ...packet,
658
+ _meta: {
659
+ remaining_tasks: remaining,
660
+ queue_position: 1,
661
+ total_in_project: (project.actions || []).length,
662
+ },
663
+ };
664
+
665
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
666
+ }
667
+
668
+ // ─── COMPLETE TASK ──────────────────────────────────────────────────────
669
+ case "phewsh_complete_task": {
670
+ const result = {
671
+ projectId: args.project_id,
672
+ taskId: args.task_id,
673
+ result: args.result,
674
+ success: args.success,
675
+ issues: args.issues,
676
+ agentId: args.agent_id,
677
+ reportedAt: new Date().toISOString(),
678
+ };
679
+
680
+ // Save result to disk
681
+ const resultFile = join(RESULTS_DIR, `${args.task_id}_${Date.now()}.json`);
682
+ writeFileSync(resultFile, JSON.stringify(result, null, 2));
683
+
684
+ // Record session event
685
+ recordSession(args.agent_id, args.project_id, "task_complete", {
686
+ taskId: args.task_id,
687
+ success: args.success,
688
+ result: args.result?.slice(0, 200),
689
+ });
690
+
691
+ // Update local .intent/status.md if applicable
692
+ if (args.project_id === "local") {
693
+ const intentDir = join(process.cwd(), ".intent");
694
+ if (existsSync(intentDir)) {
695
+ const statusFile = join(intentDir, "status.md");
696
+ const entry = `\n- [${args.success ? "x" : "!"}] ${args.result.split("\n")[0]} (${new Date().toISOString().split("T")[0]})${args.agent_id ? ` [${args.agent_id}]` : ""}\n`;
697
+ try {
698
+ if (existsSync(statusFile)) {
699
+ const existing = readFileSync(statusFile, "utf-8");
700
+ writeFileSync(statusFile, existing + entry);
701
+ } else {
702
+ writeFileSync(statusFile, `# Execution Log\n${entry}`);
703
+ }
704
+ } catch { /* non-critical — skip if write fails */ }
705
+ }
706
+ }
707
+
708
+ // Build response with auto-chain
709
+ const sections = [];
710
+ if (args.success) {
711
+ sections.push(`Task "${args.task_id}" completed successfully.`);
712
+ if (args.issues) sections.push(`Note: ${args.issues}`);
713
+ } else {
714
+ sections.push(`Task "${args.task_id}" reported as failed.`);
715
+ sections.push(`Issues: ${args.issues || args.result}`);
716
+ sections.push("The project owner will be notified to review.");
717
+ }
718
+
719
+ // Auto-chain: show next task
720
+ const project = projects.find(p => p.id === args.project_id);
721
+ if (project && args.success) {
722
+ const nextTasks = getOrderedTasks(project, "any")
723
+ .filter(a => a.id !== args.task_id);
724
+
725
+ if (nextTasks.length > 0) {
726
+ const next = nextTasks[0];
727
+ const packet = generatePacket(next, project);
728
+ sections.push("");
729
+ sections.push("---");
730
+ sections.push("## Next Task");
731
+ sections.push(`**${packet.objective.task}**`);
732
+ sections.push(`Done when: ${packet.objective.successState}`);
733
+ sections.push(`Task ID: \`${packet.id}\``);
734
+ sections.push("");
735
+ sections.push("Verify:");
736
+ packet.verification.criteria.forEach(c => sections.push(`- [ ] ${c}`));
737
+ sections.push("");
738
+ sections.push(`${nextTasks.length - 1} more tasks after this.`);
739
+ } else {
740
+ sections.push("");
741
+ sections.push("All tasks complete! Great work.");
742
+ }
743
+ }
744
+
745
+ return { content: [{ type: "text", text: sections.join("\n") }] };
746
+ }
747
+
748
+ // ─── LIST PROJECTS ──────────────────────────────────────────────────────
749
+ case "phewsh_list_projects": {
750
+ const summary = projects.map(summarizeProject);
751
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
752
+ }
753
+
754
+ // ─── GET CONTEXT ────────────────────────────────────────────────────────
755
+ case "phewsh_get_context": {
756
+ const project = projects.find(p => p.id === args.project_id);
757
+ if (!project) return { content: [{ type: "text", text: `Project "${args.project_id}" not found.` }] };
758
+ return { content: [{ type: "text", text: buildFullBriefing(project) }] };
759
+ }
760
+
761
+ // ─── CHECK VERIFICATION ─────────────────────────────────────────────────
762
+ case "phewsh_check_verification": {
763
+ const project = projects.find(p => p.id === args.project_id);
764
+ if (!project) return { content: [{ type: "text", text: `Project "${args.project_id}" not found.` }] };
765
+
766
+ const action = (project.actions || []).find(a => a.id === args.task_id);
767
+ if (!action) return { content: [{ type: "text", text: `Task "${args.task_id}" not found.` }] };
768
+
769
+ const verification = deriveVerification(action.intent);
770
+ const rollback = assessReversibility(action.intent);
771
+
772
+ const sections = [];
773
+ sections.push(`# Verification: ${action.intent}`);
774
+ sections.push("");
775
+ sections.push("Before marking this complete, confirm ALL of:");
776
+ sections.push("");
777
+ verification.criteria.forEach((c, i) => sections.push(`${i + 1}. ${c}`));
778
+ sections.push("");
779
+ if (!rollback.canRevert) {
780
+ sections.push(`**This is irreversible.** ${rollback.warning}`);
781
+ } else {
782
+ sections.push("This is reversible if needed.");
783
+ if (rollback.how) sections.push(`Rollback: ${rollback.how}`);
784
+ }
785
+
786
+ return { content: [{ type: "text", text: sections.join("\n") }] };
787
+ }
788
+
789
+ // ─── FLAG BLOCKER ───────────────────────────────────────────────────────
790
+ case "phewsh_flag_blocker": {
791
+ recordSession(args.agent_id, args.project_id, "blocker_flagged", {
792
+ taskId: args.task_id,
793
+ reason: args.reason,
794
+ needs: args.needs,
795
+ });
796
+
797
+ // Write blocker file for visibility
798
+ const blockerFile = join(RESULTS_DIR, `blocker_${args.task_id}_${Date.now()}.json`);
799
+ writeFileSync(blockerFile, JSON.stringify({
800
+ type: "blocker",
801
+ projectId: args.project_id,
802
+ taskId: args.task_id,
803
+ reason: args.reason,
804
+ needs: args.needs,
805
+ agentId: args.agent_id,
806
+ flaggedAt: new Date().toISOString(),
807
+ }, null, 2));
808
+
809
+ // Suggest next task that isn't blocked
810
+ const project = projects.find(p => p.id === args.project_id);
811
+ let suggestion = "";
812
+ if (project) {
813
+ const nextTasks = getOrderedTasks(project, "any").filter(a => a.id !== args.task_id);
814
+ if (nextTasks.length > 0) {
815
+ suggestion = `\n\nMeanwhile, you can work on: "${nextTasks[0].intent}" (task_id: ${nextTasks[0].id})`;
816
+ }
817
+ }
818
+
819
+ return { content: [{ type: "text", text: `Blocker flagged for task "${args.task_id}". The project owner will be notified.\n\nReason: ${args.reason}\nNeeds: ${args.needs || "Unknown"}${suggestion}` }] };
820
+ }
821
+
822
+ // ─── SESSION HISTORY ────────────────────────────────────────────────────
823
+ case "phewsh_session_history": {
824
+ const sessions = getRecentSessions(args.project_id, args.limit || 20);
825
+ if (sessions.length === 0) {
826
+ return { content: [{ type: "text", text: "No session history for this project yet." }] };
827
+ }
828
+
829
+ const sections = [`# Session History: ${args.project_id}`, ""];
830
+ sessions.reverse().forEach(s => {
831
+ const time = s.timestamp.split("T")[0];
832
+ const agent = s.agentId || "unknown";
833
+ switch (s.event) {
834
+ case "session_start":
835
+ sections.push(`- [${time}] ${agent} started session`);
836
+ break;
837
+ case "task_complete":
838
+ sections.push(`- [${time}] ${agent} completed: ${s.result || s.taskId} ${s.success ? "OK" : "FAILED"}`);
839
+ break;
840
+ case "blocker_flagged":
841
+ sections.push(`- [${time}] ${agent} blocked on: ${s.reason}`);
842
+ break;
843
+ default:
844
+ sections.push(`- [${time}] ${agent}: ${s.event}`);
845
+ }
846
+ });
847
+
848
+ return { content: [{ type: "text", text: sections.join("\n") }] };
849
+ }
850
+
851
+ default:
852
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
853
+ }
854
+ });
855
+
856
+ // ── Prompts (pre-built workflows agents can discover) ──
857
+
858
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
859
+ prompts: [
860
+ {
861
+ name: "work_session",
862
+ description: "Full work session: get briefed, execute tasks, report results. Best for autonomous agents that want to do as much as possible.",
863
+ arguments: [
864
+ { name: "project_id", description: "Project to work on (omit for auto-detect)", required: false },
865
+ { name: "max_tasks", description: "Maximum tasks to complete this session (default: 5)", required: false },
866
+ ],
867
+ },
868
+ {
869
+ name: "status_check",
870
+ description: "Quick status check: what's done, what's pending, any blockers. No execution.",
871
+ arguments: [
872
+ { name: "project_id", description: "Project to check", required: false },
873
+ ],
874
+ },
875
+ {
876
+ name: "review_results",
877
+ description: "Review recent agent work: what was completed, what failed, what's blocked. For human oversight.",
878
+ arguments: [
879
+ { name: "project_id", description: "Project to review", required: false },
880
+ ],
881
+ },
882
+ ],
883
+ }));
884
+
885
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
886
+ const { name, arguments: args } = request.params;
887
+ const projects = loadProjects();
888
+
889
+ switch (name) {
890
+ case "work_session": {
891
+ const projectId = args?.project_id || (projects.length === 1 ? projects[0].id : "local");
892
+ const maxTasks = args?.max_tasks || 5;
893
+ const project = projects.find(p => p.id === projectId);
894
+
895
+ return {
896
+ messages: [
897
+ {
898
+ role: "user",
899
+ content: {
900
+ type: "text",
901
+ text: `You are an autonomous agent working on the "${project?.name || projectId}" project via PHEWSH coordination.
902
+
903
+ Your workflow:
904
+ 1. Call \`phewsh_start\` with project_id: "${projectId}" to get your briefing and first task
905
+ 2. Execute the task described in the dispatch packet
906
+ 3. Call \`phewsh_complete_task\` with your results — it will auto-chain to the next task
907
+ 4. Repeat until you've completed ${maxTasks} tasks or hit a blocker
908
+ 5. If blocked, call \`phewsh_flag_blocker\` and move to the next available task
909
+
910
+ Rules:
911
+ - Always check verification criteria before marking complete
912
+ - If a task is irreversible, confirm with the user before executing
913
+ - Report issues honestly — failed tasks help the project owner make better decisions
914
+ - Stay within the constraints provided in each dispatch packet
915
+ - You are the executor, not the decision-maker. Execute what's been planned.
916
+
917
+ Start now by calling phewsh_start.`,
918
+ },
919
+ },
920
+ ],
921
+ };
922
+ }
923
+
924
+ case "status_check": {
925
+ const projectId = args?.project_id || (projects.length === 1 ? projects[0].id : "local");
926
+ return {
927
+ messages: [
928
+ {
929
+ role: "user",
930
+ content: {
931
+ type: "text",
932
+ text: `Give me a quick status check on the "${projectId}" project. Call \`phewsh_list_projects\` and \`phewsh_session_history\` with project_id: "${projectId}" to get the current state. Summarize: what's done, what's pending, any blockers, and what needs my attention.`,
933
+ },
934
+ },
935
+ ],
936
+ };
937
+ }
938
+
939
+ case "review_results": {
940
+ const projectId = args?.project_id || (projects.length === 1 ? projects[0].id : "local");
941
+ return {
942
+ messages: [
943
+ {
944
+ role: "user",
945
+ content: {
946
+ type: "text",
947
+ text: `Review the recent agent work on "${projectId}". Call \`phewsh_session_history\` with project_id: "${projectId}" and limit: 30. Then summarize:
948
+ - What was completed successfully
949
+ - What failed and why
950
+ - Any unresolved blockers
951
+ - Recommendations for what the project owner should do next
952
+
953
+ Be honest and specific. This is for human oversight.`,
954
+ },
955
+ },
956
+ ],
957
+ };
958
+ }
959
+
960
+ default:
961
+ return { messages: [] };
962
+ }
963
+ });
964
+
965
+ // ── Resources ──
966
+
967
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
968
+ const projects = loadProjects();
969
+ const resources = [];
970
+
971
+ for (const project of projects) {
972
+ resources.push({
973
+ uri: `phewsh://projects/${project.id}/briefing`,
974
+ name: `${project.name} — Full Briefing`,
975
+ description: `Complete project context: vision, plan, constraints, execution state`,
976
+ mimeType: "text/markdown",
977
+ });
978
+
979
+ if (project.artifacts) {
980
+ for (const [kind, artifact] of Object.entries(project.artifacts)) {
981
+ if (artifact?.content) {
982
+ resources.push({
983
+ uri: `phewsh://projects/${project.id}/artifacts/${kind}`,
984
+ name: `${project.name} — ${kind}`,
985
+ description: `${kind} artifact`,
986
+ mimeType: "text/markdown",
987
+ });
988
+ }
989
+ }
990
+ }
991
+ }
992
+
993
+ return { resources };
994
+ });
995
+
996
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
997
+ const { uri } = request.params;
998
+ const projects = loadProjects();
999
+
1000
+ const match = uri.match(/^phewsh:\/\/projects\/([^/]+)\/(briefing|artifacts\/(.+))$/);
1001
+ if (!match) return { contents: [{ uri, text: "Invalid resource URI", mimeType: "text/plain" }] };
1002
+
1003
+ const [, projectId, path, artifactKind] = match;
1004
+ const project = projects.find(p => p.id === projectId);
1005
+ if (!project) return { contents: [{ uri, text: `Project "${projectId}" not found`, mimeType: "text/plain" }] };
1006
+
1007
+ if (path === "briefing") {
1008
+ return { contents: [{ uri, text: buildFullBriefing(project), mimeType: "text/markdown" }] };
1009
+ }
1010
+
1011
+ if (artifactKind && project.artifacts?.[artifactKind]?.content) {
1012
+ return { contents: [{ uri, text: project.artifacts[artifactKind].content, mimeType: "text/markdown" }] };
1013
+ }
1014
+
1015
+ return { contents: [{ uri, text: "Resource not found", mimeType: "text/plain" }] };
1016
+ });
1017
+
1018
+ // ── Start ──
1019
+
1020
+ async function main() {
1021
+ const transport = new StdioServerTransport();
1022
+ await server.connect(transport);
1023
+ console.error("PHEWSH MCP Server v0.2.0 — coordination layer active");
1024
+ }
1025
+
1026
+ main().catch((err) => {
1027
+ console.error("Failed to start PHEWSH MCP server:", err);
1028
+ process.exit(1);
1029
+ });