@josephyan/qingflow-app-user-mcp 0.2.0-beta.37 → 0.2.0-beta.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.37
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.38
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.37 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.38 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "0.2.0-beta.37",
3
+ "version": "0.2.0-beta.38",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b37"
7
+ version = "0.2.0b38"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b37"
5
+ __version__ = "0.2.0b38"
@@ -69,6 +69,14 @@ class PublicViewType(str, Enum):
69
69
  gantt = "gantt"
70
70
 
71
71
 
72
+ class PublicChartType(str, Enum):
73
+ target = "target"
74
+ pie = "pie"
75
+ bar = "bar"
76
+ line = "line"
77
+ table = "table"
78
+
79
+
72
80
  class LayoutApplyMode(str, Enum):
73
81
  merge = "merge"
74
82
  replace = "replace"
@@ -491,6 +499,215 @@ class ViewUpsertPatch(StrictModel):
491
499
  return self
492
500
 
493
501
 
502
+ class ChartFilterRulePatch(StrictModel):
503
+ field_name: str = Field(validation_alias=AliasChoices("field_name", "fieldName", "field", "name"))
504
+ operator: ViewFilterOperator = Field(validation_alias=AliasChoices("operator", "op"))
505
+ values: list[Any] = Field(default_factory=list)
506
+
507
+ @model_validator(mode="before")
508
+ @classmethod
509
+ def normalize_aliases(cls, value: Any) -> Any:
510
+ if not isinstance(value, dict):
511
+ return value
512
+ payload = dict(value)
513
+ if "value" in payload and "values" not in payload:
514
+ payload["values"] = [payload.pop("value")]
515
+ raw_operator = payload.get("operator", payload.get("op"))
516
+ if isinstance(raw_operator, str):
517
+ normalized = raw_operator.strip().lower()
518
+ operator_aliases = {
519
+ "equals": ViewFilterOperator.eq.value,
520
+ "equal": ViewFilterOperator.eq.value,
521
+ "=": ViewFilterOperator.eq.value,
522
+ "not_equals": ViewFilterOperator.neq.value,
523
+ "not_equal": ViewFilterOperator.neq.value,
524
+ "!=": ViewFilterOperator.neq.value,
525
+ ">=": ViewFilterOperator.gte.value,
526
+ "<=": ViewFilterOperator.lte.value,
527
+ "any_of": ViewFilterOperator.in_.value,
528
+ "one_of": ViewFilterOperator.in_.value,
529
+ "between_any": ViewFilterOperator.in_.value,
530
+ "empty": ViewFilterOperator.is_empty.value,
531
+ "is blank": ViewFilterOperator.is_empty.value,
532
+ "blank": ViewFilterOperator.is_empty.value,
533
+ "not_empty": ViewFilterOperator.not_empty.value,
534
+ "not blank": ViewFilterOperator.not_empty.value,
535
+ }
536
+ if normalized in operator_aliases:
537
+ payload["operator"] = operator_aliases[normalized]
538
+ elif "operator" not in payload:
539
+ payload["operator"] = normalized
540
+ payload.pop("op", None)
541
+ return payload
542
+
543
+ @model_validator(mode="after")
544
+ def validate_shape(self) -> "ChartFilterRulePatch":
545
+ if self.operator in {ViewFilterOperator.is_empty, ViewFilterOperator.not_empty}:
546
+ self.values = []
547
+ return self
548
+ if not self.values:
549
+ raise ValueError("chart filter rule requires values")
550
+ return self
551
+
552
+
553
+ class ChartUpsertPatch(StrictModel):
554
+ chart_id: str | None = None
555
+ name: str
556
+ chart_type: PublicChartType
557
+ dimension_field_ids: list[str] = Field(default_factory=list)
558
+ indicator_field_ids: list[str] = Field(default_factory=list)
559
+ filters: list[ChartFilterRulePatch] = Field(default_factory=list)
560
+ question_config: list[dict[str, Any]] = Field(default_factory=list)
561
+ user_config: list[dict[str, Any]] = Field(default_factory=list)
562
+ config: dict[str, Any] = Field(default_factory=dict)
563
+
564
+ @model_validator(mode="before")
565
+ @classmethod
566
+ def normalize_aliases(cls, value: Any) -> Any:
567
+ if not isinstance(value, dict):
568
+ return value
569
+ payload = dict(value)
570
+ if "id" in payload and "chart_id" not in payload:
571
+ payload["chart_id"] = payload.pop("id")
572
+ if "type" in payload and "chart_type" not in payload:
573
+ payload["chart_type"] = payload.pop("type")
574
+ if "dimension_fields" in payload and "dimension_field_ids" not in payload:
575
+ payload["dimension_field_ids"] = payload.pop("dimension_fields")
576
+ if "indicator_fields" in payload and "indicator_field_ids" not in payload:
577
+ payload["indicator_field_ids"] = payload.pop("indicator_fields")
578
+ if "metric_field_ids" in payload and "indicator_field_ids" not in payload:
579
+ payload["indicator_field_ids"] = payload.pop("metric_field_ids")
580
+ raw_type = payload.get("chart_type")
581
+ if isinstance(raw_type, str):
582
+ normalized = raw_type.strip().lower()
583
+ aliases = {
584
+ "targetchart": PublicChartType.target.value,
585
+ "piechart": PublicChartType.pie.value,
586
+ "barchart": PublicChartType.bar.value,
587
+ "linechart": PublicChartType.line.value,
588
+ "tablechart": PublicChartType.table.value,
589
+ }
590
+ if normalized in aliases:
591
+ payload["chart_type"] = aliases[normalized]
592
+ return payload
593
+
594
+
595
+ class ChartApplyRequest(StrictModel):
596
+ app_key: str
597
+ upsert_charts: list[ChartUpsertPatch] = Field(default_factory=list)
598
+ remove_chart_ids: list[str] = Field(default_factory=list)
599
+ reorder_chart_ids: list[str] = Field(default_factory=list)
600
+
601
+ @model_validator(mode="after")
602
+ def validate_shape(self) -> "ChartApplyRequest":
603
+ if not self.upsert_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
604
+ raise ValueError("chart apply requires at least one upsert, remove, or reorder operation")
605
+ return self
606
+
607
+
608
+ class PortalComponentPositionPatch(StrictModel):
609
+ pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
610
+ pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
611
+ pc_w: int = Field(default=12, validation_alias=AliasChoices("pc_w", "pcW", "w"))
612
+ pc_h: int = Field(default=8, validation_alias=AliasChoices("pc_h", "pcH", "h"))
613
+ mobile_x: int = Field(default=0, validation_alias=AliasChoices("mobile_x", "mobileX"))
614
+ mobile_y: int = Field(default=0, validation_alias=AliasChoices("mobile_y", "mobileY"))
615
+ mobile_w: int = Field(default=12, validation_alias=AliasChoices("mobile_w", "mobileW"))
616
+ mobile_h: int = Field(default=8, validation_alias=AliasChoices("mobile_h", "mobileH"))
617
+
618
+
619
+ class PortalChartRefPatch(StrictModel):
620
+ app_key: str
621
+ chart_id: str | None = None
622
+ chart_name: str | None = None
623
+
624
+ @model_validator(mode="after")
625
+ def validate_target(self) -> "PortalChartRefPatch":
626
+ if not (self.chart_id or self.chart_name):
627
+ raise ValueError("chart_ref requires chart_id or chart_name")
628
+ return self
629
+
630
+
631
+ class PortalViewRefPatch(StrictModel):
632
+ app_key: str
633
+ view_key: str | None = None
634
+ view_name: str | None = None
635
+
636
+ @model_validator(mode="after")
637
+ def validate_target(self) -> "PortalViewRefPatch":
638
+ if not (self.view_key or self.view_name):
639
+ raise ValueError("view_ref requires view_key or view_name")
640
+ return self
641
+
642
+
643
+ class PortalSectionPatch(StrictModel):
644
+ title: str
645
+ source_type: str = Field(validation_alias=AliasChoices("source_type", "sourceType"))
646
+ position: PortalComponentPositionPatch | None = None
647
+ dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
648
+ config: dict[str, Any] = Field(default_factory=dict)
649
+ chart_ref: PortalChartRefPatch | None = None
650
+ view_ref: PortalViewRefPatch | None = None
651
+ text: str | None = None
652
+ url: str | None = None
653
+
654
+ @model_validator(mode="before")
655
+ @classmethod
656
+ def normalize_aliases(cls, value: Any) -> Any:
657
+ if not isinstance(value, dict):
658
+ return value
659
+ payload = dict(value)
660
+ raw_type = payload.get("source_type", payload.get("sourceType"))
661
+ if isinstance(raw_type, str):
662
+ payload["source_type"] = raw_type.strip().lower()
663
+ if "chartRef" in payload and "chart_ref" not in payload:
664
+ payload["chart_ref"] = payload.pop("chartRef")
665
+ if "viewRef" in payload and "view_ref" not in payload:
666
+ payload["view_ref"] = payload.pop("viewRef")
667
+ if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
668
+ payload["dash_style_config"] = payload.pop("dashStyleConfigBO")
669
+ return payload
670
+
671
+ @model_validator(mode="after")
672
+ def validate_shape(self) -> "PortalSectionPatch":
673
+ supported = {"chart", "view", "grid", "filter", "text", "link"}
674
+ if self.source_type not in supported:
675
+ raise ValueError(f"unsupported portal source_type '{self.source_type}'")
676
+ if self.source_type == "chart" and self.chart_ref is None:
677
+ raise ValueError("chart section requires chart_ref")
678
+ if self.source_type == "view" and self.view_ref is None:
679
+ raise ValueError("view section requires view_ref")
680
+ if self.source_type == "text" and self.text is None:
681
+ raise ValueError("text section requires text")
682
+ if self.source_type == "link" and self.url is None:
683
+ raise ValueError("link section requires url")
684
+ return self
685
+
686
+
687
+ class PortalApplyRequest(StrictModel):
688
+ dash_key: str | None = None
689
+ dash_name: str | None = None
690
+ package_tag_id: int | None = None
691
+ publish: bool = True
692
+ sections: list[PortalSectionPatch] = Field(default_factory=list)
693
+ auth: dict[str, Any] | None = None
694
+ icon: str | None = None
695
+ color: str | None = None
696
+ hide_copyright: bool | None = Field(default=None, validation_alias=AliasChoices("hide_copyright", "hideCopyright"))
697
+ dash_global_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_global_config", "dashGlobalConfig"))
698
+ config: dict[str, Any] = Field(default_factory=dict)
699
+
700
+ @model_validator(mode="after")
701
+ def validate_shape(self) -> "PortalApplyRequest":
702
+ if not self.dash_key and not self.package_tag_id:
703
+ raise ValueError("package_tag_id is required when dash_key is empty")
704
+ if not self.dash_key and not self.dash_name:
705
+ raise ValueError("dash_name is required when creating a portal")
706
+ if not self.sections:
707
+ raise ValueError("portal apply requires a non-empty sections list")
708
+ return self
709
+
710
+
494
711
  FieldPatch.model_rebuild()
495
712
 
496
713