@starascendin/lifeos-mcp 0.7.67 → 0.7.68
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 +18 -15
- package/dist/build-info.d.ts +2 -2
- package/dist/build-info.js +2 -2
- package/dist/index.js +284 -387
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,17 +24,20 @@ npx @starascendin/lifeos-mcp --url <your-convex-url> --user-id <your-user-id> --
|
|
|
24
24
|
| User ID | `--user-id`, `-i` | `LIFEOS_USER_ID` | Your LifeOS user ID |
|
|
25
25
|
| API Key | `--api-key`, `-k` | `LIFEOS_API_KEY` | Your API key for authentication |
|
|
26
26
|
|
|
27
|
-
### Optional
|
|
27
|
+
### Optional FalkorDB Sidecar Parameters
|
|
28
28
|
|
|
29
|
-
These are required only for the
|
|
29
|
+
These are required only for the Falkor graph tools.
|
|
30
30
|
|
|
31
31
|
| Env Variable | Description |
|
|
32
32
|
|--------------|-------------|
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
33
|
+
| `FALKOR_BROWSER_ENDPOINT` | FalkorDB Browser HTTP endpoint, defaults to `https://falkordb.apps.rjlabs.dev` |
|
|
34
|
+
| `FALKOR_GRAPH` | Falkor graph name, defaults to `lifeos_ppv` |
|
|
35
|
+
| `FALKOR_PASS` | Falkor password, used to mint short-lived browser tokens |
|
|
36
|
+
| `FALKOR_TOKEN` / `FALKOR_PAT` | Optional pre-provisioned Falkor browser token |
|
|
37
|
+
| `FALKOR_USER` | Falkor username, defaults to `default` |
|
|
38
|
+
| `FALKOR_HOST` | Browser backend Redis host, defaults to `localhost` |
|
|
39
|
+
| `FALKOR_PORT` | Browser backend Redis port, defaults to `6379` |
|
|
40
|
+
| `FALKOR_TLS` | Browser backend Redis TLS flag, defaults to `false` |
|
|
38
41
|
|
|
39
42
|
### Getting Your Credentials
|
|
40
43
|
|
|
@@ -190,16 +193,16 @@ Add to your `.mcp.json` (project root or `~/.claude/mcp.json`):
|
|
|
190
193
|
- **refresh_unified_graph_cache** - Recompute inferred graph links and refresh the cached projection
|
|
191
194
|
- **upsert_unified_graph_link / delete_unified_graph_link** - Create or remove manual links between graph nodes
|
|
192
195
|
|
|
193
|
-
###
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
196
|
-
- **
|
|
197
|
-
- **
|
|
196
|
+
### FalkorDB Sidecar Graph
|
|
197
|
+
- **falkor_graph_schema** - Inspect the Falkor graph contract, configured graph, allowed labels, relationship types, and examples
|
|
198
|
+
- **falkor_graph_query** - Run guarded read-only Cypher against the Falkor PPV sidecar
|
|
199
|
+
- **falkor_graph_link** - Create/update an agent-owned `AGENT_LINK` relationship without mutating Convex-synced nodes or inferred PPV relationships
|
|
200
|
+
- **falkor_graph_unlink** - Remove a relationship previously created by `falkor_graph_link`
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
Falkor graph rules:
|
|
200
203
|
- Convex remains canonical for entity data.
|
|
201
|
-
-
|
|
202
|
-
- Agents may query graph data and write only agent-owned
|
|
204
|
+
- FalkorDB is a graph query/read sidecar for the PPV experiment.
|
|
205
|
+
- Agents may query graph data and write only agent-owned `AGENT_LINK` relationships.
|
|
203
206
|
- Every agent-created link must include a reason and confidence.
|
|
204
207
|
- Raw read queries should include `LIMIT`.
|
|
205
208
|
|
package/dist/build-info.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.7.
|
|
2
|
-
export declare const BUILD_TIME = "2026-05-
|
|
1
|
+
export declare const VERSION = "0.7.68";
|
|
2
|
+
export declare const BUILD_TIME = "2026-05-17T19:06:09.988Z";
|
package/dist/build-info.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Regenerated on every build.
|
|
2
|
-
export const VERSION = "0.7.
|
|
3
|
-
export const BUILD_TIME = "2026-05-
|
|
2
|
+
export const VERSION = "0.7.68";
|
|
3
|
+
export const BUILD_TIME = "2026-05-17T19:06:09.988Z";
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ program
|
|
|
60
60
|
});
|
|
61
61
|
program
|
|
62
62
|
.command("ppv <action>")
|
|
63
|
-
.description("Manage PPV as JSON. Actions: workspace, graph, vision-graph, seed, vision, activate-vision, set-active-vision, identity, pillar
|
|
63
|
+
.description("Manage PPV as JSON. Actions: workspace, graph, vision-graph, seed, vision, activate-vision, set-active-vision, identity, pillar.")
|
|
64
64
|
.option("--file <file>", "JSON payload file for mutating actions. workspace, graph, vision-graph, and seed do not require a file.")
|
|
65
65
|
.action((action, commandOptions) => {
|
|
66
66
|
directCliCommand = {
|
|
@@ -4542,7 +4542,7 @@ const TOOLS = [
|
|
|
4542
4542
|
// PPV v1 tools
|
|
4543
4543
|
{
|
|
4544
4544
|
name: "get_ppv_workspace",
|
|
4545
|
-
description: "Get the PPV workspace for the selected vision, plus all owned visions, active-vision context, identity, pillars, linked projects
|
|
4545
|
+
description: "Get the PPV workspace for the selected vision, plus all owned visions, active-vision context, identity, pillars, and linked projects.",
|
|
4546
4546
|
inputSchema: {
|
|
4547
4547
|
type: "object",
|
|
4548
4548
|
properties: {
|
|
@@ -4553,7 +4553,7 @@ const TOOLS = [
|
|
|
4553
4553
|
},
|
|
4554
4554
|
{
|
|
4555
4555
|
name: "get_active_vision_graph",
|
|
4556
|
-
description: "Best default graph read for PPV questions. Get the unified active-vision graph linking PPV vision, identity, pillars,
|
|
4556
|
+
description: "Best default graph read for PPV questions. Get the unified active-vision graph linking PPV vision, identity, pillars, related projects/issues, and recent voice memos.",
|
|
4557
4557
|
inputSchema: {
|
|
4558
4558
|
type: "object",
|
|
4559
4559
|
properties: {
|
|
@@ -4730,52 +4730,52 @@ const TOOLS = [
|
|
|
4730
4730
|
},
|
|
4731
4731
|
},
|
|
4732
4732
|
{
|
|
4733
|
-
name: "
|
|
4734
|
-
description: "Inspect the
|
|
4733
|
+
name: "falkor_graph_schema",
|
|
4734
|
+
description: "Inspect the FalkorDB sidecar graph contract for agents: configured graph, mirrored PPV labels, safe Cypher rules, relationship types, and examples. Use before raw Falkor Cypher graph work.",
|
|
4735
4735
|
inputSchema: {
|
|
4736
4736
|
type: "object",
|
|
4737
4737
|
properties: {},
|
|
4738
4738
|
},
|
|
4739
4739
|
},
|
|
4740
4740
|
{
|
|
4741
|
-
name: "
|
|
4742
|
-
description: "Run guarded read-only
|
|
4741
|
+
name: "falkor_graph_query",
|
|
4742
|
+
description: "Run guarded read-only Cypher against the LifeOS FalkorDB graph sidecar. Allowed statements are MATCH, WITH, RETURN, EXPLAIN, and PROFILE query shapes only. MATCH queries should include LIMIT unless doing count aggregation. Do not use this for Convex-canonical writes.",
|
|
4743
4743
|
inputSchema: {
|
|
4744
4744
|
type: "object",
|
|
4745
4745
|
properties: {
|
|
4746
4746
|
query: {
|
|
4747
4747
|
type: "string",
|
|
4748
|
-
description: "Read-only
|
|
4748
|
+
description: "Read-only Cypher. MATCH queries should include LIMIT unless they are aggregate count queries.",
|
|
4749
4749
|
},
|
|
4750
4750
|
maxRows: {
|
|
4751
4751
|
type: "number",
|
|
4752
|
-
description: "Maximum rows returned
|
|
4752
|
+
description: "Maximum rows returned after execution. Default 50, max 200.",
|
|
4753
4753
|
},
|
|
4754
4754
|
},
|
|
4755
4755
|
required: ["query"],
|
|
4756
4756
|
},
|
|
4757
4757
|
},
|
|
4758
4758
|
{
|
|
4759
|
-
name: "
|
|
4760
|
-
description: "Create or update an agent-owned
|
|
4759
|
+
name: "falkor_graph_link",
|
|
4760
|
+
description: "Create or update an agent-owned AGENT_LINK relationship in FalkorDB between two existing LifeOS graph records. This never mutates Convex-synced nodes or inferred PPV relationships.",
|
|
4761
4761
|
inputSchema: {
|
|
4762
4762
|
type: "object",
|
|
4763
4763
|
properties: {
|
|
4764
|
-
|
|
4764
|
+
fromLabel: {
|
|
4765
4765
|
type: "string",
|
|
4766
|
-
description: "Source
|
|
4766
|
+
description: "Source Falkor node label, e.g. PpvPillar.",
|
|
4767
4767
|
},
|
|
4768
4768
|
fromId: {
|
|
4769
4769
|
type: "string",
|
|
4770
|
-
description: "Source record id
|
|
4770
|
+
description: "Source Convex record id stored in node.convexId.",
|
|
4771
4771
|
},
|
|
4772
|
-
|
|
4772
|
+
toLabel: {
|
|
4773
4773
|
type: "string",
|
|
4774
|
-
description: "Target
|
|
4774
|
+
description: "Target Falkor node label, e.g. PpvProject.",
|
|
4775
4775
|
},
|
|
4776
4776
|
toId: {
|
|
4777
4777
|
type: "string",
|
|
4778
|
-
description: "Target record id
|
|
4778
|
+
description: "Target Convex record id stored in node.convexId.",
|
|
4779
4779
|
},
|
|
4780
4780
|
kind: {
|
|
4781
4781
|
type: "string",
|
|
@@ -4799,31 +4799,31 @@ const TOOLS = [
|
|
|
4799
4799
|
additionalProperties: true,
|
|
4800
4800
|
},
|
|
4801
4801
|
},
|
|
4802
|
-
required: ["
|
|
4802
|
+
required: ["fromLabel", "fromId", "toLabel", "toId", "kind", "reason"],
|
|
4803
4803
|
},
|
|
4804
4804
|
},
|
|
4805
4805
|
{
|
|
4806
|
-
name: "
|
|
4807
|
-
description: "Remove an agent-owned
|
|
4806
|
+
name: "falkor_graph_unlink",
|
|
4807
|
+
description: "Remove an agent-owned FalkorDB AGENT_LINK relationship previously created by falkor_graph_link.",
|
|
4808
4808
|
inputSchema: {
|
|
4809
4809
|
type: "object",
|
|
4810
4810
|
properties: {
|
|
4811
|
-
|
|
4811
|
+
fromLabel: { type: "string", description: "Source Falkor node label." },
|
|
4812
4812
|
fromId: {
|
|
4813
4813
|
type: "string",
|
|
4814
|
-
description: "Source record id
|
|
4814
|
+
description: "Source Convex record id stored in node.convexId.",
|
|
4815
4815
|
},
|
|
4816
|
-
|
|
4816
|
+
toLabel: { type: "string", description: "Target Falkor node label." },
|
|
4817
4817
|
toId: {
|
|
4818
4818
|
type: "string",
|
|
4819
|
-
description: "Target record id
|
|
4819
|
+
description: "Target Convex record id stored in node.convexId.",
|
|
4820
4820
|
},
|
|
4821
4821
|
kind: {
|
|
4822
4822
|
type: "string",
|
|
4823
4823
|
description: "Relationship kind used when linking.",
|
|
4824
4824
|
},
|
|
4825
4825
|
},
|
|
4826
|
-
required: ["
|
|
4826
|
+
required: ["fromLabel", "fromId", "toLabel", "toId", "kind"],
|
|
4827
4827
|
},
|
|
4828
4828
|
},
|
|
4829
4829
|
{
|
|
@@ -4914,7 +4914,7 @@ const TOOLS = [
|
|
|
4914
4914
|
},
|
|
4915
4915
|
{
|
|
4916
4916
|
name: "delete_ppv_vision",
|
|
4917
|
-
description: "Delete a PPV vision and cascade-delete its identity
|
|
4917
|
+
description: "Delete a PPV vision and cascade-delete its identity and pillars.",
|
|
4918
4918
|
inputSchema: {
|
|
4919
4919
|
type: "object",
|
|
4920
4920
|
properties: {
|
|
@@ -5017,143 +5017,6 @@ const TOOLS = [
|
|
|
5017
5017
|
required: ["pillarId"],
|
|
5018
5018
|
},
|
|
5019
5019
|
},
|
|
5020
|
-
{
|
|
5021
|
-
name: "create_ppv_weekly_action",
|
|
5022
|
-
description: "Create an identity-aligned PPV weekly action.",
|
|
5023
|
-
inputSchema: {
|
|
5024
|
-
type: "object",
|
|
5025
|
-
properties: {
|
|
5026
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5027
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5028
|
-
pillarId: { type: "string", description: "PPV pillar ID" },
|
|
5029
|
-
projectId: {
|
|
5030
|
-
type: "string",
|
|
5031
|
-
description: "Existing LifeOS project ID",
|
|
5032
|
-
},
|
|
5033
|
-
category: { type: "string", description: "Action category" },
|
|
5034
|
-
action: { type: "string", description: "Small action to take" },
|
|
5035
|
-
status: {
|
|
5036
|
-
type: "string",
|
|
5037
|
-
enum: ["pending", "in_progress", "completed", "skipped"],
|
|
5038
|
-
description: "Action status",
|
|
5039
|
-
},
|
|
5040
|
-
scheduledDate: { type: "string", description: "ISO scheduled date" },
|
|
5041
|
-
week: { type: "string", description: "ISO week label" },
|
|
5042
|
-
},
|
|
5043
|
-
required: ["visionId", "category", "action"],
|
|
5044
|
-
},
|
|
5045
|
-
},
|
|
5046
|
-
{
|
|
5047
|
-
name: "update_ppv_weekly_action",
|
|
5048
|
-
description: "Update a PPV weekly action.",
|
|
5049
|
-
inputSchema: {
|
|
5050
|
-
type: "object",
|
|
5051
|
-
properties: {
|
|
5052
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5053
|
-
actionId: { type: "string", description: "PPV weekly action ID" },
|
|
5054
|
-
pillarId: { type: "string", description: "PPV pillar ID" },
|
|
5055
|
-
projectId: {
|
|
5056
|
-
type: "string",
|
|
5057
|
-
description: "Existing LifeOS project ID",
|
|
5058
|
-
},
|
|
5059
|
-
category: { type: "string", description: "Updated category" },
|
|
5060
|
-
action: { type: "string", description: "Updated action" },
|
|
5061
|
-
status: {
|
|
5062
|
-
type: "string",
|
|
5063
|
-
enum: ["pending", "in_progress", "completed", "skipped"],
|
|
5064
|
-
description: "Updated status",
|
|
5065
|
-
},
|
|
5066
|
-
scheduledDate: { type: "string", description: "ISO scheduled date" },
|
|
5067
|
-
week: { type: "string", description: "ISO week label" },
|
|
5068
|
-
},
|
|
5069
|
-
required: ["actionId"],
|
|
5070
|
-
},
|
|
5071
|
-
},
|
|
5072
|
-
{
|
|
5073
|
-
name: "delete_ppv_weekly_action",
|
|
5074
|
-
description: "Delete a PPV weekly action.",
|
|
5075
|
-
inputSchema: {
|
|
5076
|
-
type: "object",
|
|
5077
|
-
properties: {
|
|
5078
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5079
|
-
actionId: { type: "string", description: "PPV weekly action ID" },
|
|
5080
|
-
},
|
|
5081
|
-
required: ["actionId"],
|
|
5082
|
-
},
|
|
5083
|
-
},
|
|
5084
|
-
{
|
|
5085
|
-
name: "create_ppv_reflection",
|
|
5086
|
-
description: "Create a PPV weekly reflection.",
|
|
5087
|
-
inputSchema: {
|
|
5088
|
-
type: "object",
|
|
5089
|
-
properties: {
|
|
5090
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5091
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5092
|
-
week: { type: "string", description: "ISO week label" },
|
|
5093
|
-
questions: {
|
|
5094
|
-
type: "array",
|
|
5095
|
-
items: {
|
|
5096
|
-
type: "object",
|
|
5097
|
-
properties: {
|
|
5098
|
-
question: { type: "string" },
|
|
5099
|
-
answer: { type: "string" },
|
|
5100
|
-
},
|
|
5101
|
-
required: ["question", "answer"],
|
|
5102
|
-
},
|
|
5103
|
-
description: "Reflection questions and answers",
|
|
5104
|
-
},
|
|
5105
|
-
alignmentScore: { type: "number", description: "Optional score" },
|
|
5106
|
-
},
|
|
5107
|
-
required: ["visionId", "week", "questions"],
|
|
5108
|
-
},
|
|
5109
|
-
},
|
|
5110
|
-
{
|
|
5111
|
-
name: "delete_ppv_reflection",
|
|
5112
|
-
description: "Delete a PPV reflection.",
|
|
5113
|
-
inputSchema: {
|
|
5114
|
-
type: "object",
|
|
5115
|
-
properties: {
|
|
5116
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5117
|
-
reflectionId: { type: "string", description: "PPV reflection ID" },
|
|
5118
|
-
},
|
|
5119
|
-
required: ["reflectionId"],
|
|
5120
|
-
},
|
|
5121
|
-
},
|
|
5122
|
-
{
|
|
5123
|
-
name: "create_ppv_adjustment",
|
|
5124
|
-
description: "Create a PPV adjustment from reflection or lived evidence.",
|
|
5125
|
-
inputSchema: {
|
|
5126
|
-
type: "object",
|
|
5127
|
-
properties: {
|
|
5128
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5129
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5130
|
-
sourceReflectionId: {
|
|
5131
|
-
type: "string",
|
|
5132
|
-
description: "Source PPV reflection ID",
|
|
5133
|
-
},
|
|
5134
|
-
observation: { type: "string", description: "What was observed" },
|
|
5135
|
-
decision: { type: "string", description: "What will change" },
|
|
5136
|
-
affectedComponents: {
|
|
5137
|
-
type: "array",
|
|
5138
|
-
items: { type: "string" },
|
|
5139
|
-
description: "Components affected by the adjustment",
|
|
5140
|
-
},
|
|
5141
|
-
},
|
|
5142
|
-
required: ["visionId", "observation", "decision", "affectedComponents"],
|
|
5143
|
-
},
|
|
5144
|
-
},
|
|
5145
|
-
{
|
|
5146
|
-
name: "delete_ppv_adjustment",
|
|
5147
|
-
description: "Delete a PPV adjustment.",
|
|
5148
|
-
inputSchema: {
|
|
5149
|
-
type: "object",
|
|
5150
|
-
properties: {
|
|
5151
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5152
|
-
adjustmentId: { type: "string", description: "PPV adjustment ID" },
|
|
5153
|
-
},
|
|
5154
|
-
required: ["adjustmentId"],
|
|
5155
|
-
},
|
|
5156
|
-
},
|
|
5157
5020
|
// MCP Server Info
|
|
5158
5021
|
{
|
|
5159
5022
|
name: "get_version",
|
|
@@ -5233,22 +5096,22 @@ const PROMPTS = [
|
|
|
5233
5096
|
},
|
|
5234
5097
|
{
|
|
5235
5098
|
name: "ppv",
|
|
5236
|
-
description: "Manage the PPV life design system: vision, identity, pillars,
|
|
5099
|
+
description: "Manage the PPV life design system: vision, identity, pillars, and projects.",
|
|
5237
5100
|
arguments: [
|
|
5238
5101
|
{
|
|
5239
5102
|
name: "intent",
|
|
5240
|
-
description: "What to do with PPV, e.g. create vision,
|
|
5103
|
+
description: "What to do with PPV, e.g. create vision, update identity, add pillar, link project.",
|
|
5241
5104
|
required: false,
|
|
5242
5105
|
},
|
|
5243
5106
|
],
|
|
5244
5107
|
},
|
|
5245
5108
|
{
|
|
5246
|
-
name: "
|
|
5247
|
-
description: "Use the
|
|
5109
|
+
name: "falkor-graph",
|
|
5110
|
+
description: "Use the FalkorDB sidecar graph: query PPV graph relationships with Cypher and create controlled agent-owned graph links.",
|
|
5248
5111
|
arguments: [
|
|
5249
5112
|
{
|
|
5250
5113
|
name: "intent",
|
|
5251
|
-
description: "What to do with the
|
|
5114
|
+
description: "What to do with the Falkor graph, e.g. inspect PPV-project links or add an agent-owned PPV relationship.",
|
|
5252
5115
|
required: false,
|
|
5253
5116
|
},
|
|
5254
5117
|
],
|
|
@@ -5614,7 +5477,7 @@ Do not ask for confirmation; the user intends this planning workflow to mutate L
|
|
|
5614
5477
|
${intentClause}
|
|
5615
5478
|
|
|
5616
5479
|
Use the LifeOS MCP PPV tools:
|
|
5617
|
-
1. Call get_ppv_workspace first to inspect all owned visions, the selected/current vision, identity, pillars, linked projects,
|
|
5480
|
+
1. Call get_ppv_workspace first to inspect all owned visions, the selected/current vision, identity, pillars, linked projects, and available LifeOS projects.
|
|
5618
5481
|
2. If there is no vision and the user wants the example, call seed_ppv_beijing_workspace.
|
|
5619
5482
|
3. For vision changes, call upsert_ppv_vision.
|
|
5620
5483
|
4. If the user wants to change a vision lifecycle without editing content, call set_ppv_vision_status.
|
|
@@ -5623,8 +5486,6 @@ Use the LifeOS MCP PPV tools:
|
|
|
5623
5486
|
7. If the user wants to permanently remove a vision and its PPV subcomponents, call delete_ppv_vision.
|
|
5624
5487
|
8. For identity work, call upsert_ppv_identity.
|
|
5625
5488
|
9. For pillars, call create_ppv_pillar or update_ppv_pillar. Link only existing LifeOS project IDs from get_ppv_workspace.projects.
|
|
5626
|
-
10. For execution, call create_ppv_weekly_action or update_ppv_weekly_action.
|
|
5627
|
-
11. For learning loops, call create_ppv_reflection and create_ppv_adjustment.
|
|
5628
5489
|
|
|
5629
5490
|
Keep the system simple:
|
|
5630
5491
|
- Vision is directional and experiential, not a task list.
|
|
@@ -5632,38 +5493,37 @@ Keep the system simple:
|
|
|
5632
5493
|
- Pillars are ongoing systems.
|
|
5633
5494
|
- Projects are existing LifeOS projects.
|
|
5634
5495
|
- Weekly actions should be small, concrete, and identity-aligned.
|
|
5635
|
-
- Reflections and adjustments should update behavior based on evidence.
|
|
5636
5496
|
|
|
5637
5497
|
After mutating, report exactly what changed and any IDs needed for future updates.`,
|
|
5638
5498
|
},
|
|
5639
5499
|
},
|
|
5640
5500
|
];
|
|
5641
5501
|
},
|
|
5642
|
-
"
|
|
5502
|
+
"falkor-graph": (args) => {
|
|
5643
5503
|
const intentClause = args.intent
|
|
5644
5504
|
? `User intent: ${args.intent}`
|
|
5645
|
-
: "Start by inspecting the
|
|
5505
|
+
: "Start by inspecting the Falkor graph schema and current agent links.";
|
|
5646
5506
|
return [
|
|
5647
5507
|
{
|
|
5648
5508
|
role: "user",
|
|
5649
5509
|
content: {
|
|
5650
5510
|
type: "text",
|
|
5651
|
-
text: `Use the LifeOS
|
|
5511
|
+
text: `Use the LifeOS FalkorDB sidecar graph.
|
|
5652
5512
|
|
|
5653
5513
|
${intentClause}
|
|
5654
5514
|
|
|
5655
5515
|
Use these MCP tools:
|
|
5656
|
-
1. Call
|
|
5657
|
-
2. Use
|
|
5658
|
-
3. Use
|
|
5659
|
-
4. Use
|
|
5516
|
+
1. Call falkor_graph_schema first to inspect the allowed graph contract and examples.
|
|
5517
|
+
2. Use falkor_graph_query for read-only Cypher. MATCH/WITH queries must include LIMIT unless doing count aggregation.
|
|
5518
|
+
3. Use falkor_graph_link when you need to create a relationship between existing Falkor nodes. This writes only AGENT_LINK relationships.
|
|
5519
|
+
4. Use falkor_graph_unlink only to remove relationships previously created by falkor_graph_link.
|
|
5660
5520
|
|
|
5661
5521
|
Rules:
|
|
5662
|
-
- Convex remains canonical for entity data. Do not use
|
|
5663
|
-
-
|
|
5522
|
+
- Convex remains canonical for entity data. Do not use FalkorDB to edit projects or PPV records.
|
|
5523
|
+
- FalkorDB is the graph sidecar.
|
|
5664
5524
|
- Always include a concrete reason and confidence when linking.
|
|
5665
|
-
- Prefer precise
|
|
5666
|
-
- Keep queries scoped with LIMIT and return concise findings plus the
|
|
5525
|
+
- Prefer precise node labels and convexId values from prior reads; do not invent ids.
|
|
5526
|
+
- Keep queries scoped with LIMIT and return concise findings plus the Cypher you used when useful.`,
|
|
5667
5527
|
},
|
|
5668
5528
|
},
|
|
5669
5529
|
];
|
|
@@ -6550,11 +6410,14 @@ Keep it concise and direct. This is an accountability report, not a feel-good su
|
|
|
6550
6410
|
const CONVEX_URL = options.url || process.env.CONVEX_URL;
|
|
6551
6411
|
const API_KEY = options.apiKey || process.env.LIFEOS_API_KEY;
|
|
6552
6412
|
const USER_ID = options.userId || process.env.LIFEOS_USER_ID;
|
|
6553
|
-
const
|
|
6554
|
-
const
|
|
6555
|
-
const
|
|
6556
|
-
const
|
|
6557
|
-
const
|
|
6413
|
+
const FALKOR_BROWSER_ENDPOINT = process.env.FALKOR_BROWSER_ENDPOINT || "https://falkordb.apps.rjlabs.dev";
|
|
6414
|
+
const FALKOR_GRAPH = process.env.FALKOR_GRAPH || "lifeos_ppv";
|
|
6415
|
+
const FALKOR_USER = process.env.FALKOR_USER || "default";
|
|
6416
|
+
const FALKOR_PASS = process.env.FALKOR_PASS;
|
|
6417
|
+
const FALKOR_TOKEN = process.env.FALKOR_TOKEN || process.env.FALKOR_PAT;
|
|
6418
|
+
const FALKOR_HOST = process.env.FALKOR_HOST || "localhost";
|
|
6419
|
+
const FALKOR_PORT = process.env.FALKOR_PORT || "6379";
|
|
6420
|
+
const FALKOR_TLS = process.env.FALKOR_TLS || "false";
|
|
6558
6421
|
// Validate required configuration
|
|
6559
6422
|
const missingConfig = [];
|
|
6560
6423
|
if (!CONVEX_URL)
|
|
@@ -6666,13 +6529,6 @@ async function runDirectCliCommand(command) {
|
|
|
6666
6529
|
pillar: "create_ppv_pillar",
|
|
6667
6530
|
"update-pillar": "update_ppv_pillar",
|
|
6668
6531
|
"delete-pillar": "delete_ppv_pillar",
|
|
6669
|
-
action: "create_ppv_weekly_action",
|
|
6670
|
-
"update-action": "update_ppv_weekly_action",
|
|
6671
|
-
"delete-action": "delete_ppv_weekly_action",
|
|
6672
|
-
reflection: "create_ppv_reflection",
|
|
6673
|
-
"delete-reflection": "delete_ppv_reflection",
|
|
6674
|
-
adjustment: "create_ppv_adjustment",
|
|
6675
|
-
"delete-adjustment": "delete_ppv_adjustment",
|
|
6676
6532
|
};
|
|
6677
6533
|
const tool = actionToTool[command.action];
|
|
6678
6534
|
if (!tool) {
|
|
@@ -6708,55 +6564,6 @@ async function callConvexJsonEndpoint(path, body) {
|
|
|
6708
6564
|
}
|
|
6709
6565
|
return await response.json();
|
|
6710
6566
|
}
|
|
6711
|
-
const SURREAL_AGENT_LINK_TABLE = "lifeos_agent_links";
|
|
6712
|
-
const SURREAL_TABLE_PREFIXES = ["lifeos_", "life_", "ppv1_"];
|
|
6713
|
-
const SURREAL_RELATION_TABLES = [
|
|
6714
|
-
"ppv_has_identity",
|
|
6715
|
-
"ppv_has_pillar",
|
|
6716
|
-
"ppv_has_action",
|
|
6717
|
-
"ppv_has_reflection",
|
|
6718
|
-
"ppv_has_adjustment",
|
|
6719
|
-
"ppv_pillar_drives_action",
|
|
6720
|
-
"ppv_pillar_supports_project",
|
|
6721
|
-
"ppv_project_executes_action",
|
|
6722
|
-
"ppv_reflection_inspires_adjustment",
|
|
6723
|
-
SURREAL_AGENT_LINK_TABLE,
|
|
6724
|
-
];
|
|
6725
|
-
const SURREAL_MUTATING_KEYWORDS = [
|
|
6726
|
-
"ALTER",
|
|
6727
|
-
"CREATE",
|
|
6728
|
-
"DEFINE",
|
|
6729
|
-
"DELETE",
|
|
6730
|
-
"INSERT",
|
|
6731
|
-
"KILL",
|
|
6732
|
-
"LIVE",
|
|
6733
|
-
"REBUILD",
|
|
6734
|
-
"RELATE",
|
|
6735
|
-
"REMOVE",
|
|
6736
|
-
"SLEEP",
|
|
6737
|
-
"THROW",
|
|
6738
|
-
"UPDATE",
|
|
6739
|
-
"UPSERT",
|
|
6740
|
-
];
|
|
6741
|
-
function requireSurrealConfig() {
|
|
6742
|
-
const missing = [];
|
|
6743
|
-
if (!SURREAL_ENDPOINT)
|
|
6744
|
-
missing.push("SURREAL_ENDPOINT");
|
|
6745
|
-
if (!SURREAL_USER)
|
|
6746
|
-
missing.push("SURREAL_USER");
|
|
6747
|
-
if (!SURREAL_PASS)
|
|
6748
|
-
missing.push("SURREAL_PASS");
|
|
6749
|
-
if (missing.length > 0) {
|
|
6750
|
-
throw new Error(`Missing SurrealDB configuration: ${missing.join(", ")}. Set SURREAL_ENDPOINT, SURREAL_USER, SURREAL_PASS, SURREAL_NS, and SURREAL_DB in the agent runtime.`);
|
|
6751
|
-
}
|
|
6752
|
-
return {
|
|
6753
|
-
endpoint: SURREAL_ENDPOINT,
|
|
6754
|
-
user: SURREAL_USER,
|
|
6755
|
-
pass: SURREAL_PASS,
|
|
6756
|
-
ns: SURREAL_NS,
|
|
6757
|
-
db: SURREAL_DB,
|
|
6758
|
-
};
|
|
6759
|
-
}
|
|
6760
6567
|
function stripSqlCommentsAndStrings(sql) {
|
|
6761
6568
|
let output = "";
|
|
6762
6569
|
let index = 0;
|
|
@@ -6809,74 +6616,11 @@ function stripSqlCommentsAndStrings(sql) {
|
|
|
6809
6616
|
}
|
|
6810
6617
|
return output;
|
|
6811
6618
|
}
|
|
6812
|
-
function splitSqlStatements(sql) {
|
|
6813
|
-
return stripSqlCommentsAndStrings(sql)
|
|
6814
|
-
.split(";")
|
|
6815
|
-
.map((statement) => statement.trim())
|
|
6816
|
-
.filter(Boolean);
|
|
6817
|
-
}
|
|
6818
|
-
function assertReadOnlySurrealSql(sql) {
|
|
6819
|
-
const statements = splitSqlStatements(sql);
|
|
6820
|
-
if (statements.length === 0) {
|
|
6821
|
-
throw new Error("SurrealQL query is empty.");
|
|
6822
|
-
}
|
|
6823
|
-
if (statements.length > 5) {
|
|
6824
|
-
throw new Error("SurrealQL query is too broad. Use 5 statements or fewer.");
|
|
6825
|
-
}
|
|
6826
|
-
for (const statement of statements) {
|
|
6827
|
-
const upper = statement.toUpperCase();
|
|
6828
|
-
const startsReadOnly = upper.startsWith("SELECT ") ||
|
|
6829
|
-
upper.startsWith("INFO ") ||
|
|
6830
|
-
upper.startsWith("EXPLAIN ");
|
|
6831
|
-
if (!startsReadOnly) {
|
|
6832
|
-
throw new Error("surreal_graph_query only allows SELECT, INFO, and EXPLAIN statements. Use surreal_graph_link for controlled relationship writes.");
|
|
6833
|
-
}
|
|
6834
|
-
for (const keyword of SURREAL_MUTATING_KEYWORDS) {
|
|
6835
|
-
if (new RegExp(`\\b${keyword}\\b`, "i").test(statement)) {
|
|
6836
|
-
throw new Error(`surreal_graph_query blocked mutating keyword ${keyword}. Use guarded graph write tools instead.`);
|
|
6837
|
-
}
|
|
6838
|
-
}
|
|
6839
|
-
const isAggregateCount = /\bCOUNT\s*\(/i.test(statement);
|
|
6840
|
-
if (upper.startsWith("SELECT ") &&
|
|
6841
|
-
!/\bLIMIT\b/i.test(statement) &&
|
|
6842
|
-
!isAggregateCount) {
|
|
6843
|
-
throw new Error("SELECT queries must include LIMIT unless they are aggregate count queries.");
|
|
6844
|
-
}
|
|
6845
|
-
}
|
|
6846
|
-
}
|
|
6847
6619
|
function clampMaxRows(value) {
|
|
6848
6620
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
6849
6621
|
return 50;
|
|
6850
6622
|
return Math.max(1, Math.min(200, Math.floor(value)));
|
|
6851
6623
|
}
|
|
6852
|
-
function capSurrealResults(results, maxRows) {
|
|
6853
|
-
return results.map((entry) => {
|
|
6854
|
-
if (!Array.isArray(entry.result) || entry.result.length <= maxRows) {
|
|
6855
|
-
return entry;
|
|
6856
|
-
}
|
|
6857
|
-
return {
|
|
6858
|
-
...entry,
|
|
6859
|
-
result: entry.result.slice(0, maxRows),
|
|
6860
|
-
detail: `${entry.result.length - maxRows} additional rows omitted by lifeos-mcp maxRows=${maxRows}`,
|
|
6861
|
-
};
|
|
6862
|
-
});
|
|
6863
|
-
}
|
|
6864
|
-
function assertSurrealIdentifier(value, label) {
|
|
6865
|
-
if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) {
|
|
6866
|
-
throw new Error(`${label} must be a simple Surreal identifier.`);
|
|
6867
|
-
}
|
|
6868
|
-
return value;
|
|
6869
|
-
}
|
|
6870
|
-
function assertLifeOsNodeTable(value, label) {
|
|
6871
|
-
const table = assertSurrealIdentifier(value, label);
|
|
6872
|
-
if (!SURREAL_TABLE_PREFIXES.some((prefix) => table.startsWith(prefix))) {
|
|
6873
|
-
throw new Error(`${label} must start with one of: ${SURREAL_TABLE_PREFIXES.join(", ")}`);
|
|
6874
|
-
}
|
|
6875
|
-
if (SURREAL_RELATION_TABLES.includes(table)) {
|
|
6876
|
-
throw new Error(`${label} must be an entity table, not a relation table.`);
|
|
6877
|
-
}
|
|
6878
|
-
return table;
|
|
6879
|
-
}
|
|
6880
6624
|
function assertRecordId(value, label) {
|
|
6881
6625
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
6882
6626
|
throw new Error(`${label} is required.`);
|
|
@@ -6892,79 +6636,237 @@ function assertRelationKind(value) {
|
|
|
6892
6636
|
}
|
|
6893
6637
|
return value;
|
|
6894
6638
|
}
|
|
6895
|
-
|
|
6896
|
-
|
|
6639
|
+
const FALKOR_AGENT_RELATIONSHIP = "AGENT_LINK";
|
|
6640
|
+
const FALKOR_NODE_LABELS = [
|
|
6641
|
+
"PpvVision",
|
|
6642
|
+
"PpvIdentity",
|
|
6643
|
+
"PpvPillar",
|
|
6644
|
+
"PpvProject",
|
|
6645
|
+
];
|
|
6646
|
+
const FALKOR_INFERRED_RELATIONSHIPS = [
|
|
6647
|
+
"HAS_IDENTITY",
|
|
6648
|
+
"HAS_PILLAR",
|
|
6649
|
+
"PILLAR_SUPPORTS_PROJECT",
|
|
6650
|
+
];
|
|
6651
|
+
const FALKOR_MUTATING_KEYWORDS = [
|
|
6652
|
+
"CALL",
|
|
6653
|
+
"CREATE",
|
|
6654
|
+
"DELETE",
|
|
6655
|
+
"DROP",
|
|
6656
|
+
"FOREACH",
|
|
6657
|
+
"LOAD",
|
|
6658
|
+
"MERGE",
|
|
6659
|
+
"REMOVE",
|
|
6660
|
+
"SET",
|
|
6661
|
+
];
|
|
6662
|
+
function requireFalkorConfig() {
|
|
6663
|
+
if (!FALKOR_TOKEN && !FALKOR_PASS) {
|
|
6664
|
+
throw new Error("Missing FalkorDB configuration: set FALKOR_TOKEN/FALKOR_PAT or FALKOR_PASS in the agent runtime.");
|
|
6665
|
+
}
|
|
6666
|
+
return {
|
|
6667
|
+
endpoint: FALKOR_BROWSER_ENDPOINT,
|
|
6668
|
+
graph: FALKOR_GRAPH,
|
|
6669
|
+
username: FALKOR_USER,
|
|
6670
|
+
password: FALKOR_PASS,
|
|
6671
|
+
token: FALKOR_TOKEN,
|
|
6672
|
+
host: FALKOR_HOST,
|
|
6673
|
+
port: FALKOR_PORT,
|
|
6674
|
+
tls: FALKOR_TLS,
|
|
6675
|
+
};
|
|
6897
6676
|
}
|
|
6898
|
-
function
|
|
6899
|
-
return
|
|
6677
|
+
function sleep(ms) {
|
|
6678
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6900
6679
|
}
|
|
6901
|
-
function
|
|
6902
|
-
return
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6680
|
+
function cypherString(value) {
|
|
6681
|
+
return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
6682
|
+
}
|
|
6683
|
+
function cypherValue(value) {
|
|
6684
|
+
if (value === null || value === undefined)
|
|
6685
|
+
return "NULL";
|
|
6686
|
+
if (typeof value === "number")
|
|
6687
|
+
return Number.isFinite(value) ? String(value) : "NULL";
|
|
6688
|
+
if (typeof value === "boolean")
|
|
6689
|
+
return value ? "true" : "false";
|
|
6690
|
+
if (typeof value === "string")
|
|
6691
|
+
return cypherString(value);
|
|
6692
|
+
if (Array.isArray(value))
|
|
6693
|
+
return `[${value.map(cypherValue).join(", ")}]`;
|
|
6694
|
+
return cypherString(JSON.stringify(value));
|
|
6906
6695
|
}
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6696
|
+
function cypherProps(props) {
|
|
6697
|
+
return `{${Object.entries(props)
|
|
6698
|
+
.filter(([, value]) => value !== undefined)
|
|
6699
|
+
.map(([key, value]) => `${key}: ${cypherValue(value)}`)
|
|
6700
|
+
.join(", ")}}`;
|
|
6701
|
+
}
|
|
6702
|
+
async function requestFalkorToken(config, attempt) {
|
|
6703
|
+
if (!config.password) {
|
|
6704
|
+
throw new Error("FALKOR_PASS is required when FALKOR_TOKEN is not set.");
|
|
6705
|
+
}
|
|
6706
|
+
const tokenName = [
|
|
6707
|
+
"lifeos-mcp",
|
|
6708
|
+
attempt === 0 ? "graph" : "retry",
|
|
6709
|
+
Date.now(),
|
|
6710
|
+
Math.random().toString(36).slice(2, 10),
|
|
6711
|
+
].join("-");
|
|
6712
|
+
const response = await fetch(`${config.endpoint.replace(/\/$/, "")}/api/auth/tokens/credentials`, {
|
|
6911
6713
|
method: "POST",
|
|
6714
|
+
headers: { "Content-Type": "application/json" },
|
|
6715
|
+
body: JSON.stringify({
|
|
6716
|
+
username: config.username,
|
|
6717
|
+
password: config.password,
|
|
6718
|
+
host: config.host,
|
|
6719
|
+
port: config.port,
|
|
6720
|
+
tls: config.tls,
|
|
6721
|
+
name: tokenName,
|
|
6722
|
+
ttlSeconds: 3600,
|
|
6723
|
+
}),
|
|
6724
|
+
});
|
|
6725
|
+
const text = await response.text();
|
|
6726
|
+
if (!response.ok) {
|
|
6727
|
+
throw new Error(`FalkorDB auth failed (${response.status}): ${text}`);
|
|
6728
|
+
}
|
|
6729
|
+
const parsed = JSON.parse(text);
|
|
6730
|
+
if (!parsed.token) {
|
|
6731
|
+
throw new Error(`FalkorDB auth returned no token: ${text}`);
|
|
6732
|
+
}
|
|
6733
|
+
return parsed.token;
|
|
6734
|
+
}
|
|
6735
|
+
async function getFalkorToken(config) {
|
|
6736
|
+
if (config.token)
|
|
6737
|
+
return config.token;
|
|
6738
|
+
let lastError;
|
|
6739
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
6740
|
+
try {
|
|
6741
|
+
return await requestFalkorToken(config, attempt);
|
|
6742
|
+
}
|
|
6743
|
+
catch (error) {
|
|
6744
|
+
lastError = error;
|
|
6745
|
+
if (attempt < 2)
|
|
6746
|
+
await sleep(250 * (attempt + 1));
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
throw lastError;
|
|
6750
|
+
}
|
|
6751
|
+
function parseFalkorEventStream(text) {
|
|
6752
|
+
const data = text
|
|
6753
|
+
.split(/\r?\n/)
|
|
6754
|
+
.filter((line) => line.startsWith("data:"))
|
|
6755
|
+
.map((line) => line.slice(5).trimStart())
|
|
6756
|
+
.join("\n")
|
|
6757
|
+
.trim();
|
|
6758
|
+
if (!data)
|
|
6759
|
+
return { data: [], metadata: [] };
|
|
6760
|
+
return JSON.parse(data);
|
|
6761
|
+
}
|
|
6762
|
+
async function runFalkorQuery(query) {
|
|
6763
|
+
const config = requireFalkorConfig();
|
|
6764
|
+
const token = await getFalkorToken(config);
|
|
6765
|
+
const url = `${config.endpoint.replace(/\/$/, "")}/api/graph/${encodeURIComponent(config.graph)}?query=${encodeURIComponent(query)}&timeout=30000`;
|
|
6766
|
+
const response = await fetch(url, {
|
|
6767
|
+
method: "GET",
|
|
6912
6768
|
headers: {
|
|
6913
|
-
Authorization: `
|
|
6914
|
-
|
|
6915
|
-
Accept: "application/json",
|
|
6916
|
-
"Surreal-NS": cfg.ns,
|
|
6917
|
-
"Surreal-DB": cfg.db,
|
|
6769
|
+
Authorization: `Bearer ${token}`,
|
|
6770
|
+
Accept: "text/event-stream",
|
|
6918
6771
|
},
|
|
6919
|
-
body: sql,
|
|
6920
6772
|
});
|
|
6921
6773
|
const text = await response.text();
|
|
6922
6774
|
if (!response.ok) {
|
|
6923
|
-
throw new Error(`
|
|
6775
|
+
throw new Error(`FalkorDB query failed (${response.status}): ${text}`);
|
|
6924
6776
|
}
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6777
|
+
return parseFalkorEventStream(text);
|
|
6778
|
+
}
|
|
6779
|
+
function assertReadOnlyFalkorCypher(query) {
|
|
6780
|
+
const stripped = stripSqlCommentsAndStrings(query).trim();
|
|
6781
|
+
if (!stripped)
|
|
6782
|
+
throw new Error("FalkorDB Cypher query is empty.");
|
|
6783
|
+
if (stripped.includes(";")) {
|
|
6784
|
+
throw new Error("falkor_graph_query allows one Cypher statement at a time.");
|
|
6785
|
+
}
|
|
6786
|
+
const upper = stripped.toUpperCase();
|
|
6787
|
+
const startsReadOnly = upper.startsWith("MATCH ") ||
|
|
6788
|
+
upper.startsWith("WITH ") ||
|
|
6789
|
+
upper.startsWith("RETURN ") ||
|
|
6790
|
+
upper.startsWith("EXPLAIN ") ||
|
|
6791
|
+
upper.startsWith("PROFILE ");
|
|
6792
|
+
if (!startsReadOnly) {
|
|
6793
|
+
throw new Error("falkor_graph_query only allows read-only Cypher. Start with MATCH, WITH, RETURN, EXPLAIN, or PROFILE.");
|
|
6794
|
+
}
|
|
6795
|
+
for (const keyword of FALKOR_MUTATING_KEYWORDS) {
|
|
6796
|
+
if (new RegExp(`\\b${keyword}\\b`, "i").test(stripped)) {
|
|
6797
|
+
throw new Error(`falkor_graph_query blocked mutating keyword ${keyword}. Use falkor_graph_link for guarded relationship writes.`);
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
const isAggregateCount = /\bCOUNT\s*\(/i.test(stripped);
|
|
6801
|
+
if ((upper.startsWith("MATCH ") || upper.startsWith("WITH ")) &&
|
|
6802
|
+
!/\bLIMIT\b/i.test(stripped) &&
|
|
6803
|
+
!isAggregateCount) {
|
|
6804
|
+
throw new Error("MATCH/WITH queries must include LIMIT unless they are aggregate count queries.");
|
|
6929
6805
|
}
|
|
6930
|
-
return parsed;
|
|
6931
6806
|
}
|
|
6932
|
-
function
|
|
6807
|
+
function capFalkorResult(result, maxRows) {
|
|
6808
|
+
const rows = result.data ?? [];
|
|
6809
|
+
if (rows.length <= maxRows)
|
|
6810
|
+
return result;
|
|
6933
6811
|
return {
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6812
|
+
...result,
|
|
6813
|
+
data: rows.slice(0, maxRows),
|
|
6814
|
+
detail: `${rows.length - maxRows} additional rows omitted by lifeos-mcp maxRows=${maxRows}`,
|
|
6815
|
+
};
|
|
6816
|
+
}
|
|
6817
|
+
function assertFalkorIdentifier(value, label) {
|
|
6818
|
+
if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) {
|
|
6819
|
+
throw new Error(`${label} must be a simple Falkor identifier.`);
|
|
6820
|
+
}
|
|
6821
|
+
return value;
|
|
6822
|
+
}
|
|
6823
|
+
function assertFalkorLabel(value, label) {
|
|
6824
|
+
const nodeLabel = assertFalkorIdentifier(value, label);
|
|
6825
|
+
if (!FALKOR_NODE_LABELS.includes(nodeLabel)) {
|
|
6826
|
+
throw new Error(`${label} must be one of: ${FALKOR_NODE_LABELS.join(", ")}`);
|
|
6827
|
+
}
|
|
6828
|
+
return nodeLabel;
|
|
6829
|
+
}
|
|
6830
|
+
function falkorLinkId(parts) {
|
|
6831
|
+
return [parts.kind, parts.fromLabel, parts.fromId, parts.toLabel, parts.toId]
|
|
6832
|
+
.join("__")
|
|
6833
|
+
.replace(/[^A-Za-z0-9_:-]/g, "_")
|
|
6834
|
+
.slice(0, 512);
|
|
6835
|
+
}
|
|
6836
|
+
function falkorGraphSchema() {
|
|
6837
|
+
return {
|
|
6838
|
+
configured: Boolean(FALKOR_TOKEN || FALKOR_PASS),
|
|
6839
|
+
endpointConfigured: Boolean(FALKOR_BROWSER_ENDPOINT),
|
|
6840
|
+
graph: FALKOR_GRAPH,
|
|
6841
|
+
nodeLabels: FALKOR_NODE_LABELS,
|
|
6842
|
+
inferredRelationships: FALKOR_INFERRED_RELATIONSHIPS,
|
|
6843
|
+
agentWritableRelationshipType: FALKOR_AGENT_RELATIONSHIP,
|
|
6941
6844
|
rules: [
|
|
6942
|
-
"Use
|
|
6943
|
-
"
|
|
6944
|
-
"Use
|
|
6945
|
-
"Do not mutate Convex-synced
|
|
6845
|
+
"Use falkor_graph_query for read-only Cypher only.",
|
|
6846
|
+
"MATCH/WITH queries must include LIMIT unless they are aggregate count queries.",
|
|
6847
|
+
"Use falkor_graph_link for agent-created relationships; it writes only AGENT_LINK edges.",
|
|
6848
|
+
"Do not mutate Convex-synced nodes or inferred PPV relationships from FalkorDB. Convex remains canonical.",
|
|
6946
6849
|
"Every agent link must include a reason and confidence.",
|
|
6947
6850
|
],
|
|
6948
6851
|
examples: {
|
|
6949
|
-
|
|
6950
|
-
projectInboundLinks: "
|
|
6951
|
-
agentLinks: "
|
|
6852
|
+
ppvOverview: "MATCH (v:PpvVision)-[:HAS_PILLAR]->(p:PpvPillar)-[:PILLAR_SUPPORTS_PROJECT]->(project:PpvProject) RETURN v.convexId, v.title, p.convexId, p.title, project.convexId, project.title LIMIT 25",
|
|
6853
|
+
projectInboundLinks: "MATCH (pillar:PpvPillar)-[:PILLAR_SUPPORTS_PROJECT]->(project:PpvProject {convexId: 'PROJECT_ID'}) RETURN pillar.convexId, pillar.title, project.title LIMIT 10",
|
|
6854
|
+
agentLinks: "MATCH (a)-[r:AGENT_LINK]->(b) RETURN a.convexId, type(r), r.kind, r.reason, r.confidence, b.convexId LIMIT 25",
|
|
6952
6855
|
},
|
|
6953
6856
|
};
|
|
6954
6857
|
}
|
|
6955
|
-
async function
|
|
6858
|
+
async function falkorGraphQuery(args) {
|
|
6956
6859
|
const query = args.query;
|
|
6957
6860
|
if (typeof query !== "string") {
|
|
6958
6861
|
throw new Error("query is required.");
|
|
6959
6862
|
}
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
return capSurrealResults(results, clampMaxRows(args.maxRows));
|
|
6863
|
+
assertReadOnlyFalkorCypher(query);
|
|
6864
|
+
return capFalkorResult(await runFalkorQuery(query), clampMaxRows(args.maxRows));
|
|
6963
6865
|
}
|
|
6964
|
-
async function
|
|
6965
|
-
const
|
|
6866
|
+
async function falkorGraphLink(args) {
|
|
6867
|
+
const fromLabel = assertFalkorLabel(args.fromLabel, "fromLabel");
|
|
6966
6868
|
const fromId = assertRecordId(args.fromId, "fromId");
|
|
6967
|
-
const
|
|
6869
|
+
const toLabel = assertFalkorLabel(args.toLabel, "toLabel");
|
|
6968
6870
|
const toId = assertRecordId(args.toId, "toId");
|
|
6969
6871
|
const kind = assertRelationKind(args.kind);
|
|
6970
6872
|
const reason = assertRecordId(args.reason, "reason");
|
|
@@ -6980,8 +6882,9 @@ async function surrealGraphLink(args) {
|
|
|
6980
6882
|
? args.metadata
|
|
6981
6883
|
: {};
|
|
6982
6884
|
const now = new Date().toISOString();
|
|
6983
|
-
const linkId =
|
|
6984
|
-
const
|
|
6885
|
+
const linkId = falkorLinkId({ fromLabel, fromId, toLabel, toId, kind });
|
|
6886
|
+
const props = cypherProps({
|
|
6887
|
+
linkId,
|
|
6985
6888
|
kind,
|
|
6986
6889
|
reason,
|
|
6987
6890
|
confidence,
|
|
@@ -6989,41 +6892,35 @@ async function surrealGraphLink(args) {
|
|
|
6989
6892
|
sourceSystem: "agent",
|
|
6990
6893
|
sourceUserId: USER_ID,
|
|
6991
6894
|
userId: USER_ID,
|
|
6992
|
-
|
|
6993
|
-
targetTable: toTable,
|
|
6994
|
-
metadata,
|
|
6895
|
+
metadataJson: JSON.stringify(metadata),
|
|
6995
6896
|
createdAt: now,
|
|
6996
6897
|
updatedAt: now,
|
|
6997
|
-
};
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
`DEFINE INDEX IF NOT EXISTS ${SURREAL_AGENT_LINK_TABLE}_kind ON TABLE ${SURREAL_AGENT_LINK_TABLE} COLUMNS kind;`,
|
|
7001
|
-
`DEFINE INDEX IF NOT EXISTS ${SURREAL_AGENT_LINK_TABLE}_createdBy ON TABLE ${SURREAL_AGENT_LINK_TABLE} COLUMNS createdBy;`,
|
|
7002
|
-
`DELETE ${SURREAL_AGENT_LINK_TABLE}:\`${escapeSurrealRecordId(linkId)}\`;`,
|
|
7003
|
-
`RELATE ${surrealThing(fromTable, fromId)}->${SURREAL_AGENT_LINK_TABLE}:\`${escapeSurrealRecordId(linkId)}\`->${surrealThing(toTable, toId)} CONTENT ${JSON.stringify(content)};`,
|
|
7004
|
-
].join("\n");
|
|
7005
|
-
const results = await runSurrealSql(sql);
|
|
6898
|
+
});
|
|
6899
|
+
await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}})-[r:${FALKOR_AGENT_RELATIONSHIP} {linkId: ${cypherString(linkId)}}]->(b:${toLabel} {convexId: ${cypherString(toId)}}) DELETE r`);
|
|
6900
|
+
const result = await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}}), (b:${toLabel} {convexId: ${cypherString(toId)}}) CREATE (a)-[r:${FALKOR_AGENT_RELATIONSHIP} ${props}]->(b) RETURN r.linkId AS linkId, r.kind AS kind, r.reason AS reason, r.confidence AS confidence`);
|
|
7006
6901
|
return {
|
|
7007
6902
|
linked: true,
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
6903
|
+
relationType: FALKOR_AGENT_RELATIONSHIP,
|
|
6904
|
+
relationId: linkId,
|
|
6905
|
+
from: `${fromLabel}:${fromId}`,
|
|
6906
|
+
to: `${toLabel}:${toId}`,
|
|
7011
6907
|
kind,
|
|
7012
|
-
|
|
6908
|
+
result,
|
|
7013
6909
|
};
|
|
7014
6910
|
}
|
|
7015
|
-
async function
|
|
7016
|
-
const
|
|
6911
|
+
async function falkorGraphUnlink(args) {
|
|
6912
|
+
const fromLabel = assertFalkorLabel(args.fromLabel, "fromLabel");
|
|
7017
6913
|
const fromId = assertRecordId(args.fromId, "fromId");
|
|
7018
|
-
const
|
|
6914
|
+
const toLabel = assertFalkorLabel(args.toLabel, "toLabel");
|
|
7019
6915
|
const toId = assertRecordId(args.toId, "toId");
|
|
7020
6916
|
const kind = assertRelationKind(args.kind);
|
|
7021
|
-
const linkId =
|
|
7022
|
-
const
|
|
6917
|
+
const linkId = falkorLinkId({ fromLabel, fromId, toLabel, toId, kind });
|
|
6918
|
+
const result = await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}})-[r:${FALKOR_AGENT_RELATIONSHIP} {linkId: ${cypherString(linkId)}}]->(b:${toLabel} {convexId: ${cypherString(toId)}}) DELETE r`);
|
|
7023
6919
|
return {
|
|
7024
6920
|
unlinked: true,
|
|
7025
|
-
|
|
7026
|
-
|
|
6921
|
+
relationType: FALKOR_AGENT_RELATIONSHIP,
|
|
6922
|
+
relationId: linkId,
|
|
6923
|
+
result,
|
|
7027
6924
|
};
|
|
7028
6925
|
}
|
|
7029
6926
|
const STATIC_RESOURCES = [
|
|
@@ -7408,18 +7305,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7408
7305
|
],
|
|
7409
7306
|
};
|
|
7410
7307
|
}
|
|
7411
|
-
if (name === "
|
|
7308
|
+
if (name === "falkor_graph_schema") {
|
|
7412
7309
|
return {
|
|
7413
7310
|
content: [
|
|
7414
7311
|
{
|
|
7415
7312
|
type: "text",
|
|
7416
|
-
text: JSON.stringify(
|
|
7313
|
+
text: JSON.stringify(falkorGraphSchema(), null, 2),
|
|
7417
7314
|
},
|
|
7418
7315
|
],
|
|
7419
7316
|
};
|
|
7420
7317
|
}
|
|
7421
|
-
if (name === "
|
|
7422
|
-
const result = await
|
|
7318
|
+
if (name === "falkor_graph_query") {
|
|
7319
|
+
const result = await falkorGraphQuery(args || {});
|
|
7423
7320
|
return {
|
|
7424
7321
|
content: [
|
|
7425
7322
|
{
|
|
@@ -7429,8 +7326,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7429
7326
|
],
|
|
7430
7327
|
};
|
|
7431
7328
|
}
|
|
7432
|
-
if (name === "
|
|
7433
|
-
const result = await
|
|
7329
|
+
if (name === "falkor_graph_link") {
|
|
7330
|
+
const result = await falkorGraphLink(args || {});
|
|
7434
7331
|
return {
|
|
7435
7332
|
content: [
|
|
7436
7333
|
{
|
|
@@ -7440,8 +7337,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7440
7337
|
],
|
|
7441
7338
|
};
|
|
7442
7339
|
}
|
|
7443
|
-
if (name === "
|
|
7444
|
-
const result = await
|
|
7340
|
+
if (name === "falkor_graph_unlink") {
|
|
7341
|
+
const result = await falkorGraphUnlink(args || {});
|
|
7445
7342
|
return {
|
|
7446
7343
|
content: [
|
|
7447
7344
|
{
|
package/package.json
CHANGED