@goondocks/myco 0.18.1 → 0.19.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 (173) hide show
  1. package/README.md +17 -130
  2. package/dist/{agent-run-I4O2K2CK.js → agent-run-EADUYYAS.js} +6 -6
  3. package/dist/{agent-tasks-UOW5BQIB.js → agent-tasks-GC77JXQB.js} +6 -6
  4. package/dist/{chunk-W7WENJ6F.js → chunk-2CKDAFSX.js} +2 -2
  5. package/dist/{chunk-TOER6RNC.js → chunk-2DF4OZ2D.js} +2 -2
  6. package/dist/{chunk-CURS2TNP.js → chunk-2LN2BBKA.js} +2 -2
  7. package/dist/{chunk-RIDSOQDR.js → chunk-2OO3BRFK.js} +2 -2
  8. package/dist/{chunk-RAV5YMRU.js → chunk-3TPD6HEF.js} +4 -4
  9. package/dist/{chunk-D7TYRPRM.js → chunk-6LQIMRTC.js} +145 -145
  10. package/dist/chunk-6LQIMRTC.js.map +1 -0
  11. package/dist/{chunk-NI23QCHB.js → chunk-AELJ4PS5.js} +5 -5
  12. package/dist/{chunk-XWOQL4XN.js → chunk-CYBC2HZ3.js} +3 -3
  13. package/dist/chunk-EM63ZFKA.js +166 -0
  14. package/dist/chunk-EM63ZFKA.js.map +1 -0
  15. package/dist/{chunk-O3TRN3RC.js → chunk-INWD6AIQ.js} +2 -2
  16. package/dist/{chunk-CML4MCYF.js → chunk-KSXTNYXO.js} +2 -2
  17. package/dist/{chunk-TCSVDQF5.js → chunk-LLJMDXO2.js} +58 -58
  18. package/dist/chunk-LLJMDXO2.js.map +1 -0
  19. package/dist/{chunk-2V7HR7HB.js → chunk-MDEUXYJG.js} +4 -4
  20. package/dist/{chunk-YZPI2Y3E.js → chunk-MS6FDV45.js} +3 -3
  21. package/dist/{chunk-TLK46KKD.js → chunk-N77K772N.js} +3 -3
  22. package/dist/{chunk-E4VLWIJC.js → chunk-ODXLRR4U.js} +1 -1
  23. package/dist/{chunk-LSP5HYOO.js → chunk-OZF5EURR.js} +4 -4
  24. package/dist/{chunk-IB76KGBY.js → chunk-POEPHBQK.js} +1 -1
  25. package/dist/{chunk-TZAXQKO6.js → chunk-REN37KYI.js} +2 -2
  26. package/dist/{chunk-C3EGL5JX.js → chunk-RXROZBSK.js} +8 -8
  27. package/dist/{chunk-U3J2DDSR.js → chunk-SCI55NKY.js} +2 -2
  28. package/dist/{chunk-GDY63YAW.js → chunk-U6PF3YII.js} +79 -79
  29. package/dist/chunk-U6PF3YII.js.map +1 -0
  30. package/dist/{chunk-DPSLJ242.js → chunk-UVKQ62II.js} +3 -3
  31. package/dist/{chunk-N75GMQGA.js → chunk-UW6DGPSV.js} +3 -3
  32. package/dist/{chunk-CKJAWZQE.js → chunk-W4VHC2ES.js} +11 -3
  33. package/dist/chunk-W4VHC2ES.js.map +1 -0
  34. package/dist/{chunk-75AZFBFW.js → chunk-YPWF322W.js} +3 -3
  35. package/dist/{cli-D3TJYJ2U.js → cli-X7CFP4YD.js} +39 -39
  36. package/dist/{client-4LLEXLVK.js → client-YA33HUFY.js} +4 -4
  37. package/dist/{config-DA4IUVFL.js → config-RFB2DJC6.js} +6 -6
  38. package/dist/{detect-SZ2KDUF4.js → detect-BEOIHGBC.js} +5 -5
  39. package/dist/{detect-providers-PSVKXTWE.js → detect-providers-2OQBU4VX.js} +4 -4
  40. package/dist/{doctor-KCTXPX5D.js → doctor-FAH7N66M.js} +11 -11
  41. package/dist/{executor-UYIZC3L5.js → executor-ICTRRUBY.js} +17 -17
  42. package/dist/{init-QFNBKKDC.js → init-PTJEOTJV.js} +15 -15
  43. package/dist/{llm-SMA5ZEAW.js → llm-7D2OGDEK.js} +4 -4
  44. package/dist/{loader-Q3P3R4UP.js → loader-O2JFO2UC.js} +6 -6
  45. package/dist/{loader-SKKUMT5C.js → loader-VPE4RCIF.js} +6 -6
  46. package/dist/{main-5THODR77.js → main-EIKBLOUL.js} +341 -84
  47. package/dist/main-EIKBLOUL.js.map +1 -0
  48. package/dist/{open-7737CSPN.js → open-2JCSOLZS.js} +6 -6
  49. package/dist/{post-compact-2TJ5FPZH.js → post-compact-2HPPWPBI.js} +10 -10
  50. package/dist/{post-tool-use-FRTSICC3.js → post-tool-use-TWBBBABS.js} +9 -9
  51. package/dist/{post-tool-use-failure-KYO2NCNB.js → post-tool-use-failure-LIJYR4KL.js} +10 -10
  52. package/dist/{pre-compact-J6GCJEJR.js → pre-compact-II2CMNTG.js} +10 -10
  53. package/dist/{provider-check-AE3L5Z6R.js → provider-check-KEQNQ6LO.js} +4 -4
  54. package/dist/{registry-O2NZLO3V.js → registry-X5FDGYXT.js} +7 -7
  55. package/dist/{remove-3WZZC7AX.js → remove-L5MVYBOY.js} +11 -11
  56. package/dist/{resolution-events-XWYLLDRK.js → resolution-events-MVIZMONR.js} +4 -4
  57. package/dist/{restart-HUHEFOXU.js → restart-VIT3JBD6.js} +7 -7
  58. package/dist/{search-ZGN3LDXG.js → search-O6BB5MTO.js} +7 -7
  59. package/dist/{server-PTXLVVEE.js → server-O3UPJVBR.js} +258 -173
  60. package/dist/server-O3UPJVBR.js.map +1 -0
  61. package/dist/{session-7VV3IQMO.js → session-5JV3DQIK.js} +8 -8
  62. package/dist/{session-end-SMU55UCM.js → session-end-PZ2OXBGG.js} +9 -9
  63. package/dist/{session-start-NIMWEOIZ.js → session-start-FDGM56BX.js} +14 -14
  64. package/dist/{setup-llm-7S3VPAPN.js → setup-llm-MQK557BB.js} +10 -10
  65. package/dist/src/cli.js +1 -1
  66. package/dist/src/daemon/main.js +1 -1
  67. package/dist/src/hooks/post-tool-use.js +1 -1
  68. package/dist/src/hooks/session-end.js +1 -1
  69. package/dist/src/hooks/session-start.js +1 -1
  70. package/dist/src/hooks/stop.js +1 -1
  71. package/dist/src/hooks/user-prompt-submit.js +1 -1
  72. package/dist/src/mcp/server.js +1 -1
  73. package/dist/{stats-GEOQ2DFF.js → stats-2STTARTC.js} +11 -11
  74. package/dist/{stop-7AKYBJJ2.js → stop-WNKCMCGO.js} +9 -9
  75. package/dist/{stop-failure-NLE2EURG.js → stop-failure-6GTOBVTN.js} +10 -10
  76. package/dist/{subagent-start-LBNZF2TG.js → subagent-start-VJF5YKVX.js} +10 -10
  77. package/dist/{subagent-stop-B2Z5GYAB.js → subagent-stop-UW6HMICY.js} +10 -10
  78. package/dist/{task-completed-PO5TETJ7.js → task-completed-U4Q3XXLX.js} +10 -10
  79. package/dist/{team-DPNP2RN7.js → team-N6TXS2PF.js} +148 -103
  80. package/dist/team-N6TXS2PF.js.map +1 -0
  81. package/dist/ui/assets/{index-CiI1fwas.js → index-CHIm98OP.js} +48 -48
  82. package/dist/ui/index.html +1 -1
  83. package/dist/{update-WBWB5URU.js → update-ZYCOWKMD.js} +11 -11
  84. package/dist/{user-prompt-submit-IZJC3NV7.js → user-prompt-submit-SOYL4OWF.js} +9 -9
  85. package/dist/{verify-FNSP62I3.js → verify-P37PQ4YM.js} +8 -8
  86. package/dist/{version-QEVU66NT.js → version-XAWC277D.js} +2 -2
  87. package/package.json +19 -2
  88. package/CONTRIBUTING.md +0 -132
  89. package/dist/chunk-CKJAWZQE.js.map +0 -1
  90. package/dist/chunk-D7TYRPRM.js.map +0 -1
  91. package/dist/chunk-GDY63YAW.js.map +0 -1
  92. package/dist/chunk-RJMXDUMA.js +0 -40
  93. package/dist/chunk-RJMXDUMA.js.map +0 -1
  94. package/dist/chunk-TCSVDQF5.js.map +0 -1
  95. package/dist/main-5THODR77.js.map +0 -1
  96. package/dist/server-PTXLVVEE.js.map +0 -1
  97. package/dist/src/worker/package-lock.json +0 -4338
  98. package/dist/src/worker/package.json +0 -22
  99. package/dist/src/worker/src/auth.ts +0 -31
  100. package/dist/src/worker/src/index.ts +0 -476
  101. package/dist/src/worker/src/mcp/auth.ts +0 -65
  102. package/dist/src/worker/src/mcp/server.ts +0 -53
  103. package/dist/src/worker/src/mcp/tools/context.ts +0 -13
  104. package/dist/src/worker/src/mcp/tools/get.ts +0 -15
  105. package/dist/src/worker/src/mcp/tools/graph.ts +0 -35
  106. package/dist/src/worker/src/mcp/tools/search.ts +0 -32
  107. package/dist/src/worker/src/mcp/tools/sessions.ts +0 -24
  108. package/dist/src/worker/src/mcp/tools/skills.ts +0 -16
  109. package/dist/src/worker/src/mcp/tools/team.ts +0 -9
  110. package/dist/src/worker/src/schema.ts +0 -326
  111. package/dist/src/worker/src/search-helpers.ts +0 -70
  112. package/dist/src/worker/tsconfig.json +0 -16
  113. package/dist/src/worker/wrangler.toml +0 -30
  114. package/dist/team-DPNP2RN7.js.map +0 -1
  115. /package/dist/{agent-run-I4O2K2CK.js.map → agent-run-EADUYYAS.js.map} +0 -0
  116. /package/dist/{agent-tasks-UOW5BQIB.js.map → agent-tasks-GC77JXQB.js.map} +0 -0
  117. /package/dist/{chunk-W7WENJ6F.js.map → chunk-2CKDAFSX.js.map} +0 -0
  118. /package/dist/{chunk-TOER6RNC.js.map → chunk-2DF4OZ2D.js.map} +0 -0
  119. /package/dist/{chunk-CURS2TNP.js.map → chunk-2LN2BBKA.js.map} +0 -0
  120. /package/dist/{chunk-RIDSOQDR.js.map → chunk-2OO3BRFK.js.map} +0 -0
  121. /package/dist/{chunk-RAV5YMRU.js.map → chunk-3TPD6HEF.js.map} +0 -0
  122. /package/dist/{chunk-NI23QCHB.js.map → chunk-AELJ4PS5.js.map} +0 -0
  123. /package/dist/{chunk-XWOQL4XN.js.map → chunk-CYBC2HZ3.js.map} +0 -0
  124. /package/dist/{chunk-O3TRN3RC.js.map → chunk-INWD6AIQ.js.map} +0 -0
  125. /package/dist/{chunk-CML4MCYF.js.map → chunk-KSXTNYXO.js.map} +0 -0
  126. /package/dist/{chunk-2V7HR7HB.js.map → chunk-MDEUXYJG.js.map} +0 -0
  127. /package/dist/{chunk-YZPI2Y3E.js.map → chunk-MS6FDV45.js.map} +0 -0
  128. /package/dist/{chunk-TLK46KKD.js.map → chunk-N77K772N.js.map} +0 -0
  129. /package/dist/{chunk-E4VLWIJC.js.map → chunk-ODXLRR4U.js.map} +0 -0
  130. /package/dist/{chunk-LSP5HYOO.js.map → chunk-OZF5EURR.js.map} +0 -0
  131. /package/dist/{chunk-IB76KGBY.js.map → chunk-POEPHBQK.js.map} +0 -0
  132. /package/dist/{chunk-TZAXQKO6.js.map → chunk-REN37KYI.js.map} +0 -0
  133. /package/dist/{chunk-C3EGL5JX.js.map → chunk-RXROZBSK.js.map} +0 -0
  134. /package/dist/{chunk-U3J2DDSR.js.map → chunk-SCI55NKY.js.map} +0 -0
  135. /package/dist/{chunk-DPSLJ242.js.map → chunk-UVKQ62II.js.map} +0 -0
  136. /package/dist/{chunk-N75GMQGA.js.map → chunk-UW6DGPSV.js.map} +0 -0
  137. /package/dist/{chunk-75AZFBFW.js.map → chunk-YPWF322W.js.map} +0 -0
  138. /package/dist/{cli-D3TJYJ2U.js.map → cli-X7CFP4YD.js.map} +0 -0
  139. /package/dist/{client-4LLEXLVK.js.map → client-YA33HUFY.js.map} +0 -0
  140. /package/dist/{config-DA4IUVFL.js.map → config-RFB2DJC6.js.map} +0 -0
  141. /package/dist/{detect-SZ2KDUF4.js.map → detect-BEOIHGBC.js.map} +0 -0
  142. /package/dist/{detect-providers-PSVKXTWE.js.map → detect-providers-2OQBU4VX.js.map} +0 -0
  143. /package/dist/{doctor-KCTXPX5D.js.map → doctor-FAH7N66M.js.map} +0 -0
  144. /package/dist/{executor-UYIZC3L5.js.map → executor-ICTRRUBY.js.map} +0 -0
  145. /package/dist/{init-QFNBKKDC.js.map → init-PTJEOTJV.js.map} +0 -0
  146. /package/dist/{llm-SMA5ZEAW.js.map → llm-7D2OGDEK.js.map} +0 -0
  147. /package/dist/{loader-Q3P3R4UP.js.map → loader-O2JFO2UC.js.map} +0 -0
  148. /package/dist/{loader-SKKUMT5C.js.map → loader-VPE4RCIF.js.map} +0 -0
  149. /package/dist/{open-7737CSPN.js.map → open-2JCSOLZS.js.map} +0 -0
  150. /package/dist/{post-compact-2TJ5FPZH.js.map → post-compact-2HPPWPBI.js.map} +0 -0
  151. /package/dist/{post-tool-use-FRTSICC3.js.map → post-tool-use-TWBBBABS.js.map} +0 -0
  152. /package/dist/{post-tool-use-failure-KYO2NCNB.js.map → post-tool-use-failure-LIJYR4KL.js.map} +0 -0
  153. /package/dist/{pre-compact-J6GCJEJR.js.map → pre-compact-II2CMNTG.js.map} +0 -0
  154. /package/dist/{provider-check-AE3L5Z6R.js.map → provider-check-KEQNQ6LO.js.map} +0 -0
  155. /package/dist/{registry-O2NZLO3V.js.map → registry-X5FDGYXT.js.map} +0 -0
  156. /package/dist/{remove-3WZZC7AX.js.map → remove-L5MVYBOY.js.map} +0 -0
  157. /package/dist/{resolution-events-XWYLLDRK.js.map → resolution-events-MVIZMONR.js.map} +0 -0
  158. /package/dist/{restart-HUHEFOXU.js.map → restart-VIT3JBD6.js.map} +0 -0
  159. /package/dist/{search-ZGN3LDXG.js.map → search-O6BB5MTO.js.map} +0 -0
  160. /package/dist/{session-7VV3IQMO.js.map → session-5JV3DQIK.js.map} +0 -0
  161. /package/dist/{session-end-SMU55UCM.js.map → session-end-PZ2OXBGG.js.map} +0 -0
  162. /package/dist/{session-start-NIMWEOIZ.js.map → session-start-FDGM56BX.js.map} +0 -0
  163. /package/dist/{setup-llm-7S3VPAPN.js.map → setup-llm-MQK557BB.js.map} +0 -0
  164. /package/dist/{stats-GEOQ2DFF.js.map → stats-2STTARTC.js.map} +0 -0
  165. /package/dist/{stop-7AKYBJJ2.js.map → stop-WNKCMCGO.js.map} +0 -0
  166. /package/dist/{stop-failure-NLE2EURG.js.map → stop-failure-6GTOBVTN.js.map} +0 -0
  167. /package/dist/{subagent-start-LBNZF2TG.js.map → subagent-start-VJF5YKVX.js.map} +0 -0
  168. /package/dist/{subagent-stop-B2Z5GYAB.js.map → subagent-stop-UW6HMICY.js.map} +0 -0
  169. /package/dist/{task-completed-PO5TETJ7.js.map → task-completed-U4Q3XXLX.js.map} +0 -0
  170. /package/dist/{update-WBWB5URU.js.map → update-ZYCOWKMD.js.map} +0 -0
  171. /package/dist/{user-prompt-submit-IZJC3NV7.js.map → user-prompt-submit-SOYL4OWF.js.map} +0 -0
  172. /package/dist/{verify-FNSP62I3.js.map → verify-P37PQ4YM.js.map} +0 -0
  173. /package/dist/{version-QEVU66NT.js.map → version-XAWC277D.js.map} +0 -0
@@ -1,22 +0,0 @@
1
- {
2
- "name": "@goondocks/myco-worker",
3
- "version": "0.1.0",
4
- "private": true,
5
- "description": "Myco team sync Cloudflare Worker",
6
- "type": "module",
7
- "scripts": {
8
- "dev": "wrangler dev",
9
- "deploy": "wrangler deploy",
10
- "check": "tsc --noEmit"
11
- },
12
- "devDependencies": {
13
- "@cloudflare/workers-types": "^4.20250320.0",
14
- "typescript": "^5.7.0",
15
- "wrangler": "^4.0.0"
16
- },
17
- "dependencies": {
18
- "@modelcontextprotocol/sdk": "^1.29.0",
19
- "agents": "^0.10.0",
20
- "zod": "^4.3.6"
21
- }
22
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Bearer token auth for the Myco team sync worker.
3
- */
4
-
5
- export interface AuthEnv {
6
- MYCO_TEAM_API_KEY: string;
7
- }
8
-
9
- /**
10
- * Validate the Authorization header against the configured API key.
11
- * Returns null on success, or a 401 Response on failure.
12
- */
13
- export function validateAuth(request: Request, env: AuthEnv): Response | null {
14
- const header = request.headers.get('Authorization');
15
- if (!header) {
16
- return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
17
- status: 401,
18
- headers: { 'Content-Type': 'application/json' },
19
- });
20
- }
21
-
22
- const token = header.startsWith('Bearer ') ? header.slice(7) : '';
23
- if (!token || token !== env.MYCO_TEAM_API_KEY) {
24
- return new Response(JSON.stringify({ error: 'Invalid API key' }), {
25
- status: 401,
26
- headers: { 'Content-Type': 'application/json' },
27
- });
28
- }
29
-
30
- return null;
31
- }
@@ -1,476 +0,0 @@
1
- /**
2
- * Myco Team Sync — Cloudflare Worker
3
- *
4
- * Provides team-wide storage and vector search backed by D1 + Vectorize + Workers AI.
5
- * Each node (machine) pushes its outbox records here; the worker deduplicates by
6
- * content_hash and maintains a shared Vectorize index for semantic search.
7
- */
8
-
9
- import { initD1Schema } from './schema';
10
- import { validateAuth } from './auth';
11
- import { createMcpHandler } from 'agents/mcp';
12
- import { createMcpServerInstance } from './mcp/server';
13
- import { authenticateMcpRequest, ensureMcpToken, rotateMcpToken, getMcpTokenHash, MCP_TOKEN_KEY } from './mcp/auth';
14
- import { embedText, hydrateVectorMatches } from './search-helpers';
15
-
16
- // ---------------------------------------------------------------------------
17
- // Types
18
- // ---------------------------------------------------------------------------
19
-
20
- export interface Env {
21
- MYCO_TEAM_DB: D1Database;
22
- MYCO_TEAM_VECTORS: VectorizeIndex;
23
- AI: Ai;
24
- MYCO_TEAM_API_KEY: string;
25
- SYNC_PROTOCOL_VERSION: string;
26
- MYCO_SECRETS: KVNamespace;
27
- }
28
-
29
- /** Tables that support embedding in Vectorize. */
30
- const EMBEDDABLE_TABLES: Record<string, string> = {
31
- spores: 'content',
32
- sessions: 'summary',
33
- plans: 'content',
34
- artifacts: 'content',
35
- skill_records: 'description',
36
- };
37
-
38
- /** All tables the sync endpoint accepts records for. */
39
- const SYNCED_TABLES = [
40
- 'sessions',
41
- 'prompt_batches',
42
- 'spores',
43
- 'entities',
44
- 'graph_edges',
45
- 'entity_mentions',
46
- 'resolution_events',
47
- 'plans',
48
- 'artifacts',
49
- 'digest_extracts',
50
- 'skill_candidates',
51
- 'skill_records',
52
- 'skill_usage',
53
- ] as const;
54
-
55
- type SyncedTable = (typeof SYNCED_TABLES)[number];
56
-
57
- interface SyncRecord {
58
- table: SyncedTable;
59
- operation: 'upsert' | 'delete';
60
- id: string;
61
- machine_id: string;
62
- content_hash?: string | null;
63
- data: Record<string, unknown>;
64
- }
65
-
66
- interface ConnectPayload {
67
- machine_id: string;
68
- package_version?: string;
69
- schema_version?: number;
70
- sync_protocol_version?: number;
71
- }
72
-
73
- interface SyncPayload {
74
- sync_protocol_version: number;
75
- machine_id: string;
76
- records: SyncRecord[];
77
- }
78
-
79
- interface SearchResult {
80
- table: string;
81
- id: string;
82
- machine_id: string;
83
- score: number;
84
- data: Record<string, unknown>;
85
- }
86
-
87
- // ---------------------------------------------------------------------------
88
- // Constants
89
- // ---------------------------------------------------------------------------
90
-
91
- /** Whether initD1Schema has already run for this Worker instance. */
92
- let schemaInitialized = false;
93
-
94
- const JSON_HEADERS = { 'Content-Type': 'application/json' } as const;
95
- const DEFAULT_TOP_K = 10;
96
- const MAX_TOP_K = 50;
97
-
98
- // ---------------------------------------------------------------------------
99
- // Helpers
100
- // ---------------------------------------------------------------------------
101
-
102
- function jsonResponse(body: unknown, status = 200): Response {
103
- return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
104
- }
105
-
106
- function errorResponse(message: string, status: number): Response {
107
- return jsonResponse({ error: message }, status);
108
- }
109
-
110
- function epochSeconds(): number {
111
- return Math.floor(Date.now() / 1000);
112
- }
113
-
114
- /**
115
- * Build a Vectorize namespace ID for a record: `{table}:{id}:{machine_id}`.
116
- */
117
- function vectorId(table: string, id: string, machineId: string): string {
118
- return `${table}:${id}:${machineId}`;
119
- }
120
-
121
-
122
- /**
123
- * Build column names and placeholders for an INSERT OR REPLACE from a data object.
124
- * Always includes id and machine_id.
125
- */
126
- function buildInsertParts(
127
- table: string,
128
- data: Record<string, unknown>,
129
- id: string,
130
- machineId: string,
131
- ): { sql: string; values: unknown[] } {
132
- const row: Record<string, unknown> = { id, machine_id: machineId, ...data, synced_at: epochSeconds() };
133
-
134
- // Remove fields that don't belong in D1 (local-only fields)
135
- delete row.embedded;
136
-
137
- const columns = Object.keys(row);
138
- const placeholders = columns.map(() => '?').join(', ');
139
- const quotedColumns = columns.map((c) => (c === 'user' ? `"user"` : c)).join(', ');
140
-
141
- return {
142
- sql: `INSERT OR REPLACE INTO ${table} (${quotedColumns}) VALUES (${placeholders})`,
143
- values: Object.values(row),
144
- };
145
- }
146
-
147
- // ---------------------------------------------------------------------------
148
- // Route handlers
149
- // ---------------------------------------------------------------------------
150
-
151
- async function handleHealth(env: Env): Promise<Response> {
152
- const [countResult, storedToken] = await Promise.all([
153
- env.MYCO_TEAM_DB.prepare('SELECT COUNT(*) as count FROM nodes').first<{ count: number }>(),
154
- env.MYCO_SECRETS.get(MCP_TOKEN_KEY),
155
- ]);
156
-
157
- const count = countResult?.count ?? 0;
158
- const mcpTokenHash = storedToken ? getMcpTokenHash(storedToken) : null;
159
-
160
- return jsonResponse({ status: 'ok', nodes: count, mcp_token_hash: mcpTokenHash });
161
- }
162
-
163
- async function handleConnect(request: Request, env: Env): Promise<Response> {
164
- const body = (await request.json()) as ConnectPayload;
165
- if (!body.machine_id) {
166
- return errorResponse('machine_id is required', 400);
167
- }
168
-
169
- const now = epochSeconds();
170
-
171
- await env.MYCO_TEAM_DB.prepare(
172
- `INSERT INTO nodes (machine_id, package_version, schema_version, sync_protocol_version, last_seen, registered_at)
173
- VALUES (?, ?, ?, ?, ?, ?)
174
- ON CONFLICT (machine_id) DO UPDATE SET
175
- package_version = excluded.package_version,
176
- schema_version = excluded.schema_version,
177
- sync_protocol_version = excluded.sync_protocol_version,
178
- last_seen = excluded.last_seen`,
179
- ).bind(
180
- body.machine_id,
181
- body.package_version ?? null,
182
- body.schema_version ?? null,
183
- body.sync_protocol_version ?? null,
184
- now,
185
- now,
186
- ).run();
187
-
188
- // Return team config
189
- const configRows = await env.MYCO_TEAM_DB.prepare('SELECT key, value FROM team_config').all<{
190
- key: string;
191
- value: string;
192
- }>();
193
- const config: Record<string, string> = {};
194
- for (const row of configRows.results) {
195
- config[row.key] = row.value;
196
- }
197
-
198
- // MCP token is stored in KV (encrypted at rest), not in team_config
199
- const mcpToken = await ensureMcpToken(env.MYCO_SECRETS);
200
-
201
- return jsonResponse({
202
- status: 'connected',
203
- sync_protocol_version: parseInt(env.SYNC_PROTOCOL_VERSION, 10),
204
- config,
205
- mcp_token: mcpToken,
206
- mcp_endpoint: '/mcp',
207
- });
208
- }
209
-
210
- async function handleSync(request: Request, env: Env): Promise<Response> {
211
- const body = (await request.json()) as SyncPayload;
212
-
213
- // Version check
214
- const serverVersion = parseInt(env.SYNC_PROTOCOL_VERSION, 10);
215
- if (body.sync_protocol_version !== serverVersion) {
216
- return errorResponse(
217
- `Protocol version mismatch: client=${body.sync_protocol_version}, server=${serverVersion}`,
218
- 409,
219
- );
220
- }
221
-
222
- if (!Array.isArray(body.records) || body.records.length === 0) {
223
- return jsonResponse({ synced: 0, skipped: 0, errors: [] });
224
- }
225
-
226
- let synced = 0;
227
- let skipped = 0;
228
- const errors: Array<{ id: string; table: string; error: string }> = [];
229
-
230
- // Collect embedding tasks so they can be parallelized after DB writes
231
- const embeddingTasks: Array<() => Promise<void>> = [];
232
-
233
- for (const record of body.records) {
234
- try {
235
- if (!SYNCED_TABLES.includes(record.table)) {
236
- errors.push({ id: record.id, table: record.table, error: `Unknown table: ${record.table}` });
237
- continue;
238
- }
239
-
240
- if (record.operation === 'delete') {
241
- await handleDelete(env, record);
242
- synced++;
243
- continue;
244
- }
245
-
246
- // Check content_hash — skip if unchanged
247
- if (record.content_hash) {
248
- const existing = await env.MYCO_TEAM_DB.prepare(
249
- `SELECT content_hash FROM ${record.table} WHERE id = ? AND machine_id = ?`,
250
- )
251
- .bind(record.id, record.machine_id)
252
- .first<{ content_hash: string | null }>();
253
-
254
- if (existing?.content_hash === record.content_hash) {
255
- skipped++;
256
- continue;
257
- }
258
- }
259
-
260
- // INSERT OR REPLACE into D1
261
- const { sql, values } = buildInsertParts(record.table, record.data, record.id, record.machine_id);
262
- await env.MYCO_TEAM_DB.prepare(sql).bind(...values).run();
263
-
264
- // Queue embedding if the table has embeddable content
265
- const embeddableField = EMBEDDABLE_TABLES[record.table];
266
- if (embeddableField) {
267
- const textContent = record.data[embeddableField] as string | undefined;
268
- if (textContent) {
269
- const { table, id, machine_id } = record;
270
- if (table === 'spores' && record.data.status === 'superseded') {
271
- embeddingTasks.push(() => deleteVector(env, table, id, machine_id));
272
- } else {
273
- // Include domain-specific metadata for richer search results
274
- const extra: Record<string, string> = {};
275
- if (table === 'skill_records' && record.data.name) extra.name = record.data.name as string;
276
- if (table === 'spores' && record.data.observation_type) extra.observation_type = record.data.observation_type as string;
277
- embeddingTasks.push(() => embedAndUpsert(env, table, id, machine_id, textContent, extra));
278
- }
279
- }
280
- }
281
-
282
- synced++;
283
- } catch (err) {
284
- const message = err instanceof Error ? err.message : String(err);
285
- errors.push({ id: record.id, table: record.table, error: message });
286
- }
287
- }
288
-
289
- // Run all embedding tasks in parallel
290
- if (embeddingTasks.length > 0) {
291
- await Promise.allSettled(embeddingTasks.map((t) => t()));
292
- }
293
-
294
- // Update node last_seen
295
- await env.MYCO_TEAM_DB.prepare('UPDATE nodes SET last_seen = ? WHERE machine_id = ?')
296
- .bind(epochSeconds(), body.machine_id)
297
- .run();
298
-
299
- return jsonResponse({ synced, skipped, errors });
300
- }
301
-
302
- async function handleDelete(env: Env, record: SyncRecord): Promise<void> {
303
- await env.MYCO_TEAM_DB.prepare(`DELETE FROM ${record.table} WHERE id = ? AND machine_id = ?`)
304
- .bind(record.id, record.machine_id)
305
- .run();
306
-
307
- // Remove from Vectorize if embeddable
308
- if (record.table in EMBEDDABLE_TABLES) {
309
- await deleteVector(env, record.table, record.id, record.machine_id);
310
- }
311
- }
312
-
313
- async function embedAndUpsert(
314
- env: Env,
315
- table: string,
316
- id: string,
317
- machineId: string,
318
- text: string,
319
- extra?: Record<string, string>,
320
- ): Promise<void> {
321
- const vector = await embedText(env.AI, text);
322
- const vid = vectorId(table, id, machineId);
323
- await env.MYCO_TEAM_VECTORS.upsert([
324
- {
325
- id: vid,
326
- values: vector,
327
- metadata: { table, id, machine_id: machineId, ...extra },
328
- },
329
- ]);
330
- }
331
-
332
- async function deleteVector(env: Env, table: string, id: string, machineId: string): Promise<void> {
333
- const vid = vectorId(table, id, machineId);
334
- try {
335
- await env.MYCO_TEAM_VECTORS.deleteByIds([vid]);
336
- } catch {
337
- // Vector may not exist — safe to ignore
338
- }
339
- }
340
-
341
- async function handleSearch(request: Request, env: Env): Promise<Response> {
342
- const url = new URL(request.url);
343
- const query = url.searchParams.get('q');
344
- if (!query) {
345
- return errorResponse('Missing query parameter "q"', 400);
346
- }
347
-
348
- const topK = Math.min(parseInt(url.searchParams.get('top_k') ?? String(DEFAULT_TOP_K), 10), MAX_TOP_K);
349
-
350
- const queryVector = await embedText(env.AI, query);
351
- const matches = await env.MYCO_TEAM_VECTORS.query(queryVector, { topK, returnMetadata: 'all' });
352
-
353
- const validMatches = matches.matches
354
- .filter((m) => m.metadata)
355
- .map((m) => ({ metadata: m.metadata as { table: string; id: string; machine_id: string }, score: m.score }));
356
-
357
- const results = await hydrateVectorMatches(env.MYCO_TEAM_DB, validMatches);
358
-
359
- return jsonResponse({ results: results.map((r) => ({ table: r.type, ...r })) });
360
- }
361
-
362
- async function handleGetConfig(env: Env): Promise<Response> {
363
- const rows = await env.MYCO_TEAM_DB.prepare('SELECT key, value FROM team_config').all<{
364
- key: string;
365
- value: string;
366
- }>();
367
-
368
- const config: Record<string, string> = {};
369
- for (const row of rows.results) {
370
- config[row.key] = row.value;
371
- }
372
-
373
- return jsonResponse({
374
- config,
375
- sync_protocol_version: parseInt(env.SYNC_PROTOCOL_VERSION, 10),
376
- });
377
- }
378
-
379
- async function handlePutConfig(request: Request, env: Env): Promise<Response> {
380
- const body = (await request.json()) as Record<string, string>;
381
-
382
- const entries = Object.entries(body);
383
- if (entries.length === 0) {
384
- return errorResponse('Empty config body', 400);
385
- }
386
-
387
- const statements = entries.map(([key, value]) =>
388
- env.MYCO_TEAM_DB.prepare('INSERT OR REPLACE INTO team_config (key, value) VALUES (?, ?)').bind(key, value),
389
- );
390
-
391
- await env.MYCO_TEAM_DB.batch(statements);
392
-
393
- return jsonResponse({ updated: entries.length });
394
- }
395
-
396
- // ---------------------------------------------------------------------------
397
- // Router
398
- // ---------------------------------------------------------------------------
399
-
400
- export default {
401
- async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
402
- const url = new URL(request.url);
403
- const path = url.pathname;
404
- const method = request.method;
405
-
406
- // Health — no auth required
407
- if (method === 'GET' && path === '/health') {
408
- try {
409
- if (!schemaInitialized) {
410
- await initD1Schema(env.MYCO_TEAM_DB);
411
- schemaInitialized = true;
412
- }
413
- return await handleHealth(env);
414
- } catch (err) {
415
- const message = err instanceof Error ? err.message : String(err);
416
- return errorResponse(`Health check failed: ${message}`, 500);
417
- }
418
- }
419
-
420
- // MCP routes — separate auth from sync routes
421
- if (path.startsWith('/mcp')) {
422
- if (!schemaInitialized) {
423
- await initD1Schema(env.MYCO_TEAM_DB);
424
- schemaInitialized = true;
425
- }
426
-
427
- // Token rotation — authenticated with team API key
428
- if (path === '/mcp/rotate' && method === 'POST') {
429
- const rotateAuthError = validateAuth(request, env);
430
- if (rotateAuthError) return rotateAuthError;
431
- const newToken = await rotateMcpToken(env.MYCO_SECRETS);
432
- return jsonResponse({ token: newToken });
433
- }
434
-
435
- // MCP protocol — authenticated with MCP access token
436
- const mcpAuthError = await authenticateMcpRequest(request, env.MYCO_SECRETS);
437
- if (mcpAuthError) return mcpAuthError;
438
-
439
- const server = createMcpServerInstance(env);
440
- const handler = createMcpHandler(server);
441
- return handler(request, env, ctx);
442
- }
443
-
444
- // All other routes require auth
445
- const authError = validateAuth(request, env);
446
- if (authError) return authError;
447
-
448
- if (!schemaInitialized) {
449
- await initD1Schema(env.MYCO_TEAM_DB);
450
- schemaInitialized = true;
451
- }
452
-
453
- try {
454
- if (method === 'POST' && path === '/connect') {
455
- return await handleConnect(request, env);
456
- }
457
- if (method === 'POST' && path === '/sync') {
458
- return await handleSync(request, env);
459
- }
460
- if (method === 'GET' && path === '/search') {
461
- return await handleSearch(request, env);
462
- }
463
- if (method === 'GET' && path === '/config') {
464
- return await handleGetConfig(env);
465
- }
466
- if (method === 'PUT' && path === '/config') {
467
- return await handlePutConfig(request, env);
468
- }
469
-
470
- return errorResponse('Not found', 404);
471
- } catch (err) {
472
- const message = err instanceof Error ? err.message : String(err);
473
- return errorResponse(message, 500);
474
- }
475
- },
476
- } satisfies ExportedHandler<Env>;
@@ -1,65 +0,0 @@
1
- /**
2
- * MCP access token auth for the Myco cloud MCP server.
3
- *
4
- * Tokens are stored in Workers KV (MYCO_SECRETS namespace) under the key 'mcp_access_token'.
5
- * KV provides AES-256-GCM encryption at rest — the Cloudflare-native approach for
6
- * runtime-managed secrets. This is separate from the worker's MYCO_TEAM_API_KEY auth
7
- * used for sync routes.
8
- */
9
-
10
- export const MCP_TOKEN_KEY = 'mcp_access_token';
11
-
12
- export function generateMcpToken(): string {
13
- return crypto.randomUUID();
14
- }
15
-
16
- export function getMcpTokenHash(token: string): string {
17
- // Non-cryptographic hash for change detection, returns 8 hex chars
18
- let hash = 0;
19
- for (let i = 0; i < token.length; i++) {
20
- hash = ((hash << 5) - hash + token.charCodeAt(i)) | 0;
21
- }
22
- return Math.abs(hash).toString(16).padStart(8, '0').slice(0, 8);
23
- }
24
-
25
- export async function validateMcpToken(kv: KVNamespace, token: string): Promise<boolean> {
26
- const stored = await kv.get(MCP_TOKEN_KEY);
27
- if (!stored) return false;
28
- return stored === token;
29
- }
30
-
31
- export async function ensureMcpToken(kv: KVNamespace): Promise<string> {
32
- const existing = await kv.get(MCP_TOKEN_KEY);
33
- if (existing) return existing;
34
- const token = generateMcpToken();
35
- await kv.put(MCP_TOKEN_KEY, token);
36
- return token;
37
- }
38
-
39
- export async function rotateMcpToken(kv: KVNamespace): Promise<string> {
40
- const token = generateMcpToken();
41
- await kv.put(MCP_TOKEN_KEY, token);
42
- return token;
43
- }
44
-
45
- export async function authenticateMcpRequest(
46
- request: Request,
47
- kv: KVNamespace,
48
- ): Promise<Response | null> {
49
- const header = request.headers.get('Authorization');
50
- if (!header) {
51
- return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
52
- status: 401,
53
- headers: { 'Content-Type': 'application/json' },
54
- });
55
- }
56
- const token = header.startsWith('Bearer ') ? header.slice(7) : '';
57
- const valid = await validateMcpToken(kv, token);
58
- if (!valid) {
59
- return new Response(JSON.stringify({ error: 'Invalid MCP access token' }), {
60
- status: 401,
61
- headers: { 'Content-Type': 'application/json' },
62
- });
63
- }
64
- return null;
65
- }
@@ -1,53 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
- import type { Env } from '../index';
4
- import { handleSearch } from './tools/search';
5
- import { handleContext } from './tools/context';
6
- import { handleGet } from './tools/get';
7
- import { handleSessions } from './tools/sessions';
8
- import { handleGraph } from './tools/graph';
9
- import { handleSkills } from './tools/skills';
10
- import { handleTeam } from './tools/team';
11
-
12
- export function createMcpServerInstance(env: Env): McpServer {
13
- const server = new McpServer({ name: 'myco', version: '1.0.0' });
14
-
15
- server.tool('myco_search', 'Semantic and keyword search across all project knowledge — spores, sessions, plans, artifacts. Returns ranked results with content previews.', {
16
- query: z.string().describe('The search query'),
17
- types: z.array(z.string()).optional().describe('Filter to content types: spores, sessions, plans, artifacts'),
18
- limit: z.number().min(1).max(50).default(10).describe('Maximum results'),
19
- }, async (args) => handleSearch(args, env));
20
-
21
- server.tool('myco_context', 'Pre-synthesized project digest. Start here to understand the project. Three tiers: 1500 (executive), 5000 (deep onboarding), 10000 (comprehensive).', {
22
- tier: z.number().optional().describe('Digest depth: 1500, 5000 (default), or 10000'),
23
- }, async (args) => handleContext(args, env));
24
-
25
- server.tool('myco_get', 'Retrieve a specific item by ID and type. Use after search to get full details.', {
26
- id: z.string().describe('The item ID'),
27
- type: z.enum(['session', 'spore', 'plan', 'artifact', 'skill']).describe('The item type'),
28
- }, async (args) => handleGet(args, env));
29
-
30
- server.tool('myco_sessions', 'List and filter coding sessions. Useful for recent activity, work by branch or agent.', {
31
- limit: z.number().min(1).max(100).default(20).optional().describe('Maximum sessions'),
32
- status: z.string().optional().describe('Filter: active, completed'),
33
- agent: z.string().optional().describe('Filter: claude, codex, cursor, etc.'),
34
- branch: z.string().optional().describe('Filter by git branch'),
35
- since: z.string().optional().describe('ISO date — sessions after this date'),
36
- }, async (args) => handleSessions(args, env));
37
-
38
- server.tool('myco_graph', 'Traverse the knowledge graph from an entity or note. Returns edges and connected entities.', {
39
- node_id: z.string().describe('Entity or note ID to traverse from'),
40
- direction: z.enum(['incoming', 'outgoing', 'both']).default('both').optional().describe('Edge direction'),
41
- }, async (args) => handleGraph(args, env));
42
-
43
- server.tool('myco_skills', 'List project skills — reusable patterns extracted from project knowledge.', {
44
- status: z.string().optional().describe('Filter: active, draft, retired'),
45
- limit: z.number().min(1).max(100).default(50).optional().describe('Maximum skills'),
46
- }, async (args) => handleSkills(args, env));
47
-
48
- // handleTeam takes only env (no args) since it has no parameters
49
- server.tool('myco_team', 'List team nodes — machines/developers connected to this project with sync status.', {},
50
- async () => handleTeam(env));
51
-
52
- return server;
53
- }
@@ -1,13 +0,0 @@
1
- import type { Env } from '../../index';
2
-
3
- export async function handleContext(args: { tier?: number }, env: Pick<Env, 'MYCO_TEAM_DB'>) {
4
- const tier = args.tier ?? 5000;
5
- const row = await env.MYCO_TEAM_DB.prepare(
6
- `SELECT id, tier, content, generated_at FROM digest_extracts WHERE tier = ? ORDER BY generated_at DESC LIMIT 1`,
7
- ).bind(tier).first<{ id: string; tier: number; content: string; generated_at: number }>();
8
-
9
- if (!row) {
10
- return { content: [{ type: 'text' as const, text: JSON.stringify({ content: null, tier, message: `No digest available at tier ${tier}` }) }] };
11
- }
12
- return { content: [{ type: 'text' as const, text: JSON.stringify({ content: row.content, tier: row.tier, generated_at: row.generated_at }) }] };
13
- }
@@ -1,15 +0,0 @@
1
- import type { Env } from '../../index';
2
-
3
- const TYPE_TO_TABLE: Record<string, string> = {
4
- session: 'sessions', spore: 'spores', plan: 'plans', artifact: 'artifacts', skill: 'skill_records',
5
- };
6
-
7
- export async function handleGet(args: { id: string; type: string }, env: Pick<Env, 'MYCO_TEAM_DB'>) {
8
- const table = TYPE_TO_TABLE[args.type];
9
- if (!table) return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown type: ${args.type}` }) }] };
10
-
11
- const row = await env.MYCO_TEAM_DB.prepare(`SELECT * FROM ${table} WHERE id = ? LIMIT 1`).bind(args.id).first<Record<string, unknown>>();
12
- if (!row) return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `${args.type} '${args.id}' not found` }) }] };
13
-
14
- return { content: [{ type: 'text' as const, text: JSON.stringify(row) }] };
15
- }