@axiom-billing/mcp 0.1.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 +61 -0
- package/dist/apiClient.d.ts +26 -0
- package/dist/apiClient.js +69 -0
- package/dist/apiClient.js.map +1 -0
- package/dist/auth.d.ts +48 -0
- package/dist/auth.js +92 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/install.d.ts +2 -0
- package/dist/install.js +137 -0
- package/dist/install.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +49 -0
- package/dist/logger.js.map +1 -0
- package/dist/prompts/index.d.ts +29 -0
- package/dist/prompts/index.js +97 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/index.d.ts +37 -0
- package/dist/resources/index.js +79 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +198 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +197 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/zodToJsonSchema.d.ts +7 -0
- package/dist/zodToJsonSchema.js +46 -0
- package/dist/zodToJsonSchema.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @axiom-billing/mcp
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for Axiom. Exposes accounting tools to Claude Desktop, ChatGPT, Codex, or any MCP-capable AI client — so the user's AI can read reports, suggest reconciliations, and create draft invoices without writing custom glue code.
|
|
4
|
+
|
|
5
|
+
## Install (one command)
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx @axiom-billing/mcp install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That command:
|
|
12
|
+
|
|
13
|
+
1. Discovers your Axiom deployment's OAuth endpoints (`/.well-known/oauth-authorization-server`).
|
|
14
|
+
2. Dynamically registers a new OAuth client (RFC 7591) — no developer-portal step.
|
|
15
|
+
3. Opens your browser to the Axiom consent screen with PKCE.
|
|
16
|
+
4. Captures the redirect on a one-shot loopback listener (`http://127.0.0.1:<port>/callback`).
|
|
17
|
+
5. Exchanges the authorization code for access + refresh tokens.
|
|
18
|
+
6. Writes tokens to `~/.axiom-mcp/config.json` (mode 600).
|
|
19
|
+
7. Updates your Claude Desktop config to register the MCP server.
|
|
20
|
+
|
|
21
|
+
Restart Claude Desktop. The Axiom tools are now available in any conversation.
|
|
22
|
+
|
|
23
|
+
## Environment overrides
|
|
24
|
+
|
|
25
|
+
| Variable | Default | Meaning |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `AXIOM_API_URL` | `http://localhost:8200/api` | Base URL of the Axiom API |
|
|
28
|
+
| `AXIOM_SCOPES` | `read:ledger read:invoices read:contacts read:reports read:bank-transactions offline_access` | Space-separated OAuth scopes to request |
|
|
29
|
+
|
|
30
|
+
## What it exposes
|
|
31
|
+
|
|
32
|
+
### Read tools
|
|
33
|
+
|
|
34
|
+
- `find_contact(query)` — fuzzy match contacts.
|
|
35
|
+
- `summarize_ar_aging()` — open AR with aging buckets.
|
|
36
|
+
- `list_open_invoices(contactId?)` — unpaid invoices.
|
|
37
|
+
- `list_unmatched_bank_transactions()` — bank transactions awaiting reconciliation.
|
|
38
|
+
- `explain_ledger_entry(transactionId)` — full debit/credit breakdown of a transaction.
|
|
39
|
+
- `get_report(reportId, dateFrom?, dateTo?)` — runs any of the standard Axiom reports.
|
|
40
|
+
|
|
41
|
+
### Write tools (approval-gated)
|
|
42
|
+
|
|
43
|
+
These tools NEVER write directly. They submit a request to the per-company approval queue. A human admin/supervisor explicitly approves each one from the Axiom UI before it runs. The exact policy (auto-approve, require-approval, block) is set per-tool per-company.
|
|
44
|
+
|
|
45
|
+
- `create_invoice_draft(contactId, items, dueDate?)` — proposes a draft invoice. Never finalizes.
|
|
46
|
+
- `record_payment(invoiceId, amount, method?, receivedDate?)` — proposes a payment row.
|
|
47
|
+
- `propose_reconciliation(bankTransactionId)` — returns a suggested match. Pure read.
|
|
48
|
+
|
|
49
|
+
## Audit trail
|
|
50
|
+
|
|
51
|
+
Every API call from this server carries `X-Axiom-Source: mcp`. The Axiom audit log records every mutation with `actorType: ai` and `actorId` = the user the install belongs to. Approved write actions also link the approving admin in the audit row.
|
|
52
|
+
|
|
53
|
+
## Security
|
|
54
|
+
|
|
55
|
+
- Tokens at rest in `~/.axiom-mcp/config.json` with file mode 600.
|
|
56
|
+
- Refresh tokens rotate on every use (RFC 6749 §6); reuse of an old refresh token revokes the entire grant chain.
|
|
57
|
+
- Revoke this install at any time from **Connected Apps** in the Axiom UI.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
Internal.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client over Axiom's /api/v1 surface. Refreshes the access token
|
|
3
|
+
* before every call. Every mutation carries X-Axiom-Source: mcp so the audit
|
|
4
|
+
* log attributes correctly (actorType=ai).
|
|
5
|
+
*/
|
|
6
|
+
export declare class AxiomApiClient {
|
|
7
|
+
get<T = unknown>(path: string): Promise<T>;
|
|
8
|
+
post<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
9
|
+
put<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
10
|
+
delete<T = unknown>(path: string): Promise<T>;
|
|
11
|
+
private request;
|
|
12
|
+
getCompanyId(): string;
|
|
13
|
+
}
|
|
14
|
+
export declare const axiomApi: AxiomApiClient;
|
|
15
|
+
/** Standard /api/v1 envelope. */
|
|
16
|
+
export interface Envelope<T> {
|
|
17
|
+
success: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
data: T;
|
|
20
|
+
error: null | {
|
|
21
|
+
title: string;
|
|
22
|
+
status: number;
|
|
23
|
+
errorCode: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { refreshIfNeeded } from './auth.js';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Thin HTTP client over Axiom's /api/v1 surface. Refreshes the access token
|
|
6
|
+
* before every call. Every mutation carries X-Axiom-Source: mcp so the audit
|
|
7
|
+
* log attributes correctly (actorType=ai).
|
|
8
|
+
*/
|
|
9
|
+
export class AxiomApiClient {
|
|
10
|
+
async get(path) {
|
|
11
|
+
return await this.request('GET', path);
|
|
12
|
+
}
|
|
13
|
+
async post(path, body) {
|
|
14
|
+
return await this.request('POST', path, body);
|
|
15
|
+
}
|
|
16
|
+
async put(path, body) {
|
|
17
|
+
return await this.request('PUT', path, body);
|
|
18
|
+
}
|
|
19
|
+
async delete(path) {
|
|
20
|
+
return await this.request('DELETE', path);
|
|
21
|
+
}
|
|
22
|
+
async request(method, path, body) {
|
|
23
|
+
const cfg = loadConfig();
|
|
24
|
+
const token = await refreshIfNeeded(cfg);
|
|
25
|
+
const headers = {
|
|
26
|
+
Authorization: `Bearer ${token}`,
|
|
27
|
+
'X-Axiom-Source': 'mcp',
|
|
28
|
+
};
|
|
29
|
+
if (method !== 'GET' && method !== 'DELETE') {
|
|
30
|
+
headers['Content-Type'] = 'application/json';
|
|
31
|
+
headers['Idempotency-Key'] = randomBytes(16).toString('hex');
|
|
32
|
+
}
|
|
33
|
+
const res = await fetch(`${cfg.apiBaseUrl}${path}`, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
throw new Error(`Axiom API ${method} ${path} failed: ${res.status} ${text}`);
|
|
41
|
+
}
|
|
42
|
+
if (res.status === 204)
|
|
43
|
+
return undefined;
|
|
44
|
+
return await res.json();
|
|
45
|
+
}
|
|
46
|
+
getCompanyId() {
|
|
47
|
+
const cfg = loadConfig();
|
|
48
|
+
const fromToken = extractCompanyIdFromJwt(cfg.accessToken);
|
|
49
|
+
const companyId = cfg.companyId ?? fromToken;
|
|
50
|
+
if (!companyId) {
|
|
51
|
+
throw new Error('No companyId is bound to this MCP install. Reinstall and pick a company on the consent screen.');
|
|
52
|
+
}
|
|
53
|
+
return companyId;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function extractCompanyIdFromJwt(token) {
|
|
57
|
+
const parts = token.split('.');
|
|
58
|
+
if (parts.length < 2)
|
|
59
|
+
return null;
|
|
60
|
+
try {
|
|
61
|
+
const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
|
|
62
|
+
return payload.companyId ?? null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export const axiomApi = new AxiomApiClient();
|
|
69
|
+
//# sourceMappingURL=apiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.js","sourceRoot":"","sources":["../src/apiClient.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AxiomMcpConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discovery → DCR → Authorization-Code-with-PKCE → token-exchange,
|
|
4
|
+
* per the auth section of the automation enablement plan. The install command
|
|
5
|
+
* runs this once interactively; the server only uses {@link refreshIfNeeded}.
|
|
6
|
+
*/
|
|
7
|
+
export interface DiscoveryDocument {
|
|
8
|
+
issuer: string;
|
|
9
|
+
authorization_endpoint: string;
|
|
10
|
+
token_endpoint: string;
|
|
11
|
+
registration_endpoint: string;
|
|
12
|
+
revocation_endpoint: string;
|
|
13
|
+
jwks_uri: string;
|
|
14
|
+
scopes_supported: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function discover(apiBaseUrl: string): Promise<DiscoveryDocument>;
|
|
17
|
+
export declare function registerClient(registrationEndpoint: string, clientName: string, redirectUri: string): Promise<{
|
|
18
|
+
clientId: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function generatePkcePair(): {
|
|
21
|
+
verifier: string;
|
|
22
|
+
challenge: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function exchangeCode(params: {
|
|
25
|
+
tokenEndpoint: string;
|
|
26
|
+
code: string;
|
|
27
|
+
redirectUri: string;
|
|
28
|
+
clientId: string;
|
|
29
|
+
codeVerifier: string;
|
|
30
|
+
}): Promise<TokenResponse>;
|
|
31
|
+
export declare function refreshToken(params: {
|
|
32
|
+
tokenEndpoint: string;
|
|
33
|
+
refreshToken: string;
|
|
34
|
+
}): Promise<TokenResponse>;
|
|
35
|
+
export interface TokenResponse {
|
|
36
|
+
access_token: string;
|
|
37
|
+
token_type: string;
|
|
38
|
+
expires_in: number;
|
|
39
|
+
refresh_token: string;
|
|
40
|
+
scope: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns a valid access token, refreshing if the current one is within 60s
|
|
44
|
+
* of expiry. Updates the on-disk config in place so subsequent server calls
|
|
45
|
+
* see the new tokens.
|
|
46
|
+
*/
|
|
47
|
+
export declare function refreshIfNeeded(cfg: AxiomMcpConfig): Promise<string>;
|
|
48
|
+
export declare function getAuthenticatedConfig(): Promise<AxiomMcpConfig>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
3
|
+
export async function discover(apiBaseUrl) {
|
|
4
|
+
const url = `${apiBaseUrl.replace(/\/$/, '')}/.well-known/oauth-authorization-server`;
|
|
5
|
+
const res = await fetch(url);
|
|
6
|
+
if (!res.ok)
|
|
7
|
+
throw new Error(`OAuth discovery failed: ${res.status} ${res.statusText}`);
|
|
8
|
+
return await res.json();
|
|
9
|
+
}
|
|
10
|
+
export async function registerClient(registrationEndpoint, clientName, redirectUri) {
|
|
11
|
+
const res = await fetch(registrationEndpoint, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
clientName,
|
|
16
|
+
redirectUris: [redirectUri],
|
|
17
|
+
grantTypes: ['authorization_code', 'refresh_token'],
|
|
18
|
+
tokenEndpointAuthMethod: 'none',
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`Dynamic client registration failed: ${res.status} ${await res.text()}`);
|
|
23
|
+
}
|
|
24
|
+
const json = await res.json();
|
|
25
|
+
return { clientId: json.clientId };
|
|
26
|
+
}
|
|
27
|
+
export function generatePkcePair() {
|
|
28
|
+
const verifier = randomBytes(48).toString('base64url');
|
|
29
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
30
|
+
return { verifier, challenge };
|
|
31
|
+
}
|
|
32
|
+
export async function exchangeCode(params) {
|
|
33
|
+
const body = new URLSearchParams({
|
|
34
|
+
grant_type: 'authorization_code',
|
|
35
|
+
code: params.code,
|
|
36
|
+
redirect_uri: params.redirectUri,
|
|
37
|
+
client_id: params.clientId,
|
|
38
|
+
code_verifier: params.codeVerifier,
|
|
39
|
+
});
|
|
40
|
+
const res = await fetch(params.tokenEndpoint, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
43
|
+
body,
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
throw new Error(`Token exchange failed: ${res.status} ${await res.text()}`);
|
|
47
|
+
return await res.json();
|
|
48
|
+
}
|
|
49
|
+
export async function refreshToken(params) {
|
|
50
|
+
const body = new URLSearchParams({
|
|
51
|
+
grant_type: 'refresh_token',
|
|
52
|
+
refresh_token: params.refreshToken,
|
|
53
|
+
});
|
|
54
|
+
const res = await fetch(params.tokenEndpoint, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
57
|
+
body,
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok)
|
|
60
|
+
throw new Error(`Token refresh failed: ${res.status} ${await res.text()}`);
|
|
61
|
+
return await res.json();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns a valid access token, refreshing if the current one is within 60s
|
|
65
|
+
* of expiry. Updates the on-disk config in place so subsequent server calls
|
|
66
|
+
* see the new tokens.
|
|
67
|
+
*/
|
|
68
|
+
export async function refreshIfNeeded(cfg) {
|
|
69
|
+
const skewMs = 60_000;
|
|
70
|
+
if (cfg.expiresAt - skewMs > Date.now())
|
|
71
|
+
return cfg.accessToken;
|
|
72
|
+
const discovery = await discover(cfg.apiBaseUrl);
|
|
73
|
+
const refreshed = await refreshToken({
|
|
74
|
+
tokenEndpoint: discovery.token_endpoint,
|
|
75
|
+
refreshToken: cfg.refreshToken,
|
|
76
|
+
});
|
|
77
|
+
const updated = {
|
|
78
|
+
...cfg,
|
|
79
|
+
accessToken: refreshed.access_token,
|
|
80
|
+
refreshToken: refreshed.refresh_token,
|
|
81
|
+
expiresAt: Date.now() + refreshed.expires_in * 1000,
|
|
82
|
+
scope: refreshed.scope,
|
|
83
|
+
};
|
|
84
|
+
saveConfig(updated);
|
|
85
|
+
return updated.accessToken;
|
|
86
|
+
}
|
|
87
|
+
export async function getAuthenticatedConfig() {
|
|
88
|
+
const cfg = loadConfig();
|
|
89
|
+
await refreshIfNeeded(cfg);
|
|
90
|
+
return loadConfig(); // re-read in case refresh updated it
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,EAAkB,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAiBpE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB;IAC7C,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,yCAAyC,CAAA;IACrF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAA;IACvF,OAAO,MAAM,GAAG,CAAC,IAAI,EAAuB,CAAA;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAChC,oBAA4B,EAC5B,UAAkB,EAClB,WAAmB;IAEnB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oBAAoB,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACjB,UAAU;YACV,YAAY,EAAE,CAAC,WAAW,CAAC;YAC3B,UAAU,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;YACnD,uBAAuB,EAAE,MAAM;SAClC,CAAC;KACL,CAAC,CAAA;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC5F,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA0B,CAAA;IACrD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC5B,MAAM,QAAQ,GAAI,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IACvD,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC3E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAMlC;IACG,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC7B,UAAU,EAAK,oBAAoB;QACnC,IAAI,EAAW,MAAM,CAAC,IAAI;QAC1B,YAAY,EAAG,MAAM,CAAC,WAAW;QACjC,SAAS,EAAM,MAAM,CAAC,QAAQ;QAC9B,aAAa,EAAE,MAAM,CAAC,YAAY;KACrC,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI;KACP,CAAC,CAAA;IACF,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACxF,OAAO,MAAM,GAAG,CAAC,IAAI,EAAmB,CAAA;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAGlC;IACG,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC7B,UAAU,EAAK,eAAe;QAC9B,aAAa,EAAE,MAAM,CAAC,YAAY;KACrC,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI;KACP,CAAC,CAAA;IACF,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACvF,OAAO,MAAM,GAAG,CAAC,IAAI,EAAmB,CAAA;AAC5C,CAAC;AAUD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAmB;IACrD,MAAM,MAAM,GAAG,MAAM,CAAA;IACrB,IAAI,GAAG,CAAC,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,GAAG,CAAC,WAAW,CAAA;IAE/D,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAChD,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC;QACjC,aAAa,EAAE,SAAS,CAAC,cAAc;QACvC,YAAY,EAAE,GAAG,CAAC,YAAY;KACjC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAmB;QAC5B,GAAG,GAAG;QACN,WAAW,EAAG,SAAS,CAAC,YAAY;QACpC,YAAY,EAAE,SAAS,CAAC,aAAa;QACrC,SAAS,EAAK,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,UAAU,GAAG,IAAI;QACtD,KAAK,EAAS,SAAS,CAAC,KAAK;KAChC,CAAA;IACD,UAAU,CAAC,OAAO,CAAC,CAAA;IACnB,OAAO,OAAO,CAAC,WAAW,CAAA;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB;IACxC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;IACxB,MAAM,eAAe,CAAC,GAAG,CAAC,CAAA;IAC1B,OAAO,UAAU,EAAE,CAAA,CAAC,qCAAqC;AAC7D,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted session for the Axiom MCP install. Created by the install command,
|
|
3
|
+
* consumed by the server on every tool call.
|
|
4
|
+
*/
|
|
5
|
+
export interface AxiomMcpConfig {
|
|
6
|
+
apiBaseUrl: string;
|
|
7
|
+
clientId: string;
|
|
8
|
+
accessToken: string;
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
scope: string;
|
|
12
|
+
companyId: string | null;
|
|
13
|
+
}
|
|
14
|
+
declare const CONFIG_DIR: string;
|
|
15
|
+
declare const CONFIG_PATH: string;
|
|
16
|
+
export declare function loadConfig(): AxiomMcpConfig;
|
|
17
|
+
export declare function saveConfig(cfg: AxiomMcpConfig): void;
|
|
18
|
+
export declare function configPath(): string;
|
|
19
|
+
export declare function ensureConfigDir(): void;
|
|
20
|
+
export declare function loadOrThrow(): AxiomMcpConfig;
|
|
21
|
+
/** Test helper — allows pointing at a temp path. */
|
|
22
|
+
export declare function setConfigPathForTesting(path: string): {
|
|
23
|
+
restore: () => void;
|
|
24
|
+
};
|
|
25
|
+
export { CONFIG_DIR, CONFIG_PATH };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.axiom-mcp');
|
|
5
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
export function loadConfig() {
|
|
7
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
8
|
+
throw new Error(`Axiom MCP is not installed. Run 'npx @axiom-billing/mcp install' first.\n` +
|
|
9
|
+
`Expected config at: ${CONFIG_PATH}`);
|
|
10
|
+
}
|
|
11
|
+
return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
12
|
+
}
|
|
13
|
+
export function saveConfig(cfg) {
|
|
14
|
+
if (!existsSync(CONFIG_DIR))
|
|
15
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
16
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
17
|
+
}
|
|
18
|
+
export function configPath() {
|
|
19
|
+
return CONFIG_PATH;
|
|
20
|
+
}
|
|
21
|
+
export function ensureConfigDir() {
|
|
22
|
+
if (!existsSync(CONFIG_DIR))
|
|
23
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
24
|
+
}
|
|
25
|
+
export function loadOrThrow() {
|
|
26
|
+
const cfg = loadConfig();
|
|
27
|
+
if (cfg.expiresAt < Date.now()) {
|
|
28
|
+
throw new Error('Axiom MCP access token has expired. The server will refresh on the next call.');
|
|
29
|
+
}
|
|
30
|
+
return cfg;
|
|
31
|
+
}
|
|
32
|
+
/** Test helper — allows pointing at a temp path. */
|
|
33
|
+
export function setConfigPathForTesting(path) {
|
|
34
|
+
// Intentionally minimal — tests stub the module instead.
|
|
35
|
+
return { restore: () => { } };
|
|
36
|
+
}
|
|
37
|
+
export { CONFIG_DIR, CONFIG_PATH };
|
|
38
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAW,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAgBjC,MAAM,UAAU,GAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAA;AACjD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;AAEnD,MAAM,UAAU,UAAU;IACtB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACX,2EAA2E;YAC3E,uBAAuB,WAAW,EAAE,CACvC,CAAA;IACL,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAmB,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAmB;IAC1C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACpF,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,UAAU;IACtB,OAAO,WAAW,CAAA;AACtB,CAAC;AAED,MAAM,UAAU,eAAe;IAC3B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AACxF,CAAC;AAED,MAAM,UAAU,WAAW;IACvB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;IACxB,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAAA;IACpG,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAChD,yDAAyD;IACzD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAc,CAAC,EAAE,CAAA;AAC5C,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAA"}
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import { platform } from 'node:os';
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { discover, registerClient, exchangeCode, generatePkcePair, } from './auth.js';
|
|
10
|
+
import { saveConfig, configPath } from './config.js';
|
|
11
|
+
/**
|
|
12
|
+
* One-command install. Drives the full discovery → DCR → PKCE flow against
|
|
13
|
+
* an Axiom deployment, then writes both the MCP server config AND the
|
|
14
|
+
* Claude Desktop config block. No credentials in the command — the user
|
|
15
|
+
* approves in their browser.
|
|
16
|
+
*/
|
|
17
|
+
async function main() {
|
|
18
|
+
const apiBaseUrl = process.env.AXIOM_API_URL ?? 'http://localhost:8200/api';
|
|
19
|
+
const scopes = process.env.AXIOM_SCOPES ?? 'read:ledger read:invoices read:contacts read:reports read:bank-transactions offline_access';
|
|
20
|
+
console.error(`Axiom MCP installer — using API at ${apiBaseUrl}`);
|
|
21
|
+
const discovery = await discover(apiBaseUrl);
|
|
22
|
+
// Pick a free port on loopback for the redirect listener.
|
|
23
|
+
const { port, server, code, state } = await startLoopbackListener();
|
|
24
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
25
|
+
const { clientId } = await registerClient(discovery.registration_endpoint, 'Axiom MCP (this machine)', redirectUri);
|
|
26
|
+
const { verifier, challenge } = generatePkcePair();
|
|
27
|
+
const resource = apiBaseUrl.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
28
|
+
const authorizeUrl = new URL(discovery.authorization_endpoint);
|
|
29
|
+
authorizeUrl.searchParams.set('response_type', 'code');
|
|
30
|
+
authorizeUrl.searchParams.set('client_id', clientId);
|
|
31
|
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
|
|
32
|
+
authorizeUrl.searchParams.set('code_challenge', challenge);
|
|
33
|
+
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
34
|
+
authorizeUrl.searchParams.set('scope', scopes);
|
|
35
|
+
authorizeUrl.searchParams.set('state', state);
|
|
36
|
+
authorizeUrl.searchParams.set('resource', resource);
|
|
37
|
+
console.error(`Opening browser to: ${authorizeUrl}`);
|
|
38
|
+
openInBrowser(authorizeUrl.toString());
|
|
39
|
+
const callbackCode = await code;
|
|
40
|
+
server.close();
|
|
41
|
+
const tokens = await exchangeCode({
|
|
42
|
+
tokenEndpoint: discovery.token_endpoint,
|
|
43
|
+
code: callbackCode,
|
|
44
|
+
redirectUri,
|
|
45
|
+
clientId,
|
|
46
|
+
codeVerifier: verifier,
|
|
47
|
+
});
|
|
48
|
+
saveConfig({
|
|
49
|
+
apiBaseUrl,
|
|
50
|
+
clientId,
|
|
51
|
+
accessToken: tokens.access_token,
|
|
52
|
+
refreshToken: tokens.refresh_token,
|
|
53
|
+
expiresAt: Date.now() + tokens.expires_in * 1000,
|
|
54
|
+
scope: tokens.scope,
|
|
55
|
+
companyId: null, // server reads the companyId claim from the access token
|
|
56
|
+
});
|
|
57
|
+
console.error(`Saved tokens to ${configPath()}`);
|
|
58
|
+
writeClaudeDesktopBlock();
|
|
59
|
+
console.error('✔ Done. Restart Claude Desktop to pick up the Axiom MCP server.');
|
|
60
|
+
}
|
|
61
|
+
function startLoopbackListener() {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const state = randomBytes(16).toString('hex');
|
|
64
|
+
let resolveCode;
|
|
65
|
+
let rejectCode;
|
|
66
|
+
const code = new Promise((res, rej) => { resolveCode = res; rejectCode = rej; });
|
|
67
|
+
const server = createServer((req, res) => {
|
|
68
|
+
if (!req.url) {
|
|
69
|
+
res.writeHead(400).end('missing url');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const u = new URL(req.url, 'http://localhost');
|
|
73
|
+
if (u.pathname !== '/callback') {
|
|
74
|
+
res.writeHead(404).end();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const returnedState = u.searchParams.get('state');
|
|
78
|
+
const codeParam = u.searchParams.get('code');
|
|
79
|
+
const errorParam = u.searchParams.get('error');
|
|
80
|
+
if (errorParam) {
|
|
81
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
82
|
+
res.end(`<h1>Authorization failed</h1><p>${errorParam}</p>`);
|
|
83
|
+
rejectCode(new Error(`Authorization failed: ${errorParam}`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (returnedState !== state || !codeParam) {
|
|
87
|
+
res.writeHead(400).end('state mismatch');
|
|
88
|
+
rejectCode(new Error('state mismatch'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
92
|
+
res.end('<h1>Authorized.</h1><p>You can close this tab.</p>');
|
|
93
|
+
resolveCode(codeParam);
|
|
94
|
+
});
|
|
95
|
+
server.listen(0, '127.0.0.1', () => {
|
|
96
|
+
const address = server.address();
|
|
97
|
+
if (typeof address === 'object' && address !== null) {
|
|
98
|
+
resolve({ port: address.port, server, code, state });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function openInBrowser(url) {
|
|
104
|
+
const cmd = platform() === 'win32' ? `start "" "${url}"`
|
|
105
|
+
: platform() === 'darwin' ? `open "${url}"`
|
|
106
|
+
: `xdg-open "${url}"`;
|
|
107
|
+
exec(cmd, () => { });
|
|
108
|
+
}
|
|
109
|
+
function writeClaudeDesktopBlock() {
|
|
110
|
+
const dir = platform() === 'win32'
|
|
111
|
+
? join(process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming'), 'Claude')
|
|
112
|
+
: platform() === 'darwin'
|
|
113
|
+
? join(homedir(), 'Library', 'Application Support', 'Claude')
|
|
114
|
+
: join(homedir(), '.config', 'Claude');
|
|
115
|
+
if (!existsSync(dir))
|
|
116
|
+
mkdirSync(dir, { recursive: true });
|
|
117
|
+
const path = join(dir, 'claude_desktop_config.json');
|
|
118
|
+
let existing = {};
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
try {
|
|
121
|
+
existing = JSON.parse(readFileSync(path, 'utf8'));
|
|
122
|
+
}
|
|
123
|
+
catch { /* invalid; overwrite */ }
|
|
124
|
+
}
|
|
125
|
+
existing.mcpServers ??= {};
|
|
126
|
+
existing.mcpServers['axiom'] = {
|
|
127
|
+
command: process.execPath,
|
|
128
|
+
args: [new URL('./server.js', import.meta.url).pathname],
|
|
129
|
+
};
|
|
130
|
+
writeFileSync(path, JSON.stringify(existing, null, 2));
|
|
131
|
+
console.error(`Wrote Claude Desktop config: ${path}`);
|
|
132
|
+
}
|
|
133
|
+
main().catch(err => {
|
|
134
|
+
console.error('Install failed:', err instanceof Error ? err.message : err);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
export declare class McpLogger {
|
|
4
|
+
private server;
|
|
5
|
+
private level;
|
|
6
|
+
bind(server: Server): void;
|
|
7
|
+
setLevel(level: LoggingLevel): void;
|
|
8
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
9
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
10
|
+
notice(message: string, data?: Record<string, unknown>): void;
|
|
11
|
+
warning(message: string, data?: Record<string, unknown>): void;
|
|
12
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
13
|
+
private emit;
|
|
14
|
+
}
|
|
15
|
+
export declare const logger: McpLogger;
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-protocol logger. Emits `notifications/message` events that an MCP client
|
|
3
|
+
* can subscribe to and filter by level (controlled by the client via
|
|
4
|
+
* `logging/setLevel`). This is distinct from process stderr — that channel is
|
|
5
|
+
* still used for fatal startup errors in stdio mode where the protocol stream
|
|
6
|
+
* isn't yet up.
|
|
7
|
+
*/
|
|
8
|
+
const LEVEL_ORDER = {
|
|
9
|
+
debug: 0,
|
|
10
|
+
info: 1,
|
|
11
|
+
notice: 2,
|
|
12
|
+
warning: 3,
|
|
13
|
+
error: 4,
|
|
14
|
+
critical: 5,
|
|
15
|
+
alert: 6,
|
|
16
|
+
emergency: 7,
|
|
17
|
+
};
|
|
18
|
+
export class McpLogger {
|
|
19
|
+
server = null;
|
|
20
|
+
level = 'info';
|
|
21
|
+
bind(server) {
|
|
22
|
+
this.server = server;
|
|
23
|
+
}
|
|
24
|
+
setLevel(level) {
|
|
25
|
+
this.level = level;
|
|
26
|
+
}
|
|
27
|
+
debug(message, data) { this.emit('debug', message, data); }
|
|
28
|
+
info(message, data) { this.emit('info', message, data); }
|
|
29
|
+
notice(message, data) { this.emit('notice', message, data); }
|
|
30
|
+
warning(message, data) { this.emit('warning', message, data); }
|
|
31
|
+
error(message, data) { this.emit('error', message, data); }
|
|
32
|
+
emit(level, message, data) {
|
|
33
|
+
if (!this.server)
|
|
34
|
+
return;
|
|
35
|
+
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level])
|
|
36
|
+
return;
|
|
37
|
+
const params = {
|
|
38
|
+
level,
|
|
39
|
+
logger: 'axiom-mcp',
|
|
40
|
+
data: data ? { message, ...data } : { message },
|
|
41
|
+
};
|
|
42
|
+
// sendLoggingMessage returns a promise; we fire-and-forget so the caller
|
|
43
|
+
// (a tool handler) isn't blocked by transport backpressure. Errors here
|
|
44
|
+
// are swallowed deliberately — failing to log must never break a tool.
|
|
45
|
+
void this.server.sendLoggingMessage(params).catch(() => { });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export const logger = new McpLogger();
|
|
49
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,WAAW,GAAiC;IAC9C,KAAK,EAAM,CAAC;IACZ,IAAI,EAAO,CAAC;IACZ,MAAM,EAAK,CAAC;IACZ,OAAO,EAAI,CAAC;IACZ,KAAK,EAAM,CAAC;IACZ,QAAQ,EAAG,CAAC;IACZ,KAAK,EAAM,CAAC;IACZ,SAAS,EAAE,CAAC;CACf,CAAA;AAED,MAAM,OAAO,SAAS;IACV,MAAM,GAAkB,IAAI,CAAA;IAC5B,KAAK,GAAiB,MAAM,CAAA;IAEpC,IAAI,CAAC,MAAc;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACxB,CAAC;IAED,QAAQ,CAAC,KAAmB;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B,IAAY,IAAI,CAAC,IAAI,CAAC,OAAO,EAAI,OAAO,EAAE,IAAI,CAAC,CAAA,CAAC,CAAC;IACtG,IAAI,CAAC,OAAe,EAAE,IAA8B,IAAa,IAAI,CAAC,IAAI,CAAC,MAAM,EAAK,OAAO,EAAE,IAAI,CAAC,CAAA,CAAC,CAAC;IACtG,MAAM,CAAC,OAAe,EAAE,IAA8B,IAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAG,OAAO,EAAE,IAAI,CAAC,CAAA,CAAC,CAAC;IACtG,OAAO,CAAC,OAAe,EAAE,IAA8B,IAAU,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA,CAAC,CAAC;IACtG,KAAK,CAAC,OAAe,EAAE,IAA8B,IAAY,IAAI,CAAC,IAAI,CAAC,OAAO,EAAI,OAAO,EAAE,IAAI,CAAC,CAAA,CAAC,CAAC;IAE9F,IAAI,CAAC,KAAmB,EAAE,OAAe,EAAE,IAA8B;QAC7E,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAM;QACxB,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAM;QACxD,MAAM,MAAM,GAAG;YACX,KAAK;YACL,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE;SAClD,CAAA;QACD,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,KAAK,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAiB,CAAC,CAAC,CAAA;IAC9E,CAAC;CACJ;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts are user-facing workflow starters — they appear as slash commands
|
|
3
|
+
* or quick-actions in MCP-aware UIs. Each one expands to a `messages` array
|
|
4
|
+
* the host injects into the conversation. The bodies are instruction templates
|
|
5
|
+
* that ground the model in Axiom's tools and resources; they do NOT execute
|
|
6
|
+
* tool calls themselves.
|
|
7
|
+
*/
|
|
8
|
+
export interface PromptArgument {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
required: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface PromptDefinition {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
arguments: PromptArgument[];
|
|
17
|
+
/** Build the messages payload returned by `prompts/get`. */
|
|
18
|
+
build: (args: Record<string, string | undefined>) => {
|
|
19
|
+
description: string;
|
|
20
|
+
messages: Array<{
|
|
21
|
+
role: 'user';
|
|
22
|
+
content: {
|
|
23
|
+
type: 'text';
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare const ALL_PROMPTS: PromptDefinition[];
|