@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.
- package/README.md +17 -130
- package/dist/{agent-run-I4O2K2CK.js → agent-run-EADUYYAS.js} +6 -6
- package/dist/{agent-tasks-UOW5BQIB.js → agent-tasks-GC77JXQB.js} +6 -6
- package/dist/{chunk-W7WENJ6F.js → chunk-2CKDAFSX.js} +2 -2
- package/dist/{chunk-TOER6RNC.js → chunk-2DF4OZ2D.js} +2 -2
- package/dist/{chunk-CURS2TNP.js → chunk-2LN2BBKA.js} +2 -2
- package/dist/{chunk-RIDSOQDR.js → chunk-2OO3BRFK.js} +2 -2
- package/dist/{chunk-RAV5YMRU.js → chunk-3TPD6HEF.js} +4 -4
- package/dist/{chunk-D7TYRPRM.js → chunk-6LQIMRTC.js} +145 -145
- package/dist/chunk-6LQIMRTC.js.map +1 -0
- package/dist/{chunk-NI23QCHB.js → chunk-AELJ4PS5.js} +5 -5
- package/dist/{chunk-XWOQL4XN.js → chunk-CYBC2HZ3.js} +3 -3
- package/dist/chunk-EM63ZFKA.js +166 -0
- package/dist/chunk-EM63ZFKA.js.map +1 -0
- package/dist/{chunk-O3TRN3RC.js → chunk-INWD6AIQ.js} +2 -2
- package/dist/{chunk-CML4MCYF.js → chunk-KSXTNYXO.js} +2 -2
- package/dist/{chunk-TCSVDQF5.js → chunk-LLJMDXO2.js} +58 -58
- package/dist/chunk-LLJMDXO2.js.map +1 -0
- package/dist/{chunk-2V7HR7HB.js → chunk-MDEUXYJG.js} +4 -4
- package/dist/{chunk-YZPI2Y3E.js → chunk-MS6FDV45.js} +3 -3
- package/dist/{chunk-TLK46KKD.js → chunk-N77K772N.js} +3 -3
- package/dist/{chunk-E4VLWIJC.js → chunk-ODXLRR4U.js} +1 -1
- package/dist/{chunk-LSP5HYOO.js → chunk-OZF5EURR.js} +4 -4
- package/dist/{chunk-IB76KGBY.js → chunk-POEPHBQK.js} +1 -1
- package/dist/{chunk-TZAXQKO6.js → chunk-REN37KYI.js} +2 -2
- package/dist/{chunk-C3EGL5JX.js → chunk-RXROZBSK.js} +8 -8
- package/dist/{chunk-U3J2DDSR.js → chunk-SCI55NKY.js} +2 -2
- package/dist/{chunk-GDY63YAW.js → chunk-U6PF3YII.js} +79 -79
- package/dist/chunk-U6PF3YII.js.map +1 -0
- package/dist/{chunk-DPSLJ242.js → chunk-UVKQ62II.js} +3 -3
- package/dist/{chunk-N75GMQGA.js → chunk-UW6DGPSV.js} +3 -3
- package/dist/{chunk-CKJAWZQE.js → chunk-W4VHC2ES.js} +11 -3
- package/dist/chunk-W4VHC2ES.js.map +1 -0
- package/dist/{chunk-75AZFBFW.js → chunk-YPWF322W.js} +3 -3
- package/dist/{cli-D3TJYJ2U.js → cli-X7CFP4YD.js} +39 -39
- package/dist/{client-4LLEXLVK.js → client-YA33HUFY.js} +4 -4
- package/dist/{config-DA4IUVFL.js → config-RFB2DJC6.js} +6 -6
- package/dist/{detect-SZ2KDUF4.js → detect-BEOIHGBC.js} +5 -5
- package/dist/{detect-providers-PSVKXTWE.js → detect-providers-2OQBU4VX.js} +4 -4
- package/dist/{doctor-KCTXPX5D.js → doctor-FAH7N66M.js} +11 -11
- package/dist/{executor-UYIZC3L5.js → executor-ICTRRUBY.js} +17 -17
- package/dist/{init-QFNBKKDC.js → init-PTJEOTJV.js} +15 -15
- package/dist/{llm-SMA5ZEAW.js → llm-7D2OGDEK.js} +4 -4
- package/dist/{loader-Q3P3R4UP.js → loader-O2JFO2UC.js} +6 -6
- package/dist/{loader-SKKUMT5C.js → loader-VPE4RCIF.js} +6 -6
- package/dist/{main-5THODR77.js → main-EIKBLOUL.js} +341 -84
- package/dist/main-EIKBLOUL.js.map +1 -0
- package/dist/{open-7737CSPN.js → open-2JCSOLZS.js} +6 -6
- package/dist/{post-compact-2TJ5FPZH.js → post-compact-2HPPWPBI.js} +10 -10
- package/dist/{post-tool-use-FRTSICC3.js → post-tool-use-TWBBBABS.js} +9 -9
- package/dist/{post-tool-use-failure-KYO2NCNB.js → post-tool-use-failure-LIJYR4KL.js} +10 -10
- package/dist/{pre-compact-J6GCJEJR.js → pre-compact-II2CMNTG.js} +10 -10
- package/dist/{provider-check-AE3L5Z6R.js → provider-check-KEQNQ6LO.js} +4 -4
- package/dist/{registry-O2NZLO3V.js → registry-X5FDGYXT.js} +7 -7
- package/dist/{remove-3WZZC7AX.js → remove-L5MVYBOY.js} +11 -11
- package/dist/{resolution-events-XWYLLDRK.js → resolution-events-MVIZMONR.js} +4 -4
- package/dist/{restart-HUHEFOXU.js → restart-VIT3JBD6.js} +7 -7
- package/dist/{search-ZGN3LDXG.js → search-O6BB5MTO.js} +7 -7
- package/dist/{server-PTXLVVEE.js → server-O3UPJVBR.js} +258 -173
- package/dist/server-O3UPJVBR.js.map +1 -0
- package/dist/{session-7VV3IQMO.js → session-5JV3DQIK.js} +8 -8
- package/dist/{session-end-SMU55UCM.js → session-end-PZ2OXBGG.js} +9 -9
- package/dist/{session-start-NIMWEOIZ.js → session-start-FDGM56BX.js} +14 -14
- package/dist/{setup-llm-7S3VPAPN.js → setup-llm-MQK557BB.js} +10 -10
- package/dist/src/cli.js +1 -1
- package/dist/src/daemon/main.js +1 -1
- package/dist/src/hooks/post-tool-use.js +1 -1
- package/dist/src/hooks/session-end.js +1 -1
- package/dist/src/hooks/session-start.js +1 -1
- package/dist/src/hooks/stop.js +1 -1
- package/dist/src/hooks/user-prompt-submit.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/{stats-GEOQ2DFF.js → stats-2STTARTC.js} +11 -11
- package/dist/{stop-7AKYBJJ2.js → stop-WNKCMCGO.js} +9 -9
- package/dist/{stop-failure-NLE2EURG.js → stop-failure-6GTOBVTN.js} +10 -10
- package/dist/{subagent-start-LBNZF2TG.js → subagent-start-VJF5YKVX.js} +10 -10
- package/dist/{subagent-stop-B2Z5GYAB.js → subagent-stop-UW6HMICY.js} +10 -10
- package/dist/{task-completed-PO5TETJ7.js → task-completed-U4Q3XXLX.js} +10 -10
- package/dist/{team-DPNP2RN7.js → team-N6TXS2PF.js} +148 -103
- package/dist/team-N6TXS2PF.js.map +1 -0
- package/dist/ui/assets/{index-CiI1fwas.js → index-CHIm98OP.js} +48 -48
- package/dist/ui/index.html +1 -1
- package/dist/{update-WBWB5URU.js → update-ZYCOWKMD.js} +11 -11
- package/dist/{user-prompt-submit-IZJC3NV7.js → user-prompt-submit-SOYL4OWF.js} +9 -9
- package/dist/{verify-FNSP62I3.js → verify-P37PQ4YM.js} +8 -8
- package/dist/{version-QEVU66NT.js → version-XAWC277D.js} +2 -2
- package/package.json +19 -2
- package/CONTRIBUTING.md +0 -132
- package/dist/chunk-CKJAWZQE.js.map +0 -1
- package/dist/chunk-D7TYRPRM.js.map +0 -1
- package/dist/chunk-GDY63YAW.js.map +0 -1
- package/dist/chunk-RJMXDUMA.js +0 -40
- package/dist/chunk-RJMXDUMA.js.map +0 -1
- package/dist/chunk-TCSVDQF5.js.map +0 -1
- package/dist/main-5THODR77.js.map +0 -1
- package/dist/server-PTXLVVEE.js.map +0 -1
- package/dist/src/worker/package-lock.json +0 -4338
- package/dist/src/worker/package.json +0 -22
- package/dist/src/worker/src/auth.ts +0 -31
- package/dist/src/worker/src/index.ts +0 -476
- package/dist/src/worker/src/mcp/auth.ts +0 -65
- package/dist/src/worker/src/mcp/server.ts +0 -53
- package/dist/src/worker/src/mcp/tools/context.ts +0 -13
- package/dist/src/worker/src/mcp/tools/get.ts +0 -15
- package/dist/src/worker/src/mcp/tools/graph.ts +0 -35
- package/dist/src/worker/src/mcp/tools/search.ts +0 -32
- package/dist/src/worker/src/mcp/tools/sessions.ts +0 -24
- package/dist/src/worker/src/mcp/tools/skills.ts +0 -16
- package/dist/src/worker/src/mcp/tools/team.ts +0 -9
- package/dist/src/worker/src/schema.ts +0 -326
- package/dist/src/worker/src/search-helpers.ts +0 -70
- package/dist/src/worker/tsconfig.json +0 -16
- package/dist/src/worker/wrangler.toml +0 -30
- package/dist/team-DPNP2RN7.js.map +0 -1
- /package/dist/{agent-run-I4O2K2CK.js.map → agent-run-EADUYYAS.js.map} +0 -0
- /package/dist/{agent-tasks-UOW5BQIB.js.map → agent-tasks-GC77JXQB.js.map} +0 -0
- /package/dist/{chunk-W7WENJ6F.js.map → chunk-2CKDAFSX.js.map} +0 -0
- /package/dist/{chunk-TOER6RNC.js.map → chunk-2DF4OZ2D.js.map} +0 -0
- /package/dist/{chunk-CURS2TNP.js.map → chunk-2LN2BBKA.js.map} +0 -0
- /package/dist/{chunk-RIDSOQDR.js.map → chunk-2OO3BRFK.js.map} +0 -0
- /package/dist/{chunk-RAV5YMRU.js.map → chunk-3TPD6HEF.js.map} +0 -0
- /package/dist/{chunk-NI23QCHB.js.map → chunk-AELJ4PS5.js.map} +0 -0
- /package/dist/{chunk-XWOQL4XN.js.map → chunk-CYBC2HZ3.js.map} +0 -0
- /package/dist/{chunk-O3TRN3RC.js.map → chunk-INWD6AIQ.js.map} +0 -0
- /package/dist/{chunk-CML4MCYF.js.map → chunk-KSXTNYXO.js.map} +0 -0
- /package/dist/{chunk-2V7HR7HB.js.map → chunk-MDEUXYJG.js.map} +0 -0
- /package/dist/{chunk-YZPI2Y3E.js.map → chunk-MS6FDV45.js.map} +0 -0
- /package/dist/{chunk-TLK46KKD.js.map → chunk-N77K772N.js.map} +0 -0
- /package/dist/{chunk-E4VLWIJC.js.map → chunk-ODXLRR4U.js.map} +0 -0
- /package/dist/{chunk-LSP5HYOO.js.map → chunk-OZF5EURR.js.map} +0 -0
- /package/dist/{chunk-IB76KGBY.js.map → chunk-POEPHBQK.js.map} +0 -0
- /package/dist/{chunk-TZAXQKO6.js.map → chunk-REN37KYI.js.map} +0 -0
- /package/dist/{chunk-C3EGL5JX.js.map → chunk-RXROZBSK.js.map} +0 -0
- /package/dist/{chunk-U3J2DDSR.js.map → chunk-SCI55NKY.js.map} +0 -0
- /package/dist/{chunk-DPSLJ242.js.map → chunk-UVKQ62II.js.map} +0 -0
- /package/dist/{chunk-N75GMQGA.js.map → chunk-UW6DGPSV.js.map} +0 -0
- /package/dist/{chunk-75AZFBFW.js.map → chunk-YPWF322W.js.map} +0 -0
- /package/dist/{cli-D3TJYJ2U.js.map → cli-X7CFP4YD.js.map} +0 -0
- /package/dist/{client-4LLEXLVK.js.map → client-YA33HUFY.js.map} +0 -0
- /package/dist/{config-DA4IUVFL.js.map → config-RFB2DJC6.js.map} +0 -0
- /package/dist/{detect-SZ2KDUF4.js.map → detect-BEOIHGBC.js.map} +0 -0
- /package/dist/{detect-providers-PSVKXTWE.js.map → detect-providers-2OQBU4VX.js.map} +0 -0
- /package/dist/{doctor-KCTXPX5D.js.map → doctor-FAH7N66M.js.map} +0 -0
- /package/dist/{executor-UYIZC3L5.js.map → executor-ICTRRUBY.js.map} +0 -0
- /package/dist/{init-QFNBKKDC.js.map → init-PTJEOTJV.js.map} +0 -0
- /package/dist/{llm-SMA5ZEAW.js.map → llm-7D2OGDEK.js.map} +0 -0
- /package/dist/{loader-Q3P3R4UP.js.map → loader-O2JFO2UC.js.map} +0 -0
- /package/dist/{loader-SKKUMT5C.js.map → loader-VPE4RCIF.js.map} +0 -0
- /package/dist/{open-7737CSPN.js.map → open-2JCSOLZS.js.map} +0 -0
- /package/dist/{post-compact-2TJ5FPZH.js.map → post-compact-2HPPWPBI.js.map} +0 -0
- /package/dist/{post-tool-use-FRTSICC3.js.map → post-tool-use-TWBBBABS.js.map} +0 -0
- /package/dist/{post-tool-use-failure-KYO2NCNB.js.map → post-tool-use-failure-LIJYR4KL.js.map} +0 -0
- /package/dist/{pre-compact-J6GCJEJR.js.map → pre-compact-II2CMNTG.js.map} +0 -0
- /package/dist/{provider-check-AE3L5Z6R.js.map → provider-check-KEQNQ6LO.js.map} +0 -0
- /package/dist/{registry-O2NZLO3V.js.map → registry-X5FDGYXT.js.map} +0 -0
- /package/dist/{remove-3WZZC7AX.js.map → remove-L5MVYBOY.js.map} +0 -0
- /package/dist/{resolution-events-XWYLLDRK.js.map → resolution-events-MVIZMONR.js.map} +0 -0
- /package/dist/{restart-HUHEFOXU.js.map → restart-VIT3JBD6.js.map} +0 -0
- /package/dist/{search-ZGN3LDXG.js.map → search-O6BB5MTO.js.map} +0 -0
- /package/dist/{session-7VV3IQMO.js.map → session-5JV3DQIK.js.map} +0 -0
- /package/dist/{session-end-SMU55UCM.js.map → session-end-PZ2OXBGG.js.map} +0 -0
- /package/dist/{session-start-NIMWEOIZ.js.map → session-start-FDGM56BX.js.map} +0 -0
- /package/dist/{setup-llm-7S3VPAPN.js.map → setup-llm-MQK557BB.js.map} +0 -0
- /package/dist/{stats-GEOQ2DFF.js.map → stats-2STTARTC.js.map} +0 -0
- /package/dist/{stop-7AKYBJJ2.js.map → stop-WNKCMCGO.js.map} +0 -0
- /package/dist/{stop-failure-NLE2EURG.js.map → stop-failure-6GTOBVTN.js.map} +0 -0
- /package/dist/{subagent-start-LBNZF2TG.js.map → subagent-start-VJF5YKVX.js.map} +0 -0
- /package/dist/{subagent-stop-B2Z5GYAB.js.map → subagent-stop-UW6HMICY.js.map} +0 -0
- /package/dist/{task-completed-PO5TETJ7.js.map → task-completed-U4Q3XXLX.js.map} +0 -0
- /package/dist/{update-WBWB5URU.js.map → update-ZYCOWKMD.js.map} +0 -0
- /package/dist/{user-prompt-submit-IZJC3NV7.js.map → user-prompt-submit-SOYL4OWF.js.map} +0 -0
- /package/dist/{verify-FNSP62I3.js.map → verify-P37PQ4YM.js.map} +0 -0
- /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
|
-
}
|