catence 0.2.1-beta.27 → 0.2.1-beta.28

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.
@@ -3,7 +3,7 @@
3
3
  * Keep these values synchronized with release/manifest.json; release checks
4
4
  * deliberately reject drift before a package can be published.
5
5
  */
6
- export declare const CATENCE_RUNTIME_VERSION = "0.2.1-beta.27";
6
+ export declare const CATENCE_RUNTIME_VERSION = "0.2.1-beta.28";
7
7
  export declare const CATENCE_PROTOCOL_VERSION = 1;
8
8
  export type CatenceRuntimeCapabilities = {
9
9
  mcp: true;
@@ -3,7 +3,7 @@
3
3
  * Keep these values synchronized with release/manifest.json; release checks
4
4
  * deliberately reject drift before a package can be published.
5
5
  */
6
- export const CATENCE_RUNTIME_VERSION = '0.2.1-beta.27';
6
+ export const CATENCE_RUNTIME_VERSION = '0.2.1-beta.28';
7
7
  export const CATENCE_PROTOCOL_VERSION = 1;
8
8
  export function catenceRuntimeHealth() {
9
9
  return {
@@ -3,7 +3,7 @@ export type FindActivitiesRequest = {
3
3
  sports?: string[];
4
4
  /** Accepted as a convenience alias for `sports: [sport]`. */
5
5
  sport?: string;
6
- distanceKm?: [number, number];
6
+ distanceKm?: number[];
7
7
  nameContains?: string[];
8
8
  startDate?: string;
9
9
  endDate?: string;
@@ -619,10 +619,40 @@ async function handleUnauthenticatedDiscovery(request, response, paths) {
619
619
  * tool definitions drop the SDK's `execution` field — neither the per-result
620
620
  * `_meta.serverInfo` nor `execution` appeared in the revision's schemas for
621
621
  * earlier SDKs, but the revision asks for the former and omits the latter.
622
+ * Tool schemas also drop `propertyNames`, which strict validators reject and
623
+ * whose only instance merely restates JSON's string-key rule.
622
624
  * Errors and notifications carry no result and pass through.
623
625
  */
624
626
  const MODERN_CACHEABLE_METHODS = new Set(['server/discover', 'tools/list', 'prompts/list', 'resources/list', 'resources/templates/list', 'resources/read']);
625
627
  const MODERN_RESULT_TTL_MS = 300_000;
628
+ /** Copy a JSON schema without `propertyNames` keywords; returns the original when nothing was removed. */
629
+ function stripPropertyNames(value) {
630
+ if (Array.isArray(value)) {
631
+ let changed = false;
632
+ const copy = value.map((item) => {
633
+ const next = stripPropertyNames(item);
634
+ if (next !== item)
635
+ changed = true;
636
+ return next;
637
+ });
638
+ return changed ? copy : value;
639
+ }
640
+ if (typeof value !== 'object' || value === null)
641
+ return value;
642
+ let changed = false;
643
+ const copy = {};
644
+ for (const [key, item] of Object.entries(value)) {
645
+ if (key === 'propertyNames') {
646
+ changed = true;
647
+ continue;
648
+ }
649
+ const next = stripPropertyNames(item);
650
+ if (next !== item)
651
+ changed = true;
652
+ copy[key] = next;
653
+ }
654
+ return changed ? copy : value;
655
+ }
626
656
  /** Rewrite one JSON-RPC payload; null when it carries nothing to change. */
627
657
  function frameModernRpcPayload(payload, method) {
628
658
  let envelope;
@@ -651,10 +681,23 @@ function frameModernRpcPayload(payload, method) {
651
681
  if (method === 'tools/list' && Array.isArray(framed.tools)) {
652
682
  let stripped = false;
653
683
  const tools = framed.tools.map((tool) => {
654
- if (typeof tool !== 'object' || tool === null || Array.isArray(tool) || !('execution' in tool))
684
+ if (typeof tool !== 'object' || tool === null || Array.isArray(tool))
655
685
  return tool;
656
686
  const copy = { ...tool };
657
- delete copy.execution;
687
+ let edited = false;
688
+ if ('execution' in copy) {
689
+ delete copy.execution;
690
+ edited = true;
691
+ }
692
+ if ('inputSchema' in copy) {
693
+ const schema = stripPropertyNames(copy.inputSchema);
694
+ if (schema !== copy.inputSchema) {
695
+ copy.inputSchema = schema;
696
+ edited = true;
697
+ }
698
+ }
699
+ if (!edited)
700
+ return tool;
658
701
  stripped = true;
659
702
  return copy;
660
703
  });
@@ -583,7 +583,11 @@ export function createCatenceMcpServer(paths = resolveCatalogPaths(), dependenci
583
583
  inputSchema: {
584
584
  sport: z.string().min(1).optional(),
585
585
  sports: z.array(z.string().min(1)).min(1).max(12).optional(),
586
- distanceKm: z.tuple([z.number().min(0), z.number().min(0)]).optional(),
586
+ // Tuple-form `items` is valid draft-07 but strict clients reject it
587
+ // (ChatGPT's connector validator flagged exactly this as "Invalid MCP
588
+ // tool schema"). A bounded two-number array keeps the same contract; the
589
+ // service still enforces the ascending [minimum, maximum] order.
590
+ distanceKm: z.array(z.number().min(0)).min(2).max(2).optional(),
587
591
  nameContains: z.array(z.string().min(1)).min(1).max(12).optional(),
588
592
  startDate: z.string().date().optional(), endDate: z.string().date().optional(),
589
593
  sort: z.enum(['date_desc', 'date_asc']).optional(), limit: z.number().int().min(1).max(100).optional(), cursor: z.string().min(1).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catence",
3
- "version": "0.2.1-beta.27",
3
+ "version": "0.2.1-beta.28",
4
4
  "description": "Local MCP access to normalized Garmin, Intervals.icu, and targeted Strava fitness data.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "repository": {