qyl-mcp-server 7.0.0 → 7.1.1

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.
Files changed (50) hide show
  1. package/README.md +60 -4
  2. package/dist/auth-extensions.d.ts +20 -0
  3. package/dist/auth-extensions.js +81 -0
  4. package/dist/auth-extensions.js.map +1 -0
  5. package/dist/auth.d.ts +4 -0
  6. package/dist/auth.js +5 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/authorization.d.ts +14 -0
  9. package/dist/authorization.js +82 -0
  10. package/dist/authorization.js.map +1 -0
  11. package/dist/ci.js +2 -1
  12. package/dist/ci.js.map +1 -1
  13. package/dist/cloudflare-access.d.ts +16 -0
  14. package/dist/cloudflare-access.js +75 -0
  15. package/dist/cloudflare-access.js.map +1 -0
  16. package/dist/config.d.ts +2 -2
  17. package/dist/config.js +2 -2
  18. package/dist/config.js.map +1 -1
  19. package/dist/events-authorization.d.ts +9 -0
  20. package/dist/events-authorization.js +160 -0
  21. package/dist/events-authorization.js.map +1 -0
  22. package/dist/events.d.ts +234 -0
  23. package/dist/events.js +596 -0
  24. package/dist/events.js.map +1 -0
  25. package/dist/main.d.ts +15 -28
  26. package/dist/main.js +93 -40
  27. package/dist/main.js.map +1 -1
  28. package/dist/mcp-app.html +1 -1
  29. package/dist/mcp-dashboard.html +1 -1
  30. package/dist/mcp-home.html +4 -4
  31. package/dist/metrics-tools.js +4 -1
  32. package/dist/metrics-tools.js.map +1 -1
  33. package/dist/oauth-metadata.d.ts +8 -0
  34. package/dist/oauth-metadata.js +89 -0
  35. package/dist/oauth-metadata.js.map +1 -0
  36. package/dist/oauth.d.ts +7 -17
  37. package/dist/oauth.js +16 -68
  38. package/dist/oauth.js.map +1 -1
  39. package/dist/secret-redactor.js +2 -1
  40. package/dist/secret-redactor.js.map +1 -1
  41. package/dist/server.d.ts +6 -1
  42. package/dist/server.js +51 -12
  43. package/dist/server.js.map +1 -1
  44. package/dist/tools.d.ts +10 -0
  45. package/dist/tools.js +12 -0
  46. package/dist/tools.js.map +1 -1
  47. package/dist/webhook.d.ts +45 -0
  48. package/dist/webhook.js +198 -0
  49. package/dist/webhook.js.map +1 -0
  50. package/package.json +24 -20
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Recheck the Auth0 account, explicit application grant, user consent and RBAC
3
+ * permission without retaining the subscriber's OAuth tokens. Management
4
+ * credentials come from the host's secret store; their access token lives only
5
+ * in memory. API failures never authorize delivery.
6
+ *
7
+ * Auth0 Management API: users/{id}, users/{id}/permissions, client-grants, grants.
8
+ */
9
+ import { z } from "zod";
10
+ import { QYL_MCP_ISSUER, QYL_MCP_RESOURCE, QYL_MCP_SCOPE } from "./oauth.js";
11
+ const REQUEST_TIMEOUT_MS = 10_000;
12
+ const ACCESS_CACHE_MS = 5_000;
13
+ const MAX_CACHE_ENTRIES = 1_000;
14
+ const PAGE_SIZE = 100;
15
+ const MAX_PAGES = 10;
16
+ const TokenSchema = z.object({
17
+ access_token: z.string().min(1),
18
+ token_type: z.literal("Bearer"),
19
+ expires_in: z.number().positive(),
20
+ });
21
+ const UserSchema = z.object({ blocked: z.boolean().optional() });
22
+ const CimdClientSchema = z.object({ client_id: z.string(), external_client_id: z.string().optional() });
23
+ const PermissionSchema = z.object({
24
+ resource_server_identifier: z.string(), permission_name: z.string(),
25
+ });
26
+ const ClientGrantSchema = z.object({
27
+ client_id: z.string().optional(), audience: z.string(),
28
+ subject_type: z.string().optional(), scope: z.array(z.string()),
29
+ });
30
+ const UserGrantSchema = z.object({
31
+ clientID: z.string(), user_id: z.string(), audience: z.string(),
32
+ scope: z.array(z.string()), organization_id: z.string().optional(),
33
+ });
34
+ /** Thrown without response bodies, credentials or subscriber identifiers. */
35
+ class EventsAuthorizationUnavailable extends Error {
36
+ name = "EventsAuthorizationUnavailable";
37
+ constructor() { super("Event authorization is temporarily unavailable"); }
38
+ }
39
+ export function createEventsAuthorization(options) {
40
+ const request = options.fetch ?? fetch;
41
+ const now = options.now ?? Date.now;
42
+ let token;
43
+ let refreshing;
44
+ const pending = new Map();
45
+ const cache = new Map();
46
+ async function managementToken() {
47
+ if (token !== undefined && token.expiresAt > now())
48
+ return token.value;
49
+ refreshing ??= (async () => {
50
+ const response = await request(new URL("oauth/token", QYL_MCP_ISSUER), {
51
+ method: "POST", redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
52
+ headers: { "content-type": "application/json" },
53
+ body: JSON.stringify({
54
+ grant_type: "client_credentials", client_id: options.clientId,
55
+ client_secret: options.clientSecret, audience: `${QYL_MCP_ISSUER}api/v2/`,
56
+ }),
57
+ });
58
+ if (!response.ok)
59
+ throw new EventsAuthorizationUnavailable();
60
+ const result = TokenSchema.parse(await response.json());
61
+ token = { value: result.access_token, expiresAt: now() + Math.max(0, result.expires_in - 60) * 1_000 };
62
+ return token.value;
63
+ })().finally(() => { refreshing = undefined; });
64
+ return refreshing;
65
+ }
66
+ async function get(path, query = {}) {
67
+ const url = new URL(`api/v2/${path}`, QYL_MCP_ISSUER);
68
+ url.search = new URLSearchParams(query).toString();
69
+ const response = await request(url, {
70
+ redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
71
+ headers: { authorization: `Bearer ${await managementToken()}` },
72
+ });
73
+ if (response.status === 404)
74
+ return undefined;
75
+ if (!response.ok) {
76
+ if (response.status === 401)
77
+ token = undefined;
78
+ throw new EventsAuthorizationUnavailable();
79
+ }
80
+ return response.json();
81
+ }
82
+ async function any(path, query, schema, predicate) {
83
+ for (let page = 0; page < MAX_PAGES; page++) {
84
+ const body = await get(path, { ...query, per_page: String(PAGE_SIZE), page: String(page) });
85
+ if (body === undefined)
86
+ return false;
87
+ const entries = z.array(schema).parse(body);
88
+ if (entries.some(predicate))
89
+ return true;
90
+ if (entries.length < PAGE_SIZE)
91
+ return false;
92
+ }
93
+ // An incomplete permission listing is not evidence of revocation.
94
+ throw new EventsAuthorizationUnavailable();
95
+ }
96
+ async function applicationClientId(clientId) {
97
+ if (!clientId.startsWith("https://"))
98
+ return clientId;
99
+ // Auth0 puts the CIMD URL in the token, but its Management API grants use
100
+ // the internal application ID. Resolve through the trusted issuer only.
101
+ const body = await get("clients", {
102
+ external_client_id: clientId, fields: "client_id,external_client_id",
103
+ include_fields: "true", per_page: "2",
104
+ });
105
+ if (body === undefined)
106
+ return undefined;
107
+ const clients = z.array(CimdClientSchema).parse(body);
108
+ // Auth0's field projection can omit external_client_id. The exact query
109
+ // still filters by it; reject multiple matches or a conflicting value.
110
+ return clients.length === 1
111
+ && (clients[0].external_client_id === undefined || clients[0].external_client_id === clientId)
112
+ ? clients[0].client_id : undefined;
113
+ }
114
+ async function check(principal) {
115
+ const userPath = `users/${encodeURIComponent(principal.subject)}`;
116
+ const body = await get(userPath, { fields: "blocked", include_fields: "true" });
117
+ if (body === undefined || UserSchema.parse(body).blocked === true)
118
+ return false;
119
+ const clientId = await applicationClientId(principal.clientId);
120
+ if (clientId === undefined)
121
+ return false;
122
+ const [permission, application, consent] = await Promise.all([
123
+ any(`${userPath}/permissions`, {}, PermissionSchema, (entry) => entry.resource_server_identifier === QYL_MCP_RESOURCE && entry.permission_name === QYL_MCP_SCOPE),
124
+ any("client-grants", { client_id: clientId, audience: QYL_MCP_RESOURCE, subject_type: "user" }, ClientGrantSchema, (entry) => entry.client_id === clientId && entry.audience === QYL_MCP_RESOURCE
125
+ && entry.subject_type === "user" && entry.scope.includes(QYL_MCP_SCOPE)),
126
+ any("grants", { user_id: principal.subject, client_id: clientId, audience: QYL_MCP_RESOURCE }, UserGrantSchema, (entry) => entry.user_id === principal.subject && entry.clientID === clientId
127
+ && entry.audience === QYL_MCP_RESOURCE && entry.organization_id === undefined
128
+ && entry.scope.includes(QYL_MCP_SCOPE)),
129
+ ]);
130
+ return permission && application && consent;
131
+ }
132
+ return (principal) => {
133
+ const key = JSON.stringify([principal.subject, principal.clientId]);
134
+ const cached = cache.get(key);
135
+ if (cached !== undefined && cached.expiresAt > now())
136
+ return Promise.resolve(cached.allowed);
137
+ let checking = pending.get(key);
138
+ if (checking === undefined) {
139
+ checking = check(principal).then((allowed) => {
140
+ if (cache.size >= MAX_CACHE_ENTRIES)
141
+ cache.clear();
142
+ cache.set(key, { allowed, expiresAt: now() + ACCESS_CACHE_MS });
143
+ return allowed;
144
+ }).catch(() => {
145
+ throw new EventsAuthorizationUnavailable();
146
+ }).finally(() => { pending.delete(key); });
147
+ pending.set(key, checking);
148
+ }
149
+ return checking;
150
+ };
151
+ }
152
+ export function hostedEventsAuthorization(environment) {
153
+ const clientId = environment["MCP_EVENTS_AUTH0_CLIENT_ID"]?.trim();
154
+ const clientSecret = environment["MCP_EVENTS_AUTH0_CLIENT_SECRET"]?.trim();
155
+ if (!clientId || !clientSecret) {
156
+ throw new Error("MCP_EVENTS_STORE requires MCP_EVENTS_AUTH0_CLIENT_ID and MCP_EVENTS_AUTH0_CLIENT_SECRET for ongoing access checks");
157
+ }
158
+ return createEventsAuthorization({ clientId, clientSecret });
159
+ }
160
+ //# sourceMappingURL=events-authorization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-authorization.js","sourceRoot":"","sources":["../src/events-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAChC,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AACH,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACjE,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACxG,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,0BAA0B,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;CACpE,CAAC,CAAC;AACH,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACtD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAChE,CAAC,CAAC;AACH,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IAC/D,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACnE,CAAC,CAAC;AASH,6EAA6E;AAC7E,MAAM,8BAA+B,SAAQ,KAAK;IACvC,IAAI,GAAG,gCAAgC,CAAC;IACjD,gBAAgB,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC,CAAC;CAC3E;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAmC;IAC3E,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,IAAI,KAAuD,CAAC;IAC5D,IAAI,UAAuC,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmD,CAAC;IAEzE,KAAK,UAAU,eAAe;QAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC;QACvE,UAAU,KAAK,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,EAAE;gBACrE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;gBAClF,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,UAAU,EAAE,oBAAoB,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ;oBAC7D,aAAa,EAAE,OAAO,CAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,cAAc,SAAS;iBAC1E,CAAC;aACH,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,8BAA8B,EAAE,CAAC;YAC7D,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACxD,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;YACvG,OAAO,KAAK,CAAC,KAAK,CAAC;QACrB,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,KAAK,UAAU,GAAG,CAAC,IAAY,EAAE,KAAK,GAA2B,EAAE;QACjE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,cAAc,CAAC,CAAC;QACtD,GAAG,CAAC,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;YAClC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;YAClE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,eAAe,EAAE,EAAE,EAAE;SAChE,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;gBAAE,KAAK,GAAG,SAAS,CAAC;YAC/C,MAAM,IAAI,8BAA8B,EAAE,CAAC;QAC7C,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,UAAU,GAAG,CAAI,IAAY,EAAE,KAA6B,EAAE,MAAoB,EAAE,SAAgC;QACvH,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5F,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YACrC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;gBAAE,OAAO,IAAI,CAAC;YACzC,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS;gBAAE,OAAO,KAAK,CAAC;QAC/C,CAAC;QACD,kEAAkE;QAClE,MAAM,IAAI,8BAA8B,EAAE,CAAC;IAC7C,CAAC;IAED,KAAK,UAAU,mBAAmB,CAAC,QAAgB;QACjD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,QAAQ,CAAC;QACtD,0EAA0E;QAC1E,wEAAwE;QACxE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE;YAChC,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,8BAA8B;YACpE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG;SACtC,CAAC,CAAC;QACH,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACtD,wEAAwE;QACxE,uEAAuE;QACvE,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC;eACtB,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,kBAAkB,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC,kBAAkB,KAAK,QAAQ,CAAC;YAChG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACxC,CAAC;IAED,KAAK,UAAU,KAAK,CAAC,SAAoB;QACvC,MAAM,QAAQ,GAAG,SAAS,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAChF,IAAI,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC/D,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC3D,GAAG,CAAC,GAAG,QAAQ,cAAc,EAAE,EAAE,EAAE,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,CAC7D,KAAK,CAAC,0BAA0B,KAAK,gBAAgB,IAAI,KAAK,CAAC,eAAe,KAAK,aAAa,CAAC;YACnG,GAAG,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAC3H,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,gBAAgB;mBAChE,KAAK,CAAC,YAAY,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;YAC1E,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CACxH,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ;mBAC/D,KAAK,CAAC,QAAQ,KAAK,gBAAgB,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;mBAC1E,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;SAC1C,CAAC,CAAC;QACH,OAAO,UAAU,IAAI,WAAW,IAAI,OAAO,CAAC;IAC9C,CAAC;IAED,OAAO,CAAC,SAAS,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7F,IAAI,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC3C,IAAI,KAAK,CAAC,IAAI,IAAI,iBAAiB;oBAAE,KAAK,CAAC,KAAK,EAAE,CAAC;gBACnD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,eAAe,EAAE,CAAC,CAAC;gBAChE,OAAO,OAAO,CAAC;YACjB,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBACZ,MAAM,IAAI,8BAA8B,EAAE,CAAC;YAC7C,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,WAAyD;IACjG,MAAM,QAAQ,GAAG,WAAW,CAAC,4BAA4B,CAAC,EAAE,IAAI,EAAE,CAAC;IACnE,MAAM,YAAY,GAAG,WAAW,CAAC,gCAAgC,CAAC,EAAE,IAAI,EAAE,CAAC;IAC3E,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,mHAAmH,CAAC,CAAC;IACvI,CAAC;IACD,OAAO,yBAAyB,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * MCP Events for ChatGPT: `events/list`, `events/subscribe` and
3
+ * `events/unsubscribe` on the authenticated MCP endpoint, with signed webhook
4
+ * delivery of one event type, `trace.error`.
5
+ *
6
+ * Contract: developers.openai.com/plugins/build/mcp-events (ChatGPT supports
7
+ * webhook delivery with callback verification only) and the draft MCP Events
8
+ * design sketch in modelcontextprotocol/experimental-ext-triggers-events.
9
+ * `@modelcontextprotocol/server` 2.3.1 ships no `events/*` methods, so the
10
+ * three are custom request handlers and the capability is added by hand.
11
+ *
12
+ * Delivery source: the collector's recent-trace list, polled only while a
13
+ * subscription is live. The first poll sets a baseline, so a subscription
14
+ * never receives a backlog. Events carry `cursor: null` — there is no replay;
15
+ * traces that arrive while the server is down are not delivered.
16
+ *
17
+ * Access: subscribing, refreshing and unsubscribing each pass the same OAuth
18
+ * gate as every tool call. The required authorization callback rechecks the
19
+ * owner's current access while polling and before delivery, including retries.
20
+ * Authorization outages suspend delivery; revoked access removes the record.
21
+ */
22
+ import type { McpServer, ServerContext } from "@modelcontextprotocol/server";
23
+ import { z } from "zod";
24
+ import { AtomicJsonStore } from "./atomic-json-store.js";
25
+ import type { QylTrace } from "./wire.js";
26
+ import { type WebhookPost } from "./webhook.js";
27
+ export declare const TRACE_ERROR_EVENT_NAME = "trace.error";
28
+ /** JSON-RPC error for a callback that failed validation or verification. */
29
+ export declare const CALLBACK_ENDPOINT_ERROR = -32015;
30
+ export declare const DEFAULT_TTL_MS: number;
31
+ export declare const MIN_TTL_MS: number;
32
+ export declare const MAX_TTL_MS: number;
33
+ /**
34
+ * Live subscriptions one caller may hold. Every subscription is a stored
35
+ * entry, a delivery fan-out target and, for a new callback, an outbound
36
+ * verification POST; without a cap one account grows all three at will.
37
+ */
38
+ export declare const MAX_SUBSCRIPTIONS_PER_PRINCIPAL = 20;
39
+ /** The one event definition `events/list` returns. */
40
+ export declare const TRACE_ERROR_EVENT: {
41
+ readonly name: "trace.error";
42
+ readonly description: string;
43
+ readonly delivery: readonly ["webhook"];
44
+ readonly inputSchema: {
45
+ readonly type: "object";
46
+ readonly properties: {
47
+ readonly service_name: {
48
+ readonly type: "string";
49
+ readonly minLength: 1;
50
+ readonly maxLength: 128;
51
+ readonly description: "Only traces that involve this service.";
52
+ };
53
+ };
54
+ readonly additionalProperties: false;
55
+ };
56
+ readonly payloadSchema: {
57
+ readonly type: "object";
58
+ readonly properties: {
59
+ readonly trace_id: {
60
+ readonly type: "string";
61
+ readonly description: "Trace identifier; pass it to get_trace or display_traces.";
62
+ };
63
+ readonly root_span: {
64
+ readonly type: "string";
65
+ readonly description: "Name of the trace's root span.";
66
+ };
67
+ readonly services: {
68
+ readonly type: "array";
69
+ readonly items: {
70
+ readonly type: "string";
71
+ };
72
+ readonly description: "Services involved in the trace.";
73
+ };
74
+ readonly span_count: {
75
+ readonly type: "integer";
76
+ readonly description: "Total span count.";
77
+ };
78
+ readonly duration_ms: {
79
+ readonly type: "number";
80
+ readonly description: "Trace duration in milliseconds.";
81
+ };
82
+ readonly start_time: {
83
+ readonly type: "string";
84
+ readonly format: "date-time";
85
+ readonly description: "Trace start time.";
86
+ };
87
+ };
88
+ readonly required: readonly ["trace_id", "root_span", "services", "span_count", "duration_ms", "start_time"];
89
+ readonly additionalProperties: false;
90
+ };
91
+ };
92
+ declare const SubscribeParamsSchema: z.ZodObject<{
93
+ name: z.ZodString;
94
+ arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
95
+ delivery: z.ZodObject<{
96
+ mode: z.ZodString;
97
+ url: z.ZodString;
98
+ secret: z.ZodOptional<z.ZodString>;
99
+ }, z.core.$loose>;
100
+ cursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
101
+ ttlMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
102
+ }, z.core.$loose>;
103
+ export type SubscribeParams = z.infer<typeof SubscribeParamsSchema>;
104
+ declare const UnsubscribeParamsSchema: z.ZodObject<{
105
+ name: z.ZodString;
106
+ arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
107
+ delivery: z.ZodObject<{
108
+ mode: z.ZodOptional<z.ZodString>;
109
+ url: z.ZodString;
110
+ }, z.core.$loose>;
111
+ }, z.core.$loose>;
112
+ export type UnsubscribeParams = z.infer<typeof UnsubscribeParamsSchema>;
113
+ /** The authenticated caller a subscription belongs to. */
114
+ export interface Principal {
115
+ subject: string;
116
+ clientId: string;
117
+ }
118
+ declare const StoredSubscriptionSchema: z.ZodObject<{
119
+ id: z.ZodString;
120
+ subject: z.ZodString;
121
+ clientId: z.ZodString;
122
+ name: z.ZodLiteral<"trace.error">;
123
+ arguments: z.ZodObject<{
124
+ service_name: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strict>;
126
+ url: z.ZodString;
127
+ secret: z.ZodString;
128
+ previousSecret: z.ZodOptional<z.ZodString>;
129
+ previousSecretUntil: z.ZodOptional<z.ZodString>;
130
+ refreshBefore: z.ZodString;
131
+ updatedAt: z.ZodString;
132
+ }, z.core.$strip>;
133
+ export type StoredSubscription = z.infer<typeof StoredSubscriptionSchema>;
134
+ declare const EventStoreStateSchema: z.ZodObject<{
135
+ version: z.ZodLiteral<1>;
136
+ subscriptions: z.ZodArray<z.ZodObject<{
137
+ id: z.ZodString;
138
+ subject: z.ZodString;
139
+ clientId: z.ZodString;
140
+ name: z.ZodLiteral<"trace.error">;
141
+ arguments: z.ZodObject<{
142
+ service_name: z.ZodOptional<z.ZodString>;
143
+ }, z.core.$strict>;
144
+ url: z.ZodString;
145
+ secret: z.ZodString;
146
+ previousSecret: z.ZodOptional<z.ZodString>;
147
+ previousSecretUntil: z.ZodOptional<z.ZodString>;
148
+ refreshBefore: z.ZodString;
149
+ updatedAt: z.ZodString;
150
+ }, z.core.$strip>>;
151
+ }, z.core.$strip>;
152
+ export type EventStoreState = z.infer<typeof EventStoreStateSchema>;
153
+ /** Subscription storage that survives restarts: an atomic JSON file (0600). */
154
+ export declare function createEventStore(filePath: string): AtomicJsonStore<EventStoreState>;
155
+ /** Key-order-independent JSON, so `{a,b}` and `{b,a}` name one subscription. */
156
+ export declare function canonicalJson(value: unknown): string;
157
+ /** Deterministic ID over principal, callback URL, event name and arguments. */
158
+ export declare function subscriptionId(principal: Principal, url: string, name: string, args: unknown): string;
159
+ /** The lifetime granted for a requested `ttlMs`: bounded, and never unbounded. */
160
+ export declare function grantedTtlMs(ttlMs: number | null | undefined): number;
161
+ /** The principal of a verified request, or a JSON-RPC error without one. */
162
+ export declare function principalOf(ctx: Pick<ServerContext, "http">): Principal;
163
+ /** The `data` of a `trace.error` delivery; matches `payloadSchema`. */
164
+ export declare function traceErrorPayload(trace: QylTrace): Record<string, unknown>;
165
+ export interface EventsRuntimeOptions {
166
+ store: AtomicJsonStore<EventStoreState>;
167
+ /** Live access check. False revokes; errors suspend delivery without deleting state. */
168
+ isAuthorized: (principal: Principal) => Promise<boolean>;
169
+ /** Network seam; defaults to the public-HTTPS-only `postWebhook`. */
170
+ post?: WebhookPost;
171
+ /** Trace source; defaults to the collector's most recent traces. */
172
+ recentTraces?: () => Promise<readonly QylTrace[]>;
173
+ pollIntervalMs?: number;
174
+ now?: () => number;
175
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
176
+ log?: (message: string) => void;
177
+ }
178
+ /**
179
+ * One per process: owns the subscription store, the verification cache and the
180
+ * poller. `register` wires the three methods into each per-request McpServer.
181
+ */
182
+ export declare class EventsRuntime {
183
+ private readonly store;
184
+ private readonly isAuthorized;
185
+ private readonly post;
186
+ private readonly recentTraces;
187
+ private readonly pollIntervalMs;
188
+ private readonly now;
189
+ private readonly sleep;
190
+ private readonly log;
191
+ private readonly verified;
192
+ private seen;
193
+ private ready;
194
+ private timer;
195
+ private polling;
196
+ private stopped;
197
+ private readonly deliveries;
198
+ constructor(options: EventsRuntimeOptions);
199
+ /** Advertise `events` and answer the three methods on this server instance. */
200
+ register(server: McpServer): void;
201
+ list(): {
202
+ events: (typeof TRACE_ERROR_EVENT)[];
203
+ };
204
+ subscribe(params: SubscribeParams, principal: Principal): Promise<{
205
+ id: string;
206
+ refreshBefore: string;
207
+ cursor: null;
208
+ truncated: false;
209
+ }>;
210
+ unsubscribe(params: UnsubscribeParams, principal: Principal): Promise<Record<string, never>>;
211
+ /** A refresh always passes; a new subscription needs a free slot for its caller. */
212
+ private assertCapacity;
213
+ /**
214
+ * A signed, single-use challenge before any application data; a success is
215
+ * cached per principal and callback URL for a bounded period.
216
+ */
217
+ private verifyCallback;
218
+ /** Loads the store once; every store access waits for it. */
219
+ private initialized;
220
+ /** Start the poller if a live subscription exists; `stop` ends it. */
221
+ start(): Promise<void>;
222
+ stop(): Promise<void>;
223
+ private ensurePolling;
224
+ private isLive;
225
+ private pruneState;
226
+ private authorized;
227
+ /** One poll: drop lapsed subscriptions, then deliver each new error trace. */
228
+ poll(): Promise<void>;
229
+ private pollOnce;
230
+ private deliver;
231
+ /** POST one signed event, retrying transient failures with backoff. */
232
+ private deliverWhileActive;
233
+ }
234
+ export {};