tapimo 0.4.1 → 0.4.3
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 +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +2451 -1533
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1354 -984
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +196 -5
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/exchanges.d.ts +453 -453
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +730 -138
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +234 -0
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
- package/dist/apps/management/App.d.ts +1156 -615
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +106 -90
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +278 -146
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +130 -70
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +137 -48
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +35 -3
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +13 -18
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +78 -58
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingRoutes.d.ts +170 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +68 -6
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/package.json +2 -2
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +44 -2
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +64 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +204 -0
- package/src/apps/data/FundingRouteProviders.ts +294 -9
- package/src/apps/data/routes/funding.test.ts +90 -23
- package/src/apps/data/routes/funding.ts +315 -0
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +25 -1
- package/src/apps/mcp.ts +43 -3
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +174 -7
- package/src/internal/Auth.ts +90 -62
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +26 -0
- package/src/internal/FundingRoutes.ts +187 -18
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
|
@@ -12,14 +12,30 @@ const origin = 'http://localhost'
|
|
|
12
12
|
/** Super admin secret configured on the test app. */
|
|
13
13
|
const secret = 'tempo:sk:b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1'
|
|
14
14
|
|
|
15
|
-
/** Wildcard-scoped key: full
|
|
15
|
+
/** Wildcard-scoped key: full API access, still not a super admin. */
|
|
16
16
|
const wildcard = {
|
|
17
17
|
id: 'key_wildcard',
|
|
18
|
-
orgId: '
|
|
18
|
+
orgId: 'org_a',
|
|
19
19
|
scopes: ['*'],
|
|
20
20
|
token: 'secret_wildcard_key',
|
|
21
21
|
} satisfies TestApp.kvStore.Key
|
|
22
22
|
|
|
23
|
+
/** Key limited to management reads for Org A. */
|
|
24
|
+
const managementRead = {
|
|
25
|
+
id: 'key_management_read',
|
|
26
|
+
orgId: 'org_a',
|
|
27
|
+
scopes: ['management:read'],
|
|
28
|
+
token: 'secret_management_read_key',
|
|
29
|
+
} satisfies TestApp.kvStore.Key
|
|
30
|
+
|
|
31
|
+
/** Key limited to management writes for Org A. */
|
|
32
|
+
const managementWrite = {
|
|
33
|
+
id: 'key_management_write',
|
|
34
|
+
orgId: 'org_a',
|
|
35
|
+
scopes: ['management:write'],
|
|
36
|
+
token: 'secret_management_write_key',
|
|
37
|
+
} satisfies TestApp.kvStore.Key
|
|
38
|
+
|
|
23
39
|
/** RequestInit presenting the given token. */
|
|
24
40
|
function as(token: string) {
|
|
25
41
|
return { headers: { 'tempo-api-key': token } } as const
|
|
@@ -36,7 +52,10 @@ function json(method: 'PATCH' | 'POST', cookie: string, body: unknown) {
|
|
|
36
52
|
|
|
37
53
|
function createApp(db = TestApp.database()) {
|
|
38
54
|
return TestApp.create({
|
|
39
|
-
auth: {
|
|
55
|
+
auth: {
|
|
56
|
+
keys: [TestApp.key, managementRead, managementWrite, wildcard],
|
|
57
|
+
superAdmin: { secret },
|
|
58
|
+
},
|
|
40
59
|
db,
|
|
41
60
|
session: { wallet: { origin } },
|
|
42
61
|
})
|
|
@@ -81,6 +100,19 @@ describe('GET /orgs', () => {
|
|
|
81
100
|
`)
|
|
82
101
|
})
|
|
83
102
|
|
|
103
|
+
test('accepts a management API key in the query parameter', async () => {
|
|
104
|
+
const app = await seeded()
|
|
105
|
+
const response = await app.request(`/v1/orgs?key=${encodeURIComponent(managementRead.token)}`)
|
|
106
|
+
const body = await TestApp.json(response, Orgs.schema.listOrganizations.Response)
|
|
107
|
+
|
|
108
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
109
|
+
expect(body.data.map(({ id }) => id)).toMatchInlineSnapshot(`
|
|
110
|
+
[
|
|
111
|
+
"org_a",
|
|
112
|
+
]
|
|
113
|
+
`)
|
|
114
|
+
})
|
|
115
|
+
|
|
84
116
|
test('lists only owned organizations for a session', async () => {
|
|
85
117
|
const app = await seeded()
|
|
86
118
|
const cookie = await session(app)
|
|
@@ -112,24 +144,26 @@ describe('GET /orgs', () => {
|
|
|
112
144
|
`)
|
|
113
145
|
})
|
|
114
146
|
|
|
115
|
-
test('rejects API keys without the
|
|
147
|
+
test('rejects API keys without the management read scope', async () => {
|
|
116
148
|
const response = await (await seeded()).request('/v1/orgs', as(TestApp.key.token))
|
|
117
149
|
expect(response.status).toBe(403)
|
|
118
150
|
})
|
|
119
151
|
|
|
120
|
-
test('
|
|
152
|
+
test('lists only the owning organization for scoped and wildcard keys', async () => {
|
|
153
|
+
const app = await seeded()
|
|
154
|
+
const scoped = await app.request('/v1/orgs', as(managementRead.token))
|
|
155
|
+
expect(scoped.status).toBe(200)
|
|
156
|
+
expect(
|
|
157
|
+
(await TestApp.json(scoped, Orgs.schema.listOrganizations.Response)).data.map(({ id }) => id),
|
|
158
|
+
).toEqual(['org_a'])
|
|
159
|
+
|
|
121
160
|
const response = await (await seeded()).request('/v1/orgs', as(wildcard.token))
|
|
122
|
-
expect(response.status).toBe(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"code": "forbidden",
|
|
129
|
-
"message": "API key not permitted for this route",
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
`)
|
|
161
|
+
expect(response.status).toBe(200)
|
|
162
|
+
expect(
|
|
163
|
+
(await TestApp.json(response, Orgs.schema.listOrganizations.Response)).data.map(
|
|
164
|
+
({ id }) => id,
|
|
165
|
+
),
|
|
166
|
+
).toEqual(['org_a'])
|
|
133
167
|
})
|
|
134
168
|
|
|
135
169
|
test('rejects the secret when super admin is unconfigured', async () => {
|
|
@@ -200,6 +234,13 @@ describe('POST /orgs', () => {
|
|
|
200
234
|
})
|
|
201
235
|
|
|
202
236
|
describe('GET /orgs/:orgId', () => {
|
|
237
|
+
test('binds API keys to their owning organization', async () => {
|
|
238
|
+
const app = await seeded()
|
|
239
|
+
expect((await app.request('/v1/orgs/org_a', as(managementRead.token))).status).toBe(200)
|
|
240
|
+
expect((await app.request('/v1/orgs/org_b', as(managementRead.token))).status).toBe(404)
|
|
241
|
+
expect((await app.request('/v1/orgs/org_a', as(managementWrite.token))).status).toBe(403)
|
|
242
|
+
})
|
|
243
|
+
|
|
203
244
|
test('hides foreign organizations behind 404', async () => {
|
|
204
245
|
const app = createApp()
|
|
205
246
|
const owner = await session(app)
|
|
@@ -219,6 +260,27 @@ describe('GET /orgs/:orgId', () => {
|
|
|
219
260
|
})
|
|
220
261
|
|
|
221
262
|
describe('PATCH /orgs/:orgId', () => {
|
|
263
|
+
test('requires the management write scope', async () => {
|
|
264
|
+
const app = await seeded()
|
|
265
|
+
const write = await app.request(
|
|
266
|
+
`/v1/orgs/org_a?key=${encodeURIComponent(managementWrite.token)}`,
|
|
267
|
+
{
|
|
268
|
+
body: JSON.stringify({ name: 'Managed' }),
|
|
269
|
+
headers: { 'content-type': 'application/json' },
|
|
270
|
+
method: 'PATCH',
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
expect(write.status).toBe(200)
|
|
274
|
+
expect((await TestApp.json(write, Orgs.schema.Organization)).name).toBe('Managed')
|
|
275
|
+
|
|
276
|
+
const readOnly = await app.request('/v1/orgs/org_a', {
|
|
277
|
+
body: JSON.stringify({ name: 'Denied' }),
|
|
278
|
+
headers: { 'content-type': 'application/json', 'tempo-api-key': managementRead.token },
|
|
279
|
+
method: 'PATCH',
|
|
280
|
+
})
|
|
281
|
+
expect(readOnly.status).toBe(403)
|
|
282
|
+
})
|
|
283
|
+
|
|
222
284
|
test('renames an owned organization', async () => {
|
|
223
285
|
const app = createApp()
|
|
224
286
|
const cookie = await session(app)
|
|
@@ -46,7 +46,7 @@ export namespace schema {
|
|
|
46
46
|
role: z
|
|
47
47
|
.optional(z.enum(Memberships.roles))
|
|
48
48
|
.check(
|
|
49
|
-
z.describe("The caller's role
|
|
49
|
+
z.describe("The caller's role; absent for API keys and the super admin."),
|
|
50
50
|
z.meta({ examples: ['owner'] }),
|
|
51
51
|
),
|
|
52
52
|
updatedAt: z
|
|
@@ -104,17 +104,13 @@ export namespace schema {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* Mounts
|
|
108
|
-
* owned by the user that created it). The super admin bypasses ownership;
|
|
109
|
-
* inaccessible or absent ids render `404` so foreign org ids are
|
|
110
|
-
* indistinguishable from missing ones. Projects nest under `/orgs/:orgId` in
|
|
111
|
-
* the projects app.
|
|
107
|
+
* Mounts organizations. Sessions access memberships, API keys access their owning organization, and the super admin bypasses ownership.
|
|
112
108
|
*/
|
|
113
109
|
export function orgs() {
|
|
114
110
|
return new Hono<App.Environment>()
|
|
115
111
|
.get(
|
|
116
112
|
'/v1/orgs',
|
|
117
|
-
Auth.policy({ session: true }),
|
|
113
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
118
114
|
OpenApi.describeRoute({
|
|
119
115
|
operationId: 'listOrganizations',
|
|
120
116
|
responses: OpenApi.responses({
|
|
@@ -127,18 +123,21 @@ export function orgs() {
|
|
|
127
123
|
tags: ['Organizations'],
|
|
128
124
|
}),
|
|
129
125
|
async (c) => {
|
|
130
|
-
if (Auth.narrowAccess) return Auth.
|
|
131
|
-
|
|
132
|
-
const principal = Auth.getPrincipal(c)
|
|
133
|
-
if (principal?.type !== 'session' && principal?.type !== 'super_admin')
|
|
134
|
-
return sessionRequired(c)
|
|
126
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
135
127
|
|
|
136
128
|
const db = Db.get(c.get('db'))
|
|
137
129
|
try {
|
|
130
|
+
const principal = Auth.getPrincipal(c)
|
|
131
|
+
if (!principal || principal.type === 'public')
|
|
132
|
+
throw new Error('organization auth policy admitted an unsupported principal')
|
|
138
133
|
const records =
|
|
139
134
|
principal.type === 'super_admin'
|
|
140
135
|
? await Organizations.list(db)
|
|
141
|
-
:
|
|
136
|
+
: principal.type === 'api_key'
|
|
137
|
+
? [await Organizations.get(db, principal.orgId)].filter(
|
|
138
|
+
(record): record is Organizations.Record => record !== undefined,
|
|
139
|
+
)
|
|
140
|
+
: await Organizations.listByMember(db, principal.id)
|
|
142
141
|
return c.json(
|
|
143
142
|
Response.validated(schema.listOrganizations.Response, {
|
|
144
143
|
data: records.map(serializeOrganization),
|
|
@@ -203,7 +202,7 @@ export function orgs() {
|
|
|
203
202
|
)
|
|
204
203
|
.get(
|
|
205
204
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}',
|
|
206
|
-
Auth.policy({ session: true }),
|
|
205
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
207
206
|
Auth.ensureOrg(),
|
|
208
207
|
OpenApi.validate('param', schema.Params, {
|
|
209
208
|
code: 'param_invalid',
|
|
@@ -219,7 +218,7 @@ export function orgs() {
|
|
|
219
218
|
tags: ['Organizations'],
|
|
220
219
|
}),
|
|
221
220
|
async (c) => {
|
|
222
|
-
if (Auth.narrowAccess) return Auth.
|
|
221
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
223
222
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
224
223
|
if (OpenApi.narrowValidation)
|
|
225
224
|
return OpenApi.validationError(c, {
|
|
@@ -238,7 +237,7 @@ export function orgs() {
|
|
|
238
237
|
)
|
|
239
238
|
.patch(
|
|
240
239
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}',
|
|
241
|
-
Auth.policy({ session: true }),
|
|
240
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
242
241
|
Auth.ensureOrg({ role: 'owner' }),
|
|
243
242
|
OpenApi.validate('param', schema.Params, {
|
|
244
243
|
code: 'param_invalid',
|
|
@@ -258,7 +257,7 @@ export function orgs() {
|
|
|
258
257
|
tags: ['Organizations'],
|
|
259
258
|
}),
|
|
260
259
|
async (c) => {
|
|
261
|
-
if (Auth.narrowAccess) return Auth.
|
|
260
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
262
261
|
if (OpenApi.narrowValidation)
|
|
263
262
|
return OpenApi.validationError(c, {
|
|
264
263
|
code: 'param_invalid',
|
|
@@ -289,7 +288,7 @@ export function orgs() {
|
|
|
289
288
|
)
|
|
290
289
|
.delete(
|
|
291
290
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}',
|
|
292
|
-
Auth.policy({ session: true }),
|
|
291
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
293
292
|
Auth.ensureOrg({ role: 'owner' }),
|
|
294
293
|
OpenApi.validate('param', schema.Params, {
|
|
295
294
|
code: 'param_invalid',
|
|
@@ -309,7 +308,7 @@ export function orgs() {
|
|
|
309
308
|
tags: ['Organizations'],
|
|
310
309
|
}),
|
|
311
310
|
async (c) => {
|
|
312
|
-
if (Auth.narrowAccess) return Auth.
|
|
311
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
313
312
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
314
313
|
if (OpenApi.narrowValidation)
|
|
315
314
|
return OpenApi.validationError(c, {
|
|
@@ -321,7 +320,9 @@ export function orgs() {
|
|
|
321
320
|
try {
|
|
322
321
|
const kv = c.get('kv')
|
|
323
322
|
if (kv)
|
|
324
|
-
for (const record of await ApiKeys.listByOrg(kv.store, Auth.org(c).id
|
|
323
|
+
for (const record of await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
|
|
324
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
325
|
+
}))
|
|
325
326
|
await ApiKeys.revoke(kv.store, record.id)
|
|
326
327
|
const deleted = await Organizations.deleteOrganization(db, Auth.org(c).id)
|
|
327
328
|
if (!deleted) return organizationNotFound(c)
|
|
@@ -125,15 +125,13 @@ export namespace schema {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Mounts the
|
|
129
|
-
* owning organization. Access follows the org scope (owning session or super
|
|
130
|
-
* admin); inaccessible or absent ids render `404`.
|
|
128
|
+
* Mounts projects under their owning organization. Access follows the scoped session, API key, or super admin; inaccessible projects return `404`.
|
|
131
129
|
*/
|
|
132
130
|
export function projects() {
|
|
133
131
|
return new Hono<App.Environment>()
|
|
134
132
|
.get(
|
|
135
133
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects',
|
|
136
|
-
Auth.policy({ session: true }),
|
|
134
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
137
135
|
Auth.ensureOrg(),
|
|
138
136
|
OpenApi.validate('param', schema.Params, {
|
|
139
137
|
code: 'param_invalid',
|
|
@@ -152,7 +150,7 @@ export function projects() {
|
|
|
152
150
|
tags: ['Projects'],
|
|
153
151
|
}),
|
|
154
152
|
async (c) => {
|
|
155
|
-
if (Auth.narrowAccess) return Auth.
|
|
153
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
156
154
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
157
155
|
if (OpenApi.narrowValidation)
|
|
158
156
|
return OpenApi.validationError(c, {
|
|
@@ -176,7 +174,7 @@ export function projects() {
|
|
|
176
174
|
)
|
|
177
175
|
.post(
|
|
178
176
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects',
|
|
179
|
-
Auth.policy({ session: true }),
|
|
177
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
180
178
|
Auth.ensureOrg({ role: 'admin' }),
|
|
181
179
|
OpenApi.validate('param', schema.Params, {
|
|
182
180
|
code: 'param_invalid',
|
|
@@ -196,7 +194,7 @@ export function projects() {
|
|
|
196
194
|
tags: ['Projects'],
|
|
197
195
|
}),
|
|
198
196
|
async (c) => {
|
|
199
|
-
if (Auth.narrowAccess) return Auth.
|
|
197
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
200
198
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
201
199
|
if (OpenApi.narrowValidation)
|
|
202
200
|
return OpenApi.validationError(c, {
|
|
@@ -221,7 +219,7 @@ export function projects() {
|
|
|
221
219
|
)
|
|
222
220
|
.get(
|
|
223
221
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}',
|
|
224
|
-
Auth.policy({ session: true }),
|
|
222
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
225
223
|
Auth.ensureOrg(),
|
|
226
224
|
Auth.ensureProject(),
|
|
227
225
|
OpenApi.validate('param', schema.ProjectParams, {
|
|
@@ -238,7 +236,7 @@ export function projects() {
|
|
|
238
236
|
tags: ['Projects'],
|
|
239
237
|
}),
|
|
240
238
|
async (c) => {
|
|
241
|
-
if (Auth.narrowAccess) return Auth.
|
|
239
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
242
240
|
if (Auth.narrowScope) return Auth.ensureProjectError(c)
|
|
243
241
|
if (OpenApi.narrowValidation)
|
|
244
242
|
return OpenApi.validationError(c, {
|
|
@@ -251,7 +249,7 @@ export function projects() {
|
|
|
251
249
|
)
|
|
252
250
|
.patch(
|
|
253
251
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}',
|
|
254
|
-
Auth.policy({ session: true }),
|
|
252
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
255
253
|
Auth.ensureOrg({ role: 'admin' }),
|
|
256
254
|
Auth.ensureProject(),
|
|
257
255
|
OpenApi.validate('param', schema.ProjectParams, {
|
|
@@ -272,7 +270,7 @@ export function projects() {
|
|
|
272
270
|
tags: ['Projects'],
|
|
273
271
|
}),
|
|
274
272
|
async (c) => {
|
|
275
|
-
if (Auth.narrowAccess) return Auth.
|
|
273
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
276
274
|
if (OpenApi.narrowValidation)
|
|
277
275
|
return OpenApi.validationError(c, {
|
|
278
276
|
code: 'param_invalid',
|
|
@@ -298,7 +296,7 @@ export function projects() {
|
|
|
298
296
|
)
|
|
299
297
|
.delete(
|
|
300
298
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}',
|
|
301
|
-
Auth.policy({ session: true }),
|
|
299
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
302
300
|
Auth.ensureOrg({ role: 'admin' }),
|
|
303
301
|
Auth.ensureProject(),
|
|
304
302
|
OpenApi.validate('param', schema.ProjectParams, {
|
|
@@ -318,7 +316,7 @@ export function projects() {
|
|
|
318
316
|
tags: ['Projects'],
|
|
319
317
|
}),
|
|
320
318
|
async (c) => {
|
|
321
|
-
if (Auth.narrowAccess) return Auth.
|
|
319
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
322
320
|
if (Auth.narrowScope) return Auth.ensureProjectError(c)
|
|
323
321
|
if (OpenApi.narrowValidation)
|
|
324
322
|
return OpenApi.validationError(c, {
|
|
@@ -332,6 +330,7 @@ export function projects() {
|
|
|
332
330
|
if (kv)
|
|
333
331
|
for (const record of await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
|
|
334
332
|
projectId: Auth.project(c).id,
|
|
333
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
335
334
|
}))
|
|
336
335
|
await ApiKeys.revoke(kv.store, record.id)
|
|
337
336
|
const deleted = await Projects.deleteProject(db, Auth.project(c).id)
|
|
@@ -19,6 +19,8 @@ describe('scopes', () => {
|
|
|
19
19
|
[
|
|
20
20
|
"data:read",
|
|
21
21
|
"indexer:query",
|
|
22
|
+
"management:read",
|
|
23
|
+
"management:write",
|
|
22
24
|
"rpc-relay:read",
|
|
23
25
|
"rpc-relay:sponsor",
|
|
24
26
|
"webhooks:read",
|
|
@@ -26,4 +28,18 @@ describe('scopes', () => {
|
|
|
26
28
|
]
|
|
27
29
|
`)
|
|
28
30
|
})
|
|
31
|
+
|
|
32
|
+
test('includes host scopes configured on the app', async () => {
|
|
33
|
+
const custom = {
|
|
34
|
+
description: 'Read project accounts.',
|
|
35
|
+
scope: 'accounts:read',
|
|
36
|
+
selfServe: true,
|
|
37
|
+
} as const satisfies Scope.Entry
|
|
38
|
+
const app = TestApp.create({ scopes: [custom] })
|
|
39
|
+
const response = await app.request('/v1/scopes')
|
|
40
|
+
const body = await TestApp.json(response, Scopes.schema.getScopes.Response)
|
|
41
|
+
|
|
42
|
+
expect(response.status).toBe(200)
|
|
43
|
+
expect(body.data).toEqual([...Scope.catalog, custom])
|
|
44
|
+
})
|
|
29
45
|
})
|
|
@@ -6,7 +6,6 @@ import * as Auth from '../../../internal/Auth.js'
|
|
|
6
6
|
import * as Cache from '../../../internal/Cache.js'
|
|
7
7
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
8
8
|
import * as Response from '../../../internal/Response.js'
|
|
9
|
-
import * as Scope from '../../../Scope.js'
|
|
10
9
|
|
|
11
10
|
/** Zod schemas owned by the scope-catalog resource. */
|
|
12
11
|
export namespace schema {
|
|
@@ -22,7 +21,7 @@ export namespace schema {
|
|
|
22
21
|
z.meta({ examples: ['Read webhook subscriptions.'] }),
|
|
23
22
|
),
|
|
24
23
|
scope: z
|
|
25
|
-
.
|
|
24
|
+
.string()
|
|
26
25
|
.check(
|
|
27
26
|
z.describe('Scope identifier used in API-key grants and route policies.'),
|
|
28
27
|
z.meta({ examples: ['data:read'] }),
|
|
@@ -30,7 +29,7 @@ export namespace schema {
|
|
|
30
29
|
selfServe: z
|
|
31
30
|
.boolean()
|
|
32
31
|
.check(
|
|
33
|
-
z.describe('Whether
|
|
32
|
+
z.describe('Whether an eligible session may self-mint this scope.'),
|
|
34
33
|
z.meta({ examples: [true] }),
|
|
35
34
|
),
|
|
36
35
|
})
|
|
@@ -47,13 +46,13 @@ export namespace schema {
|
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
48
|
* Mounts the `/scopes` resource: the issuable API-key scope vocabulary, served
|
|
50
|
-
* from
|
|
49
|
+
* from the configured catalog. Public catalog data: anonymous callers are served
|
|
51
50
|
* within the public quota; the session lane lets signed-in consoles skip it.
|
|
52
51
|
*/
|
|
53
52
|
export function scopes() {
|
|
54
53
|
return new Hono<App.Environment>().get(
|
|
55
54
|
'/v1/scopes',
|
|
56
|
-
Auth.policy({ public: true, session: true }),
|
|
55
|
+
Auth.policy({ apiKey: { scopes: [] }, public: true, session: true }),
|
|
57
56
|
OpenApi.describeRoute({
|
|
58
57
|
description: 'See which scopes can be granted to API keys.',
|
|
59
58
|
operationId: 'getScopes',
|
|
@@ -65,7 +64,7 @@ export function scopes() {
|
|
|
65
64
|
}),
|
|
66
65
|
Cache.response<App.Environment>({
|
|
67
66
|
cacheControl: Cache.policies.stable,
|
|
68
|
-
name: 'tempo-api:scopes:
|
|
67
|
+
name: 'tempo-api:scopes:v2',
|
|
69
68
|
key: (c) => {
|
|
70
69
|
const url = new URL(c.req.url)
|
|
71
70
|
url.search = ''
|
|
@@ -76,7 +75,7 @@ export function scopes() {
|
|
|
76
75
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
77
76
|
return c.json(
|
|
78
77
|
Response.validated(schema.getScopes.Response, {
|
|
79
|
-
data:
|
|
78
|
+
data: c.get('scopeCatalog').map((entry) => ({ ...entry })),
|
|
80
79
|
}),
|
|
81
80
|
200,
|
|
82
81
|
)
|
|
@@ -285,16 +285,12 @@ export namespace schema {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
/**
|
|
289
|
-
* Mounts the usage surface: project request usage (read from a host-supplied
|
|
290
|
-
* analytics capability) and an organization's sponsorship volume and fees
|
|
291
|
-
* over time. Session lane; reads for members.
|
|
292
|
-
*/
|
|
288
|
+
/** Mounts organization request and sponsorship usage. Reads require membership or a scoped API key. */
|
|
293
289
|
export function usage() {
|
|
294
290
|
return new Hono<Environment>()
|
|
295
291
|
.get(
|
|
296
292
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/usage/requests',
|
|
297
|
-
Auth.policy({ session: true }),
|
|
293
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
298
294
|
Auth.ensureOrg(),
|
|
299
295
|
OpenApi.validate('param', schema.getRequestUsage.Params, {
|
|
300
296
|
code: 'param_invalid',
|
|
@@ -327,7 +323,7 @@ export function usage() {
|
|
|
327
323
|
tags: ['Usage'],
|
|
328
324
|
}),
|
|
329
325
|
async (c) => {
|
|
330
|
-
if (Auth.narrowAccess) return Auth.
|
|
326
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
331
327
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
332
328
|
if (OpenApi.narrowValidation)
|
|
333
329
|
return OpenApi.validationError(c, {
|
|
@@ -398,7 +394,7 @@ export function usage() {
|
|
|
398
394
|
)
|
|
399
395
|
.get(
|
|
400
396
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/usage/sponsorships',
|
|
401
|
-
Auth.policy({ session: true }),
|
|
397
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
402
398
|
Auth.ensureOrg(),
|
|
403
399
|
OpenApi.validate('param', schema.getSponsorshipUsage.Params, {
|
|
404
400
|
code: 'param_invalid',
|
|
@@ -423,7 +419,7 @@ export function usage() {
|
|
|
423
419
|
tags: ['Usage'],
|
|
424
420
|
}),
|
|
425
421
|
async (c) => {
|
|
426
|
-
if (Auth.narrowAccess) return Auth.
|
|
422
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
427
423
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
428
424
|
if (OpenApi.narrowValidation)
|
|
429
425
|
return OpenApi.validationError(c, {
|
|
@@ -498,11 +494,10 @@ async function legacyKeyIds(
|
|
|
498
494
|
): Promise<readonly string[]> {
|
|
499
495
|
const kv = c.get('kv')
|
|
500
496
|
if (!kv) return []
|
|
501
|
-
const records = await ApiKeys.listByOrg(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
)
|
|
497
|
+
const records = await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
|
|
498
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
499
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
500
|
+
})
|
|
506
501
|
return records
|
|
507
502
|
.filter(
|
|
508
503
|
(record) => options.environment === undefined || record.environment === options.environment,
|
package/src/apps/mcp.test.ts
CHANGED
|
@@ -121,6 +121,30 @@ test('scopes tools to the data domain', async () => {
|
|
|
121
121
|
expect(tools.some((name) => name.startsWith('v1_me'))).toBe(false)
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
+
test('keeps tool input schemas compact', async () => {
|
|
125
|
+
const { host } = createHost()
|
|
126
|
+
await initialize(host)
|
|
127
|
+
|
|
128
|
+
const body = await request(host, { id: 2, jsonrpc: '2.0', method: 'tools/list', params: {} })
|
|
129
|
+
const tools = body.result.tools as {
|
|
130
|
+
name: string
|
|
131
|
+
inputSchema: { properties?: Record<string, { description?: string }> }
|
|
132
|
+
}[]
|
|
133
|
+
const tool = tools.find((entry) => entry.name === 'v1_tokens_get')
|
|
134
|
+
const properties = tool?.inputSchema.properties ?? {}
|
|
135
|
+
|
|
136
|
+
// Credential inputs stay off the schema; auth rides the forwarded header.
|
|
137
|
+
expect(properties['authorization']).toBeUndefined()
|
|
138
|
+
expect(properties['tempo-api-key']).toBeUndefined()
|
|
139
|
+
// Shared parameters carry the terse tool descriptions.
|
|
140
|
+
expect(properties['limit']?.description).toBe('Items per page.')
|
|
141
|
+
expect(properties['cursor']?.description).toBe(
|
|
142
|
+
'Keyset cursor from a previous response `nextCursor`; omit for the first page.',
|
|
143
|
+
)
|
|
144
|
+
// `compact` strips examples from generated schemas.
|
|
145
|
+
expect(JSON.stringify(tool?.inputSchema)).not.toContain('"examples"')
|
|
146
|
+
})
|
|
147
|
+
|
|
124
148
|
test('executes generated tools against the composed app', async () => {
|
|
125
149
|
// Verified tokens are database-backed, so seed the default chain.
|
|
126
150
|
const db = TestApp.database()
|
|
@@ -176,7 +200,7 @@ test('documents POST /mcp and keeps it open under auth enforcement', async () =>
|
|
|
176
200
|
expect(operation?.operationId).toBe('mcpRequest')
|
|
177
201
|
expect(operation?.tags).toEqual(['MCP'])
|
|
178
202
|
// Anonymous and any-valid-key access; no required scopes.
|
|
179
|
-
expect(operation?.security).toEqual([{ apiKey: [] }, { bearerAuth: [] }, {}])
|
|
203
|
+
expect(operation?.security).toEqual([{ apiKey: [] }, { queryApiKey: [] }, { bearerAuth: [] }, {}])
|
|
180
204
|
|
|
181
205
|
// Surfaces under `Data API` in the reference, but the tool filter excludes the `MCP` tag.
|
|
182
206
|
const groups = Object.fromEntries(spec['x-tagGroups'].map((group) => [group.name, group.tags]))
|
package/src/apps/mcp.ts
CHANGED
|
@@ -15,7 +15,7 @@ const excludedTags = ['CoinGecko', 'MCP']
|
|
|
15
15
|
|
|
16
16
|
/** Usage guidance surfaced to MCP clients on initialize. */
|
|
17
17
|
const instructions =
|
|
18
|
-
"Tools mirror the Tempo API's data domain. Authenticated tools accept a Tempo API key
|
|
18
|
+
"Tools mirror the Tempo API's data domain. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
|
|
19
19
|
|
|
20
20
|
/** Zod schemas owned by the MCP endpoint's OpenAPI description. */
|
|
21
21
|
export namespace schema {
|
|
@@ -97,7 +97,15 @@ export function mcp(options: mcp.Options) {
|
|
|
97
97
|
},
|
|
98
98
|
mcp: { instructions },
|
|
99
99
|
openapi: scope(document),
|
|
100
|
-
|
|
100
|
+
// `compact` + `security: false` keep tool schemas lean: no examples,
|
|
101
|
+
// oversized regexes, or per-tool credential inputs (auth rides the
|
|
102
|
+
// forwarded `Authorization` header).
|
|
103
|
+
openapiConfig: {
|
|
104
|
+
compact: true,
|
|
105
|
+
forwardHeaders: ['authorization'],
|
|
106
|
+
mode: 'namespace',
|
|
107
|
+
security: false,
|
|
108
|
+
},
|
|
101
109
|
version,
|
|
102
110
|
})
|
|
103
111
|
if (docs !== false)
|
|
@@ -217,11 +225,43 @@ function scope(document: Document): Openapi.OpenAPISpec {
|
|
|
217
225
|
return tags_operation?.some((tag) => tags.has(tag)) ?? false
|
|
218
226
|
})
|
|
219
227
|
if (entries.some(([method]) => methods.includes(method)))
|
|
220
|
-
paths[path] = Object.fromEntries(
|
|
228
|
+
paths[path] = Object.fromEntries(
|
|
229
|
+
entries.map(([method, operation]) =>
|
|
230
|
+
methods.includes(method) ? [method, condense(operation)] : [method, operation],
|
|
231
|
+
),
|
|
232
|
+
)
|
|
221
233
|
}
|
|
222
234
|
return { ...document, paths }
|
|
223
235
|
}
|
|
224
236
|
|
|
237
|
+
/** Terse tool descriptions for parameters repeated across most operations; the public document keeps the full prose. */
|
|
238
|
+
const parameterDescriptions: Record<string, string> = {
|
|
239
|
+
chainId: 'Chain: `mainnet` (default), `testnet`, or a numeric chain id.',
|
|
240
|
+
cursor: 'Keyset cursor from a previous response `nextCursor`; omit for the first page.',
|
|
241
|
+
include: 'Comma-separated related resources to include.',
|
|
242
|
+
limit: 'Items per page.',
|
|
243
|
+
order: 'Sort order: `asc` or `desc` (default).',
|
|
244
|
+
page: '1-indexed page number; mutually exclusive with `cursor`.',
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Swaps shared query-parameter prose for the terse tool descriptions. */
|
|
248
|
+
function condense(operation: unknown) {
|
|
249
|
+
const { parameters } = operation as {
|
|
250
|
+
parameters?: readonly { in?: string; name?: string }[] | undefined
|
|
251
|
+
}
|
|
252
|
+
if (!parameters) return operation
|
|
253
|
+
return {
|
|
254
|
+
...(operation as object),
|
|
255
|
+
parameters: parameters.map((parameter) => {
|
|
256
|
+
const description =
|
|
257
|
+
parameter.in === 'query' && parameter.name
|
|
258
|
+
? parameterDescriptions[parameter.name]
|
|
259
|
+
: undefined
|
|
260
|
+
return description ? { ...parameter, description } : parameter
|
|
261
|
+
}),
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
225
265
|
export declare namespace mcp {
|
|
226
266
|
/** Options for the MCP route group. */
|
|
227
267
|
type Options = {
|