@desplega.ai/agent-swarm 1.129.0 → 1.131.0
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 +2 -2
- package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
- package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
- package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
- package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
- package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
- package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
- package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
- package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
- package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
- package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
- package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
- package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
- package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
- package/dist/cli-3fj60etf.js +153 -0
- package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
- package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
- package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
- package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
- package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
- package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
- package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
- package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
- package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
- package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
- package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
- package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
- package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
- package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
- package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
- package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
- package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
- package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
- package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
- package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
- package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
- package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
- package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
- package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
- package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
- package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
- package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
- package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
- package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
- package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
- package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
- package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
- package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
- package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
- package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
- package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
- package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
- package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
- package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
- package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
- package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
- package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
- package/dist/cli-zsspvvm2.js +490 -0
- package/dist/cli.js +27 -25
- package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
- package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
- package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
- package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
- package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
- package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
- package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
- package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
- package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
- package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
- package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
- package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
- package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
- package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
- package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
- package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
- package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
- package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
- package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
- package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
- package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
- package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
- package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
- package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
- package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
- package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
- package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
- package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
- package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
- package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
- package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
- package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
- package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
- package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
- package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
- package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
- package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
- package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
- package/openapi.json +34039 -11036
- package/package.json +8 -6
- package/src/apps/definition.ts +368 -26
- package/src/apps/row-store.ts +66 -10
- package/src/apps/schema-migrate.ts +95 -1
- package/src/apps/store.ts +13 -4
- package/src/apps/sync-run-as.ts +18 -0
- package/src/apps/sync.ts +934 -0
- package/src/apps/version.ts +16 -2
- package/src/assets/key.ts +8 -1
- package/src/be/asset-key-audit.ts +3 -1
- package/src/be/db-queries/mcp-oauth.ts +297 -24
- package/src/be/db.ts +42 -1
- package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
- package/src/be/scripts/db.ts +5 -2
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
- package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
- package/src/be/seed-scripts/index.ts +18 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/http/active-sessions.ts +26 -18
- package/src/http/agents.ts +94 -27
- package/src/http/api-keys.ts +91 -17
- package/src/http/approval-requests.ts +87 -10
- package/src/http/apps.ts +361 -53
- package/src/http/assets.ts +165 -31
- package/src/http/budgets.ts +7 -5
- package/src/http/codex-oauth-keep-warm.ts +21 -3
- package/src/http/config.ts +46 -16
- package/src/http/context.ts +33 -6
- package/src/http/ecosystem.ts +20 -6
- package/src/http/events.ts +24 -9
- package/src/http/favorites.ts +20 -6
- package/src/http/fs.ts +60 -17
- package/src/http/heartbeat.ts +18 -4
- package/src/http/inbox-state.ts +12 -6
- package/src/http/integrations.ts +25 -5
- package/src/http/kv.ts +36 -15
- package/src/http/mcp-bridge.ts +5 -1
- package/src/http/mcp-oauth.ts +332 -17
- package/src/http/mcp-servers.ts +56 -18
- package/src/http/memory.ts +282 -29
- package/src/http/metrics.ts +80 -17
- package/src/http/oauth-callback.ts +10 -4
- package/src/http/oauth-generic.ts +5 -1
- package/src/http/oauth-locks.ts +6 -3
- package/src/http/openapi.ts +57 -9
- package/src/http/page-proxy.ts +25 -5
- package/src/http/pages-public.ts +37 -20
- package/src/http/pages.ts +93 -28
- package/src/http/poll.ts +89 -5
- package/src/http/pricing.ts +9 -6
- package/src/http/prompt-templates.ts +85 -21
- package/src/http/route-def.ts +67 -4
- package/src/http/schedules.ts +63 -14
- package/src/http/script-connection-proxy.ts +30 -4
- package/src/http/script-connections.ts +393 -43
- package/src/http/script-runs.ts +73 -19
- package/src/http/scripts.ts +257 -39
- package/src/http/session-data.ts +87 -14
- package/src/http/sessions.ts +80 -5
- package/src/http/skills.ts +115 -35
- package/src/http/stats.ts +94 -13
- package/src/http/task-templates.ts +6 -4
- package/src/http/tasks.ts +230 -61
- package/src/http/trackers/jira.ts +90 -30
- package/src/http/trackers/linear.ts +46 -12
- package/src/http/users.ts +162 -25
- package/src/http/utils.ts +12 -2
- package/src/http/webhooks.ts +30 -16
- package/src/http/workflow-events.ts +18 -5
- package/src/http/workflows.ts +128 -39
- package/src/http/x.ts +37 -6
- package/src/oauth/ensure-mcp-token.ts +16 -1
- package/src/oauth/mcp-wrapper.ts +281 -26
- package/src/providers/pi-mono-adapter.ts +1 -1
- package/src/scripts-runtime/credential-broker/README.md +1 -1
- package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/stdlib/fetch.ts +6 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tests/apps-rbac.test.ts +110 -0
- package/src/tests/apps-spike2.test.ts +4 -1
- package/src/tests/apps-sync-engine.test.ts +2540 -0
- package/src/tests/apps-sync.test.ts +1627 -0
- package/src/tests/asset-key-api.test.ts +48 -2
- package/src/tests/asset-key-invariants.test.ts +92 -0
- package/src/tests/asset-key-migration.test.ts +174 -8
- package/src/tests/asset-key-normalization.test.ts +2 -0
- package/src/tests/credential-broker.test.ts +27 -2
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
- package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
- package/src/tests/mcp-oauth-queries.test.ts +153 -0
- package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
- package/src/tests/memory-http-recall-gating.test.ts +2 -0
- package/src/tests/openapi-response-contract.test.ts +138 -0
- package/src/tests/rbac-admission-e2e.test.ts +68 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/scripts-runtime-fetch.test.ts +121 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/swarm-tool-result-gate.test.ts +32 -0
- package/src/tests/task-working-dir.test.ts +8 -4
- package/src/tests/whoami-route.test.ts +127 -0
- package/src/tests/workflow-engine-v2.test.ts +80 -7
- package/src/tests/workflow-swarm-script.test.ts +104 -0
- package/src/tools/app-get.ts +10 -2
- package/src/tools/app-rollback.ts +1 -0
- package/src/tools/app-sync.ts +112 -0
- package/src/tools/script-common.ts +20 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +11 -0
- package/src/tools/workflows/create-workflow.ts +4 -1
- package/src/types.ts +1394 -1206
- package/src/utils/template.ts +8 -1
- package/src/utils/zod-openapi.ts +11 -0
- package/src/workflows/engine.ts +27 -4
- package/src/workflows/executors/swarm-script.ts +28 -0
- package/src/workflows/retry-poller.ts +19 -2
- package/templates/skills/apps/content.md +94 -5
- package/templates/skills/asset-namespaces/content.md +2 -2
- package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
- package/templates/skills/scheduled-task-resilience/config.json +14 -0
- package/templates/skills/scheduled-task-resilience/content.md +97 -0
- package/dist/cli-7wmybrt5.js +0 -115
- package/dist/cli-d91pvmq1.js +0 -14
package/src/apps/version.ts
CHANGED
|
@@ -102,7 +102,11 @@ export function decodeAppVersion(appVersion: AppVersion): AppVersion {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function rollbackSnapshot(
|
|
105
|
+
function rollbackSnapshot(
|
|
106
|
+
version: AppVersion,
|
|
107
|
+
writer: { writerAgentId?: string | null; writerIsUser?: boolean },
|
|
108
|
+
existingDefinition: unknown,
|
|
109
|
+
): {
|
|
106
110
|
name: string;
|
|
107
111
|
description: string | null;
|
|
108
112
|
definition: AppDefinition;
|
|
@@ -132,9 +136,16 @@ function rollbackSnapshot(version: AppVersion): {
|
|
|
132
136
|
]);
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
// A rollback is an ordinary definition write by whoever invoked it, not a
|
|
140
|
+
// trusted restore: the historical snapshot may reintroduce foreign-owned or
|
|
141
|
+
// lead-run script references the writer could never add directly, so the
|
|
142
|
+
// same ownership/grandfathering checks apply against the CURRENT definition.
|
|
135
143
|
const parsed = parseAppDefinition(upgradeAppDefinition(snapshot.definition), {
|
|
136
144
|
currentAppId: version.appId,
|
|
137
145
|
resolveApp: getApp,
|
|
146
|
+
writerAgentId: writer.writerAgentId ?? null,
|
|
147
|
+
...(writer.writerIsUser === undefined ? {} : { writerIsUser: writer.writerIsUser }),
|
|
148
|
+
existingDefinition,
|
|
138
149
|
});
|
|
139
150
|
if (!parsed.success) throw new AppRollbackDefinitionError(version.version, parsed.issues);
|
|
140
151
|
return {
|
|
@@ -155,6 +166,9 @@ export async function rollbackApp(input: {
|
|
|
155
166
|
migration?: AppMigration;
|
|
156
167
|
forceElementBreak?: string[];
|
|
157
168
|
changedByAgentId?: string;
|
|
169
|
+
/** Writer principal for the restored definition's ownership checks. */
|
|
170
|
+
writerAgentId?: string | null;
|
|
171
|
+
writerIsUser?: boolean;
|
|
158
172
|
}): Promise<{ app: AppRecord; migration: AppMigrationReport }> {
|
|
159
173
|
return withAppDefinitionLock(input.appId, async () => {
|
|
160
174
|
const existing = getApp(input.appId);
|
|
@@ -164,7 +178,7 @@ export async function rollbackApp(input: {
|
|
|
164
178
|
(candidate) => candidate.version === input.version,
|
|
165
179
|
);
|
|
166
180
|
if (!version) throw new AppRollbackVersionNotFoundError(input.version);
|
|
167
|
-
const snapshot = rollbackSnapshot(version);
|
|
181
|
+
const snapshot = rollbackSnapshot(version, input, existing.definition);
|
|
168
182
|
|
|
169
183
|
const migrated = await migrateAppSchema({
|
|
170
184
|
appId: input.appId,
|
package/src/assets/key.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export const DEFAULT_ASSET_KEY = "shared/" as const;
|
|
2
2
|
export const ASSET_KEY_MAX_LENGTH = 255;
|
|
3
3
|
|
|
4
|
-
export type AssetKeyResource =
|
|
4
|
+
export type AssetKeyResource =
|
|
5
|
+
| "task"
|
|
6
|
+
| "workflow"
|
|
7
|
+
| "schedule"
|
|
8
|
+
| "page"
|
|
9
|
+
| "app"
|
|
10
|
+
| "script"
|
|
11
|
+
| `fs:${string}`;
|
|
5
12
|
|
|
6
13
|
export type SharedAssetNamespace = {
|
|
7
14
|
root: "shared";
|
|
@@ -12,7 +12,7 @@ export type AssetKeyAuditCode =
|
|
|
12
12
|
export type AssetKeyAuditIssue = {
|
|
13
13
|
severity: AssetKeyAuditSeverity;
|
|
14
14
|
code: AssetKeyAuditCode;
|
|
15
|
-
entityType: "task" | "workflow" | "schedule" | "page" | "file";
|
|
15
|
+
entityType: "task" | "workflow" | "schedule" | "page" | "app" | "script" | "file";
|
|
16
16
|
entityId: string;
|
|
17
17
|
message: string;
|
|
18
18
|
};
|
|
@@ -33,6 +33,8 @@ const KEY_TABLES = [
|
|
|
33
33
|
{ entityType: "workflow", sql: 'SELECT id, "key" as key FROM workflows' },
|
|
34
34
|
{ entityType: "schedule", sql: 'SELECT id, "key" as key FROM scheduled_tasks' },
|
|
35
35
|
{ entityType: "page", sql: 'SELECT id, "key" as key FROM pages' },
|
|
36
|
+
{ entityType: "app", sql: 'SELECT id, "key" as key FROM apps' },
|
|
37
|
+
{ entityType: "script", sql: 'SELECT id, "key" as key FROM scripts' },
|
|
36
38
|
{ entityType: "file", sql: 'SELECT id, "key" as key FROM asset_key_mappings' },
|
|
37
39
|
] as const;
|
|
38
40
|
|
|
@@ -57,6 +57,7 @@ export interface McpOAuthToken {
|
|
|
57
57
|
revocationUrl: string | null;
|
|
58
58
|
dcrClientId: string | null;
|
|
59
59
|
dcrClientSecret: string | null;
|
|
60
|
+
tokenEndpointAuthMethod: string | null;
|
|
60
61
|
clientSource: McpOAuthClientSource;
|
|
61
62
|
status: McpOAuthStatus;
|
|
62
63
|
lastErrorMessage: string | null;
|
|
@@ -74,12 +75,15 @@ export interface McpOAuthPendingRow {
|
|
|
74
75
|
nonce: string | null;
|
|
75
76
|
resourceUrl: string;
|
|
76
77
|
authorizationServerIssuer: string;
|
|
78
|
+
registrationEndpoint: string | null;
|
|
77
79
|
authorizeUrl: string;
|
|
78
80
|
tokenUrl: string;
|
|
79
81
|
revocationUrl: string | null;
|
|
80
82
|
scopes: string | null;
|
|
83
|
+
registeredScopes: string[] | null;
|
|
81
84
|
dcrClientId: string | null;
|
|
82
85
|
dcrClientSecret: string | null;
|
|
86
|
+
tokenEndpointAuthMethod: string | null;
|
|
83
87
|
redirectUri: string;
|
|
84
88
|
finalRedirect: string | null;
|
|
85
89
|
createdAt: string;
|
|
@@ -172,6 +176,10 @@ function decryptTokenRow(row: UnifiedMcpTokenRow): McpOAuthToken {
|
|
|
172
176
|
: clientKey
|
|
173
177
|
? decryptSecret(row.clientSecret, clientKey)
|
|
174
178
|
: row.clientSecret,
|
|
179
|
+
tokenEndpointAuthMethod:
|
|
180
|
+
typeof metadata.tokenEndpointAuthMethod === "string"
|
|
181
|
+
? metadata.tokenEndpointAuthMethod
|
|
182
|
+
: null,
|
|
175
183
|
clientSource,
|
|
176
184
|
status: statusFromUnified(row.status),
|
|
177
185
|
lastErrorMessage: row.lastErrorMessage,
|
|
@@ -199,18 +207,28 @@ function upsertMcpApp(input: {
|
|
|
199
207
|
mcpServerId: string;
|
|
200
208
|
resourceUrl: string;
|
|
201
209
|
authorizationServerIssuer: string;
|
|
210
|
+
registrationEndpoint?: string | null;
|
|
202
211
|
authorizeUrl: string;
|
|
203
212
|
tokenUrl: string;
|
|
204
213
|
revocationUrl?: string | null;
|
|
205
214
|
scopes?: string | null;
|
|
215
|
+
// The scope set the client was actually REGISTERED with at the provider —
|
|
216
|
+
// distinct from `scopes` above, which becomes the token's GRANTED scope
|
|
217
|
+
// once a callback completes. Omit (undefined) when this call isn't a
|
|
218
|
+
// fresh registration, so the previously-recorded value survives; pass an
|
|
219
|
+
// explicit array (possibly empty) only at the point a registration — fresh
|
|
220
|
+
// or reused-as-is — is known to have happened.
|
|
221
|
+
registeredScopes?: string[];
|
|
206
222
|
dcrClientId?: string | null;
|
|
207
223
|
dcrClientSecret?: string | null;
|
|
224
|
+
tokenEndpointAuthMethod?: string | null;
|
|
208
225
|
clientSource: McpOAuthClientSource;
|
|
209
226
|
redirectUri?: string;
|
|
210
227
|
}): string {
|
|
211
|
-
// An app is reused only through an existing authorization's
|
|
212
|
-
// Looking up by mcpServerId alone would collapse
|
|
213
|
-
// dimension and let one user's client context
|
|
228
|
+
// An app is reused only through an existing authorization's or pending
|
|
229
|
+
// attempt's exact appId. Looking up by mcpServerId alone would collapse
|
|
230
|
+
// the dormant per-user dimension and let one user's client context
|
|
231
|
+
// overwrite another's.
|
|
214
232
|
const existing = input.appId
|
|
215
233
|
? (getDb()
|
|
216
234
|
.query(
|
|
@@ -225,11 +243,23 @@ function upsertMcpApp(input: {
|
|
|
225
243
|
scopes: string;
|
|
226
244
|
} | null)
|
|
227
245
|
: null;
|
|
246
|
+
// A write means the client is currently in use / believed valid — clear
|
|
247
|
+
// any stale `invalidated` flag left over from a prior provider rejection.
|
|
248
|
+
const existingMetadata = parseObject(existing?.metadata);
|
|
228
249
|
const metadata = JSON.stringify({
|
|
229
|
-
...
|
|
250
|
+
...existingMetadata,
|
|
230
251
|
resourceUrl: input.resourceUrl,
|
|
231
252
|
authorizationServerIssuer: input.authorizationServerIssuer,
|
|
253
|
+
registrationEndpoint: input.registrationEndpoint ?? null,
|
|
232
254
|
clientSource: input.clientSource,
|
|
255
|
+
...(input.tokenEndpointAuthMethod !== undefined
|
|
256
|
+
? { tokenEndpointAuthMethod: input.tokenEndpointAuthMethod }
|
|
257
|
+
: {}),
|
|
258
|
+
registeredScopes:
|
|
259
|
+
input.registeredScopes !== undefined
|
|
260
|
+
? input.registeredScopes
|
|
261
|
+
: (existingMetadata.registeredScopes ?? null),
|
|
262
|
+
invalidated: false,
|
|
233
263
|
});
|
|
234
264
|
const encryptedClientSecret =
|
|
235
265
|
input.dcrClientSecret == null || input.dcrClientSecret === ""
|
|
@@ -312,6 +342,185 @@ export function listMcpOAuthTokensForMcp(mcpServerId: string): McpOAuthToken[] {
|
|
|
312
342
|
return rows.map(decryptTokenRow);
|
|
313
343
|
}
|
|
314
344
|
|
|
345
|
+
// ─── DCR client reuse (avoid re-registering on every /authorize call) ────────
|
|
346
|
+
|
|
347
|
+
export interface StoredMcpOAuthClient {
|
|
348
|
+
appId: string;
|
|
349
|
+
clientId: string;
|
|
350
|
+
clientSecret: string | null;
|
|
351
|
+
authorizeUrl: string;
|
|
352
|
+
tokenUrl: string;
|
|
353
|
+
revocationUrl: string | null;
|
|
354
|
+
redirectUri: string;
|
|
355
|
+
scopes: string[];
|
|
356
|
+
// The scope set the client was registered with, tracked separately from
|
|
357
|
+
// `scopes` (which holds the token's granted scope once connected). Null
|
|
358
|
+
// means unknown — a legacy row from before this field was recorded.
|
|
359
|
+
registeredScopes: string[] | null;
|
|
360
|
+
resourceUrl: string;
|
|
361
|
+
authorizationServerIssuer: string;
|
|
362
|
+
registrationEndpoint: string | null;
|
|
363
|
+
// Null means unknown — a row written before this field was recorded. Callers
|
|
364
|
+
// must resolve it through authMethodForStoredClient, not the RFC default.
|
|
365
|
+
tokenEndpointAuthMethod: string | null;
|
|
366
|
+
clientSource: McpOAuthClientSource;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
type RawAppRow = {
|
|
370
|
+
id: string;
|
|
371
|
+
clientId: string;
|
|
372
|
+
clientSecret: string | null;
|
|
373
|
+
clientSecretEncrypted: number;
|
|
374
|
+
authorizeUrl: string;
|
|
375
|
+
tokenUrl: string;
|
|
376
|
+
revocationUrl: string | null;
|
|
377
|
+
redirectUri: string;
|
|
378
|
+
scopes: string;
|
|
379
|
+
metadata: string;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
function readMcpOAuthApp(appId: string): StoredMcpOAuthClient | null {
|
|
383
|
+
const row = getDb()
|
|
384
|
+
.query(
|
|
385
|
+
`SELECT id, clientId, clientSecret, clientSecretEncrypted, authorizeUrl, tokenUrl,
|
|
386
|
+
revocationUrl, redirectUri, scopes, metadata
|
|
387
|
+
FROM oauth_apps WHERE id = ?`,
|
|
388
|
+
)
|
|
389
|
+
.get(appId) as RawAppRow | null;
|
|
390
|
+
if (!row || !row.clientId) return null;
|
|
391
|
+
|
|
392
|
+
const metadata = parseObject(row.metadata);
|
|
393
|
+
if (metadata.invalidated === true) return null;
|
|
394
|
+
|
|
395
|
+
const clientSource =
|
|
396
|
+
metadata.clientSource === "manual" ||
|
|
397
|
+
metadata.clientSource === "dcr" ||
|
|
398
|
+
metadata.clientSource === "preregistered"
|
|
399
|
+
? metadata.clientSource
|
|
400
|
+
: "dcr";
|
|
401
|
+
// Manual clients are resolved through manualClientFromToken (http layer);
|
|
402
|
+
// never surface them through this reuse path.
|
|
403
|
+
if (clientSource === "manual") return null;
|
|
404
|
+
|
|
405
|
+
const clientKey = row.clientSecretEncrypted === 1 ? getEncryptionKey() : null;
|
|
406
|
+
return {
|
|
407
|
+
appId: row.id,
|
|
408
|
+
clientId: row.clientId,
|
|
409
|
+
clientSecret:
|
|
410
|
+
row.clientSecret == null
|
|
411
|
+
? null
|
|
412
|
+
: clientKey
|
|
413
|
+
? decryptSecret(row.clientSecret, clientKey)
|
|
414
|
+
: row.clientSecret,
|
|
415
|
+
authorizeUrl: row.authorizeUrl,
|
|
416
|
+
tokenUrl: row.tokenUrl,
|
|
417
|
+
revocationUrl: row.revocationUrl,
|
|
418
|
+
redirectUri: row.redirectUri,
|
|
419
|
+
scopes: (() => {
|
|
420
|
+
try {
|
|
421
|
+
const parsed = JSON.parse(row.scopes || "[]");
|
|
422
|
+
return Array.isArray(parsed) ? (parsed as string[]) : [];
|
|
423
|
+
} catch {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
})(),
|
|
427
|
+
resourceUrl: typeof metadata.resourceUrl === "string" ? metadata.resourceUrl : "",
|
|
428
|
+
authorizationServerIssuer:
|
|
429
|
+
typeof metadata.authorizationServerIssuer === "string"
|
|
430
|
+
? metadata.authorizationServerIssuer
|
|
431
|
+
: "",
|
|
432
|
+
registrationEndpoint:
|
|
433
|
+
typeof metadata.registrationEndpoint === "string" ? metadata.registrationEndpoint : null,
|
|
434
|
+
registeredScopes: Array.isArray(metadata.registeredScopes)
|
|
435
|
+
? (metadata.registeredScopes as string[])
|
|
436
|
+
: null,
|
|
437
|
+
tokenEndpointAuthMethod:
|
|
438
|
+
typeof metadata.tokenEndpointAuthMethod === "string"
|
|
439
|
+
? metadata.tokenEndpointAuthMethod
|
|
440
|
+
: null,
|
|
441
|
+
clientSource,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function rawPendingAppIdForUser(mcpServerId: string, userId: string | null): string | null {
|
|
446
|
+
const row = getDb()
|
|
447
|
+
.query(
|
|
448
|
+
`SELECT p.appId FROM oauth_pending p
|
|
449
|
+
JOIN oauth_apps a ON a.id = p.appId
|
|
450
|
+
WHERE p.flow = 'mcp' AND a.mcpServerId = ?
|
|
451
|
+
AND ${userId == null ? "p.userId IS NULL" : "p.userId = ?"}
|
|
452
|
+
ORDER BY p.createdAt DESC LIMIT 1`,
|
|
453
|
+
)
|
|
454
|
+
.get(...(userId == null ? [mcpServerId] : [mcpServerId, userId])) as { appId: string } | null;
|
|
455
|
+
return row?.appId ?? null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Find a previously-registered (or manually-preregistered, excluded here)
|
|
460
|
+
* DCR client that can be reused for this connector+user instead of running a
|
|
461
|
+
* fresh RFC 7591 registration. Checks the existing token's app first (covers
|
|
462
|
+
* re-authorizing an already-connected DCR client), then the most recent
|
|
463
|
+
* still-live pending attempt's app (covers retrying an abandoned flow before
|
|
464
|
+
* the 10-minute GC sweep). Callers must still verify AS identity
|
|
465
|
+
* (issuer/registrationEndpoint/redirectUri) against fresh discovery before
|
|
466
|
+
* trusting the result — this only returns what's stored.
|
|
467
|
+
*/
|
|
468
|
+
export function findReusableMcpOAuthClient(
|
|
469
|
+
mcpServerId: string,
|
|
470
|
+
userId: string | null = null,
|
|
471
|
+
): StoredMcpOAuthClient | null {
|
|
472
|
+
const tokenRow = rawMcpToken(mcpServerId, userId);
|
|
473
|
+
if (tokenRow) {
|
|
474
|
+
const client = readMcpOAuthApp(tokenRow.appId);
|
|
475
|
+
if (client) return client;
|
|
476
|
+
}
|
|
477
|
+
const pendingAppId = rawPendingAppIdForUser(mcpServerId, userId);
|
|
478
|
+
if (pendingAppId) {
|
|
479
|
+
const client = readMcpOAuthApp(pendingAppId);
|
|
480
|
+
if (client) return client;
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Mark an app's stored DCR client as invalid without deleting it (deleting
|
|
487
|
+
* would cascade and destroy authorization history). Idempotent per
|
|
488
|
+
* client_id: a client stays the same row's `clientId` until the next
|
|
489
|
+
* successful registration clears `invalidated` (see `upsertMcpApp`), so once
|
|
490
|
+
* this fires, later invalid_client errors against that SAME still-stored
|
|
491
|
+
* client — from the other call sites, or repeated automatic-refresh retries
|
|
492
|
+
* before the next /authorize re-registers — are no-ops instead of redundant
|
|
493
|
+
* writes.
|
|
494
|
+
*/
|
|
495
|
+
function invalidateMcpOAuthApp(appId: string): void {
|
|
496
|
+
const row = getDb().query("SELECT metadata FROM oauth_apps WHERE id = ?").get(appId) as {
|
|
497
|
+
metadata: string;
|
|
498
|
+
} | null;
|
|
499
|
+
if (!row) return;
|
|
500
|
+
|
|
501
|
+
const meta = parseObject(row.metadata);
|
|
502
|
+
if (meta.invalidated === true) return;
|
|
503
|
+
|
|
504
|
+
const metadata = JSON.stringify({ ...meta, invalidated: true });
|
|
505
|
+
getDb()
|
|
506
|
+
.query(
|
|
507
|
+
"UPDATE oauth_apps SET metadata = ?, updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?",
|
|
508
|
+
)
|
|
509
|
+
.run(metadata, appId);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Mark the stored DCR client for this connector+user as invalid. The next
|
|
514
|
+
* /authorize call's findReusableMcpOAuthClient will skip it and register
|
|
515
|
+
* fresh exactly once.
|
|
516
|
+
*/
|
|
517
|
+
export function invalidateMcpOAuthClient(mcpServerId: string, userId: string | null = null): void {
|
|
518
|
+
const tokenRow = rawMcpToken(mcpServerId, userId);
|
|
519
|
+
const appId = tokenRow?.appId ?? rawPendingAppIdForUser(mcpServerId, userId);
|
|
520
|
+
if (!appId) return;
|
|
521
|
+
invalidateMcpOAuthApp(appId);
|
|
522
|
+
}
|
|
523
|
+
|
|
315
524
|
export interface UpsertMcpOAuthTokenInput {
|
|
316
525
|
mcpServerId: string;
|
|
317
526
|
userId?: string | null;
|
|
@@ -322,16 +531,23 @@ export interface UpsertMcpOAuthTokenInput {
|
|
|
322
531
|
scope?: string | null;
|
|
323
532
|
resourceUrl: string;
|
|
324
533
|
authorizationServerIssuer: string;
|
|
534
|
+
registrationEndpoint?: string | null;
|
|
325
535
|
authorizeUrl: string;
|
|
326
536
|
tokenUrl: string;
|
|
327
537
|
revocationUrl?: string | null;
|
|
538
|
+
// Pass the pending row's registeredScopes (undefined if it doesn't know
|
|
539
|
+
// one) — never pass an explicit null here, or a reuse flow that already
|
|
540
|
+
// has a correctly-tracked value on the still-live app row gets clobbered.
|
|
541
|
+
registeredScopes?: string[];
|
|
328
542
|
dcrClientId?: string | null;
|
|
329
543
|
dcrClientSecret?: string | null;
|
|
544
|
+
tokenEndpointAuthMethod?: string | null;
|
|
330
545
|
clientSource: McpOAuthClientSource;
|
|
331
546
|
status?: McpOAuthStatus;
|
|
332
547
|
lastErrorMessage?: string | null;
|
|
333
548
|
lastRefreshedAt?: string | null;
|
|
334
549
|
connectedByUserId?: string | null;
|
|
550
|
+
redirectUri?: string;
|
|
335
551
|
}
|
|
336
552
|
|
|
337
553
|
export function upsertMcpOAuthToken(input: UpsertMcpOAuthTokenInput): void {
|
|
@@ -343,12 +559,16 @@ export function upsertMcpOAuthToken(input: UpsertMcpOAuthTokenInput): void {
|
|
|
343
559
|
mcpServerId: input.mcpServerId,
|
|
344
560
|
resourceUrl: input.resourceUrl,
|
|
345
561
|
authorizationServerIssuer: input.authorizationServerIssuer,
|
|
562
|
+
registrationEndpoint: input.registrationEndpoint ?? null,
|
|
346
563
|
authorizeUrl: input.authorizeUrl,
|
|
347
564
|
tokenUrl: input.tokenUrl,
|
|
348
565
|
revocationUrl: input.revocationUrl,
|
|
566
|
+
redirectUri: input.redirectUri,
|
|
349
567
|
scopes: input.scope,
|
|
568
|
+
registeredScopes: input.registeredScopes,
|
|
350
569
|
dcrClientId: input.dcrClientId,
|
|
351
570
|
dcrClientSecret: input.dcrClientSecret,
|
|
571
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
352
572
|
clientSource: input.clientSource,
|
|
353
573
|
});
|
|
354
574
|
upsertAuthorization({
|
|
@@ -423,6 +643,13 @@ export function deleteMcpOAuthToken(mcpServerId: string, userId: string | null =
|
|
|
423
643
|
WHERE id = ?`,
|
|
424
644
|
)
|
|
425
645
|
.run(encryptSecret("", getEncryptionKey()), existing.id);
|
|
646
|
+
if (result.changes === 1) {
|
|
647
|
+
// Disconnect is the canonical user recovery gesture. Clear the stored
|
|
648
|
+
// DCR client too, or Reconnect silently hands back the same client_id —
|
|
649
|
+
// rawMcpToken has no status filter, so findReusableMcpOAuthClient would
|
|
650
|
+
// otherwise still surface a "revoked" token's client as reusable.
|
|
651
|
+
invalidateMcpOAuthApp(existing.appId);
|
|
652
|
+
}
|
|
426
653
|
return result.changes === 1;
|
|
427
654
|
}
|
|
428
655
|
|
|
@@ -441,54 +668,91 @@ export interface InsertMcpOAuthPendingInput {
|
|
|
441
668
|
nonce?: string | null;
|
|
442
669
|
resourceUrl: string;
|
|
443
670
|
authorizationServerIssuer: string;
|
|
671
|
+
registrationEndpoint?: string | null;
|
|
444
672
|
authorizeUrl: string;
|
|
445
673
|
tokenUrl: string;
|
|
446
674
|
revocationUrl?: string | null;
|
|
447
675
|
scopes?: string | null;
|
|
676
|
+
// Set only when this pending attempt just performed a fresh DCR
|
|
677
|
+
// registration (or is otherwise the authority on what the client was
|
|
678
|
+
// registered with) — see upsertMcpApp's `registeredScopes` doc.
|
|
679
|
+
registeredScopes?: string[];
|
|
448
680
|
dcrClientId?: string | null;
|
|
449
681
|
dcrClientSecret?: string | null;
|
|
682
|
+
tokenEndpointAuthMethod?: string | null;
|
|
450
683
|
redirectUri: string;
|
|
451
684
|
finalRedirect?: string | null;
|
|
452
685
|
}
|
|
453
686
|
|
|
454
687
|
export function insertMcpOAuthPending(input: InsertMcpOAuthPendingInput): void {
|
|
455
688
|
getDb().transaction(() => {
|
|
456
|
-
const
|
|
689
|
+
const userId = input.userId ?? null;
|
|
690
|
+
const existingToken = rawMcpToken(input.mcpServerId, userId);
|
|
457
691
|
const clientSource = existingToken
|
|
458
692
|
? decryptTokenRow(existingToken).clientSource
|
|
459
693
|
: input.dcrClientId
|
|
460
694
|
? "dcr"
|
|
461
695
|
: "preregistered";
|
|
462
|
-
// A pending attempt must not mutate
|
|
463
|
-
//
|
|
464
|
-
//
|
|
696
|
+
// A pending attempt must not mutate a *valid* connected app — a different
|
|
697
|
+
// client context might be tried concurrently while the old one keeps
|
|
698
|
+
// working. But if the connected app was invalidated (provider rejected
|
|
699
|
+
// it — see invalidateMcpOAuthClient), this pending attempt IS the
|
|
700
|
+
// recovery registration for that exact appId, so it must update the row
|
|
701
|
+
// in place; otherwise the invalidated flag never clears and every future
|
|
702
|
+
// /authorize call re-registers again (an infinite-registration loop).
|
|
703
|
+
// If no authorization exists yet, reuse the most recent still-live
|
|
704
|
+
// pending attempt's app for this same connector+user (this is what lets
|
|
705
|
+
// a retried/abandoned authorize call share one DCR client instead of
|
|
706
|
+
// registering a fresh one — see findReusableMcpOAuthClient in the http
|
|
707
|
+
// layer) instead of always creating a new row; consume/GC removes it
|
|
708
|
+
// once it's orphaned.
|
|
709
|
+
const existingTokenAppValid = existingToken
|
|
710
|
+
? readMcpOAuthApp(existingToken.appId) != null
|
|
711
|
+
: false;
|
|
712
|
+
const reuseAppId = existingToken
|
|
713
|
+
? existingToken.appId
|
|
714
|
+
: rawPendingAppIdForUser(input.mcpServerId, userId);
|
|
465
715
|
const appId =
|
|
466
|
-
existingToken
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
716
|
+
existingToken && existingTokenAppValid
|
|
717
|
+
? existingToken.appId
|
|
718
|
+
: upsertMcpApp({
|
|
719
|
+
...(reuseAppId ? { appId: reuseAppId } : {}),
|
|
720
|
+
mcpServerId: input.mcpServerId,
|
|
721
|
+
resourceUrl: input.resourceUrl,
|
|
722
|
+
authorizationServerIssuer: input.authorizationServerIssuer,
|
|
723
|
+
registrationEndpoint: input.registrationEndpoint ?? null,
|
|
724
|
+
authorizeUrl: input.authorizeUrl,
|
|
725
|
+
tokenUrl: input.tokenUrl,
|
|
726
|
+
revocationUrl: input.revocationUrl,
|
|
727
|
+
scopes: input.scopes,
|
|
728
|
+
registeredScopes: input.registeredScopes,
|
|
729
|
+
dcrClientId: input.dcrClientId,
|
|
730
|
+
dcrClientSecret: input.dcrClientSecret,
|
|
731
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
732
|
+
clientSource,
|
|
733
|
+
redirectUri: input.redirectUri,
|
|
734
|
+
});
|
|
480
735
|
const contextJson = JSON.stringify({
|
|
481
736
|
resourceUrl: input.resourceUrl,
|
|
482
737
|
authorizationServerIssuer: input.authorizationServerIssuer,
|
|
738
|
+
registrationEndpoint: input.registrationEndpoint ?? null,
|
|
483
739
|
authorizeUrl: input.authorizeUrl,
|
|
484
740
|
tokenUrl: input.tokenUrl,
|
|
485
741
|
revocationUrl: input.revocationUrl ?? null,
|
|
486
742
|
scopes: input.scopes ?? null,
|
|
743
|
+
// Carried through the pending row so a first-ever connect's callback
|
|
744
|
+
// can restore it: consumeMcpOAuthPending's deleteOrphanMcpApp deletes
|
|
745
|
+
// this app the moment the pending row is consumed (no authorization
|
|
746
|
+
// exists yet), so the metadata written by insertMcpOAuthPending's own
|
|
747
|
+
// upsertMcpApp call above does not survive to the following
|
|
748
|
+
// upsertMcpOAuthToken call — it re-creates the row from scratch.
|
|
749
|
+
registeredScopes: input.registeredScopes ?? null,
|
|
487
750
|
dcrClientId: input.dcrClientId ?? null,
|
|
488
751
|
dcrClientSecret:
|
|
489
752
|
input.dcrClientSecret == null
|
|
490
753
|
? null
|
|
491
754
|
: encryptSecret(input.dcrClientSecret, getEncryptionKey()),
|
|
755
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod ?? null,
|
|
492
756
|
clientSource,
|
|
493
757
|
});
|
|
494
758
|
getDb()
|
|
@@ -501,12 +765,12 @@ export function insertMcpOAuthPending(input: InsertMcpOAuthPendingInput): void {
|
|
|
501
765
|
.run(
|
|
502
766
|
input.state,
|
|
503
767
|
appId,
|
|
504
|
-
|
|
768
|
+
userId ? `user:${userId}` : "default",
|
|
505
769
|
encryptSecret(input.codeVerifier, getEncryptionKey()),
|
|
506
770
|
input.nonce ?? null,
|
|
507
771
|
input.redirectUri,
|
|
508
772
|
input.finalRedirect ?? null,
|
|
509
|
-
|
|
773
|
+
userId,
|
|
510
774
|
contextJson,
|
|
511
775
|
);
|
|
512
776
|
})();
|
|
@@ -569,14 +833,23 @@ export function consumeMcpOAuthPending(state: string): McpOAuthPendingRow | null
|
|
|
569
833
|
typeof context.authorizationServerIssuer === "string"
|
|
570
834
|
? context.authorizationServerIssuer
|
|
571
835
|
: "",
|
|
836
|
+
registrationEndpoint:
|
|
837
|
+
typeof context.registrationEndpoint === "string" ? context.registrationEndpoint : null,
|
|
572
838
|
authorizeUrl: typeof context.authorizeUrl === "string" ? context.authorizeUrl : "",
|
|
573
839
|
tokenUrl: typeof context.tokenUrl === "string" ? context.tokenUrl : "",
|
|
574
840
|
revocationUrl: typeof context.revocationUrl === "string" ? context.revocationUrl : null,
|
|
575
841
|
scopes: typeof context.scopes === "string" ? context.scopes : null,
|
|
842
|
+
registeredScopes: Array.isArray(context.registeredScopes)
|
|
843
|
+
? (context.registeredScopes as string[])
|
|
844
|
+
: null,
|
|
576
845
|
dcrClientId: typeof context.dcrClientId === "string" ? context.dcrClientId : null,
|
|
577
846
|
dcrClientSecret: encryptedClientSecret
|
|
578
847
|
? decryptSecret(encryptedClientSecret, getEncryptionKey())
|
|
579
848
|
: null,
|
|
849
|
+
tokenEndpointAuthMethod:
|
|
850
|
+
typeof context.tokenEndpointAuthMethod === "string"
|
|
851
|
+
? context.tokenEndpointAuthMethod
|
|
852
|
+
: null,
|
|
580
853
|
redirectUri: row.redirectUri,
|
|
581
854
|
finalRedirect: row.finalRedirect,
|
|
582
855
|
createdAt: normalizeDateRequired(row.createdAt),
|
package/src/be/db.ts
CHANGED
|
@@ -3936,6 +3936,22 @@ function assetSummaryQueries(types: Set<AssetEntityType>): string[] {
|
|
|
3936
3936
|
FROM pages`,
|
|
3937
3937
|
);
|
|
3938
3938
|
}
|
|
3939
|
+
if (types.has("app")) {
|
|
3940
|
+
queries.push(
|
|
3941
|
+
`SELECT 'app' AS entityType, id, "key", name AS label,
|
|
3942
|
+
updated_at AS updatedAt, NULL AS providerId, NULL AS providerOrgId,
|
|
3943
|
+
NULL AS providerDriveId, NULL AS providerKey
|
|
3944
|
+
FROM apps`,
|
|
3945
|
+
);
|
|
3946
|
+
}
|
|
3947
|
+
if (types.has("script")) {
|
|
3948
|
+
queries.push(
|
|
3949
|
+
`SELECT 'script' AS entityType, id, "key", name AS label,
|
|
3950
|
+
updatedAt, NULL AS providerId, NULL AS providerOrgId,
|
|
3951
|
+
NULL AS providerDriveId, NULL AS providerKey
|
|
3952
|
+
FROM scripts`,
|
|
3953
|
+
);
|
|
3954
|
+
}
|
|
3939
3955
|
if (types.has("file")) {
|
|
3940
3956
|
queries.push(
|
|
3941
3957
|
`SELECT 'file' AS entityType, id, "key", provider_key AS label,
|
|
@@ -3951,7 +3967,9 @@ function assetSummaryQueries(types: Set<AssetEntityType>): string[] {
|
|
|
3951
3967
|
|
|
3952
3968
|
export function listAssetSummaries(filters?: AssetSummaryFilters): AssetSummary[] {
|
|
3953
3969
|
const requestedTypes = new Set<AssetEntityType>(
|
|
3954
|
-
filters?.types?.length
|
|
3970
|
+
filters?.types?.length
|
|
3971
|
+
? filters.types
|
|
3972
|
+
: ["task", "workflow", "schedule", "page", "app", "script", "file"],
|
|
3955
3973
|
);
|
|
3956
3974
|
const queries = assetSummaryQueries(requestedTypes);
|
|
3957
3975
|
if (queries.length === 0) return [];
|
|
@@ -4014,6 +4032,18 @@ function currentAssetKey(entityType: AssetEntityType, id: string): string | null
|
|
|
4014
4032
|
.prepare<{ key: string }, [string]>('SELECT "key" AS key FROM pages WHERE id = ?')
|
|
4015
4033
|
.get(id)?.key ?? null
|
|
4016
4034
|
);
|
|
4035
|
+
case "app":
|
|
4036
|
+
return (
|
|
4037
|
+
getDb()
|
|
4038
|
+
.prepare<{ key: string }, [string]>('SELECT "key" AS key FROM apps WHERE id = ?')
|
|
4039
|
+
.get(id)?.key ?? null
|
|
4040
|
+
);
|
|
4041
|
+
case "script":
|
|
4042
|
+
return (
|
|
4043
|
+
getDb()
|
|
4044
|
+
.prepare<{ key: string }, [string]>('SELECT "key" AS key FROM scripts WHERE id = ?')
|
|
4045
|
+
.get(id)?.key ?? null
|
|
4046
|
+
);
|
|
4017
4047
|
case "file":
|
|
4018
4048
|
return getAssetKeyMapping(id)?.key ?? null;
|
|
4019
4049
|
}
|
|
@@ -4109,6 +4139,17 @@ export function moveAssetKey(input: {
|
|
|
4109
4139
|
input.id,
|
|
4110
4140
|
]);
|
|
4111
4141
|
break;
|
|
4142
|
+
case "app":
|
|
4143
|
+
getDb().run('UPDATE apps SET "key" = ?, updated_at = ? WHERE id = ?', [key, now, input.id]);
|
|
4144
|
+
break;
|
|
4145
|
+
case "script":
|
|
4146
|
+
getDb().run('UPDATE scripts SET "key" = ?, updatedAt = ?, updated_by = ? WHERE id = ?', [
|
|
4147
|
+
key,
|
|
4148
|
+
now,
|
|
4149
|
+
input.changedBy ?? null,
|
|
4150
|
+
input.id,
|
|
4151
|
+
]);
|
|
4152
|
+
break;
|
|
4112
4153
|
case "file":
|
|
4113
4154
|
getDb().run(
|
|
4114
4155
|
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = ? WHERE id = ?',
|