clearance-mcp 1.0.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 ADDED
@@ -0,0 +1,118 @@
1
+ # clearance-mcp
2
+
3
+ > Model Context Protocol server for [Clearance](https://clearance.nauti-labs.com) — the human approval API for AI agent commerce.
4
+
5
+ Stop letting agents move money you didn't approve. One API call. The human approves with one tap. Any service verifies the signed token.
6
+
7
+ ---
8
+
9
+ ## What This Is
10
+
11
+ An MCP server that exposes Clearance as a set of tools Claude (and other MCP-compatible clients) can call natively. Your AI agent can:
12
+
13
+ - Request human approval before spending money
14
+ - Check approval status
15
+ - Verify signed tokens
16
+ - Manage API keys and usage
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ### Via npm (recommended)
23
+
24
+ ```bash
25
+ npm install -g clearance-mcp
26
+ ```
27
+
28
+ Or run directly via npx:
29
+
30
+ ```bash
31
+ npx -y clearance-mcp
32
+ ```
33
+
34
+ ### Via GitHub (if npm not available)
35
+
36
+ ```bash
37
+ npx -y github:Nauti-Labs/clearance-mcp
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Configuration
43
+
44
+ ### Claude Desktop
45
+
46
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "clearance": {
52
+ "command": "npx",
53
+ "args": ["-y", "clearance-mcp"],
54
+ "env": {
55
+ "CLEARANCE_API_KEY": "clr_live_..."
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Environment Variables
63
+
64
+ | Variable | Required | Description |
65
+ |----------|----------|-------------|
66
+ | `CLEARANCE_API_KEY` | ✅ | Your Clearance API key (get free at clearance.nauti-labs.com) |
67
+ | `CLEARANCE_BASE_URL` | ❌ | Override API base URL (default: https://clearance.nauti-labs.com) |
68
+
69
+ ---
70
+
71
+ ## Available Tools
72
+
73
+ | Tool | Description |
74
+ |------|-------------|
75
+ | `clearances_create` | Create a new human approval request |
76
+ | `clearances_get` | Get a single clearance by ID |
77
+ | `clearances_list` | List all clearances with optional filtering |
78
+ | `clearances_approve` | Approve a pending clearance |
79
+ | `clearances_deny` | Deny a pending clearance |
80
+ | `verify_token` | Verify a signed approval token |
81
+ | `keys_create` | Create a new API key |
82
+ | `usage_get` | Check current usage and limits |
83
+
84
+ ---
85
+
86
+ ## Example Usage
87
+
88
+ In Claude, after the MCP server is connected:
89
+
90
+ > **You:** "I want to book a $500 flight. Can you ask for approval first?"
91
+ >
92
+ > **Claude:** *calls `clearances_create` with title="Book flight to Austin", budget_amount=500*
93
+ >
94
+ > **Claude:** "I've created an approval request. Here's the link: [Approve](https://clearance.nauti-labs.com/approve/clr_abc123). Once you approve, I'll proceed with the booking."
95
+
96
+ ---
97
+
98
+ ## Pricing
99
+
100
+ Clearance pricing:
101
+
102
+ - **Starter** — Free, 50 clearances/month
103
+ - **Pro** — $19/mo, 1,000 clearances/month
104
+ - **Scale** — $49/mo, 10,000 clearances/month
105
+
106
+ Get your free key at [clearance.nauti-labs.com](https://clearance.nauti-labs.com).
107
+
108
+ ---
109
+
110
+ ## Links
111
+
112
+ - **Product:** [clearance.nauti-labs.com](https://clearance.nauti-labs.com)
113
+ - **Company:** [nauti-labs.com](https://nauti-labs.com)
114
+ - **API Docs:** [clearance.nauti-labs.com/v1/docs](https://clearance.nauti-labs.com/v1/docs)
115
+
116
+ ---
117
+
118
+ Built by [Nauti-Labs LLC](https://nauti-labs.com) · Mont Belvieu, TX
@@ -0,0 +1,20 @@
1
+ import type { ClearanceRequest, ClearanceResponse, ClearanceListResponse, VerifyTokenResponse, KeyCreateRequest, KeyCreateResponse, UsageResponse } from "./types.js";
2
+ export declare class ClearanceClient {
3
+ private baseUrl;
4
+ private apiKey;
5
+ private timeoutMs;
6
+ constructor(apiKey: string, baseUrl?: string, timeoutMs?: number);
7
+ private request;
8
+ createClearance(data: ClearanceRequest): Promise<ClearanceResponse>;
9
+ getClearance(id: string): Promise<ClearanceResponse>;
10
+ listClearances(params?: {
11
+ status?: string;
12
+ limit?: number;
13
+ offset?: number;
14
+ }): Promise<ClearanceListResponse>;
15
+ approveClearance(id: string): Promise<ClearanceResponse>;
16
+ denyClearance(id: string, reason?: string): Promise<ClearanceResponse>;
17
+ verifyToken(token: string): Promise<VerifyTokenResponse>;
18
+ createKey(data: KeyCreateRequest): Promise<KeyCreateResponse>;
19
+ getUsage(): Promise<UsageResponse>;
20
+ }
package/dist/client.js ADDED
@@ -0,0 +1,68 @@
1
+ export class ClearanceClient {
2
+ baseUrl;
3
+ apiKey;
4
+ timeoutMs;
5
+ constructor(apiKey, baseUrl = "https://clearance.nauti-labs.com", timeoutMs = 10000) {
6
+ this.apiKey = apiKey;
7
+ this.baseUrl = baseUrl.replace(/\/$/, "");
8
+ this.timeoutMs = timeoutMs;
9
+ }
10
+ async request(method, path, body) {
11
+ const url = `${this.baseUrl}${path}`;
12
+ const controller = new AbortController();
13
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
14
+ try {
15
+ const response = await fetch(url, {
16
+ method,
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ "X-API-Key": this.apiKey,
20
+ },
21
+ body: body ? JSON.stringify(body) : undefined,
22
+ signal: controller.signal,
23
+ });
24
+ clearTimeout(timeout);
25
+ if (!response.ok) {
26
+ const text = await response.text();
27
+ throw new Error(`Clearance API error ${response.status}: ${text || response.statusText}`);
28
+ }
29
+ return (await response.json());
30
+ }
31
+ catch (err) {
32
+ clearTimeout(timeout);
33
+ throw err;
34
+ }
35
+ }
36
+ async createClearance(data) {
37
+ return this.request("POST", "/v1/clearances", data);
38
+ }
39
+ async getClearance(id) {
40
+ return this.request("GET", `/v1/clearances/${encodeURIComponent(id)}`);
41
+ }
42
+ async listClearances(params) {
43
+ const query = new URLSearchParams();
44
+ if (params?.status)
45
+ query.set("status", params.status);
46
+ if (params?.limit !== undefined)
47
+ query.set("limit", String(params.limit));
48
+ if (params?.offset !== undefined)
49
+ query.set("offset", String(params.offset));
50
+ const qs = query.toString();
51
+ return this.request("GET", `/v1/clearances${qs ? "?" + qs : ""}`);
52
+ }
53
+ async approveClearance(id) {
54
+ return this.request("POST", `/v1/clearances/${encodeURIComponent(id)}/approve`);
55
+ }
56
+ async denyClearance(id, reason) {
57
+ return this.request("POST", `/v1/clearances/${encodeURIComponent(id)}/deny`, { reason });
58
+ }
59
+ async verifyToken(token) {
60
+ return this.request("GET", `/v1/verify/${encodeURIComponent(token)}`);
61
+ }
62
+ async createKey(data) {
63
+ return this.request("POST", "/v1/keys", data);
64
+ }
65
+ async getUsage() {
66
+ return this.request("GET", "/v1/usage");
67
+ }
68
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { ClearanceClient } from "./client.js";
6
+ const API_KEY = process.env.CLEARANCE_API_KEY;
7
+ const BASE_URL = process.env.CLEARANCE_BASE_URL || "https://clearance.nauti-labs.com";
8
+ if (!API_KEY) {
9
+ console.error("CLEARANCE_API_KEY environment variable is required");
10
+ process.exit(1);
11
+ }
12
+ const client = new ClearanceClient(API_KEY, BASE_URL);
13
+ const TOOLS = [
14
+ {
15
+ name: "clearances_create",
16
+ description: "Create a new human approval request. The agent describes what it wants to do, the human gets a URL to approve or deny with one tap. Returns the clearance with an approve_url and deny_url.",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ title: {
21
+ type: "string",
22
+ description: "Human-readable description of what the agent wants to do",
23
+ },
24
+ scope: {
25
+ type: "string",
26
+ description: "Machine-readable scope tag (e.g. 'purchase:flight', 'transfer:usdc')",
27
+ },
28
+ budget_amount: {
29
+ type: "number",
30
+ description: "Maximum amount the agent is requesting to spend",
31
+ },
32
+ budget_currency: {
33
+ type: "string",
34
+ description: "Currency code (default: USD)",
35
+ default: "USD",
36
+ },
37
+ metadata: {
38
+ type: "object",
39
+ description: "Arbitrary key-value data attached to the clearance",
40
+ },
41
+ webhook_url: {
42
+ type: "string",
43
+ description: "URL to POST when the clearance is approved or denied",
44
+ },
45
+ },
46
+ required: ["title"],
47
+ },
48
+ },
49
+ {
50
+ name: "clearances_get",
51
+ description: "Get a single clearance by ID. Returns the full status, token, approval URL, and metadata.",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ id: {
56
+ type: "string",
57
+ description: "Clearance ID (e.g. clr_a1b2c3d4)",
58
+ },
59
+ },
60
+ required: ["id"],
61
+ },
62
+ },
63
+ {
64
+ name: "clearances_list",
65
+ description: "List all clearances with optional filtering by status, pagination.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ status: {
70
+ type: "string",
71
+ description: "Filter by status: pending, approved, denied, revoked",
72
+ },
73
+ limit: {
74
+ type: "number",
75
+ description: "Max items per page (default: 20)",
76
+ default: 20,
77
+ },
78
+ offset: {
79
+ type: "number",
80
+ description: "Pagination offset (default: 0)",
81
+ default: 0,
82
+ },
83
+ },
84
+ required: [],
85
+ },
86
+ },
87
+ {
88
+ name: "clearances_approve",
89
+ description: "Approve a pending clearance by ID. This is typically done by the human, not the agent — but the API allows it for automated flows. Returns the updated clearance with a signed token.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ id: {
94
+ type: "string",
95
+ description: "Clearance ID to approve",
96
+ },
97
+ },
98
+ required: ["id"],
99
+ },
100
+ },
101
+ {
102
+ name: "clearances_deny",
103
+ description: "Deny a pending clearance by ID. Optionally provide a reason. Returns the updated clearance.",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ id: {
108
+ type: "string",
109
+ description: "Clearance ID to deny",
110
+ },
111
+ reason: {
112
+ type: "string",
113
+ description: "Optional reason for denial",
114
+ },
115
+ },
116
+ required: ["id"],
117
+ },
118
+ },
119
+ {
120
+ name: "verify_token",
121
+ description: "Verify a signed clearance token. Any service can verify a token without authentication — this is how you prove a clearance was approved.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ token: {
126
+ type: "string",
127
+ description: "The JWT token returned when a clearance was approved",
128
+ },
129
+ },
130
+ required: ["token"],
131
+ },
132
+ },
133
+ {
134
+ name: "keys_create",
135
+ description: "Create a new Clearance API key. You can request a specific tier (starter, pro, scale). Returns the new key and its limits.",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {
139
+ tier: {
140
+ type: "string",
141
+ description: "Tier: starter (free), pro ($19/mo), scale ($49/mo)",
142
+ default: "starter",
143
+ },
144
+ email: {
145
+ type: "string",
146
+ description: "Optional email for key notifications",
147
+ },
148
+ },
149
+ required: [],
150
+ },
151
+ },
152
+ {
153
+ name: "usage_get",
154
+ description: "Get current usage and limits for this API key. Shows tier, monthly quota, used count, remaining count, and reset date.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {},
158
+ required: [],
159
+ },
160
+ },
161
+ ];
162
+ const server = new Server({
163
+ name: "clearance-mcp",
164
+ version: "1.0.0",
165
+ }, {
166
+ capabilities: {
167
+ tools: {},
168
+ },
169
+ });
170
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
171
+ return { tools: TOOLS };
172
+ });
173
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
174
+ const { name, arguments: args } = request.params;
175
+ try {
176
+ let result;
177
+ switch (name) {
178
+ case "clearances_create": {
179
+ const { title, scope, budget_amount, budget_currency, metadata, webhook_url } = args;
180
+ result = await client.createClearance({
181
+ title: String(title),
182
+ scope: scope ? String(scope) : undefined,
183
+ budget_amount: typeof budget_amount === "number" ? budget_amount : undefined,
184
+ budget_currency: budget_currency ? String(budget_currency) : undefined,
185
+ metadata: typeof metadata === "object" && metadata !== null ? metadata : undefined,
186
+ webhook_url: webhook_url ? String(webhook_url) : undefined,
187
+ });
188
+ break;
189
+ }
190
+ case "clearances_get": {
191
+ const { id } = args;
192
+ result = await client.getClearance(id);
193
+ break;
194
+ }
195
+ case "clearances_list": {
196
+ const { status, limit, offset } = args;
197
+ result = await client.listClearances({
198
+ status: status ? String(status) : undefined,
199
+ limit: typeof limit === "number" ? limit : undefined,
200
+ offset: typeof offset === "number" ? offset : undefined,
201
+ });
202
+ break;
203
+ }
204
+ case "clearances_approve": {
205
+ const { id } = args;
206
+ result = await client.approveClearance(id);
207
+ break;
208
+ }
209
+ case "clearances_deny": {
210
+ const { id, reason } = args;
211
+ result = await client.denyClearance(id, reason);
212
+ break;
213
+ }
214
+ case "verify_token": {
215
+ const { token } = args;
216
+ result = await client.verifyToken(token);
217
+ break;
218
+ }
219
+ case "keys_create": {
220
+ const { tier, email } = args;
221
+ result = await client.createKey({
222
+ tier: tier ? String(tier) : undefined,
223
+ email: email ? String(email) : undefined,
224
+ });
225
+ break;
226
+ }
227
+ case "usage_get": {
228
+ result = await client.getUsage();
229
+ break;
230
+ }
231
+ default:
232
+ throw new Error(`Unknown tool: ${name}`);
233
+ }
234
+ return {
235
+ content: [
236
+ {
237
+ type: "text",
238
+ text: JSON.stringify(result, null, 2),
239
+ },
240
+ ],
241
+ };
242
+ }
243
+ catch (err) {
244
+ const message = err instanceof Error ? err.message : String(err);
245
+ return {
246
+ content: [
247
+ {
248
+ type: "text",
249
+ text: `Error: ${message}`,
250
+ },
251
+ ],
252
+ isError: true,
253
+ };
254
+ }
255
+ });
256
+ async function main() {
257
+ const transport = new StdioServerTransport();
258
+ await server.connect(transport);
259
+ console.error("Clearance MCP server running on stdio");
260
+ }
261
+ main().catch((err) => {
262
+ console.error("Fatal error:", err);
263
+ process.exit(1);
264
+ });
@@ -0,0 +1,67 @@
1
+ export interface ClearanceRequest {
2
+ title: string;
3
+ scope?: string;
4
+ budget_amount?: number;
5
+ budget_currency?: string;
6
+ metadata?: Record<string, unknown>;
7
+ webhook_url?: string;
8
+ }
9
+ export interface ClearanceResponse {
10
+ id: string;
11
+ title: string;
12
+ scope: string | null;
13
+ budget_amount: number | null;
14
+ budget_currency: string | null;
15
+ status: "pending" | "approved" | "denied" | "revoked";
16
+ approve_url: string;
17
+ deny_url: string;
18
+ token: string | null;
19
+ metadata: Record<string, unknown> | null;
20
+ created_at: string;
21
+ approved_at: string | null;
22
+ denied_at: string | null;
23
+ denied_reason: string | null;
24
+ }
25
+ export interface ClearanceListResponse {
26
+ items: ClearanceResponse[];
27
+ total: number;
28
+ limit: number;
29
+ offset: number;
30
+ }
31
+ export interface VerifyTokenResponse {
32
+ valid: boolean;
33
+ clearance_id: string;
34
+ status: string;
35
+ created_at: string;
36
+ approved_at: string | null;
37
+ metadata: Record<string, unknown> | null;
38
+ }
39
+ export interface KeyCreateRequest {
40
+ tier?: string;
41
+ email?: string;
42
+ }
43
+ export interface KeyCreateResponse {
44
+ key: string;
45
+ tier: string;
46
+ clearances_per_month: number;
47
+ created_at: string;
48
+ }
49
+ export interface UsageResponse {
50
+ tier: string;
51
+ clearances_per_month: number;
52
+ used_this_month: number;
53
+ remaining_this_month: number;
54
+ reset_date: string;
55
+ }
56
+ export interface ClearanceApproveRequest {
57
+ id: string;
58
+ }
59
+ export interface ClearanceDenyRequest {
60
+ id: string;
61
+ reason?: string;
62
+ }
63
+ export interface ClearanceListRequest {
64
+ status?: string;
65
+ limit?: number;
66
+ offset?: number;
67
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ // Clearance API types
2
+ export {};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "clearance-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Clearance — human approval API for AI agent commerce",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "clearance-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "clearance",
17
+ "approval",
18
+ "agent",
19
+ "ai",
20
+ "payments"
21
+ ],
22
+ "author": "Nauti-Labs LLC",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.0.0",
26
+ "zod": "^3.22.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.19.43",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }
package/src/client.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { ClearanceRequest, ClearanceResponse, ClearanceListResponse, VerifyTokenResponse, KeyCreateRequest, KeyCreateResponse, UsageResponse } from "./types.js";
2
+
3
+ export class ClearanceClient {
4
+ private baseUrl: string;
5
+ private apiKey: string;
6
+ private timeoutMs: number;
7
+
8
+ constructor(apiKey: string, baseUrl = "https://clearance.nauti-labs.com", timeoutMs = 10000) {
9
+ this.apiKey = apiKey;
10
+ this.baseUrl = baseUrl.replace(/\/$/, "");
11
+ this.timeoutMs = timeoutMs;
12
+ }
13
+
14
+ private async request<T>(
15
+ method: string,
16
+ path: string,
17
+ body?: unknown
18
+ ): Promise<T> {
19
+ const url = `${this.baseUrl}${path}`;
20
+ const controller = new AbortController();
21
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
22
+
23
+ try {
24
+ const response = await fetch(url, {
25
+ method,
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ "X-API-Key": this.apiKey,
29
+ },
30
+ body: body ? JSON.stringify(body) : undefined,
31
+ signal: controller.signal,
32
+ });
33
+
34
+ clearTimeout(timeout);
35
+
36
+ if (!response.ok) {
37
+ const text = await response.text();
38
+ throw new Error(
39
+ `Clearance API error ${response.status}: ${text || response.statusText}`
40
+ );
41
+ }
42
+
43
+ return (await response.json()) as T;
44
+ } catch (err) {
45
+ clearTimeout(timeout);
46
+ throw err;
47
+ }
48
+ }
49
+
50
+ async createClearance(data: ClearanceRequest): Promise<ClearanceResponse> {
51
+ return this.request<ClearanceResponse>("POST", "/v1/clearances", data);
52
+ }
53
+
54
+ async getClearance(id: string): Promise<ClearanceResponse> {
55
+ return this.request<ClearanceResponse>("GET", `/v1/clearances/${encodeURIComponent(id)}`);
56
+ }
57
+
58
+ async listClearances(params?: {
59
+ status?: string;
60
+ limit?: number;
61
+ offset?: number;
62
+ }): Promise<ClearanceListResponse> {
63
+ const query = new URLSearchParams();
64
+ if (params?.status) query.set("status", params.status);
65
+ if (params?.limit !== undefined) query.set("limit", String(params.limit));
66
+ if (params?.offset !== undefined) query.set("offset", String(params.offset));
67
+ const qs = query.toString();
68
+ return this.request<ClearanceListResponse>("GET", `/v1/clearances${qs ? "?" + qs : ""}`);
69
+ }
70
+
71
+ async approveClearance(id: string): Promise<ClearanceResponse> {
72
+ return this.request<ClearanceResponse>("POST", `/v1/clearances/${encodeURIComponent(id)}/approve`);
73
+ }
74
+
75
+ async denyClearance(id: string, reason?: string): Promise<ClearanceResponse> {
76
+ return this.request<ClearanceResponse>("POST", `/v1/clearances/${encodeURIComponent(id)}/deny`, { reason });
77
+ }
78
+
79
+ async verifyToken(token: string): Promise<VerifyTokenResponse> {
80
+ return this.request<VerifyTokenResponse>("GET", `/v1/verify/${encodeURIComponent(token)}`);
81
+ }
82
+
83
+ async createKey(data: KeyCreateRequest): Promise<KeyCreateResponse> {
84
+ return this.request<KeyCreateResponse>("POST", "/v1/keys", data);
85
+ }
86
+
87
+ async getUsage(): Promise<UsageResponse> {
88
+ return this.request<UsageResponse>("GET", "/v1/usage");
89
+ }
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ type Tool,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+ import { ClearanceClient } from "./client.js";
10
+
11
+ const API_KEY = process.env.CLEARANCE_API_KEY;
12
+ const BASE_URL = process.env.CLEARANCE_BASE_URL || "https://clearance.nauti-labs.com";
13
+
14
+ if (!API_KEY) {
15
+ console.error("CLEARANCE_API_KEY environment variable is required");
16
+ process.exit(1);
17
+ }
18
+
19
+ const client = new ClearanceClient(API_KEY, BASE_URL);
20
+
21
+ const TOOLS: Tool[] = [
22
+ {
23
+ name: "clearances_create",
24
+ description: "Create a new human approval request. The agent describes what it wants to do, the human gets a URL to approve or deny with one tap. Returns the clearance with an approve_url and deny_url.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ title: {
29
+ type: "string",
30
+ description: "Human-readable description of what the agent wants to do",
31
+ },
32
+ scope: {
33
+ type: "string",
34
+ description: "Machine-readable scope tag (e.g. 'purchase:flight', 'transfer:usdc')",
35
+ },
36
+ budget_amount: {
37
+ type: "number",
38
+ description: "Maximum amount the agent is requesting to spend",
39
+ },
40
+ budget_currency: {
41
+ type: "string",
42
+ description: "Currency code (default: USD)",
43
+ default: "USD",
44
+ },
45
+ metadata: {
46
+ type: "object",
47
+ description: "Arbitrary key-value data attached to the clearance",
48
+ },
49
+ webhook_url: {
50
+ type: "string",
51
+ description: "URL to POST when the clearance is approved or denied",
52
+ },
53
+ },
54
+ required: ["title"],
55
+ },
56
+ },
57
+ {
58
+ name: "clearances_get",
59
+ description: "Get a single clearance by ID. Returns the full status, token, approval URL, and metadata.",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {
63
+ id: {
64
+ type: "string",
65
+ description: "Clearance ID (e.g. clr_a1b2c3d4)",
66
+ },
67
+ },
68
+ required: ["id"],
69
+ },
70
+ },
71
+ {
72
+ name: "clearances_list",
73
+ description: "List all clearances with optional filtering by status, pagination.",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: {
77
+ status: {
78
+ type: "string",
79
+ description: "Filter by status: pending, approved, denied, revoked",
80
+ },
81
+ limit: {
82
+ type: "number",
83
+ description: "Max items per page (default: 20)",
84
+ default: 20,
85
+ },
86
+ offset: {
87
+ type: "number",
88
+ description: "Pagination offset (default: 0)",
89
+ default: 0,
90
+ },
91
+ },
92
+ required: [],
93
+ },
94
+ },
95
+ {
96
+ name: "clearances_approve",
97
+ description: "Approve a pending clearance by ID. This is typically done by the human, not the agent — but the API allows it for automated flows. Returns the updated clearance with a signed token.",
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ id: {
102
+ type: "string",
103
+ description: "Clearance ID to approve",
104
+ },
105
+ },
106
+ required: ["id"],
107
+ },
108
+ },
109
+ {
110
+ name: "clearances_deny",
111
+ description: "Deny a pending clearance by ID. Optionally provide a reason. Returns the updated clearance.",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ id: {
116
+ type: "string",
117
+ description: "Clearance ID to deny",
118
+ },
119
+ reason: {
120
+ type: "string",
121
+ description: "Optional reason for denial",
122
+ },
123
+ },
124
+ required: ["id"],
125
+ },
126
+ },
127
+ {
128
+ name: "verify_token",
129
+ description: "Verify a signed clearance token. Any service can verify a token without authentication — this is how you prove a clearance was approved.",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ token: {
134
+ type: "string",
135
+ description: "The JWT token returned when a clearance was approved",
136
+ },
137
+ },
138
+ required: ["token"],
139
+ },
140
+ },
141
+ {
142
+ name: "keys_create",
143
+ description: "Create a new Clearance API key. You can request a specific tier (starter, pro, scale). Returns the new key and its limits.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ tier: {
148
+ type: "string",
149
+ description: "Tier: starter (free), pro ($19/mo), scale ($49/mo)",
150
+ default: "starter",
151
+ },
152
+ email: {
153
+ type: "string",
154
+ description: "Optional email for key notifications",
155
+ },
156
+ },
157
+ required: [],
158
+ },
159
+ },
160
+ {
161
+ name: "usage_get",
162
+ description: "Get current usage and limits for this API key. Shows tier, monthly quota, used count, remaining count, and reset date.",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {},
166
+ required: [],
167
+ },
168
+ },
169
+ ];
170
+
171
+ const server = new Server(
172
+ {
173
+ name: "clearance-mcp",
174
+ version: "1.0.0",
175
+ },
176
+ {
177
+ capabilities: {
178
+ tools: {},
179
+ },
180
+ }
181
+ );
182
+
183
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
184
+ return { tools: TOOLS };
185
+ });
186
+
187
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
+ const { name, arguments: args } = request.params;
189
+
190
+ try {
191
+ let result: unknown;
192
+
193
+ switch (name) {
194
+ case "clearances_create": {
195
+ const { title, scope, budget_amount, budget_currency, metadata, webhook_url } = args as Record<string, unknown>;
196
+ result = await client.createClearance({
197
+ title: String(title),
198
+ scope: scope ? String(scope) : undefined,
199
+ budget_amount: typeof budget_amount === "number" ? budget_amount : undefined,
200
+ budget_currency: budget_currency ? String(budget_currency) : undefined,
201
+ metadata: typeof metadata === "object" && metadata !== null ? (metadata as Record<string, unknown>) : undefined,
202
+ webhook_url: webhook_url ? String(webhook_url) : undefined,
203
+ });
204
+ break;
205
+ }
206
+ case "clearances_get": {
207
+ const { id } = args as Record<string, string>;
208
+ result = await client.getClearance(id);
209
+ break;
210
+ }
211
+ case "clearances_list": {
212
+ const { status, limit, offset } = args as Record<string, unknown>;
213
+ result = await client.listClearances({
214
+ status: status ? String(status) : undefined,
215
+ limit: typeof limit === "number" ? limit : undefined,
216
+ offset: typeof offset === "number" ? offset : undefined,
217
+ });
218
+ break;
219
+ }
220
+ case "clearances_approve": {
221
+ const { id } = args as Record<string, string>;
222
+ result = await client.approveClearance(id);
223
+ break;
224
+ }
225
+ case "clearances_deny": {
226
+ const { id, reason } = args as Record<string, string>;
227
+ result = await client.denyClearance(id, reason);
228
+ break;
229
+ }
230
+ case "verify_token": {
231
+ const { token } = args as Record<string, string>;
232
+ result = await client.verifyToken(token);
233
+ break;
234
+ }
235
+ case "keys_create": {
236
+ const { tier, email } = args as Record<string, unknown>;
237
+ result = await client.createKey({
238
+ tier: tier ? String(tier) : undefined,
239
+ email: email ? String(email) : undefined,
240
+ });
241
+ break;
242
+ }
243
+ case "usage_get": {
244
+ result = await client.getUsage();
245
+ break;
246
+ }
247
+ default:
248
+ throw new Error(`Unknown tool: ${name}`);
249
+ }
250
+
251
+ return {
252
+ content: [
253
+ {
254
+ type: "text",
255
+ text: JSON.stringify(result, null, 2),
256
+ },
257
+ ],
258
+ };
259
+ } catch (err) {
260
+ const message = err instanceof Error ? err.message : String(err);
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: `Error: ${message}`,
266
+ },
267
+ ],
268
+ isError: true,
269
+ };
270
+ }
271
+ });
272
+
273
+ async function main() {
274
+ const transport = new StdioServerTransport();
275
+ await server.connect(transport);
276
+ console.error("Clearance MCP server running on stdio");
277
+ }
278
+
279
+ main().catch((err) => {
280
+ console.error("Fatal error:", err);
281
+ process.exit(1);
282
+ });
package/src/types.ts ADDED
@@ -0,0 +1,78 @@
1
+ // Clearance API types
2
+
3
+ export interface ClearanceRequest {
4
+ title: string;
5
+ scope?: string;
6
+ budget_amount?: number;
7
+ budget_currency?: string;
8
+ metadata?: Record<string, unknown>;
9
+ webhook_url?: string;
10
+ }
11
+
12
+ export interface ClearanceResponse {
13
+ id: string;
14
+ title: string;
15
+ scope: string | null;
16
+ budget_amount: number | null;
17
+ budget_currency: string | null;
18
+ status: "pending" | "approved" | "denied" | "revoked";
19
+ approve_url: string;
20
+ deny_url: string;
21
+ token: string | null;
22
+ metadata: Record<string, unknown> | null;
23
+ created_at: string;
24
+ approved_at: string | null;
25
+ denied_at: string | null;
26
+ denied_reason: string | null;
27
+ }
28
+
29
+ export interface ClearanceListResponse {
30
+ items: ClearanceResponse[];
31
+ total: number;
32
+ limit: number;
33
+ offset: number;
34
+ }
35
+
36
+ export interface VerifyTokenResponse {
37
+ valid: boolean;
38
+ clearance_id: string;
39
+ status: string;
40
+ created_at: string;
41
+ approved_at: string | null;
42
+ metadata: Record<string, unknown> | null;
43
+ }
44
+
45
+ export interface KeyCreateRequest {
46
+ tier?: string;
47
+ email?: string;
48
+ }
49
+
50
+ export interface KeyCreateResponse {
51
+ key: string;
52
+ tier: string;
53
+ clearances_per_month: number;
54
+ created_at: string;
55
+ }
56
+
57
+ export interface UsageResponse {
58
+ tier: string;
59
+ clearances_per_month: number;
60
+ used_this_month: number;
61
+ remaining_this_month: number;
62
+ reset_date: string;
63
+ }
64
+
65
+ export interface ClearanceApproveRequest {
66
+ id: string;
67
+ }
68
+
69
+ export interface ClearanceDenyRequest {
70
+ id: string;
71
+ reason?: string;
72
+ }
73
+
74
+ export interface ClearanceListRequest {
75
+ status?: string;
76
+ limit?: number;
77
+ offset?: number;
78
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }