@sendinel/mcp-server 1.0.89 → 1.0.91

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.
@@ -1,4 +1,4 @@
1
- export { FALLBACK_MODEL_IDS, HAIKU_4_5_MODEL_ID, MODEL_REGISTRY, ROLE, getAnthropicFastModelId, getAnthropicHaiku45ModelId, getAnthropicFrontierModelId, getAnthropicModelId, getAnthropicReasoningModelId, getModelIdForRole, resolveModel, type ModelMetadata, type ModelRole, type ResolvedModel, } from '@desler/models';
1
+ export { FALLBACK_MODEL_IDS, HAIKU_4_5_MODEL_ID, MODEL_REGISTRY, ROLE, getAnthropicFastModelId, getAnthropicHaiku45ModelId, getAnthropicFrontierModelId, getAnthropicModelId, getAnthropicReasoningModelId, getModelIdForRole, resolveModel, parseModelJson, type ModelMetadata, type ModelRole, type ResolvedModel, } from '@desler/models';
2
2
  import { type ModelRole, type ResolvedModel } from '@desler/models';
3
3
  export declare const DEFAULT_ANTHROPIC_MODEL_ID: string;
4
4
  export declare const DEFAULT_ANTHROPIC_FAST_MODEL_ID: string;
@@ -1,4 +1,4 @@
1
- export { FALLBACK_MODEL_IDS, HAIKU_4_5_MODEL_ID, MODEL_REGISTRY, ROLE, getAnthropicFastModelId, getAnthropicHaiku45ModelId, getAnthropicFrontierModelId, getAnthropicModelId, getAnthropicReasoningModelId, getModelIdForRole, resolveModel, } from '@desler/models';
1
+ export { FALLBACK_MODEL_IDS, HAIKU_4_5_MODEL_ID, MODEL_REGISTRY, ROLE, getAnthropicFastModelId, getAnthropicHaiku45ModelId, getAnthropicFrontierModelId, getAnthropicModelId, getAnthropicReasoningModelId, getModelIdForRole, resolveModel, parseModelJson, } from '@desler/models';
2
2
  import { MODEL_REGISTRY, resolveModel } from '@desler/models';
3
3
  import { adminDb } from './db.js';
4
4
  export const DEFAULT_ANTHROPIC_MODEL_ID = MODEL_REGISTRY.default.id;
package/build/audit.js CHANGED
@@ -36,6 +36,7 @@ export async function logAudit(opts) {
36
36
  resource_id: opts.resourceId ?? null,
37
37
  details: {
38
38
  ...(opts.details ?? {}),
39
+ ...(context?.actorType === 'synchronex_scoped_jwt' ? { kid: context.kid ?? null } : {}),
39
40
  ...(context?.workspaceId ? { workspace_id: context.workspaceId } : {}),
40
41
  // #4004: same actor + agent identity the dashboard records in api_usage. Only fields known for
41
42
  // this request are written; nothing is defaulted.
package/build/auth.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface ApiKeyInfo {
8
8
  authMethod?: 'api_key' | 'scoped_jwt';
9
9
  actsAsUserId?: string | null;
10
10
  workspaceId?: string | null;
11
+ kid?: string | null;
11
12
  }
12
13
  export declare class McpAuthenticationError extends Error {
13
14
  readonly status: 401 | 403 | 503;
package/build/auth.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { decodeProtectedHeader, errors } from 'jose';
1
2
  import { adminDb } from './db.js';
2
3
  import { ALL_TOOL_GROUPS } from './server.js';
3
4
  import { looksLikeScopedJwt, scopedJwtVerificationEnabled, verifyScopedToken, } from './scoped-token.js';
@@ -82,7 +83,15 @@ export async function authenticateBearer(token, overrideProjectId) {
82
83
  try {
83
84
  claims = await verifyScopedToken(token);
84
85
  }
85
- catch {
86
+ catch (error) {
87
+ let kid = null;
88
+ try {
89
+ const header = decodeProtectedHeader(token);
90
+ kid = typeof header.kid === 'string' ? header.kid.slice(0, 64) : null;
91
+ }
92
+ catch { /* Unreadable headers have an unknown key id. */ }
93
+ const code = error instanceof errors.JOSEError ? error.code : null;
94
+ console.warn('[scoped-jwt] verification failed', { kid, code });
86
95
  throw new McpAuthenticationError('Invalid or expired token', 401, 'INVALID_SCOPED_TOKEN');
87
96
  }
88
97
  if (claims.scope === 'data'
@@ -107,6 +116,7 @@ export async function authenticateBearer(token, overrideProjectId) {
107
116
  action: 'mcp.authenticate',
108
117
  resource_type: 'mcp_transport',
109
118
  details: {
119
+ kid: claims.kid ?? null,
110
120
  workspace_id: claims.workspaceId,
111
121
  token_scope: claims.scope,
112
122
  },
@@ -120,6 +130,7 @@ export async function authenticateBearer(token, overrideProjectId) {
120
130
  authMethod: 'scoped_jwt',
121
131
  actsAsUserId: claims.userId,
122
132
  workspaceId: claims.workspaceId,
133
+ kid: claims.kid,
123
134
  };
124
135
  }
125
136
  let info = await validateKey(token);
package/build/db.d.ts CHANGED
@@ -44,6 +44,7 @@ export declare function runWithClient<T>(client: any, fn: () => Promise<T>, requ
44
44
  agentLabel?: string | null;
45
45
  keyId?: string | null;
46
46
  workspaceId?: string | null;
47
+ kid?: string | null;
47
48
  v1Caller?: V1Caller;
48
49
  }): Promise<T>;
49
50
  export declare function runWithToolContext<T>(toolGroup: string, toolName: string, fn: () => Promise<T> | T): Promise<T>;
@@ -65,6 +66,7 @@ export declare function requestContext(): {
65
66
  agentLabel?: string | null;
66
67
  keyId?: string | null;
67
68
  workspaceId?: string | null;
69
+ kid?: string | null;
68
70
  v1Caller?: V1Caller;
69
71
  } | undefined;
70
72
  export declare function projectClientForWriteback(): any | null;
package/build/db.js CHANGED
@@ -75,6 +75,7 @@ export function requestContext() {
75
75
  agentLabel: ctx.agentLabel,
76
76
  keyId: ctx.keyId,
77
77
  workspaceId: ctx.workspaceId,
78
+ kid: ctx.kid,
78
79
  v1Caller: ctx.v1Caller,
79
80
  }
80
81
  : undefined;
package/build/index.js CHANGED
@@ -115,6 +115,7 @@ async function runHttp() {
115
115
  actorId: info.actsAsUserId ?? null,
116
116
  authKind: info.authMethod === 'scoped_jwt' ? 'scoped_jwt' : 'api_key',
117
117
  workspaceId: info.workspaceId ?? null,
118
+ kid: info.kid ?? null,
118
119
  });
119
120
  });
120
121
  app.get('/health', (_req, res) => res.json({ ok: true }));
@@ -1,6 +1,7 @@
1
1
  export declare const SCOPED_TOKEN_SCOPES: readonly ['data', 'service:provision'];
2
2
  export type ScopedTokenScope = (typeof SCOPED_TOKEN_SCOPES)[number];
3
3
  export type ScopedTokenClaims = {
4
+ kid: string | null;
4
5
  userId: string;
5
6
  projectId: string | null;
6
7
  workspaceId: string | null;
@@ -1,4 +1,4 @@
1
- import { createRemoteJWKSet, jwtVerify } from 'jose';
1
+ import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from 'jose';
2
2
  const ISSUER = 'synchronex';
3
3
  const AUDIENCE = 'sendinel';
4
4
  const ALGORITHM = 'RS256';
@@ -50,7 +50,9 @@ export async function verifyScopedToken(token) {
50
50
  if (payload.workspace_id !== null && payload.workspace_id !== undefined && typeof payload.workspace_id !== 'string') {
51
51
  throw new Error('Scoped token workspace claim is invalid');
52
52
  }
53
+ const { kid } = decodeProtectedHeader(token);
53
54
  return {
55
+ kid: typeof kid === 'string' ? kid : null,
54
56
  userId,
55
57
  projectId,
56
58
  workspaceId,
@@ -4,7 +4,7 @@ import Anthropic from '@anthropic-ai/sdk';
4
4
  import { adminDb, db } from '../db.js';
5
5
  import { getProjectId } from '../project.js';
6
6
  import { buildVoicePrompt, deriveTemplateVariables } from '../lib/voice.js';
7
- import { getAnthropicFastModelId } from '../anthropic-model.js';
7
+ import { parseModelJson, getAnthropicFastModelId } from '../anthropic-model.js';
8
8
  import { BLOCK_GENERATION_SCHEMA, blocksToHtml, blocksToText, normalizeComposerBlocks } from '../lib/composer-blocks.js';
9
9
  // Haiku 5.5 (fast role) thinks before answering; thinking counts toward max_tokens (#4193).
10
10
  const FAST_MIN_MAX_TOKENS = 8000;
@@ -34,16 +34,10 @@ function getAnthropicClient() {
34
34
  return new Anthropic({ apiKey });
35
35
  }
36
36
  function extractJsonArray(raw) {
37
- const match = raw.match(/\[[\s\S]*\]/);
38
- if (!match)
39
- throw new Error('No JSON array in response');
40
- return JSON.parse(match[0]);
37
+ return parseModelJson(raw, { kind: 'array' });
41
38
  }
42
39
  function extractJsonObject(raw) {
43
- const match = raw.match(/\{[\s\S]*\}/);
44
- if (!match)
45
- throw new Error('No JSON object in response');
46
- return JSON.parse(match[0]);
40
+ return parseModelJson(raw, { kind: 'object' });
47
41
  }
48
42
  export function registerCompoundTools(server) {
49
43
  server.registerTool('setup_campaign_from_brief', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
4
4
  "description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
5
5
  "mcpName": "ai.sendinel/mcp-server",
6
6
  "type": "module",