From bc9aedb9eaf46f7e4efd9e09dc969754738ea06c Mon Sep 17 00:00:00 2001 From: straeter Date: Thu, 19 Feb 2026 10:04:07 +0100 Subject: [PATCH 1/4] add remaining relationship types --- docs/mcp-server.md | 2 +- docs/reference/MERGE.md | 2 +- everyrow-mcp/README.md | 2 +- everyrow-mcp/pyproject.toml | 2 +- everyrow-mcp/src/everyrow_mcp/server.py | 9 ++++++--- pyproject.toml | 2 +- skills/everyrow-sdk/SKILL.md | 2 +- src/everyrow/generated/models/merge_operation.py | 5 ++++- .../models/merge_operation_relationship_type_type_0.py | 2 ++ src/everyrow/ops.py | 6 +++--- uv.lock | 4 ++-- 11 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/mcp-server.md b/docs/mcp-server.md index a827eb12..0fd17657 100644 --- a/docs/mcp-server.md +++ b/docs/mcp-server.md @@ -60,7 +60,7 @@ Join two CSVs using intelligent entity matching (LEFT JOIN semantics). | `right_csv` | string | Yes | The lookup/reference table — its columns are appended to matches; unmatched left rows get nulls. | | `merge_on_left` | string | No | Only set if you expect exact string matches on this column or want to draw agent attention to it. Fine to omit. | | `merge_on_right` | string | No | Only set if you expect exact string matches on this column or want to draw agent attention to it. Fine to omit. | -| `relationship_type` | string | No | `many_to_one` (default) — multiple left rows can match one right row. `one_to_one` — only when both tables have unique entities of the same kind. | +| `relationship_type` | string | No | `many_to_one` (default) — multiple left rows can match one right row. `one_to_one` — unique matching between left and right rows. `one_to_many` — one left row can match multiple right rows. `many_to_many` — multiple left rows can match multiple right rows. For `one_to_many` and `many_to_many`, multiple matches are joined with `" \| "` in each added column. | | `use_web_search` | string | No | `auto` (default), `yes`, or `no`. | Returns `task_id` and `session_url`. Call `everyrow_progress` to monitor. diff --git a/docs/reference/MERGE.md b/docs/reference/MERGE.md index 1db2a38b..41e22fdc 100644 --- a/docs/reference/MERGE.md +++ b/docs/reference/MERGE.md @@ -57,7 +57,7 @@ A DataFrame with all left table columns plus matched right table columns. Rows t | `right_table` | DataFrame | The lookup/reference table — its columns are appended to matches; unmatched left rows get nulls. | | `merge_on_left` | Optional[str] | Only set if you expect exact string matches on this column or want to draw agent attention to it. Auto-detected if omitted. | | `merge_on_right` | Optional[str] | Only set if you expect exact string matches on this column or want to draw agent attention to it. Auto-detected if omitted. | -| `relationship_type` | Optional[str] | `"many_to_one"` (default) — multiple left rows can match one right row. `"one_to_one"` — only when both tables have unique entities of the same kind. | +| `relationship_type` | Optional[str] | `"many_to_one"` (default) — multiple left rows can match one right row. `"one_to_one"` — unique matching between left and right rows. `"one_to_many"` — one left row can match multiple right rows. `"many_to_many"` — multiple left rows can match multiple right rows. For `one_to_many` and `many_to_many`, multiple matches are joined with `" \| "` in each added column. | | `use_web_search` | Optional[str] | `"auto"` (default), `"yes"`, or `"no"`. Controls whether agents use web search to resolve matches. | | `session` | Session | Optional, auto-created if omitted | diff --git a/everyrow-mcp/README.md b/everyrow-mcp/README.md index f0bfc223..7ee10060 100644 --- a/everyrow-mcp/README.md +++ b/everyrow-mcp/README.md @@ -113,7 +113,7 @@ Parameters: - merge_on_left: (optional) Only set if you expect exact string matches on this column or want to draw agent attention to it. Fine to omit. - merge_on_right: (optional) Only set if you expect exact string matches on this column or want to draw agent attention to it. Fine to omit. - use_web_search: (optional) "auto" (default), "yes", or "no" -- relationship_type: (optional) "many_to_one" (default) — multiple left rows can match one right row. "one_to_one" — only when both tables have unique entities of the same kind. +- relationship_type: (optional) "many_to_one" (default) if multiple left rows can match one right row, "one_to_one" matches must be unique, "one_to_many" one left row can match multiple right rows, "many_to_many" multiple left rows can match multiple right rows. For one_to_many and many_to_many, multiple matches are joined with " | " in each added column. ``` Example: Match software products (left, enriched) to parent companies (right, lookup): Photoshop -> Adobe diff --git a/everyrow-mcp/pyproject.toml b/everyrow-mcp/pyproject.toml index 6e3ab74b..e43336c3 100644 --- a/everyrow-mcp/pyproject.toml +++ b/everyrow-mcp/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "everyrow-mcp" -version = "0.3.3" +version = "0.3.4" description = "MCP server for everyrow: agent ops at spreadsheet scale" readme = "README.md" requires-python = ">=3.12" diff --git a/everyrow-mcp/src/everyrow_mcp/server.py b/everyrow-mcp/src/everyrow_mcp/server.py index 8f859862..fc12c01d 100644 --- a/everyrow-mcp/src/everyrow_mcp/server.py +++ b/everyrow-mcp/src/everyrow_mcp/server.py @@ -235,9 +235,9 @@ class MergeInput(BaseModel): use_web_search: Literal["auto", "yes", "no"] | None = Field( default=None, description='Control web search: "auto", "yes", or "no".' ) - relationship_type: Literal["many_to_one", "one_to_one"] | None = Field( + relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = Field( default=None, - description="Leave unset for the default many_to_one, which is correct in most cases. many_to_one: multiple left rows can match one right row (e.g. products → companies). one_to_one: each left row matches at most one right row AND vice versa. Only use one_to_one when both tables represent unique entities of the same kind.", + description='Control merge relationship type / cardinality between the two tables: "many_to_one" (default) allows multiple left rows to match one right row (e.g. matching reviews to product), "one_to_one" enforces unique matching between left and right rows (e.g. CEO to company), "one_to_many" allows one left row to match multiple right rows (e.g. company to products), "many_to_many" allows multiple left rows to match multiple right rows (e.g. companies to investors). For one_to_many and many_to_many, multiple matches are represented by joining the right-table values with " | " in each added column.', ) @field_validator("left_csv", "right_csv") @@ -609,7 +609,6 @@ async def everyrow_merge(params: MergeInput) -> list[TextContent]: - merge_on_left/merge_on_right: only set if you expect exact string matches on the chosen columns or want to draw agent attention to them. Fine to omit. - relationship_type: defaults to many_to_one, which is correct in most cases. - Only set one_to_one when both tables have unique entities of the same kind. Examples: - Match software products (left, enriched) to parent companies (right, lookup): @@ -618,6 +617,10 @@ async def everyrow_merge(params: MergeInput) -> list[TextContent]: Genentech -> Roche. relationship_type: many_to_one. - Join two contact lists with different name formats: relationship_type: one_to_one (each person appears once in each list). + - Match a company (left) to its products (right): + relationship_type: one_to_many (one company has many products). + - Match companies (left) to investors (right): + relationship_type: many_to_many (companies share investors and vice versa). This function submits the task and returns immediately with a task_id and session_url. After receiving a result from this tool, share the session_url with the user. diff --git a/pyproject.toml b/pyproject.toml index 900d4536..c039fe68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ members = ["everyrow-mcp"] [project] name = "everyrow" -version = "0.3.3" +version = "0.3.4" description = "An SDK for everyrow.io: agent ops at spreadsheet scale" readme = "README.md" requires-python = ">=3.12" diff --git a/skills/everyrow-sdk/SKILL.md b/skills/everyrow-sdk/SKILL.md index 61163688..6de849c0 100644 --- a/skills/everyrow-sdk/SKILL.md +++ b/skills/everyrow-sdk/SKILL.md @@ -125,7 +125,7 @@ Parameters: - output_path: Directory or full .csv path for output - merge_on_left: (optional) Only set if you expect exact string matches on the chosen column or want to draw agent attention to it. Fine to omit. - merge_on_right: (optional) Only set if you expect exact string matches on the chosen column or want to draw agent attention to it. Fine to omit. -- relationship_type: (optional) Defaults to "many_to_one", which is correct in most cases (e.g. products → companies). Only set "one_to_one" when both tables have unique entities of the same kind. +- relationship_type: (optional) Defaults to "many_to_one", which is correct in most cases (e.g. products → companies). "one_to_one" when both tables have unique entities of the same kind. "one_to_many" when one left row can match multiple right rows (e.g. company → products). "many_to_many" when multiple left rows can match multiple right rows (e.g. companies → investors). For one_to_many and many_to_many, multiple matches are joined with " | " in each added column. - use_web_search: (optional) "auto" (default), "yes", or "no" ``` diff --git a/src/everyrow/generated/models/merge_operation.py b/src/everyrow/generated/models/merge_operation.py index 01fca9df..ff53f533 100644 --- a/src/everyrow/generated/models/merge_operation.py +++ b/src/everyrow/generated/models/merge_operation.py @@ -37,7 +37,10 @@ class MergeOperation: without initial LLM merge Default: MergeOperationUseWebSearchType0.AUTO. relationship_type (MergeOperationRelationshipTypeType0 | None | Unset): Control merge relationship behavior: 'many_to_one' (default) allows multiple left rows to match the same right row, 'one_to_one' enforces unique - matches and resolves clashes Default: MergeOperationRelationshipTypeType0.MANY_TO_ONE. + matches and resolves clashes, 'one_to_many' allows one left row to match multiple right rows, + 'many_to_many' allows multiple left rows to match multiple right rows. For one_to_many and many_to_many, + multiple matches are joined with " | " in each added column. Default: + MergeOperationRelationshipTypeType0.MANY_TO_ONE. session_id (None | Unset | UUID): Session ID. If not provided, a new session is auto-created for this task. """ diff --git a/src/everyrow/generated/models/merge_operation_relationship_type_type_0.py b/src/everyrow/generated/models/merge_operation_relationship_type_type_0.py index 6720f75f..37d14bbf 100644 --- a/src/everyrow/generated/models/merge_operation_relationship_type_type_0.py +++ b/src/everyrow/generated/models/merge_operation_relationship_type_type_0.py @@ -4,6 +4,8 @@ class MergeOperationRelationshipTypeType0(str, Enum): MANY_TO_ONE = "many_to_one" ONE_TO_ONE = "one_to_one" + ONE_TO_MANY = "one_to_many" + MANY_TO_MANY = "many_to_many" def __str__(self) -> str: return str(self.value) diff --git a/src/everyrow/ops.py b/src/everyrow/ops.py index f6e69de1..4eab52fb 100644 --- a/src/everyrow/ops.py +++ b/src/everyrow/ops.py @@ -580,7 +580,7 @@ async def merge( merge_on_left: str | None = None, merge_on_right: str | None = None, use_web_search: Literal["auto", "yes", "no"] | None = None, - relationship_type: Literal["many_to_one", "one_to_one"] | None = None, + relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = None, ) -> MergeResult: """Merge two tables using AI (LEFT JOIN semantics). @@ -592,7 +592,7 @@ async def merge( merge_on_left: Only set if you expect exact string matches on this column or want to draw agent attention to it. Auto-detected if omitted. merge_on_right: Only set if you expect exact string matches on this column or want to draw agent attention to it. Auto-detected if omitted. use_web_search: Control web search behavior: "auto" (default) tries LLM merge first then conditionally searches, "no" skips web search entirely, "yes" forces web search on every row. - relationship_type: Defaults to "many_to_one", which is correct in most cases (multiple left rows can match one right row, e.g. products → companies). Only use "one_to_one" when both tables have unique entities of the same kind. + relationship_type: Control merge relationship type / cardinality between the two tables: "many_to_one" (default) allows multiple left rows to match one right row (e.g. matching reviews to product), "one_to_one" enforces unique matching between left and right rows (e.g. CEO to company), "one_to_many" allows one left row to match multiple right rows (e.g. company to products), "many_to_many" allows multiple left rows to match multiple right rows (e.g. companies to investors). For one_to_many and many_to_many, multiple matches are represented by joining the right-table values with " | " in each added column. Returns: MergeResult containing the merged table and match breakdown by method (exact, fuzzy, llm, web) @@ -639,7 +639,7 @@ async def merge_async( merge_on_left: str | None = None, merge_on_right: str | None = None, use_web_search: Literal["auto", "yes", "no"] | None = None, - relationship_type: Literal["many_to_one", "one_to_one"] | None = None, + relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = None, ) -> MergeTask: """Submit a merge task asynchronously. diff --git a/uv.lock b/uv.lock index 3f6263b4..88fc6ec0 100644 --- a/uv.lock +++ b/uv.lock @@ -289,7 +289,7 @@ wheels = [ [[package]] name = "everyrow" -version = "0.3.3" +version = "0.3.4" source = { editable = "." } dependencies = [ { name = "attrs" }, @@ -346,7 +346,7 @@ dev = [ [[package]] name = "everyrow-mcp" -version = "0.3.3" +version = "0.3.4" source = { editable = "everyrow-mcp" } dependencies = [ { name = "everyrow" }, From 9bbf47820807795db3c1ee6f0df78ae4af362bb8 Mon Sep 17 00:00:00 2001 From: straeter Date: Tue, 24 Feb 2026 18:06:26 +0100 Subject: [PATCH 2/4] add docs about | separator to tools.py --- everyrow-mcp/src/everyrow_mcp/tools.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 711e5670..6969ccb7 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -436,6 +436,8 @@ async def everyrow_merge(params: MergeInput, ctx: EveryRowContext) -> list[TextC - merge_on_left/merge_on_right: only set if you expect exact string matches on the chosen columns or want to draw agent attention to them. Fine to omit. - relationship_type: defaults to many_to_one, which is correct in most cases. + For one_to_many and many_to_many, multiple right-table matches are joined + with " | " in each added column. Examples: - Match software products (left, enriched) to parent companies (right, lookup): @@ -445,9 +447,11 @@ async def everyrow_merge(params: MergeInput, ctx: EveryRowContext) -> list[TextC - Join two contact lists with different name formats: relationship_type: one_to_one (each person appears once in each list). - Match a company (left) to its products (right): - relationship_type: one_to_many (one company has many products). + relationship_type: one_to_many (one company has many products; + matched product names joined with " | "). - Match companies (left) to investors (right): - relationship_type: many_to_many (companies share investors and vice versa). + relationship_type: many_to_many (companies share investors and vice versa; + matched values joined with " | "). This function submits the task and returns immediately with a task_id and session_url. After receiving a result from this tool, share the session_url with the user. From 22fbcab2d5b75d919c99b452fb6decfe9e30a9cf Mon Sep 17 00:00:00 2001 From: straeter Date: Fri, 27 Feb 2026 10:20:08 +0100 Subject: [PATCH 3/4] fix formatting --- everyrow-mcp/src/everyrow_mcp/models.py | 4 +++- src/everyrow/ops.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/everyrow-mcp/src/everyrow_mcp/models.py b/everyrow-mcp/src/everyrow_mcp/models.py index 3d6a635f..98ccb4e0 100644 --- a/everyrow-mcp/src/everyrow_mcp/models.py +++ b/everyrow-mcp/src/everyrow_mcp/models.py @@ -365,7 +365,9 @@ class MergeInput(BaseModel): default=None, description='Control web search: "auto", "yes", or "no".', ) - relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = Field( + relationship_type: ( + Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None + ) = Field( default=None, description='Control merge relationship type / cardinality between the two tables: "many_to_one" (default) allows multiple left rows to match one right row (e.g. matching reviews to product), "one_to_one" enforces unique matching between left and right rows (e.g. CEO to company), "one_to_many" allows one left row to match multiple right rows (e.g. company to products), "many_to_many" allows multiple left rows to match multiple right rows (e.g. companies to investors). For one_to_many and many_to_many, multiple matches are represented by joining the right-table values with " | " in each added column.', ) diff --git a/src/everyrow/ops.py b/src/everyrow/ops.py index e0f843d6..4941d327 100644 --- a/src/everyrow/ops.py +++ b/src/everyrow/ops.py @@ -583,7 +583,10 @@ async def merge( merge_on_left: str | None = None, merge_on_right: str | None = None, use_web_search: Literal["auto", "yes", "no"] | None = None, - relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = None, + relationship_type: Literal[ + "many_to_one", "one_to_one", "one_to_many", "many_to_many" + ] + | None = None, ) -> MergeResult: """Merge two tables using AI (LEFT JOIN semantics). @@ -642,7 +645,10 @@ async def merge_async( merge_on_left: str | None = None, merge_on_right: str | None = None, use_web_search: Literal["auto", "yes", "no"] | None = None, - relationship_type: Literal["many_to_one", "one_to_one", "one_to_many", "many_to_many"] | None = None, + relationship_type: Literal[ + "many_to_one", "one_to_one", "one_to_many", "many_to_many" + ] + | None = None, ) -> MergeTask: """Submit a merge task asynchronously. From 0e9b729d7e3516627bb06f0f9a74f8446e021336 Mon Sep 17 00:00:00 2001 From: straeter Date: Fri, 27 Feb 2026 10:27:45 +0100 Subject: [PATCH 4/4] ad skipped link --- docs-site/scripts/check-links.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs-site/scripts/check-links.py b/docs-site/scripts/check-links.py index dcd2f02d..5f88bf0d 100644 --- a/docs-site/scripts/check-links.py +++ b/docs-site/scripts/check-links.py @@ -71,6 +71,7 @@ "https://www.kaggle.com/code/rafaelpoyiadzi/active-learning-with-an-llm-oracle", "https://www.kaggle.com/datasets/tunguz/pubmed-title-abstracts-2019-baseline", "https://arxiv.org/abs/2506.21558", + "https://media.githubusercontent.com/media/futuresearch/everyrow-sdk/refs/heads/main/docs/data/fda_products.csv" }