@venturekit/mcp 0.0.0-dev.20260608102211
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/LICENSE +191 -0
- package/README.md +137 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/json-schema.d.ts +35 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +139 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/migrations/vk_mcp_0001_tokens.sql +63 -0
- package/dist/path.d.ts +22 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +27 -0
- package/dist/path.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +177 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +82 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +111 -0
- package/dist/store.js.map +1 -0
- package/dist/tokens.d.ts +74 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +94 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
- package/src/migrations/vk_mcp_0001_tokens.sql +63 -0
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque bearer-token primitives for MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* An MCP server authenticates agents with a long-lived bearer token. A
|
|
5
|
+
* host issues one, stores only a hash + a short lookup prefix, and shows
|
|
6
|
+
* the plaintext exactly once. These helpers own the parts that are
|
|
7
|
+
* identical across every host — the token format, secure generation,
|
|
8
|
+
* and the prefix/parse helpers — while leaving hashing and storage to
|
|
9
|
+
* the host, which knows its own DB and crypto policy.
|
|
10
|
+
*
|
|
11
|
+
* Token shape: `<scheme>_<scope>_<secret>`, e.g. `vk_acme_Xy3…`.
|
|
12
|
+
* - `scheme` namespaces the token (default `vk`).
|
|
13
|
+
* - `scope` is an opaque routing hint (e.g. a tenant slug) the host can
|
|
14
|
+
* read *before* hashing, to narrow a DB lookup to one tenant. It is
|
|
15
|
+
* optional; without it the token is `<scheme>_<secret>`.
|
|
16
|
+
* - `secret` is URL-safe base64 (`A-Za-z0-9_-`) random entropy.
|
|
17
|
+
*
|
|
18
|
+
* `node:crypto` is the only import (a Node builtin), so the package keeps
|
|
19
|
+
* its no-third-party-runtime-dependency property.
|
|
20
|
+
*/
|
|
21
|
+
import { randomBytes } from 'node:crypto';
|
|
22
|
+
const DEFAULT_SCHEME = 'vk';
|
|
23
|
+
const DEFAULT_BYTES = 24;
|
|
24
|
+
const MIN_BYTES = 16;
|
|
25
|
+
/** Chars kept from the front of a token as its (non-secret) lookup key. */
|
|
26
|
+
export const DEFAULT_PREFIX_LENGTH = 10;
|
|
27
|
+
/** A `scheme` / `scope` segment: lowercase alnum, dashes allowed inside. */
|
|
28
|
+
const SEGMENT_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
29
|
+
/**
|
|
30
|
+
* Mint a new bearer token + its lookup prefix. The secret is drawn from
|
|
31
|
+
* a CSPRNG (`node:crypto`); the host should hash {@link GeneratedBearerToken.token}
|
|
32
|
+
* (bcrypt/argon2/SHA-256 — its choice) and persist only the hash + prefix.
|
|
33
|
+
*/
|
|
34
|
+
export function generateBearerToken(opts = {}) {
|
|
35
|
+
const scheme = opts.scheme ?? DEFAULT_SCHEME;
|
|
36
|
+
if (!SEGMENT_RE.test(scheme)) {
|
|
37
|
+
throw new Error(`Invalid token scheme '${scheme}' (must be lowercase alnum/dash)`);
|
|
38
|
+
}
|
|
39
|
+
if (opts.scope !== undefined && !SEGMENT_RE.test(opts.scope)) {
|
|
40
|
+
throw new Error(`Invalid token scope '${opts.scope}' (must be lowercase alnum/dash)`);
|
|
41
|
+
}
|
|
42
|
+
const bytes = opts.byteLength ?? DEFAULT_BYTES;
|
|
43
|
+
if (!Number.isInteger(bytes) || bytes < MIN_BYTES) {
|
|
44
|
+
throw new Error(`byteLength must be an integer >= ${MIN_BYTES} for adequate entropy`);
|
|
45
|
+
}
|
|
46
|
+
const secret = randomBytes(bytes).toString('base64url');
|
|
47
|
+
const token = opts.scope ? `${scheme}_${opts.scope}_${secret}` : `${scheme}_${secret}`;
|
|
48
|
+
return { token, prefix: bearerTokenPrefix(token) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The first `length` characters of a token. Stored alongside the hash so
|
|
52
|
+
* a lookup is `WHERE prefix = ?` (one indexed read) rather than a scan +
|
|
53
|
+
* hash-compare over every row. NOT a secret — safe to show in a UI.
|
|
54
|
+
*/
|
|
55
|
+
export function bearerTokenPrefix(token, length = DEFAULT_PREFIX_LENGTH) {
|
|
56
|
+
return token.slice(0, length);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse a `<scheme>_<scope>_<secret>` token into its parts, or `null` if
|
|
60
|
+
* the shape doesn't match (callers should fail closed on `null`). The
|
|
61
|
+
* secret must be at least 16 URL-safe chars, which rejects obviously
|
|
62
|
+
* truncated or malformed tokens before any DB / hash work.
|
|
63
|
+
*/
|
|
64
|
+
export function parseScopedToken(token) {
|
|
65
|
+
const m = /^(?<scheme>[a-z0-9-]+)_(?<scope>[a-z0-9-]+)_(?<secret>[A-Za-z0-9_-]{16,})$/.exec(token);
|
|
66
|
+
if (!m?.groups)
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
scheme: m.groups['scheme'],
|
|
70
|
+
scope: m.groups['scope'],
|
|
71
|
+
secret: m.groups['secret'],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract the token from an `Authorization` header value, stripping a
|
|
76
|
+
* leading `Bearer ` (case-insensitive). Returns the raw value when no
|
|
77
|
+
* scheme prefix is present, or `null` for an empty/missing header.
|
|
78
|
+
*/
|
|
79
|
+
export function parseBearer(header) {
|
|
80
|
+
if (!header)
|
|
81
|
+
return null;
|
|
82
|
+
const trimmed = header.trim();
|
|
83
|
+
if (!trimmed)
|
|
84
|
+
return null;
|
|
85
|
+
const lower = trimmed.toLowerCase();
|
|
86
|
+
// A bare `Bearer` scheme with no token (e.g. `Bearer `) carries no credential.
|
|
87
|
+
if (lower === 'bearer')
|
|
88
|
+
return null;
|
|
89
|
+
if (lower.startsWith('bearer ')) {
|
|
90
|
+
return trimmed.slice(7).trim() || null;
|
|
91
|
+
}
|
|
92
|
+
return trimmed;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the transport-agnostic MCP server.
|
|
3
|
+
*
|
|
4
|
+
* The package is intentionally dependency-light: `zod` is a *type-only*
|
|
5
|
+
* peer dependency (the dispatcher only ever calls `.safeParse` on a
|
|
6
|
+
* schema the host supplies, and the JSON-Schema serializer reads
|
|
7
|
+
* `._def` structurally), so nothing here pulls zod in at runtime.
|
|
8
|
+
*
|
|
9
|
+
* The two generic parameters thread two host-owned shapes through the
|
|
10
|
+
* dispatcher:
|
|
11
|
+
* - `TContext` — the per-call value handed to every `McpTool.run`.
|
|
12
|
+
* The CMS uses `{ tenant, actorTokenPrefix, ... }`.
|
|
13
|
+
* - `TPrincipal` — the opaque authenticated identity returned by
|
|
14
|
+
* `authenticate` and forwarded to `rateLimit` /
|
|
15
|
+
* `wrapToolCall` / `createContext`. The CMS uses the
|
|
16
|
+
* `{ token, tenant }` pair from its bearer-token check.
|
|
17
|
+
*/
|
|
18
|
+
import type { ZodType, ZodTypeDef } from 'zod';
|
|
19
|
+
export interface JsonRpcRequest {
|
|
20
|
+
jsonrpc: '2.0';
|
|
21
|
+
id: number | string | null;
|
|
22
|
+
method: string;
|
|
23
|
+
params?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface JsonRpcError {
|
|
26
|
+
code: number;
|
|
27
|
+
message: string;
|
|
28
|
+
data?: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface JsonRpcResponse {
|
|
31
|
+
jsonrpc: '2.0';
|
|
32
|
+
id: number | string | null;
|
|
33
|
+
result?: unknown;
|
|
34
|
+
error?: JsonRpcError;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A single callable tool. `inputSchema` is any Zod schema; the `unknown`
|
|
38
|
+
* Input variance lets schemas using `.default()` (where Zod splits the
|
|
39
|
+
* Input/Output types so Input becomes optional) satisfy this type.
|
|
40
|
+
*/
|
|
41
|
+
export interface McpTool<TInput = unknown, TContext = unknown, TOutput = unknown> {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema: ZodType<TInput, ZodTypeDef, unknown>;
|
|
45
|
+
run(args: TInput, ctx: TContext): Promise<TOutput>;
|
|
46
|
+
}
|
|
47
|
+
/** A keyed bag of tools sharing one `TContext`. */
|
|
48
|
+
export type McpToolRegistry<TContext = unknown> = Record<string, McpTool<any, TContext, any>>;
|
|
49
|
+
export interface McpServerInfo {
|
|
50
|
+
name: string;
|
|
51
|
+
version: string;
|
|
52
|
+
}
|
|
53
|
+
/** Outcome of {@link McpServerConfig.authenticate}. */
|
|
54
|
+
export interface McpAuthResult<TPrincipal> {
|
|
55
|
+
/**
|
|
56
|
+
* Tool allow-list for this principal. `null` = every registered tool
|
|
57
|
+
* is callable. A populated set restricts both `tools/list` and
|
|
58
|
+
* `tools/call`.
|
|
59
|
+
*/
|
|
60
|
+
allowedTools: ReadonlySet<string> | null;
|
|
61
|
+
/** Opaque identity forwarded to the other hooks. */
|
|
62
|
+
principal: TPrincipal;
|
|
63
|
+
}
|
|
64
|
+
/** Metadata handed to {@link McpServerConfig.wrapToolCall}. */
|
|
65
|
+
export interface McpToolCallInfo<TPrincipal> {
|
|
66
|
+
toolName: string;
|
|
67
|
+
/** The validated, schema-coerced tool arguments. */
|
|
68
|
+
input: unknown;
|
|
69
|
+
principal: TPrincipal;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Everything the dispatcher needs that is *host-specific*. Authentication,
|
|
73
|
+
* rate limiting, audit logging, and context assembly are all hooks so the
|
|
74
|
+
* protocol loop itself stays free of any app concern.
|
|
75
|
+
*/
|
|
76
|
+
export interface McpServerConfig<TContext, TPrincipal> {
|
|
77
|
+
serverInfo: McpServerInfo;
|
|
78
|
+
tools: McpToolRegistry<TContext>;
|
|
79
|
+
/** Defaults to `'2025-03-26'`. */
|
|
80
|
+
protocolVersion?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Resolve + authorize the caller. Throw to reject — errors carrying a
|
|
83
|
+
* numeric `statusCode` (and optional `rpcCode` / `data` / `retryAfterSec`)
|
|
84
|
+
* are mapped to the JSON-RPC error response; otherwise a 401 is assumed.
|
|
85
|
+
*/
|
|
86
|
+
authenticate(authHeader: string | null): Promise<McpAuthResult<TPrincipal>>;
|
|
87
|
+
/** Build the per-call context passed to the tool's `run`. May be async. */
|
|
88
|
+
createContext(principal: TPrincipal, toolName: string): TContext | Promise<TContext>;
|
|
89
|
+
/** Optional pre-dispatch throttle. Throw (ideally an {@link McpError}) to reject. */
|
|
90
|
+
rateLimit?(principal: TPrincipal, toolName: string): Promise<void>;
|
|
91
|
+
/** Optional wrapper around each tool invocation (e.g. an audit-log bracket). */
|
|
92
|
+
wrapToolCall?<T>(info: McpToolCallInfo<TPrincipal>, run: () => Promise<T>): Promise<T>;
|
|
93
|
+
}
|
|
94
|
+
export interface McpHandlerInput {
|
|
95
|
+
/** The raw `Authorization` header value, or null. */
|
|
96
|
+
authHeader: string | null;
|
|
97
|
+
/** The parsed JSON-RPC request body. */
|
|
98
|
+
body: unknown;
|
|
99
|
+
}
|
|
100
|
+
export interface McpHandlerOutput {
|
|
101
|
+
/** HTTP status the transport should respond with. */
|
|
102
|
+
statusCode: number;
|
|
103
|
+
/** The JSON-RPC envelope to serialize back to the client. */
|
|
104
|
+
response: JsonRpcResponse;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the transport-agnostic MCP server.
|
|
3
|
+
*
|
|
4
|
+
* The package is intentionally dependency-light: `zod` is a *type-only*
|
|
5
|
+
* peer dependency (the dispatcher only ever calls `.safeParse` on a
|
|
6
|
+
* schema the host supplies, and the JSON-Schema serializer reads
|
|
7
|
+
* `._def` structurally), so nothing here pulls zod in at runtime.
|
|
8
|
+
*
|
|
9
|
+
* The two generic parameters thread two host-owned shapes through the
|
|
10
|
+
* dispatcher:
|
|
11
|
+
* - `TContext` — the per-call value handed to every `McpTool.run`.
|
|
12
|
+
* The CMS uses `{ tenant, actorTokenPrefix, ... }`.
|
|
13
|
+
* - `TPrincipal` — the opaque authenticated identity returned by
|
|
14
|
+
* `authenticate` and forwarded to `rateLimit` /
|
|
15
|
+
* `wrapToolCall` / `createContext`. The CMS uses the
|
|
16
|
+
* `{ token, tenant }` pair from its bearer-token check.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@venturekit/mcp",
|
|
3
|
+
"version": "0.0.0-dev.20260608102211",
|
|
4
|
+
"description": "Transport-agnostic Model Context Protocol (MCP) server primitives for VentureKit: JSON-RPC dispatch, a typed tool registry, and a Zod→JSON-Schema serializer.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src/migrations/*.sql"
|
|
11
|
+
],
|
|
12
|
+
"vk": {
|
|
13
|
+
"migrations": "src/migrations"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/venturekit-dev/venturekit.private.git",
|
|
18
|
+
"directory": "packages/mcp"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"registry": "https://registry.npmjs.org",
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"venturekit",
|
|
33
|
+
"mcp",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"json-rpc",
|
|
36
|
+
"tools",
|
|
37
|
+
"agents"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"zod": "^3.22.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.6.0",
|
|
44
|
+
"typescript": "^5.3.0",
|
|
45
|
+
"zod": "^3.22.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc && node -e \"require('fs').cpSync('src/migrations','dist/migrations',{recursive:true})\"",
|
|
49
|
+
"dev": "tsc --watch",
|
|
50
|
+
"clean": "rm -rf dist"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
-- @venturekit/mcp — initial schema: mcp_tokens.
|
|
2
|
+
--
|
|
3
|
+
-- Storage for the bearer tokens an MCP server issues to agents
|
|
4
|
+
-- (Cascade/Windsurf workflows, CI bots, scripts). Ships WITH the
|
|
5
|
+
-- package so any app that depends on `@venturekit/mcp` gets token
|
|
6
|
+
-- management out of the box — `vk migrate` auto-discovers this file via
|
|
7
|
+
-- the package's `vk.migrations` field (mirrors @venturekit-pro/audit's
|
|
8
|
+
-- `audit_events`).
|
|
9
|
+
--
|
|
10
|
+
-- # Generic by construction
|
|
11
|
+
--
|
|
12
|
+
-- The package owns the MCP protocol, not any host's tenancy model, so
|
|
13
|
+
-- the owner key is a free-form `scope` (text) — NOT a foreign key into
|
|
14
|
+
-- some `tenants` table that only the CMS has. A host maps `scope` to
|
|
15
|
+
-- whatever it owns: the VentureKit CMS uses the tenant slug (which is
|
|
16
|
+
-- also the token's `vk_<scope>_…` segment, letting the verifier resolve
|
|
17
|
+
-- the owner before hashing). `created_by` is likewise a bare text audit
|
|
18
|
+
-- field (a Cognito sub, a user id, a CI run id — the host decides), with
|
|
19
|
+
-- no FK.
|
|
20
|
+
--
|
|
21
|
+
-- # Security
|
|
22
|
+
--
|
|
23
|
+
-- Only a hash of the plaintext + a short lookup `prefix` are stored; the
|
|
24
|
+
-- plaintext is shown once at issuance and is unrecoverable. The hash is
|
|
25
|
+
-- scrypt (see `store.ts:hashTokenSecret`) — self-describing
|
|
26
|
+
-- (`scrypt$N$r$p$salt$dk`) so params can evolve without a column change.
|
|
27
|
+
|
|
28
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS mcp_tokens (
|
|
31
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
-- Owner key. Host-defined; the CMS uses the tenant slug. Also the
|
|
33
|
+
-- `vk_<scope>_…` segment of the plaintext, so the verifier can narrow
|
|
34
|
+
-- to one owner before hashing.
|
|
35
|
+
scope text NOT NULL,
|
|
36
|
+
-- Human label shown in the issuing UI ("Cascade workflow", "CI bot").
|
|
37
|
+
label text NOT NULL,
|
|
38
|
+
-- First chars of the plaintext (`vk_<scope>_…`) — the lookup key the
|
|
39
|
+
-- verifier narrows on before hash-comparing candidates.
|
|
40
|
+
prefix varchar(24) NOT NULL,
|
|
41
|
+
-- scrypt hash of the full plaintext (`scrypt$N$r$p$salt$dk`).
|
|
42
|
+
token_hash text NOT NULL,
|
|
43
|
+
-- Comma-separated MCP tool whitelist; NULL = every registered tool.
|
|
44
|
+
allowed_tools text,
|
|
45
|
+
last_used_at timestamptz,
|
|
46
|
+
revoked_at timestamptz,
|
|
47
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
48
|
+
-- Free-form audit field (no FK): a user id / Cognito sub / CI id.
|
|
49
|
+
created_by text
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- Label is unique per scope: stops accidental dupes in the UI and
|
|
53
|
+
-- bounds the verifier's prefix-collision hash loop.
|
|
54
|
+
CREATE UNIQUE INDEX IF NOT EXISTS mcp_tokens_scope_label_unique
|
|
55
|
+
ON mcp_tokens (scope, lower(label));
|
|
56
|
+
|
|
57
|
+
-- Verifier hot path: WHERE scope = $1 AND prefix = $2 AND revoked_at IS NULL.
|
|
58
|
+
CREATE INDEX IF NOT EXISTS mcp_tokens_lookup_idx
|
|
59
|
+
ON mcp_tokens (scope, prefix) WHERE revoked_at IS NULL;
|
|
60
|
+
|
|
61
|
+
-- Owner list: WHERE scope = $1 ORDER BY created_at DESC.
|
|
62
|
+
CREATE INDEX IF NOT EXISTS mcp_tokens_scope_created_idx
|
|
63
|
+
ON mcp_tokens (scope, created_at DESC);
|