catence 0.2.1-beta.24 → 0.2.1-beta.26

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.24";
6
+ export declare const CATENCE_RUNTIME_VERSION = "0.2.1-beta.26";
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.24';
6
+ export const CATENCE_RUNTIME_VERSION = '0.2.1-beta.26';
7
7
  export const CATENCE_PROTOCOL_VERSION = 1;
8
8
  export function catenceRuntimeHealth() {
9
9
  return {
@@ -109,6 +109,11 @@ export async function createOAuthApp(options) {
109
109
  app.get('/.well-known/oauth-authorization-server', serveMetadata(oauthMetadata));
110
110
  app.get('/.well-known/oauth-protected-resource', serveMetadata(protectedResourceMetadata));
111
111
  app.get('/.well-known/oauth-protected-resource/mcp', serveMetadata(protectedResourceMetadata));
112
+ // The connector platform probes OIDC-style discovery on every validation
113
+ // pass (observed hitting the auth gate as a 401 right after fetching the
114
+ // RFC 8414 document); answer it publicly with the same metadata.
115
+ app.get('/.well-known/openid-configuration', serveMetadata(oauthMetadata));
116
+ app.get('/.well-known/openid-configuration/mcp', serveMetadata(oauthMetadata));
112
117
  // DCR runs through the library's router, whose own express.json() would
113
118
  // re-parse (and reject) an already-consumed stream; parsing here first
114
119
  // leaves the sanitized body in place for it.
@@ -33,6 +33,9 @@ function logRequestCompletion(request, response, pathname) {
33
33
  if (process.env.CATENCE_HTTP_ACCESS_LOG === '0')
34
34
  return;
35
35
  const startedAt = Date.now();
36
+ // Captured before the compatibility bridge rewrites the header so the log
37
+ // always shows the value the client actually sent.
38
+ const rawProtocolVersion = request.headers['mcp-protocol-version'];
36
39
  response.once('finish', () => {
37
40
  const status = response.statusCode;
38
41
  const interesting = pathname === '/mcp' ||
@@ -46,7 +49,7 @@ function logRequestCompletion(request, response, pathname) {
46
49
  const userAgent = request.headers['user-agent'];
47
50
  if (typeof userAgent === 'string' && userAgent)
48
51
  parts.push(`ua="${userAgent.slice(0, 120)}"`);
49
- const protocolVersion = request.headers['mcp-protocol-version'];
52
+ const protocolVersion = rawProtocolVersion;
50
53
  if (typeof protocolVersion === 'string' && protocolVersion)
51
54
  parts.push(`mcp-protocol-version=${protocolVersion}`);
52
55
  const mcpMethod = request.headers['mcp-method'];
@@ -526,7 +529,11 @@ async function handleUnauthenticatedDiscovery(request, response, paths) {
526
529
  * The bundled SDK emits the legacy shape, so responses negotiated under a
527
530
  * dated 2026 revision are rewritten on the way out. List results vary with the
528
531
  * caller's scope, so cacheable responses from this server are always
529
- * `private`. Errors and notifications carry no result and pass through.
532
+ * `private`. Every result also repeats the server identity in `_meta`, and
533
+ * tool definitions drop the SDK's `execution` field — neither the per-result
534
+ * `_meta.serverInfo` nor `execution` appeared in the revision's schemas for
535
+ * earlier SDKs, but the revision asks for the former and omits the latter.
536
+ * Errors and notifications carry no result and pass through.
530
537
  */
531
538
  const MODERN_CACHEABLE_METHODS = new Set(['server/discover', 'tools/list', 'prompts/list', 'resources/list', 'resources/templates/list', 'resources/read']);
532
539
  const MODERN_RESULT_TTL_MS = 300_000;
@@ -551,6 +558,25 @@ function frameModernRpcPayload(payload, method) {
551
558
  framed.resultType = 'complete';
552
559
  changed = true;
553
560
  }
561
+ if (!('_meta' in framed)) {
562
+ framed._meta = { 'io.modelcontextprotocol/serverInfo': CATENCE_MCP_SERVER_INFO };
563
+ changed = true;
564
+ }
565
+ if (method === 'tools/list' && Array.isArray(framed.tools)) {
566
+ let stripped = false;
567
+ const tools = framed.tools.map((tool) => {
568
+ if (typeof tool !== 'object' || tool === null || Array.isArray(tool) || !('execution' in tool))
569
+ return tool;
570
+ const copy = { ...tool };
571
+ delete copy.execution;
572
+ stripped = true;
573
+ return copy;
574
+ });
575
+ if (stripped) {
576
+ framed.tools = tools;
577
+ changed = true;
578
+ }
579
+ }
554
580
  if (method !== undefined && MODERN_CACHEABLE_METHODS.has(method)) {
555
581
  if (!('ttlMs' in framed)) {
556
582
  framed.ttlMs = MODERN_RESULT_TTL_MS;
@@ -702,7 +728,11 @@ async function handleMcpRequest(request, response, paths, principal) {
702
728
  // The HTTP gate has already authenticated this request; the MCP server
703
729
  // re-enforces the principal's athlete scope on every tool and roster.
704
730
  const server = createCatenceMcpServer(paths, {}, { scope: { athleteIds: principal.athleteIds } });
705
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
731
+ // JSON responses mirror OpenAI's own reference implementation: a single
732
+ // request/response has no need for a stream, and a JSON body survives
733
+ // validators (and intermediaries) that would mishandle an SSE frame. The
734
+ // spec requires clients to accept both, so this stays compatible.
735
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
706
736
  let cleanedUp = false;
707
737
  const cleanup = async () => {
708
738
  if (cleanedUp)
@@ -810,7 +840,7 @@ export function createCatenceHttpServer(options = {}) {
810
840
  // The OAuth endpoints are the auth surface itself: they bypass the gate
811
841
  // and CORS entirely and are delegated to the Express sub-app. When OAuth
812
842
  // is effectively disabled they answer 404 — no metadata is advertised.
813
- if (pathname.startsWith('/.well-known/oauth-') || pathname.startsWith('/oauth/')) {
843
+ if (pathname.startsWith('/.well-known/oauth-') || pathname.startsWith('/.well-known/openid-configuration') || pathname.startsWith('/oauth/')) {
814
844
  const config = effectiveOauth();
815
845
  if (config.enabled && config.publicUrl !== undefined) {
816
846
  const handle = await getOauthApp(config.publicUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catence",
3
- "version": "0.2.1-beta.24",
3
+ "version": "0.2.1-beta.26",
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": {