@yottagraph-app/aether-instructions 1.0.1

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.
@@ -0,0 +1,108 @@
1
+ ---
2
+ description: Rules for developing MCP servers in the mcp-servers/ directory
3
+ globs: mcp-servers/**
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # MCP Server Development (FastMCP)
8
+
9
+ This project supports developing and deploying custom MCP (Model Context Protocol) servers alongside the UI. Servers live in the `mcp-servers/` directory and deploy to Google Cloud Run via the `/deploy_mcp` command.
10
+
11
+ MCP servers expose tools that agents can call. They act as bridges between AI agents and external data sources or APIs.
12
+
13
+ ## Directory Structure
14
+
15
+ Each MCP server is a self-contained Python package:
16
+
17
+ ```
18
+ mcp-servers/<server-name>/
19
+ ├── server.py # Required: FastMCP server definition, must define `mcp = FastMCP("...")`
20
+ ├── requirements.txt # Required: must include fastmcp
21
+ └── Dockerfile # Optional: auto-generated if missing
22
+ ```
23
+
24
+ ## Writing an MCP Server
25
+
26
+ Use [FastMCP](https://github.com/jlowin/fastmcp) (v2.x) to define servers:
27
+
28
+ ```python
29
+ import os
30
+ from fastmcp import FastMCP
31
+
32
+ mcp = FastMCP("my-data-server")
33
+
34
+ @mcp.tool()
35
+ def search_records(query: str, limit: int = 10) -> list[dict]:
36
+ """Search records matching the query.
37
+
38
+ Args:
39
+ query: Search text to match against record names and descriptions.
40
+ limit: Maximum number of results to return.
41
+
42
+ Returns:
43
+ List of matching records with id, name, and description fields.
44
+ """
45
+ # Your implementation here
46
+ return [{"id": "1", "name": "Example", "description": "..."}]
47
+
48
+ @mcp.tool()
49
+ def get_record(record_id: str) -> dict:
50
+ """Get a specific record by ID.
51
+
52
+ Args:
53
+ record_id: The unique identifier of the record.
54
+
55
+ Returns:
56
+ The full record with all fields.
57
+ """
58
+ return {"id": record_id, "name": "Example", "data": {}}
59
+
60
+ if __name__ == "__main__":
61
+ port = int(os.environ.get("PORT", 8080))
62
+ mcp.run(transport="sse", host="0.0.0.0", port=port)
63
+ ```
64
+
65
+ Key rules:
66
+ - The FastMCP instance MUST be named `mcp` — the Dockerfile runs `fastmcp run server:mcp` to find it
67
+ - FastMCP 2.x takes a single positional name argument: `FastMCP("name")`. Do NOT pass `name=` or `description=` as keyword arguments
68
+ - Use `@mcp.tool()` decorators to define tools
69
+ - Tool docstrings are essential: agents read them to understand the tool's purpose, parameters, and return values
70
+ - Use type hints on all tool parameters and return values
71
+
72
+ ## Local Testing
73
+
74
+ Test MCP servers locally:
75
+
76
+ ```bash
77
+ cd mcp-servers/<server-name>
78
+ pip install -r requirements.txt
79
+ python server.py
80
+ ```
81
+
82
+ This starts the server on port 8080 (or `$PORT`). You can also use the FastMCP CLI, which matches the production Dockerfile entry point:
83
+
84
+ ```bash
85
+ python -m fastmcp run server:mcp --transport streamable-http --host 0.0.0.0 --port 8080
86
+ ```
87
+
88
+ ## Deployment
89
+
90
+ Deploy with the `/deploy_mcp` Cursor command. This will:
91
+ 1. Build a container image via Cloud Build
92
+ 2. Deploy to Cloud Run with IAM authentication
93
+ 3. Grant invoker permissions to tenant and Portal service accounts
94
+ 4. Register the server in Firestore (via the Portal API)
95
+ 5. Add the server to `.cursor/mcp.json` for Cursor IDE access
96
+
97
+ ## Connecting MCP Servers to Agents
98
+
99
+ Once deployed, agents can connect to your MCP server. Add the Cloud Run URL to your agent's MCP configuration. The agent's service account (from `broadchurch.yaml`) is already authorized to invoke the server.
100
+
101
+ ## MCP Server Design Guidelines
102
+
103
+ - One server per data domain or external service
104
+ - Keep tools focused and well-documented
105
+ - Return structured data (dicts/lists), not raw strings
106
+ - Handle errors by returning descriptive error messages in the response
107
+ - Use environment variables for API keys and configuration (never hardcode secrets)
108
+ - For secrets, use GCP Secret Manager and access at runtime
@@ -0,0 +1,123 @@
1
+ ---
2
+ description: Apply when working with user preferences, settings persistence, usePrefsStore, the Pref class, KV storage, or app namespacing (NUXT_PUBLIC_APP_ID).
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # User Preferences
7
+
8
+ Preferences are persisted to Upstash Redis (KV) via `usePrefsStore()` from
9
+ `~/composables/usePrefsStore.ts`, backed by `KVPrefsStore` which calls
10
+ `/api/kv/*` endpoints. The KV store is provisioned automatically during
11
+ project creation and connected via Vercel env vars (`KV_REST_API_URL`,
12
+ `KV_REST_API_TOKEN`).
13
+
14
+ ## Two-Tier Namespacing
15
+
16
+ Preferences are scoped by the app ID set in `NUXT_PUBLIC_APP_ID`:
17
+
18
+ - **App-specific**: `/users/{userId}/apps/{appId}/settings/general`
19
+ - **Global (cross-app)**: `/users/{userId}/global/settings/general`
20
+
21
+ ## The Pref Class
22
+
23
+ `Pref<T>` is a reactive wrapper that auto-syncs to KV. Defined in
24
+ `usePrefsStore.ts`.
25
+
26
+ ```typescript
27
+ const myPref = new Pref<string>(docPath, 'fieldName', 'defaultValue');
28
+ await myPref.initialize();
29
+
30
+ myPref.r.value; // reactive ref (use in templates)
31
+ myPref.v; // getter shorthand
32
+ myPref.set('new value'); // persists to KV
33
+ ```
34
+
35
+ Values are JSON-serialized. The `Pref` sets up a watcher after
36
+ `initialize()` so any change to `.r` auto-persists.
37
+
38
+ ## usePrefsStore()
39
+
40
+ ```typescript
41
+ const { readDoc, listDocuments, deleteCollection } = usePrefsStore();
42
+ ```
43
+
44
+ The backing store auto-initializes on first use — no need to call
45
+ `initializePrefsStore()` manually.
46
+
47
+ ## Local Development
48
+
49
+ When KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) are not set
50
+ (e.g. local dev without Vercel env vars), the KV server routes return
51
+ `undefined` for reads and silently skip writes. Prefs will work with
52
+ their default values but won't persist across page refreshes.
53
+
54
+ For full local persistence, copy the KV credentials from your Vercel
55
+ project settings into your local `.env` file (or use the Portal's
56
+ "Get .env file" feature).
57
+
58
+ ## Direct API Alternative
59
+
60
+ If you prefer not to use the `Pref<T>` class, you can call the KV
61
+ routes directly:
62
+
63
+ ```typescript
64
+ // Read
65
+ const value = await $fetch('/api/kv/read', {
66
+ params: { docPath: '/users/abc/settings', fieldName: 'theme' }
67
+ });
68
+
69
+ // Write
70
+ await $fetch('/api/kv/write', {
71
+ method: 'POST',
72
+ body: { docPath: '/users/abc/settings', fieldName: 'theme', value: '"dark"' }
73
+ });
74
+ ```
75
+
76
+ ## Feature-Scoped Preferences
77
+
78
+ Features should namespace preferences under the app's prefix:
79
+
80
+ ```typescript
81
+ function useMyFeaturePrefs() {
82
+ const { appId } = useRuntimeConfig().public;
83
+ const { userId } = useUserState();
84
+ const path = `/users/${userId.value}/apps/${appId}/features/my-feature`;
85
+
86
+ const myPref = new Pref<boolean>(path, 'enabled', true);
87
+ return { myPref };
88
+ }
89
+ ```
90
+
91
+ ## KV Architecture
92
+
93
+ - **Server**: `server/utils/redis.ts` initializes the Upstash Redis client
94
+ from `KV_REST_API_URL` and `KV_REST_API_TOKEN` env vars
95
+ - **API routes**: `server/api/kv/` (read, write, delete, documents)
96
+ - **Client store**: `utils/kvPrefsStore.ts` implements `PrefsStore` by
97
+ calling the KV API routes
98
+ - **Key format**: `prefs:users:{userId}:apps:{appId}:settings:general`
99
+ (doc-style paths converted to colon-separated Redis keys)
100
+
101
+ ## Local Development Without KV
102
+
103
+ When KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) aren't configured,
104
+ `Pref<T>` still works with its default value but won't persist across page
105
+ refreshes. For local dev, use `localStorage` directly as a lightweight
106
+ alternative:
107
+
108
+ ```typescript
109
+ const saved = localStorage.getItem('watchlist');
110
+ const watchlist = ref<string[]>(saved ? JSON.parse(saved) : []);
111
+ watch(watchlist, (val) => localStorage.setItem('watchlist', JSON.stringify(val)), { deep: true });
112
+ ```
113
+
114
+ Use `Pref<T>` for production persistence and `localStorage` for local-only
115
+ development when KV isn't available.
116
+
117
+ ## Scope Guidance
118
+
119
+ | App-specific | Global |
120
+ |---|---|
121
+ | Layout prefs, favorites | Language |
122
+ | Watchlists, feature settings | Accessibility |
123
+ | Feature-specific settings | Timezone, notifications |
@@ -0,0 +1,99 @@
1
+ ---
2
+ description: Nitro server-side API routes and utilities in server/
3
+ globs: server/**
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Nitro Server Routes
8
+
9
+ The `server/` directory contains Nuxt's Nitro server layer. These routes deploy
10
+ with the app to Vercel -- they are NOT a separate service. They handle
11
+ server-side concerns like KV storage, database access, and image proxying
12
+ that can't run in the browser.
13
+
14
+ ## Directory Layout
15
+
16
+ ```
17
+ server/
18
+ ├── api/
19
+ │ ├── kv/ # KV (Upstash Redis) CRUD — read, write, delete, documents, status
20
+ │ └── avatar/[url].ts # Avatar image proxy
21
+ └── utils/
22
+ ├── redis.ts # Upstash Redis client init (from Vercel KV env vars)
23
+ └── cookies.ts # Cookie handling (@hapi/iron)
24
+ ```
25
+
26
+ ## Adding Routes
27
+
28
+ Follow Nitro file-based routing. The filename determines the HTTP method and
29
+ path:
30
+
31
+ ```
32
+ server/api/my-resource.get.ts → GET /api/my-resource
33
+ server/api/my-resource.post.ts → POST /api/my-resource
34
+ server/api/my-resource/[id].get.ts → GET /api/my-resource/:id
35
+ ```
36
+
37
+ Route handler pattern:
38
+
39
+ ```typescript
40
+ export default defineEventHandler(async (event) => {
41
+ const params = getQuery(event); // query string
42
+ const body = await readBody(event); // POST body
43
+ const id = getRouterParam(event, 'id'); // path params
44
+
45
+ // ... implementation ...
46
+ return { result: 'data' };
47
+ });
48
+ ```
49
+
50
+ ## KV Storage (Upstash Redis)
51
+
52
+ `server/utils/redis.ts` initializes the Upstash Redis client from env vars
53
+ that Vercel auto-injects when a KV store is connected:
54
+
55
+ - `KV_REST_API_URL` — Redis REST API endpoint
56
+ - `KV_REST_API_TOKEN` — Auth token
57
+
58
+ ```typescript
59
+ import { getRedis, toRedisKey } from '~/server/utils/redis';
60
+
61
+ const redis = getRedis();
62
+ if (redis) {
63
+ await redis.hset(toRedisKey('/users/abc/settings'), { theme: 'dark' });
64
+ const theme = await redis.hget(toRedisKey('/users/abc/settings'), 'theme');
65
+ }
66
+ ```
67
+
68
+ Returns `null` if KV is not configured (env vars missing). Always check.
69
+
70
+ ## Supabase (PostgreSQL)
71
+
72
+ If Supabase is connected to the project, Vercel auto-injects these env vars:
73
+
74
+ - `NUXT_PUBLIC_SUPABASE_URL` — Supabase project URL
75
+ - `NUXT_PUBLIC_SUPABASE_ANON_KEY` — Public anon key (safe for client-side)
76
+ - `SUPABASE_SERVICE_ROLE_KEY` — Server-only service role key (never expose to client)
77
+ - `SUPABASE_DB_URL` — Direct Postgres connection string
78
+
79
+ Use `@supabase/supabase-js` for the Supabase client:
80
+
81
+ ```typescript
82
+ import { createClient } from '@supabase/supabase-js';
83
+
84
+ const supabase = createClient(
85
+ process.env.NUXT_PUBLIC_SUPABASE_URL!,
86
+ process.env.SUPABASE_SERVICE_ROLE_KEY!, // server routes only
87
+ );
88
+
89
+ const { data } = await supabase.from('my_table').select('*');
90
+ ```
91
+
92
+ For client-side access, use the anon key instead of the service role key.
93
+
94
+ ## Key Differences from Client-Side Code
95
+
96
+ - Server routes run on the server (Node.js), not in the browser
97
+ - They have access to Redis, Supabase, secrets, and server-only APIs
98
+ - They do NOT have access to Vue composables, Vuetify, or any client-side code
99
+ - Use `defineEventHandler`, not Vue component patterns
@@ -0,0 +1,73 @@
1
+ ---
2
+ description: "Error recovery and build failure troubleshooting. Apply when something broke, build failed, npm run build errors, or user wants to restore previous behavior."
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Restoring Broken Functionality from Git History
7
+
8
+ **Trigger phrases:** "this used to work", "this broke", "it was working before", "I want it back the way it was", "it looked right before", or similar.
9
+
10
+ **Important:** If the user says it "just" worked or worked "a moment ago", the working version may not be in any commit yet — it may be an uncommitted change you made earlier in the conversation. In that case, review your own chat history to find what changed and revert it directly. This workflow is for restoring functionality that existed in a **previous commit**.
11
+
12
+ ## 1. Gather Context
13
+
14
+ Ask the user:
15
+ - What specifically broke or changed?
16
+ - When do they remember it last working? (A rough timeframe, a branch, a specific action, etc.)
17
+
18
+ ## 2. Find the Working Commit
19
+
20
+ Use `git log` (with relevant flags like `--oneline`, `--since`, `-- <path>`) to locate a commit where the feature was working. Show candidates to the user so they can help narrow it down.
21
+
22
+ ## 3. Confirm the Working State
23
+
24
+ Check out the candidate commit so the user can verify it's the version they want. Save any uncommitted work first and resolve any conflicts or discrepancies that arise when switching between commits.
25
+
26
+ If it's not right, try other commits. Collaborate with the user until the correct commit is identified. Once confirmed, return to the working branch.
27
+
28
+ ## 4. Extract Only What's Needed
29
+
30
+ From the confirmed commit, extract **only** the parts that fix the regression — this could be entire files or as little as a single line. Do NOT blindly take the whole commit if only part of it is relevant.
31
+
32
+ Show the user the combined result (current code + restored pieces) and have them confirm it works as desired **before** creating a commit.
33
+
34
+ ## 5. Commit the Restoration
35
+
36
+ Only after the user confirms the restored version is correct, follow the standard git workflow (see `git-support.mdc`) to commit the changes.
37
+
38
+ # Common Build Errors
39
+
40
+ When `npm run build` fails, check these common causes:
41
+
42
+ ### `Cannot find module '~/composables/...'` or `'~/utils/...'`
43
+
44
+ Wrong import path or the file doesn't exist. Nuxt auto-imports everything in `composables/` but NOT `utils/` -- use explicit imports for utils:
45
+
46
+ ```typescript
47
+ import { myHelper } from '~/utils/myHelper';
48
+ ```
49
+
50
+ ### `Type 'X' is not assignable to type 'Y'`
51
+
52
+ Usually an API response shape mismatch. Common case: `getSchema()` nests data under `response.schema` but TypeScript types suggest top-level access. See the `api` rule's API Gotchas section.
53
+
54
+ ### `SyntaxError` or blank page with "missing export"
55
+
56
+ Nuxt's auto-import scanner misdetected a function parameter as an export. Verify the `imports:dirs` hook in `nuxt.config.ts` excludes `utils/` from scanning:
57
+
58
+ ```typescript
59
+ hooks: {
60
+ 'imports:dirs': (dirs: string[]) => {
61
+ const idx = dirs.findIndex((d) => d.endsWith('/utils'));
62
+ if (idx !== -1) dirs.splice(idx, 1);
63
+ },
64
+ },
65
+ ```
66
+
67
+ ### Prettier pre-commit failure
68
+
69
+ Run `npm run format` then `git add -A` and retry the commit. Do not run Prettier directly -- always use `npm run format`.
70
+
71
+ ### `sh: nuxt: command not found`
72
+
73
+ Dependencies aren't installed. Run `npm install` first.
@@ -0,0 +1,76 @@
1
+ ---
2
+ description: Apply when creating or editing page templates, layouts, scrollable content, data tables, or loading states in Vue/Vuetify components.
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # UI Patterns
7
+
8
+ ## Vuetify Layout System
9
+
10
+ - Use `fill-height` class on containers that need full height
11
+ - Use Vuetify components (`v-card`, `v-btn`, `v-data-table`) over custom implementations
12
+ - Use Vuetify spacing utilities (`pa-4`, `ma-2`) and grid system (`v-row`, `v-col`)
13
+
14
+ ## Page Layout Template
15
+
16
+ For pages with a header and scrollable content, use flexbox:
17
+ - `d-flex flex-column` on the column container
18
+ - `flex-shrink-0` on fixed elements (header, toolbar)
19
+ - `flex-grow-1 overflow-y-auto` on scrollable content
20
+ - Never use `calc(100vh - Xpx)` -- let flexbox handle sizing
21
+ - Never nest multiple scroll containers
22
+
23
+ Full page template covering all four data states (loading, error, empty, content):
24
+
25
+ ```vue
26
+ <template>
27
+ <div class="d-flex flex-column fill-height">
28
+ <div class="flex-shrink-0 pa-4">
29
+ <PageHeader title="Page Title" icon="mdi-view-dashboard" />
30
+ </div>
31
+ <div class="flex-grow-1 overflow-y-auto pa-4">
32
+ <v-progress-circular v-if="loading" indeterminate class="ma-auto d-block" />
33
+ <v-alert v-else-if="error" type="error" variant="tonal" closable>
34
+ {{ error }}
35
+ </v-alert>
36
+ <v-empty-state
37
+ v-else-if="!items.length"
38
+ headline="No data yet"
39
+ icon="mdi-database-off"
40
+ />
41
+ <div v-else>
42
+ <!-- Content here -->
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </template>
47
+ ```
48
+
49
+ ## Dialogs
50
+
51
+ - Cards inside `v-dialog` automatically get `variant="flat"` (solid background) via the nested Vuetify default in `nuxt.config.ts`. No manual override needed.
52
+ - Use `v-card` directly inside `v-dialog` — it will have a solid surface background despite the global `outlined` default.
53
+ - See the **cookbook** rule for a full dialog pattern.
54
+
55
+ ## Loading States
56
+
57
+ Use `v-progress-circular` for inline loading and `v-skeleton-loader` for layout-preserving placeholders:
58
+
59
+ ```vue
60
+ <v-progress-circular v-if="loading" indeterminate />
61
+ <div v-else>
62
+ <!-- Content -->
63
+ </div>
64
+ ```
65
+
66
+ ## Data Tables
67
+
68
+ ```vue
69
+ <v-data-table :headers="headers" :items="items" :loading="loading" density="comfortable" hover>
70
+ <template v-slot:item.actions="{ item }">
71
+ <v-btn icon size="small" @click="selectItem(item)">
72
+ <v-icon>mdi-eye</v-icon>
73
+ </v-btn>
74
+ </template>
75
+ </v-data-table>
76
+ ```
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: test-api-queries
3
+ description: Test Elemental API queries before integrating into app code. Use when writing code that calls the query server.
4
+ ---
5
+
6
+ # Test API Queries
7
+
8
+ **When writing code that calls the Query Server, test queries with this CLI tool before integrating them into app code.**
9
+
10
+ This prevents wasted iteration cycles from incorrect assumptions about API responses.
11
+
12
+ ## Workflow
13
+
14
+ 1. **Test the query** using the CLI tool below
15
+ 2. **Verify the response** matches what you expect
16
+ 3. **Then write the app code** with confidence
17
+
18
+ ## CLI Tool
19
+
20
+ Location: `~/.cursor/skills/test-api-queries/query-api.js`
21
+
22
+ ### Requirements
23
+
24
+ The CLI reads these from the project's `.env` file or shell environment:
25
+
26
+ - `AUTH0_M2M_DEV_TOKEN` - Auth0 M2M dev token for API access
27
+ - `NUXT_PUBLIC_QUERY_SERVER_ADDRESS` - Query server URL
28
+
29
+ **Before using this tool**, check that both variables are set in the project's `.env` file. If either is missing or empty, add them to `.env` (see `.env.example` for the format). Tell the user to ask the engineering team for the token value if they don't have it.
30
+
31
+ ### Usage
32
+
33
+ ```bash
34
+ node ~/.cursor/skills/test-api-queries/query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
35
+ ```
36
+
37
+ ### Examples
38
+
39
+ ```bash
40
+ # Search for entities by name
41
+ node ~/.cursor/skills/test-api-queries/query-api.js POST /entities/search \
42
+ '{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
43
+
44
+ # Get entity details by ID
45
+ node ~/.cursor/skills/test-api-queries/query-api.js GET /entities/00416400910670863867
46
+
47
+ # Find entities by type
48
+ node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
49
+ '{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
50
+
51
+ # Find entities with a specific property
52
+ node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
53
+ '{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
54
+
55
+ # Get entity properties
56
+ node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/entities/properties \
57
+ '{"eids":["00416400910670863867"],"pids":[8,313]}'
58
+ ```
59
+
60
+ ### Notes
61
+
62
+ - Run from the project directory so the `.env` file is found
63
+ - `/elemental/find` and `/elemental/entities/properties` require form-encoded bodies—the tool handles this automatically