@yottagraph-app/aether-instructions 1.1.33 → 1.1.35
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/package.json +1 -1
- package/rules/agents-data.mdc +63 -4
- package/rules/agents.mdc +5 -1
- package/skills/data-model/edgar/DATA_DICTIONARY.md +60 -4
- package/skills/data-model/edgar/schema.yaml +106 -15
- package/skills/data-model/fred/DATA_DICTIONARY.md +4 -8
- package/skills/data-model/fred/schema.yaml +2 -2
- package/skills/data-model/industries/schema.yaml +64 -0
- package/skills/elemental-mcp-patterns/SKILL.md +621 -0
- package/variants/mcp-only/rules/agents-data.mdc +50 -2
package/package.json
CHANGED
package/rules/agents-data.mdc
CHANGED
|
@@ -80,10 +80,69 @@ etc.) handle entity resolution, NEID formatting, and schema lookups
|
|
|
80
80
|
automatically. Use the `data-model` skill docs for entity types,
|
|
81
81
|
properties, and relationship schemas.
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
### Wiring McpToolset (transport class)
|
|
84
|
+
|
|
85
|
+
The Elemental MCP server uses **Streamable HTTP** transport. Use
|
|
86
|
+
`StreamableHTTPConnectionParams` — **NOT** `SseConnectionParams`. The
|
|
87
|
+
`SseConnectionParams` class is for older SSE-based MCP servers and will
|
|
88
|
+
silently fail against the Elemental server (the agent starts with zero
|
|
89
|
+
tools and the LLM hallucinates code).
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from google.adk.tools.mcp_tool import McpToolset
|
|
93
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
|
|
94
|
+
|
|
95
|
+
McpToolset(
|
|
96
|
+
connection_params=StreamableHTTPConnectionParams(url=mcp_url)
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Resolve the MCP URL from the environment or `broadchurch.yaml`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import os
|
|
104
|
+
from pathlib import Path
|
|
105
|
+
import yaml
|
|
106
|
+
|
|
107
|
+
def _get_mcp_url(server_name: str = "elemental") -> str:
|
|
108
|
+
"""Resolve MCP server URL from env or broadchurch.yaml."""
|
|
109
|
+
env_url = os.environ.get("ELEMENTAL_MCP_URL")
|
|
110
|
+
if env_url:
|
|
111
|
+
return env_url
|
|
112
|
+
for candidate in [Path("broadchurch.yaml"), Path(__file__).parent / "broadchurch.yaml"]:
|
|
113
|
+
if candidate.exists():
|
|
114
|
+
config = yaml.safe_load(candidate.read_text()) or {}
|
|
115
|
+
gw = config.get("gateway", {})
|
|
116
|
+
org_id = config.get("tenant", {}).get("org_id", "")
|
|
117
|
+
if gw.get("url") and org_id:
|
|
118
|
+
return f"{gw['url'].rstrip('/')}/api/mcp/{org_id}/{server_name}/mcp"
|
|
119
|
+
return config.get("mcp", {}).get(server_name, "")
|
|
120
|
+
return ""
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For **local dev**, set the env var:
|
|
124
|
+
```bash
|
|
125
|
+
export ELEMENTAL_MCP_URL="https://mcp.news.prod.g.lovelace.ai/elemental/mcp"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
In **production**, the agent reads the gateway URL and org_id from
|
|
129
|
+
`broadchurch.yaml` and routes through the Portal MCP proxy, which handles
|
|
130
|
+
authentication automatically.
|
|
131
|
+
|
|
132
|
+
### Silent failure warning
|
|
133
|
+
|
|
134
|
+
If `McpToolset` cannot connect (wrong transport class, bad URL, or server
|
|
135
|
+
down), **the agent starts with zero MCP tools and no error is raised**.
|
|
136
|
+
The LLM will then hallucinate tool calls instead of executing real ones.
|
|
137
|
+
Always validate the MCP URL at agent startup:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
mcp_url = _get_mcp_url()
|
|
141
|
+
if not mcp_url:
|
|
142
|
+
raise RuntimeError("No MCP URL — check broadchurch.yaml or ELEMENTAL_MCP_URL env var")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### MCP response patterns
|
|
87
146
|
|
|
88
147
|
**Read the `elemental-mcp-patterns` skill** (`skills/elemental-mcp-patterns/`)
|
|
89
148
|
before writing tool code. It covers MCP response shapes, property type
|
package/rules/agents.mdc
CHANGED
|
@@ -48,7 +48,11 @@ Key rules:
|
|
|
48
48
|
- Pin dependency versions in `requirements.txt` for reproducible deployments
|
|
49
49
|
|
|
50
50
|
For agents that call the **Elemental API** (`broadchurch_auth`, endpoints,
|
|
51
|
-
local `ELEMENTAL_*` env vars), see the `agents-data` rule.
|
|
51
|
+
local `ELEMENTAL_*` env vars), see the `agents-data` rule. For agents
|
|
52
|
+
using **Elemental MCP tools**, the `agents-data` rule also covers
|
|
53
|
+
`McpToolset` wiring — use `StreamableHTTPConnectionParams` (not
|
|
54
|
+
`SseConnectionParams`) and read the `elemental-mcp-patterns` skill for
|
|
55
|
+
response handling patterns.
|
|
52
56
|
|
|
53
57
|
## Local Testing
|
|
54
58
|
|
|
@@ -52,7 +52,32 @@ Each SEC form type is a separate flavor, namespaced under `sec`. All share the s
|
|
|
52
52
|
| `sec::13f_hr` | 13F-HR | Institutional investment manager holdings |
|
|
53
53
|
| `sec::def_14a` | DEF 14A | Definitive proxy statement |
|
|
54
54
|
|
|
55
|
-
Sub-
|
|
55
|
+
### Sub-Records (8-K Events, Form 4 Transactions, Form 3 Holdings)
|
|
56
|
+
|
|
57
|
+
Sub-records are **separate graph entities**, not nested properties on the parent filing. Each sub-record has its own NEID and can be queried independently. They use the same flavor as their parent filing (e.g., `sec::8_k` for 8-K events, `sec::form_4` for Form 4 transactions).
|
|
58
|
+
|
|
59
|
+
**Entity naming pattern:**
|
|
60
|
+
- 8-K events: `{accession_number}_evt_{n}` (e.g., `0000320193-24-000067_evt_1`)
|
|
61
|
+
- Form 4 transactions: `{accession_number}_trx_{n}` (e.g., `0000320193-24-000067_trx_1`)
|
|
62
|
+
- Form 3 holdings: `{accession_number}_holding_{n}`
|
|
63
|
+
|
|
64
|
+
**Relationships on sub-records:**
|
|
65
|
+
- `filed` — points to the parent filing's accession number
|
|
66
|
+
- `issued_by` — points to the company
|
|
67
|
+
|
|
68
|
+
**Critical querying note:** Properties like `form_8k_event`, `form_8k_item_code`, `transaction_type`, `shares_transacted`, and other sub-record-specific properties do NOT appear on the parent filing entity or the organization entity. You must traverse the graph to the sub-record entities to access them.
|
|
69
|
+
|
|
70
|
+
**Traversal path:**
|
|
71
|
+
```
|
|
72
|
+
organization --[filed]--> 8-K filing --[linked, distance 1]--> event sub-records
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Example: Finding 8-K event sub-records for a company:**
|
|
76
|
+
1. Get filing NEIDs from the organization's `filed` property
|
|
77
|
+
2. Filter to 8-K filings by checking `form_type == "8-K"`
|
|
78
|
+
3. For each 8-K filing, use a `linked` expression (distance 1, direction both) to find connected entities
|
|
79
|
+
4. Exclude the organization and the filing itself from results — the remaining entities are the event sub-records
|
|
80
|
+
5. Query those sub-record NEIDs for `form_8k_event` and `form_8k_item_code`
|
|
56
81
|
|
|
57
82
|
### `person`
|
|
58
83
|
|
|
@@ -161,12 +186,34 @@ The six core financial properties appear on both the **organization** and its **
|
|
|
161
186
|
|
|
162
187
|
#### 8-K Corporate Events (source: `edgar_8k`)
|
|
163
188
|
|
|
164
|
-
Data source: 8-K current report filings.
|
|
189
|
+
Data source: 8-K current report filings.
|
|
190
|
+
|
|
191
|
+
> **Important:** These properties are on **event sub-record entities**, not on the parent 8-K filing or the organization. See the [Sub-Records section](#sub-records-8-k-events-form-4-transactions-form-3-holdings) above for how to find them.
|
|
165
192
|
|
|
193
|
+
**Core event properties:**
|
|
166
194
|
* `form_8k_event` — Snake_case event identifier. Examples: `"material_agreement"`, `"officer_director_change"`
|
|
167
195
|
* `form_8k_item_code` — Raw SEC item number. Examples: `"1.01"`, `"5.02"`
|
|
196
|
+
* `event_severity` — Event importance classification. Values: `"critical"`, `"high"`, `"medium"`, `"low"`
|
|
168
197
|
* `category` — Sub-classification of Item 8.01 Other Events. Examples: `"cybersecurity_incident"`
|
|
169
198
|
|
|
199
|
+
**Sub-record identity and relationships:**
|
|
200
|
+
* `accession_number` — Synthetic sub-record identifier. Example: `"0000320193-24-000067_evt_1"`
|
|
201
|
+
* `filed` — Relationship: event sub-record → parent 8-K filing
|
|
202
|
+
* `issued_by` — Relationship: event sub-record → company
|
|
203
|
+
|
|
204
|
+
**Item 8.01 keyword flags** (set to `"true"` when matched):
|
|
205
|
+
* `8k_cybersecurity_keyword` — Cybersecurity-related disclosure detected
|
|
206
|
+
* `8k_litigation_keyword` — Litigation-related disclosure detected
|
|
207
|
+
* `8k_regulatory_keyword` — Regulatory-related disclosure detected
|
|
208
|
+
* `8k_operational_keyword` — Operational issue disclosure detected
|
|
209
|
+
|
|
210
|
+
**ABS event flags** (Items 6.01–6.05, set to `"true"` when applicable):
|
|
211
|
+
* `abs_servicing_event` — Item 6.01: ABS servicing event
|
|
212
|
+
* `abs_servicer_change` — Item 6.02: ABS servicer change
|
|
213
|
+
* `abs_credit_enhancement_change` — Item 6.03: ABS credit enhancement change
|
|
214
|
+
* `abs_failure_event` — Item 6.04: ABS failure to make distribution
|
|
215
|
+
* `abs_securities_act` — Item 6.05: ABS Securities Act updating disclosure
|
|
216
|
+
|
|
170
217
|
#### Beneficial Ownership (source: `edgar_sc_13d`, `edgar_sc_13g`)
|
|
171
218
|
|
|
172
219
|
Data source: SC 13D/G XML filings. Properties on the filer organization.
|
|
@@ -240,8 +287,14 @@ Data source: Form 3/4 XML.
|
|
|
240
287
|
|
|
241
288
|
#### Form 4 Transactions (source: `edgar_4`)
|
|
242
289
|
|
|
243
|
-
|
|
290
|
+
> **Important:** These properties are on **transaction sub-record entities**, not on the parent Form 4 filing. See the [Sub-Records section](#sub-records-8-k-events-form-4-transactions-form-3-holdings) above for how to find them.
|
|
291
|
+
|
|
292
|
+
**Sub-record identity and relationships:**
|
|
293
|
+
* `accession_number` — Synthetic sub-record identifier. Example: `"0000320193-24-000067_trx_1"`
|
|
294
|
+
* `filed` — Relationship: transaction sub-record → parent Form 4 filing
|
|
295
|
+
* `issued_by` — Relationship: transaction sub-record → issuer company
|
|
244
296
|
|
|
297
|
+
**Transaction properties:**
|
|
245
298
|
* `transaction_type` — Human-readable code description. Examples: `"Open market or private purchase"`, `"Grant, award, or other acquisition"`
|
|
246
299
|
* `transaction_date` — Transaction date (YYYY-MM-DD)
|
|
247
300
|
* `acquired_disposed_code` — `"A"` (acquired) or `"D"` (disposed)
|
|
@@ -284,6 +337,8 @@ Data source: 13F-HR XML information table.
|
|
|
284
337
|
|
|
285
338
|
## Entity Relationships
|
|
286
339
|
|
|
340
|
+
> **Sub-record traversal:** Sub-records (8-K events, Form 4 transactions, Form 3 holdings) are separate document entities. To find them, traverse from the parent filing using a `linked` expression (distance 1). The sub-record's `filed` property points back to the parent filing, and `issued_by` points to the company.
|
|
341
|
+
|
|
287
342
|
```
|
|
288
343
|
organization ──[filed]────────────────────→ document
|
|
289
344
|
organization ──[filing_reference]─────────→ document
|
|
@@ -296,7 +351,8 @@ document ──[refers_to]──────────────
|
|
|
296
351
|
document ──[filer]────────────────────→ organization (SC 13D/G)
|
|
297
352
|
document ──[group_member]─────────────→ organization (SC 13D)
|
|
298
353
|
document ──[compares_to]──────────────→ organization (DEF 14A)
|
|
299
|
-
document
|
|
354
|
+
document (sub-record)──[filed]────────────────────→ document (8-K event → parent 8-K filing)
|
|
355
|
+
document (sub-record)──[issued_by]────────────────→ organization (8-K event / Form 4 txn → company)
|
|
300
356
|
person ──[is_officer]───────────────→ organization
|
|
301
357
|
person ──[is_director]──────────────→ organization
|
|
302
358
|
person ──[is_ten_percent_owner]─────→ organization
|
|
@@ -126,6 +126,14 @@ flavors:
|
|
|
126
126
|
strong_id_properties: ["accession_number"]
|
|
127
127
|
passive: true
|
|
128
128
|
|
|
129
|
+
- name: "filing"
|
|
130
|
+
namespace: "sec"
|
|
131
|
+
description: "SEC filing (generic, for form types without a specific modeled flavor)"
|
|
132
|
+
display_name: "SEC Filing"
|
|
133
|
+
mergeability: not_mergeable
|
|
134
|
+
strong_id_properties: ["accession_number"]
|
|
135
|
+
passive: true
|
|
136
|
+
|
|
129
137
|
- name: "person"
|
|
130
138
|
description: "A real person as opposed to a fictional character, such as a CEO, politician, or public figure"
|
|
131
139
|
display_name: "Person"
|
|
@@ -140,6 +148,13 @@ flavors:
|
|
|
140
148
|
strong_id_properties: ["cusip_number"]
|
|
141
149
|
passive: true
|
|
142
150
|
|
|
151
|
+
- name: "industry"
|
|
152
|
+
description: "A Standard Industrial Classification (SIC) industry category assigned by the SEC"
|
|
153
|
+
display_name: "Industry"
|
|
154
|
+
mergeability: not_mergeable
|
|
155
|
+
strong_id_properties: ["sic_code"]
|
|
156
|
+
passive: true
|
|
157
|
+
|
|
143
158
|
# =============================================================================
|
|
144
159
|
# PROPERTIES — Organization
|
|
145
160
|
# =============================================================================
|
|
@@ -176,7 +191,7 @@ properties:
|
|
|
176
191
|
description: "Four-digit Standard Industrial Classification code"
|
|
177
192
|
display_name: "SIC Code"
|
|
178
193
|
mergeability: not_mergeable
|
|
179
|
-
domain_flavors: ["organization"]
|
|
194
|
+
domain_flavors: ["organization", "industry"]
|
|
180
195
|
passive: true
|
|
181
196
|
|
|
182
197
|
- name: "sic_description"
|
|
@@ -184,7 +199,7 @@ properties:
|
|
|
184
199
|
description: "Human-readable SIC code description"
|
|
185
200
|
display_name: "SIC Description"
|
|
186
201
|
mergeability: not_mergeable
|
|
187
|
-
domain_flavors: ["organization"]
|
|
202
|
+
domain_flavors: ["organization", "industry"]
|
|
188
203
|
passive: true
|
|
189
204
|
|
|
190
205
|
- name: "state_of_incorporation"
|
|
@@ -574,7 +589,7 @@ properties:
|
|
|
574
589
|
description: "SEC accession number uniquely identifying a filing"
|
|
575
590
|
display_name: "Accession Number"
|
|
576
591
|
mergeability: not_mergeable
|
|
577
|
-
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
592
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
578
593
|
passive: true
|
|
579
594
|
|
|
580
595
|
- name: "form_type"
|
|
@@ -582,7 +597,7 @@ properties:
|
|
|
582
597
|
description: "Normalized SEC form type (e.g. 10-K, SC 13D, 4)"
|
|
583
598
|
display_name: "Form Type"
|
|
584
599
|
mergeability: not_mergeable
|
|
585
|
-
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
600
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
586
601
|
passive: true
|
|
587
602
|
|
|
588
603
|
- name: "filing_date"
|
|
@@ -590,7 +605,7 @@ properties:
|
|
|
590
605
|
description: "Date the filing was submitted to the SEC (YYYY-MM-DD)"
|
|
591
606
|
display_name: "Filing Date"
|
|
592
607
|
mergeability: not_mergeable
|
|
593
|
-
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
608
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
594
609
|
passive: true
|
|
595
610
|
|
|
596
611
|
- name: "report_date"
|
|
@@ -598,7 +613,7 @@ properties:
|
|
|
598
613
|
description: "End date of the primary reporting period (YYYY-MM-DD)"
|
|
599
614
|
display_name: "Report Date"
|
|
600
615
|
mergeability: not_mergeable
|
|
601
|
-
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
616
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
602
617
|
passive: true
|
|
603
618
|
|
|
604
619
|
# ── XBRL Financial Facts (key concepts) ──
|
|
@@ -1717,11 +1732,19 @@ relationships:
|
|
|
1717
1732
|
# ── Organization → Filing ──
|
|
1718
1733
|
|
|
1719
1734
|
- name: "filed"
|
|
1720
|
-
description: "Link from a company to an SEC filing document
|
|
1735
|
+
description: "Link from a company or person to an SEC filing document they filed, or from a sub-record (event, transaction, holding) to its parent filing"
|
|
1721
1736
|
display_name: "Filed"
|
|
1722
1737
|
mergeability: not_mergeable
|
|
1723
|
-
domain_flavors: ["organization", "sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
1724
|
-
target_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
1738
|
+
domain_flavors: ["organization", "person", "sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
1739
|
+
target_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
1740
|
+
passive: true
|
|
1741
|
+
|
|
1742
|
+
- name: "is_issuer_of"
|
|
1743
|
+
description: "Link from an issuer organization to an ownership filing (Forms 3/4, SC 13D/G) where the CIK is the issuer, not the filer"
|
|
1744
|
+
display_name: "Is Issuer Of"
|
|
1745
|
+
mergeability: not_mergeable
|
|
1746
|
+
domain_flavors: ["organization"]
|
|
1747
|
+
target_flavors: ["sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::filing"]
|
|
1725
1748
|
passive: true
|
|
1726
1749
|
|
|
1727
1750
|
- name: "filing_reference"
|
|
@@ -1735,10 +1758,10 @@ relationships:
|
|
|
1735
1758
|
# ── Filing → Organization ──
|
|
1736
1759
|
|
|
1737
1760
|
- name: "issued_by"
|
|
1738
|
-
description: "Link from a filing to the company it pertains to (the filer for most forms; the issuer for ownership forms)"
|
|
1761
|
+
description: "Link from a filing or financial instrument to the company it pertains to (the filer for most forms; the issuer for ownership forms and 13F holdings)"
|
|
1739
1762
|
display_name: "Issued By"
|
|
1740
1763
|
mergeability: not_mergeable
|
|
1741
|
-
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a"]
|
|
1764
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing", "financial_instrument"]
|
|
1742
1765
|
target_flavors: ["organization"]
|
|
1743
1766
|
passive: true
|
|
1744
1767
|
|
|
@@ -1751,10 +1774,10 @@ relationships:
|
|
|
1751
1774
|
passive: true
|
|
1752
1775
|
|
|
1753
1776
|
- name: "filer"
|
|
1754
|
-
description: "Link from a filing to the
|
|
1777
|
+
description: "Link from a filing to the entity (person or organization) who filed it"
|
|
1755
1778
|
display_name: "Filer"
|
|
1756
1779
|
mergeability: not_mergeable
|
|
1757
|
-
domain_flavors: ["sec::sc_13d", "sec::sc_13g", "sec::13f_hr"]
|
|
1780
|
+
domain_flavors: ["sec::10_k", "sec::10_q", "sec::20_f", "sec::8_k", "sec::6_k", "sec::40_f", "sec::form_3", "sec::form_4", "sec::sc_13d", "sec::sc_13g", "sec::13f_hr", "sec::def_14a", "sec::filing"]
|
|
1758
1781
|
target_flavors: ["organization", "person"]
|
|
1759
1782
|
passive: true
|
|
1760
1783
|
|
|
@@ -1803,10 +1826,10 @@ relationships:
|
|
|
1803
1826
|
# ── Organization → Financial Instrument ──
|
|
1804
1827
|
|
|
1805
1828
|
- name: "holds_position"
|
|
1806
|
-
description: "Link from an investment manager to a security it holds (13F-HR)"
|
|
1829
|
+
description: "Link from an investment manager or fund to a security it holds (13F-HR, N-PORT)"
|
|
1807
1830
|
display_name: "Holds Position"
|
|
1808
1831
|
mergeability: not_mergeable
|
|
1809
|
-
domain_flavors: ["organization"]
|
|
1832
|
+
domain_flavors: ["organization", "financial_instrument"]
|
|
1810
1833
|
target_flavors: ["financial_instrument"]
|
|
1811
1834
|
passive: true
|
|
1812
1835
|
|
|
@@ -1844,6 +1867,16 @@ relationships:
|
|
|
1844
1867
|
target_flavors: ["organization"]
|
|
1845
1868
|
passive: true
|
|
1846
1869
|
|
|
1870
|
+
# ── Organization → Financial Instrument (issuer link) ──
|
|
1871
|
+
|
|
1872
|
+
- name: "issues_security"
|
|
1873
|
+
description: "Link from an issuing company to a financial instrument it has issued, identified by CUSIP (SC 13D/G)"
|
|
1874
|
+
display_name: "Issues Security"
|
|
1875
|
+
mergeability: not_mergeable
|
|
1876
|
+
domain_flavors: ["organization"]
|
|
1877
|
+
target_flavors: ["financial_instrument"]
|
|
1878
|
+
passive: true
|
|
1879
|
+
|
|
1847
1880
|
# =============================================================================
|
|
1848
1881
|
# ATTRIBUTES
|
|
1849
1882
|
# =============================================================================
|
|
@@ -1891,3 +1924,61 @@ attributes:
|
|
|
1891
1924
|
description: "Fiscal period: FY (annual), Q1, Q2, Q3 (quarterly)"
|
|
1892
1925
|
display_name: "Filing Period"
|
|
1893
1926
|
mergeability: not_mergeable
|
|
1927
|
+
|
|
1928
|
+
# ── holds_position relationship attributes (13F-HR) ──
|
|
1929
|
+
|
|
1930
|
+
- property: "holds_position"
|
|
1931
|
+
name: "position_value"
|
|
1932
|
+
type: string
|
|
1933
|
+
description: "Market value of the position in thousands of USD"
|
|
1934
|
+
display_name: "Position Value"
|
|
1935
|
+
mergeability: not_mergeable
|
|
1936
|
+
|
|
1937
|
+
- property: "holds_position"
|
|
1938
|
+
name: "shares_held"
|
|
1939
|
+
type: string
|
|
1940
|
+
description: "Number of shares or principal amount held"
|
|
1941
|
+
display_name: "Shares Held"
|
|
1942
|
+
mergeability: not_mergeable
|
|
1943
|
+
|
|
1944
|
+
- property: "holds_position"
|
|
1945
|
+
name: "instrument_type"
|
|
1946
|
+
type: string
|
|
1947
|
+
description: "Type of instrument: SH (shares) or PRN (principal amount)"
|
|
1948
|
+
display_name: "Instrument Type"
|
|
1949
|
+
mergeability: not_mergeable
|
|
1950
|
+
|
|
1951
|
+
- property: "holds_position"
|
|
1952
|
+
name: "put_call"
|
|
1953
|
+
type: string
|
|
1954
|
+
description: "Option type if applicable: PUT or CALL"
|
|
1955
|
+
display_name: "Put/Call"
|
|
1956
|
+
mergeability: not_mergeable
|
|
1957
|
+
|
|
1958
|
+
- property: "holds_position"
|
|
1959
|
+
name: "investment_discretion"
|
|
1960
|
+
type: string
|
|
1961
|
+
description: "Investment discretion: SOLE, SHARED, or DEFINED"
|
|
1962
|
+
display_name: "Investment Discretion"
|
|
1963
|
+
mergeability: not_mergeable
|
|
1964
|
+
|
|
1965
|
+
- property: "holds_position"
|
|
1966
|
+
name: "voting_authority_sole"
|
|
1967
|
+
type: string
|
|
1968
|
+
description: "Shares with sole voting authority"
|
|
1969
|
+
display_name: "Voting Authority (Sole)"
|
|
1970
|
+
mergeability: not_mergeable
|
|
1971
|
+
|
|
1972
|
+
- property: "holds_position"
|
|
1973
|
+
name: "voting_authority_shared"
|
|
1974
|
+
type: string
|
|
1975
|
+
description: "Shares with shared voting authority"
|
|
1976
|
+
display_name: "Voting Authority (Shared)"
|
|
1977
|
+
mergeability: not_mergeable
|
|
1978
|
+
|
|
1979
|
+
- property: "holds_position"
|
|
1980
|
+
name: "voting_authority_none"
|
|
1981
|
+
type: string
|
|
1982
|
+
description: "Shares with no voting authority"
|
|
1983
|
+
display_name: "Voting Authority (None)"
|
|
1984
|
+
mergeability: not_mergeable
|
|
@@ -48,7 +48,7 @@ A traded security, index, or reference rate for which FRED publishes price or yi
|
|
|
48
48
|
- Primary key: entity name (e.g. "10-Year U.S. Treasury", "S&P 500"). No strong ID — resolved by name with MERGEABLE mergeability.
|
|
49
49
|
- Entity resolver: named entity, MERGEABLE. Disambiguation snippet includes series title, frequency, and units.
|
|
50
50
|
- Source: `fred-source`
|
|
51
|
-
- Entities produced: U.S. Treasuries (1Y, 2Y, 10Y, 30Y, 3M bill), SOFR, SONIA,
|
|
51
|
+
- Entities produced: U.S. Treasuries (1Y, 2Y, 10Y, 30Y, 3M bill), SOFR, SONIA, NASDAQ Composite, CBOE VIX, FX pairs (JPY/USD, KRW/USD, USD/EUR, USD/GBP, CAD/USD, CNY/USD), U.S. Dollar Index, U.S. Dollar Advanced Economy Index
|
|
52
52
|
|
|
53
53
|
### `product`
|
|
54
54
|
|
|
@@ -77,7 +77,7 @@ These atoms appear once per series record, timestamped at the first observation
|
|
|
77
77
|
* `fred_series_id`
|
|
78
78
|
* Definition: FRED's unique alphanumeric identifier for the series.
|
|
79
79
|
* Source flavor: location, organization, financial_instrument, product
|
|
80
|
-
* Examples: `"GDP"`, `"UNRATE"`, `"DGS10"
|
|
80
|
+
* Examples: `"GDP"`, `"UNRATE"`, `"DGS10"`
|
|
81
81
|
* Derivation: `id` field from the FRED `/series` API response.
|
|
82
82
|
|
|
83
83
|
* `notes`
|
|
@@ -198,7 +198,6 @@ Each observation in a series becomes one atom timestamped at the observation dat
|
|
|
198
198
|
* `bank_lending_standards` — Net % of banks tightening C&I loan standards for large firms (Percent, quarterly). Series: DRTSCILM.
|
|
199
199
|
* `bank_lending_standards_small` — Net % of banks tightening C&I loan standards for small firms (Percent, quarterly). Series: DRTSCLNM. **Note: FRED reports this series as discontinued/nonexistent as of 2026-03.**
|
|
200
200
|
* `bank_lending_standards_cre` — Net % of banks tightening CRE lending standards (Percent, quarterly). Series: DRTSRCL. **Note: FRED reports this series as discontinued/nonexistent as of 2026-03.**
|
|
201
|
-
* `baa_10y_spread` — Moody's Baa corporate bond yield minus 10Y Treasury (Percent, daily). Series: BAA10Y.
|
|
202
201
|
* `credit_card_delinquency_rate` — Delinquency rate on credit card loans, all commercial banks (Percent, quarterly). Series: DRCCLACBS.
|
|
203
202
|
* `cre_delinquency_rate` — Delinquency rate on CRE loans excluding farmland (Percent, quarterly). Series: DRCRELEXFACBS.
|
|
204
203
|
|
|
@@ -215,8 +214,6 @@ Each observation in a series becomes one atom timestamped at the observation dat
|
|
|
215
214
|
* `building_permits` — New privately-owned housing units authorized, SAAR (Thousands of Units, monthly). Series: PERMIT.
|
|
216
215
|
* `housing_months_supply` — Monthly supply of new houses, seasonally adjusted (Months' Supply, monthly). Series: MSACSR.
|
|
217
216
|
* `home_price_median` — Median sales price of houses sold (Dollars, quarterly). Series: MSPUS.
|
|
218
|
-
* `home_price_index` — S&P CoreLogic Case-Shiller U.S. National Home Price Index (Index Jan 2000=100, monthly). Series: CSUSHPINSA.
|
|
219
|
-
* `home_price_index_20city` — S&P CoreLogic Case-Shiller 20-City Composite Home Price Index (Index Jan 2000=100, monthly). Series: SPCS20RSA.
|
|
220
217
|
* `home_price_index_fhfa` — FHFA all-transactions house price index (Index 1980:Q1=100, quarterly). Series: USSTHPI.
|
|
221
218
|
* `mortgage_rate_30y` — 30-year fixed rate mortgage average (Percent, weekly). Series: MORTGAGE30US.
|
|
222
219
|
* `rental_vacancy_rate` — Rental housing vacancy rate (Percent, quarterly). Series: RRVRUSQ156N.
|
|
@@ -286,8 +283,7 @@ Each observation in a series becomes one atom timestamped at the observation dat
|
|
|
286
283
|
#### Financial Markets (financial_instrument)
|
|
287
284
|
|
|
288
285
|
* `yield` — Market yield or interest rate for a fixed-income instrument (Percent). Series: DGS1, DGS2, DGS10, DGS30, DTB3, TB3MS, SOFR, IUDSOIA.
|
|
289
|
-
* `price` — Market price, index level, or exchange rate (varies by instrument). Series:
|
|
290
|
-
* `option_adjusted_spread` — Option-adjusted spread for a bond index (Percent, daily). Series: BAMLH0A0HYM2.
|
|
286
|
+
* `price` — Market price, index level, or exchange rate (varies by instrument). Series: NASDAQCOM, VIXCLS, DTWEXBGS, TWEXAFEGSMTH, DEXUSEU, DEXUSUK, DEXJPUS, DEXKOUS, EXCAUS, DEXCHUS.
|
|
291
287
|
|
|
292
288
|
#### Commodity Prices (product)
|
|
293
289
|
|
|
@@ -308,6 +304,6 @@ The FRED source produces no `data_nindex` relationship properties. All atoms are
|
|
|
308
304
|
```
|
|
309
305
|
location ── [fed_funds_rate, gdp, unemployment_rate, cpi, ...] (scalar observation properties)
|
|
310
306
|
organization ── [fed_funds_rate, policy_rate, monetary_base, ...]
|
|
311
|
-
financial_instrument ── [yield, price
|
|
307
|
+
financial_instrument ── [yield, price]
|
|
312
308
|
product ── [price]
|
|
313
309
|
```
|
|
@@ -77,7 +77,7 @@ properties:
|
|
|
77
77
|
display_name: "FRED Series ID"
|
|
78
78
|
mergeability: not_mergeable
|
|
79
79
|
domain_flavors: ["fred_series"]
|
|
80
|
-
examples: ["GDP", "UNRATE", "DGS10"
|
|
80
|
+
examples: ["GDP", "UNRATE", "DGS10"]
|
|
81
81
|
passive: true
|
|
82
82
|
|
|
83
83
|
- name: "name"
|
|
@@ -86,7 +86,7 @@ properties:
|
|
|
86
86
|
display_name: "Name"
|
|
87
87
|
mergeability: not_mergeable
|
|
88
88
|
domain_flavors: ["fred_series"]
|
|
89
|
-
examples: ["Gross Domestic Product", "Federal Funds Effective Rate"
|
|
89
|
+
examples: ["Gross Domestic Product", "Federal Funds Effective Rate"]
|
|
90
90
|
passive: true
|
|
91
91
|
|
|
92
92
|
- name: "notes"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Dataset schema for SIC industry classifications derived from SEC EDGAR filings.
|
|
2
|
+
#
|
|
3
|
+
# This schema introduces the `industry` flavor — a Standard Industrial
|
|
4
|
+
# Classification (SIC) category identified by its 4-digit code. Organizations
|
|
5
|
+
# are linked to their industry via the `works_in_industry` relationship.
|
|
6
|
+
#
|
|
7
|
+
# All elements are passive (deterministic extraction from filing headers).
|
|
8
|
+
name: "industries"
|
|
9
|
+
description: "SIC industry classifications linking SEC-registered organizations to their Standard Industrial Classification codes"
|
|
10
|
+
|
|
11
|
+
extraction:
|
|
12
|
+
flavors: closed
|
|
13
|
+
properties: closed
|
|
14
|
+
relationships: closed
|
|
15
|
+
attributes: closed
|
|
16
|
+
|
|
17
|
+
flavors:
|
|
18
|
+
- name: "industry"
|
|
19
|
+
description: "A Standard Industrial Classification (SIC) industry category assigned by the SEC"
|
|
20
|
+
display_name: "Industry"
|
|
21
|
+
mergeability: not_mergeable
|
|
22
|
+
strong_id_properties: ["sic_code"]
|
|
23
|
+
passive: true
|
|
24
|
+
|
|
25
|
+
- name: "organization"
|
|
26
|
+
description: "A particular business, institution, or organization such as a corporation, university, government agency, or non-profit"
|
|
27
|
+
display_name: "Organization"
|
|
28
|
+
mergeability: not_mergeable
|
|
29
|
+
strong_id_properties: ["company_cik"]
|
|
30
|
+
passive: true
|
|
31
|
+
|
|
32
|
+
properties:
|
|
33
|
+
- name: "sic_code"
|
|
34
|
+
type: string
|
|
35
|
+
description: "Four-digit Standard Industrial Classification code"
|
|
36
|
+
display_name: "SIC Code"
|
|
37
|
+
mergeability: not_mergeable
|
|
38
|
+
domain_flavors: ["industry", "organization"]
|
|
39
|
+
passive: true
|
|
40
|
+
|
|
41
|
+
- name: "sic_description"
|
|
42
|
+
type: string
|
|
43
|
+
description: "Human-readable SIC code description"
|
|
44
|
+
display_name: "SIC Description"
|
|
45
|
+
mergeability: not_mergeable
|
|
46
|
+
domain_flavors: ["industry", "organization"]
|
|
47
|
+
passive: true
|
|
48
|
+
|
|
49
|
+
- name: "company_cik"
|
|
50
|
+
type: string
|
|
51
|
+
description: "SEC Central Index Key, 10-digit zero-padded"
|
|
52
|
+
display_name: "CIK"
|
|
53
|
+
mergeability: not_mergeable
|
|
54
|
+
domain_flavors: ["organization"]
|
|
55
|
+
passive: true
|
|
56
|
+
|
|
57
|
+
relationships:
|
|
58
|
+
- name: "works_in_industry"
|
|
59
|
+
description: "Link from an organization to its SIC industry classification"
|
|
60
|
+
display_name: "Works In Industry"
|
|
61
|
+
mergeability: not_mergeable
|
|
62
|
+
domain_flavors: ["organization"]
|
|
63
|
+
target_flavors: ["industry"]
|
|
64
|
+
passive: true
|
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: elemental-mcp-patterns
|
|
3
|
+
description: How to correctly call Elemental MCP tools and process their responses when writing Python ADK agent tool functions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Elemental MCP Patterns
|
|
7
|
+
|
|
8
|
+
This skill is for **build agents writing Python tool code** that calls
|
|
9
|
+
Elemental MCP tools. It covers MCP wiring, transport configuration,
|
|
10
|
+
response shapes, property type handling, and copy-paste patterns for
|
|
11
|
+
common operations.
|
|
12
|
+
|
|
13
|
+
For domain knowledge (what entity types and properties exist), see the
|
|
14
|
+
**data-model** skill.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## MCP Wiring: Connecting ADK Agents to Elemental MCP
|
|
19
|
+
|
|
20
|
+
### Transport class
|
|
21
|
+
|
|
22
|
+
The Elemental MCP server uses **Streamable HTTP** transport. You **must**
|
|
23
|
+
use `StreamableHTTPConnectionParams`:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from google.adk.tools.mcp_tool import McpToolset
|
|
27
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> **Do NOT use `SseConnectionParams`.** `SseConnectionParams` is for
|
|
31
|
+
> legacy SSE-based MCP servers. Using it against the Elemental MCP server
|
|
32
|
+
> will silently fail — the agent starts with zero tools and no error is
|
|
33
|
+
> raised. The LLM then hallucinates tool calls.
|
|
34
|
+
|
|
35
|
+
### Resolving the MCP URL
|
|
36
|
+
|
|
37
|
+
The MCP URL comes from the environment (`ELEMENTAL_MCP_URL`) for local
|
|
38
|
+
dev, or from `broadchurch.yaml` for deployed agents:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import os
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
import yaml
|
|
44
|
+
|
|
45
|
+
def _get_mcp_url(server_name: str = "elemental") -> str:
|
|
46
|
+
"""Resolve MCP server URL from env or broadchurch.yaml."""
|
|
47
|
+
env_url = os.environ.get("ELEMENTAL_MCP_URL")
|
|
48
|
+
if env_url:
|
|
49
|
+
return env_url
|
|
50
|
+
for candidate in [Path("broadchurch.yaml"), Path(__file__).parent / "broadchurch.yaml"]:
|
|
51
|
+
if candidate.exists():
|
|
52
|
+
config = yaml.safe_load(candidate.read_text()) or {}
|
|
53
|
+
gw = config.get("gateway", {})
|
|
54
|
+
org_id = config.get("tenant", {}).get("org_id", "")
|
|
55
|
+
if gw.get("url") and org_id:
|
|
56
|
+
return f"{gw['url'].rstrip('/')}/api/mcp/{org_id}/{server_name}/mcp"
|
|
57
|
+
return config.get("mcp", {}).get(server_name, "")
|
|
58
|
+
return ""
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Complete wiring example
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from google.adk.agents import Agent
|
|
65
|
+
from google.adk.tools.mcp_tool import McpToolset
|
|
66
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
|
|
67
|
+
|
|
68
|
+
mcp_url = _get_mcp_url() # function from above
|
|
69
|
+
if not mcp_url:
|
|
70
|
+
raise RuntimeError("No MCP URL — check broadchurch.yaml or ELEMENTAL_MCP_URL env var")
|
|
71
|
+
|
|
72
|
+
root_agent = Agent(
|
|
73
|
+
model="gemini-2.0-flash",
|
|
74
|
+
name="my_mcp_agent",
|
|
75
|
+
instruction="You are a research assistant with access to the Lovelace knowledge graph.",
|
|
76
|
+
tools=[
|
|
77
|
+
my_custom_tool, # your Python tool functions
|
|
78
|
+
McpToolset(connection_params=StreamableHTTPConnectionParams(url=mcp_url)),
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `McpToolset` automatically discovers all MCP tools at startup and
|
|
84
|
+
exposes them to the agent. To restrict which tools are exposed, use
|
|
85
|
+
`tool_filter`:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
McpToolset(
|
|
89
|
+
connection_params=StreamableHTTPConnectionParams(url=mcp_url),
|
|
90
|
+
tool_filter=["elemental_get_entity", "elemental_get_related", "elemental_get_events"],
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Silent failure mode
|
|
95
|
+
|
|
96
|
+
If `McpToolset` cannot connect, **no error is raised at agent startup**.
|
|
97
|
+
The agent simply has zero MCP tools. Symptoms:
|
|
98
|
+
- The agent never calls any `elemental_*` tools
|
|
99
|
+
- The LLM fabricates code or data instead of using tools
|
|
100
|
+
- No connection error in logs
|
|
101
|
+
|
|
102
|
+
**Always validate** the MCP URL and check for tool availability during
|
|
103
|
+
development. If MCP tools aren't working, verify:
|
|
104
|
+
1. The URL is correct (check `broadchurch.yaml` `mcp.elemental` or env var)
|
|
105
|
+
2. You're using `StreamableHTTPConnectionParams` (not `SseConnectionParams`)
|
|
106
|
+
3. The MCP server is reachable (try `curl -X POST <url> -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'`)
|
|
107
|
+
|
|
108
|
+
### Local dev setup
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
export ELEMENTAL_MCP_URL="https://mcp.news.prod.g.lovelace.ai/elemental/mcp"
|
|
112
|
+
export GOOGLE_CLOUD_PROJECT=broadchurch
|
|
113
|
+
export GOOGLE_CLOUD_LOCATION=us-central1
|
|
114
|
+
export GOOGLE_GENAI_USE_VERTEXAI=1
|
|
115
|
+
cd agents/
|
|
116
|
+
adk web
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Tool Quick Reference
|
|
122
|
+
|
|
123
|
+
| Tool | Use when you need to... |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `elemental_get_entity` | Resolve an entity by name or ID, fetch its properties (supports `history` for time-series) |
|
|
126
|
+
| `elemental_get_related` | Find related entities (requires `related_flavor`); use `direction` and `relationship_types` to filter |
|
|
127
|
+
| `elemental_get_events` | Get typed events with categories, dates, participants |
|
|
128
|
+
| `elemental_get_citations` | Look up provenance for `ref` hashes returned by other tools |
|
|
129
|
+
| `elemental_get_schema` | Discover flavors (entity types), property names, and property types |
|
|
130
|
+
| `elemental_get_relationships` | Get relationship types and counts between two entities |
|
|
131
|
+
| `elemental_graph_neighborhood` | Get the most influential neighbors of an entity |
|
|
132
|
+
| `elemental_graph_sentiment` | Sentiment time series, trend analysis, and statistics from news articles |
|
|
133
|
+
| `elemental_introspect` | Discover what data **actually exists**: entity counts, populated properties with fill rates, sample values. Use before building features to verify data availability. |
|
|
134
|
+
| `elemental_traverse` | Stateful graph navigation — build a working set of entities across multiple calls (start → expand → filter → inspect) |
|
|
135
|
+
| `elemental_health` | Health check — verify MCP server connectivity |
|
|
136
|
+
|
|
137
|
+
### MCP Prompts
|
|
138
|
+
|
|
139
|
+
The server also exposes built-in prompts that provide pre-composed
|
|
140
|
+
workflows:
|
|
141
|
+
|
|
142
|
+
| Prompt | Purpose |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `company-deep-dive` | Comprehensive company research workflow |
|
|
145
|
+
| `blast-radius` | Analyze impact/connections radiating from an entity |
|
|
146
|
+
| `event-monitor` | Track events and developments for entities |
|
|
147
|
+
|
|
148
|
+
### MCP Resources
|
|
149
|
+
|
|
150
|
+
Documentation resources are available directly from the server:
|
|
151
|
+
|
|
152
|
+
| Resource | Description |
|
|
153
|
+
|---|---|
|
|
154
|
+
| `elemental_data_model` | Entity types, properties, and relationships |
|
|
155
|
+
| `elemental_guide` | Usage guide for the MCP tools |
|
|
156
|
+
| `elemental_schema` | Live schema data |
|
|
157
|
+
| `elemental_workflows` | Common workflow patterns and examples |
|
|
158
|
+
| `elemental_mcp_server_info` | Server capabilities and configuration |
|
|
159
|
+
|
|
160
|
+
These prompts and resources can be useful shortcuts — check if your MCP
|
|
161
|
+
client supports them before building equivalent logic from scratch.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Response Shapes
|
|
166
|
+
|
|
167
|
+
Every MCP tool call returns a JSON dict. Below are annotated examples of
|
|
168
|
+
the three tools you'll use most.
|
|
169
|
+
|
|
170
|
+
### elemental_get_entity
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
result = await mcp_call("elemental_get_entity", {
|
|
174
|
+
"entity": "Intel",
|
|
175
|
+
"properties": ["country", "total_revenue", "ticker_symbol", "net_income"]
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
# Response shape:
|
|
179
|
+
{
|
|
180
|
+
"entity": {
|
|
181
|
+
"neid": "04926132345040704022",
|
|
182
|
+
"name": "Intel Corporation",
|
|
183
|
+
"flavor": "organization",
|
|
184
|
+
"properties": {
|
|
185
|
+
"country": {
|
|
186
|
+
"value": "5816460566439750832", # <-- THIS IS A NEID, NOT A NAME
|
|
187
|
+
"ref": "ref_a3f2b1c8"
|
|
188
|
+
},
|
|
189
|
+
"total_revenue": {
|
|
190
|
+
"value": 52900000000,
|
|
191
|
+
"ref": "ref_d4e5f678",
|
|
192
|
+
"attributes": {"filing_period": "FY"}
|
|
193
|
+
},
|
|
194
|
+
"ticker_symbol": {
|
|
195
|
+
"value": "INTC"
|
|
196
|
+
},
|
|
197
|
+
"net_income": {
|
|
198
|
+
"value": -267000000,
|
|
199
|
+
"ref": "ref_b2c3d4e5"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Key points:
|
|
207
|
+
- `entity` is `null` if resolution failed
|
|
208
|
+
- Each property value is `{"value": ..., "ref"?: "...", "attributes"?: {...}}`
|
|
209
|
+
- **`value` can be a NEID** for reference-typed properties — see
|
|
210
|
+
"The Property Type Problem" below
|
|
211
|
+
- `ref` is a citation hash — pass it through to the LLM exactly as-is
|
|
212
|
+
- `low_confidence` means the entity match is fuzzy — confirm with the user
|
|
213
|
+
|
|
214
|
+
### elemental_get_related
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
result = await mcp_call("elemental_get_related", {
|
|
218
|
+
"entity": "Intel",
|
|
219
|
+
"related_flavor": "person", # REQUIRED
|
|
220
|
+
"relationship_types": ["board_member_of"], # optional filter
|
|
221
|
+
"related_properties": ["nationality", "title"], # properties on each person
|
|
222
|
+
"limit": 20
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
# Response shape:
|
|
226
|
+
{
|
|
227
|
+
"resolved": {
|
|
228
|
+
"neid": "04926132345040704022",
|
|
229
|
+
"name": "Intel Corporation",
|
|
230
|
+
"flavor": "organization"
|
|
231
|
+
},
|
|
232
|
+
"total": 12,
|
|
233
|
+
"relationships": [
|
|
234
|
+
{
|
|
235
|
+
"neid": "08371625409283746152",
|
|
236
|
+
"name": "Patrick Gelsinger",
|
|
237
|
+
"flavor": "person",
|
|
238
|
+
"relationship_types": ["board_member_of"],
|
|
239
|
+
"properties": {
|
|
240
|
+
"nationality": {"value": "United States"},
|
|
241
|
+
"title": {"value": "CEO"}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
# ... more related entities
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Key points:
|
|
250
|
+
- `related_flavor` is **required** — you must specify what type of entity
|
|
251
|
+
to look for
|
|
252
|
+
- `resolved` is the center entity (can be `null` if resolution failed)
|
|
253
|
+
- Each item in `relationships` has the same property value shape as
|
|
254
|
+
`elemental_get_entity`
|
|
255
|
+
- Use `direction` (`"outgoing"`, `"incoming"`, `"both"`) to control
|
|
256
|
+
traversal direction
|
|
257
|
+
|
|
258
|
+
### elemental_get_events
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
result = await mcp_call("elemental_get_events", {
|
|
262
|
+
"entity": "Intel",
|
|
263
|
+
"categories": ["Bankruptcy", "IPO", "Regulatory Action"], # optional
|
|
264
|
+
"time_range": {"after": "2025-01-01"}, # optional
|
|
265
|
+
"include_participants": True, # optional
|
|
266
|
+
"limit": 20
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
# Response shape:
|
|
270
|
+
{
|
|
271
|
+
"resolved": {
|
|
272
|
+
"neid": "04926132345040704022",
|
|
273
|
+
"name": "Intel Corporation"
|
|
274
|
+
},
|
|
275
|
+
"total": 3,
|
|
276
|
+
"events": [
|
|
277
|
+
{
|
|
278
|
+
"neid": "09283746152837461528",
|
|
279
|
+
"name": "Intel CHIPS Act Award",
|
|
280
|
+
"flavor": "event",
|
|
281
|
+
"properties": {
|
|
282
|
+
"category": {"value": "Regulatory Action"},
|
|
283
|
+
"date": {"value": "2025-03-15"},
|
|
284
|
+
"description": {"value": "Intel awarded $8.5B in CHIPS Act funding"},
|
|
285
|
+
"likelihood": {"value": 0.95}
|
|
286
|
+
},
|
|
287
|
+
"participants": [
|
|
288
|
+
{
|
|
289
|
+
"neid": "04926132345040704022",
|
|
290
|
+
"name": "Intel Corporation",
|
|
291
|
+
"flavor": "organization",
|
|
292
|
+
"relationship_types": ["participant"]
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Key points:
|
|
301
|
+
- Events have typed fields: `category`, `date`, `description`, `likelihood`
|
|
302
|
+
- Use `categories` to filter — do NOT try to find events by scanning
|
|
303
|
+
property names for keywords
|
|
304
|
+
- `participants` only included when `include_participants` is `True`
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## The Property Type Problem
|
|
309
|
+
|
|
310
|
+
This is the single biggest source of bugs in MCP-based agents.
|
|
311
|
+
|
|
312
|
+
**The issue:** property values can be entity references (NEIDs), not
|
|
313
|
+
display text. If you render them raw, the user sees `"5816460566439750832"`
|
|
314
|
+
instead of `"United States"`.
|
|
315
|
+
|
|
316
|
+
### Step 1: Get the schema to learn property types
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
schema = await mcp_call("elemental_get_schema", {"flavor": "organization"})
|
|
320
|
+
|
|
321
|
+
# Response includes a properties array:
|
|
322
|
+
# [
|
|
323
|
+
# {"name": "country", "type": "nindex", ...}, <-- entity reference!
|
|
324
|
+
# {"name": "total_revenue", "type": "float", ...},
|
|
325
|
+
# {"name": "ticker_symbol", "type": "string", ...},
|
|
326
|
+
# {"name": "industry", "type": "nindex", ...}, <-- entity reference!
|
|
327
|
+
# ]
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Step 2: Build a type map (once per session)
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
def build_property_type_map(schema_result: dict) -> dict[str, str]:
|
|
334
|
+
"""Map property names to their types from schema."""
|
|
335
|
+
type_map = {}
|
|
336
|
+
for prop in schema_result.get("properties", []):
|
|
337
|
+
type_map[prop["name"]] = prop["type"]
|
|
338
|
+
return type_map
|
|
339
|
+
|
|
340
|
+
# Cache this — don't re-fetch schema for every tool call
|
|
341
|
+
property_types = build_property_type_map(schema)
|
|
342
|
+
# {"country": "nindex", "total_revenue": "float", "ticker_symbol": "string", ...}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Step 3: Resolve reference values before display
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
async def format_properties(
|
|
349
|
+
properties: dict,
|
|
350
|
+
property_types: dict[str, str],
|
|
351
|
+
mcp_call,
|
|
352
|
+
) -> dict[str, str]:
|
|
353
|
+
"""Format property values for user-facing display."""
|
|
354
|
+
formatted = {}
|
|
355
|
+
for name, prop in properties.items():
|
|
356
|
+
value = prop["value"]
|
|
357
|
+
prop_type = property_types.get(name, "string")
|
|
358
|
+
|
|
359
|
+
if prop_type == "nindex" and isinstance(value, (str, int)):
|
|
360
|
+
# This is an entity reference — resolve to display name
|
|
361
|
+
resolved = await mcp_call("elemental_get_entity", {
|
|
362
|
+
"entity_id": {"id": str(value), "id_type": "neid"}
|
|
363
|
+
})
|
|
364
|
+
entity = resolved.get("entity")
|
|
365
|
+
formatted[name] = entity["name"] if entity else str(value)
|
|
366
|
+
elif isinstance(value, (int, float)) and abs(value) >= 1_000_000:
|
|
367
|
+
formatted[name] = _format_large_number(value)
|
|
368
|
+
else:
|
|
369
|
+
formatted[name] = str(value)
|
|
370
|
+
return formatted
|
|
371
|
+
|
|
372
|
+
def _format_large_number(n: float) -> str:
|
|
373
|
+
"""Format large numbers: 52900000000 -> '$52.9B'."""
|
|
374
|
+
abs_n = abs(n)
|
|
375
|
+
if abs_n >= 1e12:
|
|
376
|
+
return f"${n/1e12:.1f}T"
|
|
377
|
+
if abs_n >= 1e9:
|
|
378
|
+
return f"${n/1e9:.1f}B"
|
|
379
|
+
if abs_n >= 1e6:
|
|
380
|
+
return f"${n/1e6:.1f}M"
|
|
381
|
+
return f"${n:,.0f}"
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Property type values you'll encounter
|
|
385
|
+
|
|
386
|
+
| Schema type | Value is | How to display |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `string` | Plain text | Display directly |
|
|
389
|
+
| `integer`, `float` | Number | Format with units (check `unit` in schema) |
|
|
390
|
+
| `nindex` | Entity NEID | **Must resolve** via `elemental_get_entity` |
|
|
391
|
+
| `boolean` | `true`/`false` | Display as Yes/No |
|
|
392
|
+
| `datetime` | ISO 8601 string | Format as human-readable date |
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Common Patterns
|
|
397
|
+
|
|
398
|
+
### Resolve and cache an entity
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
async def resolve_entity(
|
|
402
|
+
name: str,
|
|
403
|
+
session_state: dict,
|
|
404
|
+
mcp_call,
|
|
405
|
+
neid: str | None = None,
|
|
406
|
+
) -> dict | None:
|
|
407
|
+
"""Resolve entity, using cache if available."""
|
|
408
|
+
cache = session_state.setdefault("entities", {})
|
|
409
|
+
|
|
410
|
+
# Check cache by NEID or name
|
|
411
|
+
cache_key = neid or name.lower()
|
|
412
|
+
if cache_key in cache:
|
|
413
|
+
return cache[cache_key]
|
|
414
|
+
|
|
415
|
+
params = {}
|
|
416
|
+
if neid:
|
|
417
|
+
params["entity_id"] = {"id": neid, "id_type": "neid"}
|
|
418
|
+
else:
|
|
419
|
+
params["entity"] = name
|
|
420
|
+
|
|
421
|
+
result = await mcp_call("elemental_get_entity", params)
|
|
422
|
+
entity = result.get("entity")
|
|
423
|
+
if not entity:
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
# Cache by both NEID and lowercase name
|
|
427
|
+
cache[entity["neid"]] = entity
|
|
428
|
+
cache[entity["name"].lower()] = entity
|
|
429
|
+
return entity
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Build a rich entity briefing
|
|
433
|
+
|
|
434
|
+
Don't return a one-paragraph summary. Chain multiple calls to build a
|
|
435
|
+
comprehensive report:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
async def entity_briefing(name: str, mcp_call, session_state: dict) -> str:
|
|
439
|
+
"""Build a comprehensive entity briefing."""
|
|
440
|
+
# 1. Resolve + properties
|
|
441
|
+
entity = await mcp_call("elemental_get_entity", {
|
|
442
|
+
"entity": name,
|
|
443
|
+
"properties": [
|
|
444
|
+
"country", "ticker_symbol", "total_revenue", "net_income",
|
|
445
|
+
"total_assets", "industry", "lei", "company_cik"
|
|
446
|
+
]
|
|
447
|
+
})
|
|
448
|
+
if not entity.get("entity"):
|
|
449
|
+
return f"Could not resolve entity: {name}"
|
|
450
|
+
|
|
451
|
+
e = entity["entity"]
|
|
452
|
+
neid = e["neid"]
|
|
453
|
+
report_parts = [f"# {e['name']}", f"Type: {e.get('flavor', 'unknown')}"]
|
|
454
|
+
|
|
455
|
+
# 2. Format properties (handling nindex resolution)
|
|
456
|
+
if e.get("properties"):
|
|
457
|
+
schema = await mcp_call("elemental_get_schema", {"flavor": e.get("flavor", "")})
|
|
458
|
+
ptypes = build_property_type_map(schema)
|
|
459
|
+
props = await format_properties(e["properties"], ptypes, mcp_call)
|
|
460
|
+
for k, v in props.items():
|
|
461
|
+
report_parts.append(f"- {k}: {v}")
|
|
462
|
+
|
|
463
|
+
# 3. Key relationships
|
|
464
|
+
for related_flavor, label in [("person", "Key People"), ("organization", "Related Orgs")]:
|
|
465
|
+
related = await mcp_call("elemental_get_related", {
|
|
466
|
+
"entity_id": {"id": neid, "id_type": "neid"},
|
|
467
|
+
"related_flavor": related_flavor,
|
|
468
|
+
"limit": 10
|
|
469
|
+
})
|
|
470
|
+
if related.get("relationships"):
|
|
471
|
+
report_parts.append(f"\n## {label}")
|
|
472
|
+
for r in related["relationships"]:
|
|
473
|
+
types = ", ".join(r.get("relationship_types", []))
|
|
474
|
+
report_parts.append(f"- {r['name']} ({types})")
|
|
475
|
+
|
|
476
|
+
# 4. Recent events
|
|
477
|
+
events = await mcp_call("elemental_get_events", {
|
|
478
|
+
"entity_id": {"id": neid, "id_type": "neid"},
|
|
479
|
+
"limit": 10
|
|
480
|
+
})
|
|
481
|
+
if events.get("events"):
|
|
482
|
+
report_parts.append("\n## Recent Events")
|
|
483
|
+
for ev in events["events"]:
|
|
484
|
+
props = ev.get("properties", {})
|
|
485
|
+
date = props.get("date", {}).get("value", "")
|
|
486
|
+
cat = props.get("category", {}).get("value", "")
|
|
487
|
+
desc = props.get("description", {}).get("value", ev["name"])
|
|
488
|
+
report_parts.append(f"- [{date}] {cat}: {desc}")
|
|
489
|
+
|
|
490
|
+
return "\n".join(report_parts)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Fetch events correctly
|
|
494
|
+
|
|
495
|
+
Always use `elemental_get_events`. Never try to find events by scanning
|
|
496
|
+
property names or PID names for keywords like "event" or "filing".
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
# CORRECT — use the dedicated events tool
|
|
500
|
+
events = await mcp_call("elemental_get_events", {
|
|
501
|
+
"entity": "Intel",
|
|
502
|
+
"categories": ["Regulatory Action", "Acquisition"],
|
|
503
|
+
"time_range": {"after": "2025-01-01"},
|
|
504
|
+
"include_participants": True
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
# WRONG — do NOT scan properties/PIDs for event-like names
|
|
508
|
+
# This matches "filed" (a document relationship), not actual events
|
|
509
|
+
schema = await mcp_call("elemental_get_schema", {"flavor": "organization"})
|
|
510
|
+
event_pids = [p for p in schema["properties"] if "event" in p["name"]] # BAD
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Fetch related entities with properties
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
# Get board members with their titles and nationalities
|
|
517
|
+
board = await mcp_call("elemental_get_related", {
|
|
518
|
+
"entity": "JPMorgan Chase",
|
|
519
|
+
"related_flavor": "person",
|
|
520
|
+
"relationship_types": ["board_member_of", "is_officer"],
|
|
521
|
+
"related_properties": ["title", "nationality"],
|
|
522
|
+
"limit": 30
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
for person in board.get("relationships", []):
|
|
526
|
+
name = person["name"]
|
|
527
|
+
title = person.get("properties", {}).get("title", {}).get("value", "")
|
|
528
|
+
print(f"{name} — {title}")
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Common Properties & Relationships Quick Reference
|
|
534
|
+
|
|
535
|
+
Use `elemental_introspect` to discover what's actually populated for a
|
|
536
|
+
given flavor. Use `elemental_get_schema` for the full property list. The
|
|
537
|
+
tables below are a starting-point cheat sheet — not exhaustive.
|
|
538
|
+
|
|
539
|
+
### Properties by flavor
|
|
540
|
+
|
|
541
|
+
| Flavor | Common properties |
|
|
542
|
+
|---|---|
|
|
543
|
+
| `organization` | `country` (nindex), `ticker_symbol`, `total_revenue`, `net_income`, `total_assets`, `industry` (nindex), `lei`, `company_cik`, `ein`, `website` |
|
|
544
|
+
| `person` | `nationality` (nindex), `title`, `birth_date`, `gender` |
|
|
545
|
+
| `government_body` | `country` (nindex), `jurisdiction` |
|
|
546
|
+
| `article` | `headline`, `published_date`, `source`, `url` |
|
|
547
|
+
| `event` | `category`, `date`, `description`, `likelihood` |
|
|
548
|
+
| `financial_instrument` | `ticker_symbol`, `exchange`, `currency` |
|
|
549
|
+
|
|
550
|
+
Properties marked `(nindex)` are entity references — their raw value is
|
|
551
|
+
a NEID that must be resolved to a display name. See "The Property Type
|
|
552
|
+
Problem" above.
|
|
553
|
+
|
|
554
|
+
### Relationship types and direction
|
|
555
|
+
|
|
556
|
+
The `direction` parameter on `elemental_get_related` controls traversal.
|
|
557
|
+
Getting it wrong returns zero results with no error.
|
|
558
|
+
|
|
559
|
+
| Relationship | Meaning | Direction from center |
|
|
560
|
+
|---|---|---|
|
|
561
|
+
| `board_member_of` | Person sits on org's board | `"incoming"` when center is org |
|
|
562
|
+
| `is_officer` | Person is an officer of org | `"incoming"` when center is org |
|
|
563
|
+
| `subsidiary_of` | Org is a subsidiary of parent | `"outgoing"` from subsidiary |
|
|
564
|
+
| `owns` | Entity owns another entity | `"outgoing"` from owner |
|
|
565
|
+
| `appears_in` | Entity mentioned in article | `"both"` is usually safest |
|
|
566
|
+
| `participant` | Entity participates in event | `"both"` is usually safest |
|
|
567
|
+
| `filed` | Org filed a document | `"outgoing"` from org |
|
|
568
|
+
| `works_at` | Person works at org | `"outgoing"` from person |
|
|
569
|
+
|
|
570
|
+
> **Tip:** If you're unsure about direction, use `"both"` (the default)
|
|
571
|
+
> first and check the results. Then narrow to `"incoming"` or
|
|
572
|
+
> `"outgoing"` once you know which way the edges point. You can also use
|
|
573
|
+
> `elemental_get_relationships` to see all relationship types and counts
|
|
574
|
+
> between two specific entities.
|
|
575
|
+
|
|
576
|
+
> **Tip:** Use `elemental_introspect(flavor="organization")` to see which
|
|
577
|
+
> properties and relationships are actually populated with data and their
|
|
578
|
+
> fill rates. This prevents building features against empty data.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Anti-Patterns
|
|
583
|
+
|
|
584
|
+
These are mistakes previous agents have made. Do not repeat them.
|
|
585
|
+
|
|
586
|
+
1. **Do not substring-match PID names to find events or filings.**
|
|
587
|
+
PIDs like `"filed"` are document relationship IDs, not event timestamps.
|
|
588
|
+
Use `elemental_get_events` for events.
|
|
589
|
+
|
|
590
|
+
2. **Do not render raw NEID values in user-facing text.** If a property
|
|
591
|
+
value looks like a large number (`5816460566439750832`), it's probably
|
|
592
|
+
a `nindex` reference. Check the schema.
|
|
593
|
+
|
|
594
|
+
3. **Do not skip schema lookup.** Property types are not guessable from
|
|
595
|
+
names alone. `"country"` looks like it should be a string, but it's
|
|
596
|
+
an `nindex` (entity reference). Always call `elemental_get_schema`
|
|
597
|
+
at least once per flavor.
|
|
598
|
+
|
|
599
|
+
4. **Do not return thin briefings.** When a user asks "tell me about
|
|
600
|
+
Intel," they expect a comprehensive research report — not a single
|
|
601
|
+
paragraph. Chain entity + related + events into a thorough response.
|
|
602
|
+
|
|
603
|
+
5. **Do not fabricate citation refs.** Only include `[ref_...]` markers
|
|
604
|
+
when the `ref` field is actually present in the tool response data.
|
|
605
|
+
The client renders these as numbered citations with source links.
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Citation Handling
|
|
610
|
+
|
|
611
|
+
Property values may include a `ref` field (e.g. `"ref_a3f2b1c8"`). When
|
|
612
|
+
building user-facing text, include the ref in brackets after the fact:
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
Revenue was $52.9B [ref_a3f2b1c8]
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
The chat UI translates these into numbered source citations. Rules:
|
|
619
|
+
- Only include refs that are present in the actual response data
|
|
620
|
+
- Copy the ref string exactly — never construct or modify refs
|
|
621
|
+
- Omit the bracket when no ref is present for a value
|
|
@@ -6,7 +6,13 @@ globs: agents/**
|
|
|
6
6
|
|
|
7
7
|
# Agents: Elemental MCP (MCP-only)
|
|
8
8
|
|
|
9
|
-
In **mcp-only** mode, **Elemental MCP** is the primary way agents reach the knowledge graph. **`broadchurch_auth` HTTP** to the Query Server is the **api-mcp** path; here you wire **MCP tools** into ADK
|
|
9
|
+
In **mcp-only** mode, **Elemental MCP** is the primary way agents reach the knowledge graph. **`broadchurch_auth` HTTP** to the Query Server is the **api-mcp** path; here you wire **MCP tools** into ADK via **McpToolset** + **`StreamableHTTPConnectionParams`** to your Elemental MCP URL — from env or `broadchurch.yaml` / gateway.
|
|
10
|
+
|
|
11
|
+
> **Transport class:** The Elemental MCP server uses **Streamable HTTP**
|
|
12
|
+
> transport. Use `StreamableHTTPConnectionParams` — **NOT**
|
|
13
|
+
> `SseConnectionParams`. Using `SseConnectionParams` will silently fail:
|
|
14
|
+
> the agent starts with zero tools and the LLM hallucinates code. See the
|
|
15
|
+
> "Wiring McpToolset" section below for a working snippet.
|
|
10
16
|
|
|
11
17
|
## Primary agent patterns (all first-class)
|
|
12
18
|
|
|
@@ -18,10 +24,52 @@ In **mcp-only** mode, **Elemental MCP** is the primary way agents reach the know
|
|
|
18
24
|
|
|
19
25
|
You can deploy **multiple agents** with different mixes of A–C.
|
|
20
26
|
|
|
27
|
+
## Wiring McpToolset
|
|
28
|
+
|
|
29
|
+
Use `StreamableHTTPConnectionParams` to connect to the Elemental MCP
|
|
30
|
+
server. Resolve the URL from `broadchurch.yaml` (gateway-proxied in
|
|
31
|
+
production) or fall back to `ELEMENTAL_MCP_URL` for local dev.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import os
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
import yaml
|
|
37
|
+
from google.adk.tools.mcp_tool import McpToolset
|
|
38
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
|
|
39
|
+
|
|
40
|
+
def _get_mcp_url(server_name: str = "elemental") -> str:
|
|
41
|
+
"""Resolve MCP server URL from env or broadchurch.yaml."""
|
|
42
|
+
env_url = os.environ.get("ELEMENTAL_MCP_URL")
|
|
43
|
+
if env_url:
|
|
44
|
+
return env_url
|
|
45
|
+
for candidate in [Path("broadchurch.yaml"), Path(__file__).parent / "broadchurch.yaml"]:
|
|
46
|
+
if candidate.exists():
|
|
47
|
+
config = yaml.safe_load(candidate.read_text()) or {}
|
|
48
|
+
gw = config.get("gateway", {})
|
|
49
|
+
org_id = config.get("tenant", {}).get("org_id", "")
|
|
50
|
+
if gw.get("url") and org_id:
|
|
51
|
+
return f"{gw['url'].rstrip('/')}/api/mcp/{org_id}/{server_name}/mcp"
|
|
52
|
+
return config.get("mcp", {}).get(server_name, "")
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
mcp_url = _get_mcp_url()
|
|
56
|
+
mcp_tools = [McpToolset(connection_params=StreamableHTTPConnectionParams(url=mcp_url))] if mcp_url else []
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> **Silent failure warning:** If `McpToolset` cannot connect (wrong
|
|
60
|
+
> transport, bad URL, server down), the agent starts with **zero MCP
|
|
61
|
+
> tools** and the LLM will hallucinate code instead of calling tools.
|
|
62
|
+
> Always verify tool count at startup:
|
|
63
|
+
>
|
|
64
|
+
> ```python
|
|
65
|
+
> if not mcp_url:
|
|
66
|
+
> raise RuntimeError("No MCP URL configured — check broadchurch.yaml or ELEMENTAL_MCP_URL env var")
|
|
67
|
+
> ```
|
|
68
|
+
|
|
21
69
|
## Python stack (typical)
|
|
22
70
|
|
|
23
71
|
- `google-adk` — agent framework
|
|
24
|
-
-
|
|
72
|
+
- ADK **McpToolset** + **`StreamableHTTPConnectionParams`** — `ELEMENTAL_MCP_URL` or gateway-proxied MCP URL
|
|
25
73
|
- **`psycopg2-binary`** — only when an agent **persists** to `DATABASE_URL`
|
|
26
74
|
|
|
27
75
|
```bash
|