@pilot-status/mcp-server 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/LICENSE +21 -0
- package/README.md +230 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +100 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +204 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/config.d.ts +54 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +79 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/http-transport.d.ts +42 -0
- package/dist/lib/http-transport.d.ts.map +1 -0
- package/dist/lib/http-transport.js +228 -0
- package/dist/lib/http-transport.js.map +1 -0
- package/dist/lib/register.d.ts +28 -0
- package/dist/lib/register.d.ts.map +1 -0
- package/dist/lib/register.js +69 -0
- package/dist/lib/register.js.map +1 -0
- package/dist/lib/server.d.ts +16 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +23 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/tools/analytics.d.ts +3 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +22 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/api-keys.d.ts +3 -0
- package/dist/tools/api-keys.d.ts.map +1 -0
- package/dist/tools/api-keys.js +35 -0
- package/dist/tools/api-keys.js.map +1 -0
- package/dist/tools/branding.d.ts +3 -0
- package/dist/tools/branding.d.ts.map +1 -0
- package/dist/tools/branding.js +35 -0
- package/dist/tools/branding.js.map +1 -0
- package/dist/tools/conversations.d.ts +3 -0
- package/dist/tools/conversations.d.ts.map +1 -0
- package/dist/tools/conversations.js +18 -0
- package/dist/tools/conversations.js.map +1 -0
- package/dist/tools/groups.d.ts +3 -0
- package/dist/tools/groups.d.ts.map +1 -0
- package/dist/tools/groups.js +17 -0
- package/dist/tools/groups.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +26 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/messages.d.ts +3 -0
- package/dist/tools/messages.d.ts.map +1 -0
- package/dist/tools/messages.js +134 -0
- package/dist/tools/messages.js.map +1 -0
- package/dist/tools/numbers.d.ts +3 -0
- package/dist/tools/numbers.d.ts.map +1 -0
- package/dist/tools/numbers.js +52 -0
- package/dist/tools/numbers.js.map +1 -0
- package/dist/tools/shared.d.ts +19 -0
- package/dist/tools/shared.d.ts.map +1 -0
- package/dist/tools/shared.js +27 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/subscription.d.ts +3 -0
- package/dist/tools/subscription.d.ts.map +1 -0
- package/dist/tools/subscription.js +39 -0
- package/dist/tools/subscription.js.map +1 -0
- package/dist/tools/templates.d.ts +3 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +68 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/types.d.ts +23 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +20 -0
- package/dist/tools/types.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for the Pilot Status MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Transport is selected (in priority order) by:
|
|
5
|
+
* 1. a positional CLI arg or flag: `stdio` | `http` | `--stdio` | `--http`
|
|
6
|
+
* 2. the `MCP_TRANSPORT` env var (`stdio` | `http`)
|
|
7
|
+
* 3. default: `stdio`
|
|
8
|
+
*
|
|
9
|
+
* The public API base URL defaults to production (`https://pilotstatuss.com`).
|
|
10
|
+
* The API key is read from `PILOT_STATUS_API_KEY` and is required for stdio
|
|
11
|
+
* (single-tenant process). In HTTP mode the key is taken per-request from the
|
|
12
|
+
* incoming `x-api-key` header, falling back to `PILOT_STATUS_API_KEY` if set.
|
|
13
|
+
*
|
|
14
|
+
* OAuth 2.1 (claude.ai custom connector): this process is the *resource server*.
|
|
15
|
+
* It is a "dumb forwarder" — it does NOT verify access tokens. A `Bearer` token
|
|
16
|
+
* on `POST /mcp` is forwarded verbatim to the public `/v1` API, which validates
|
|
17
|
+
* it. The authorization server lives in the Pilot Status backend; this server
|
|
18
|
+
* only advertises it via `GET /.well-known/oauth-protected-resource`:
|
|
19
|
+
* - `MCP_RESOURCE_URL` — the public URL of THIS resource server.
|
|
20
|
+
* - `PILOT_STATUS_AUTH_SERVER_URL` — the authorization server (the backend).
|
|
21
|
+
*/
|
|
22
|
+
export type McpTransport = "stdio" | "http";
|
|
23
|
+
export interface ResolvedConfig {
|
|
24
|
+
transport: McpTransport;
|
|
25
|
+
/** Public API base URL, no trailing slash (e.g. https://pilotstatuss.com). */
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
/** Raw `ps_*` API key (stdio default; HTTP fallback). May be undefined in HTTP mode. */
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** Optional `x-api-key-id` companion header value. */
|
|
30
|
+
apiKeyId?: string;
|
|
31
|
+
/** Public URL of THIS MCP resource server (OAuth `resource`), no trailing slash. */
|
|
32
|
+
resourceUrl: string;
|
|
33
|
+
/** OAuth authorization server base URL (the Pilot Status backend), no trailing slash. */
|
|
34
|
+
authServerUrl: string;
|
|
35
|
+
/** HTTP transport listen port. */
|
|
36
|
+
port: number;
|
|
37
|
+
/** HTTP transport bind host. */
|
|
38
|
+
host: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const DEFAULT_API_URL = "https://pilotstatuss.com";
|
|
41
|
+
export declare const DEFAULT_AUTH_SERVER_URL = "https://pilotstatuss.com";
|
|
42
|
+
export declare const DEFAULT_RESOURCE_URL = "https://mcp.pilotstatuss.com";
|
|
43
|
+
export declare const DEFAULT_HTTP_PORT = 8787;
|
|
44
|
+
export declare const DEFAULT_HTTP_HOST = "0.0.0.0";
|
|
45
|
+
/**
|
|
46
|
+
* Advisory OAuth scopes advertised in the protected-resource metadata. The
|
|
47
|
+
* authorization server (Pilot Status backend) is the source of truth; this list
|
|
48
|
+
* is a hint to clients about the granularity available.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SCOPES_SUPPORTED: readonly ["messages:send", "messages:read", "conversations:read", "numbers:read", "numbers:write", "templates:read", "templates:write", "analytics:read"];
|
|
51
|
+
export declare function resolveConfig(argv?: string[], env?: NodeJS.ProcessEnv): ResolvedConfig;
|
|
52
|
+
export declare const SERVER_NAME = "pilot-status";
|
|
53
|
+
export declare const SERVER_VERSION = "0.1.0";
|
|
54
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,YAAY,CAAC;IACxB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,aAAa,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAClE,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AACnE,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,2JASnB,CAAC;AAgBX,wBAAgB,aAAa,CACzB,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACrC,cAAc,CAyBhB;AAED,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,eAAO,MAAM,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for the Pilot Status MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Transport is selected (in priority order) by:
|
|
5
|
+
* 1. a positional CLI arg or flag: `stdio` | `http` | `--stdio` | `--http`
|
|
6
|
+
* 2. the `MCP_TRANSPORT` env var (`stdio` | `http`)
|
|
7
|
+
* 3. default: `stdio`
|
|
8
|
+
*
|
|
9
|
+
* The public API base URL defaults to production (`https://pilotstatuss.com`).
|
|
10
|
+
* The API key is read from `PILOT_STATUS_API_KEY` and is required for stdio
|
|
11
|
+
* (single-tenant process). In HTTP mode the key is taken per-request from the
|
|
12
|
+
* incoming `x-api-key` header, falling back to `PILOT_STATUS_API_KEY` if set.
|
|
13
|
+
*
|
|
14
|
+
* OAuth 2.1 (claude.ai custom connector): this process is the *resource server*.
|
|
15
|
+
* It is a "dumb forwarder" — it does NOT verify access tokens. A `Bearer` token
|
|
16
|
+
* on `POST /mcp` is forwarded verbatim to the public `/v1` API, which validates
|
|
17
|
+
* it. The authorization server lives in the Pilot Status backend; this server
|
|
18
|
+
* only advertises it via `GET /.well-known/oauth-protected-resource`:
|
|
19
|
+
* - `MCP_RESOURCE_URL` — the public URL of THIS resource server.
|
|
20
|
+
* - `PILOT_STATUS_AUTH_SERVER_URL` — the authorization server (the backend).
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_API_URL = "https://pilotstatuss.com";
|
|
23
|
+
export const DEFAULT_AUTH_SERVER_URL = "https://pilotstatuss.com";
|
|
24
|
+
export const DEFAULT_RESOURCE_URL = "https://mcp.pilotstatuss.com";
|
|
25
|
+
export const DEFAULT_HTTP_PORT = 8787;
|
|
26
|
+
export const DEFAULT_HTTP_HOST = "0.0.0.0";
|
|
27
|
+
/**
|
|
28
|
+
* Advisory OAuth scopes advertised in the protected-resource metadata. The
|
|
29
|
+
* authorization server (Pilot Status backend) is the source of truth; this list
|
|
30
|
+
* is a hint to clients about the granularity available.
|
|
31
|
+
*/
|
|
32
|
+
export const SCOPES_SUPPORTED = [
|
|
33
|
+
"messages:send",
|
|
34
|
+
"messages:read",
|
|
35
|
+
"conversations:read",
|
|
36
|
+
"numbers:read",
|
|
37
|
+
"numbers:write",
|
|
38
|
+
"templates:read",
|
|
39
|
+
"templates:write",
|
|
40
|
+
"analytics:read",
|
|
41
|
+
];
|
|
42
|
+
function stripTrailingSlash(url) {
|
|
43
|
+
return url.replace(/\/+$/, "");
|
|
44
|
+
}
|
|
45
|
+
function resolveTransport(argv, env) {
|
|
46
|
+
const flags = argv.map((a) => a.trim().toLowerCase());
|
|
47
|
+
if (flags.includes("http") || flags.includes("--http"))
|
|
48
|
+
return "http";
|
|
49
|
+
if (flags.includes("stdio") || flags.includes("--stdio"))
|
|
50
|
+
return "stdio";
|
|
51
|
+
const fromEnv = (env.MCP_TRANSPORT ?? "").trim().toLowerCase();
|
|
52
|
+
if (fromEnv === "http")
|
|
53
|
+
return "http";
|
|
54
|
+
if (fromEnv === "stdio")
|
|
55
|
+
return "stdio";
|
|
56
|
+
return "stdio";
|
|
57
|
+
}
|
|
58
|
+
export function resolveConfig(argv = process.argv.slice(2), env = process.env) {
|
|
59
|
+
const baseUrl = stripTrailingSlash((env.PILOT_STATUS_API_URL ?? "").trim() || DEFAULT_API_URL);
|
|
60
|
+
const apiKey = (env.PILOT_STATUS_API_KEY ?? "").trim() || undefined;
|
|
61
|
+
const apiKeyId = (env.PILOT_STATUS_API_KEY_ID ?? "").trim() || undefined;
|
|
62
|
+
const resourceUrl = stripTrailingSlash((env.MCP_RESOURCE_URL ?? "").trim() || DEFAULT_RESOURCE_URL);
|
|
63
|
+
const authServerUrl = stripTrailingSlash((env.PILOT_STATUS_AUTH_SERVER_URL ?? "").trim() || DEFAULT_AUTH_SERVER_URL);
|
|
64
|
+
const port = Number.parseInt(env.PORT ?? "", 10) || DEFAULT_HTTP_PORT;
|
|
65
|
+
const host = (env.HOST ?? "").trim() || DEFAULT_HTTP_HOST;
|
|
66
|
+
return {
|
|
67
|
+
transport: resolveTransport(argv, env),
|
|
68
|
+
baseUrl,
|
|
69
|
+
apiKey,
|
|
70
|
+
apiKeyId,
|
|
71
|
+
resourceUrl,
|
|
72
|
+
authServerUrl,
|
|
73
|
+
port,
|
|
74
|
+
host,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export const SERVER_NAME = "pilot-status";
|
|
78
|
+
export const SERVER_VERSION = "0.1.0";
|
|
79
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
|
+
import { type ResolvedConfig } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `WWW-Authenticate` challenge that points the client at this resource
|
|
5
|
+
* server's protected-resource metadata, kicking off OAuth discovery. The format
|
|
6
|
+
* is exactly `Bearer resource_metadata="<url>"`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function wwwAuthenticateHeader(config: ResolvedConfig): string;
|
|
9
|
+
/** RFC 9728 protected-resource metadata document for this resource server. */
|
|
10
|
+
export declare function protectedResourceMetadata(config: ResolvedConfig): {
|
|
11
|
+
resource: string;
|
|
12
|
+
authorization_servers: string[];
|
|
13
|
+
bearer_methods_supported: string[];
|
|
14
|
+
scopes_supported: string[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Wrap `res.writeHead` so that, if an upstream bearer-mode 401 was observed
|
|
18
|
+
* (`isUnauthorized()` returns true) by the time the response is committed, the
|
|
19
|
+
* status is rewritten to 401 with a `WWW-Authenticate` challenge — relaying the
|
|
20
|
+
* upstream re-auth requirement to the MCP client. No-op once headers are sent.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyBearerUnauthorizedInterceptor(res: ServerResponse, isUnauthorized: () => boolean, wwwAuthenticate: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Handle a single Streamable-HTTP request in *stateless* mode: a fresh server +
|
|
25
|
+
* transport per request, no session reuse (sessionIdGenerator: undefined).
|
|
26
|
+
*
|
|
27
|
+
* Authentication precedence:
|
|
28
|
+
* 1. `Authorization: Bearer <token>` → OAuth mode. The token is forwarded
|
|
29
|
+
* verbatim to `/v1`; this server does not verify it (dumb forwarder). If
|
|
30
|
+
* `/v1` rejects it with 401, the response is relayed as 401 + a
|
|
31
|
+
* `WWW-Authenticate` challenge so the client re-authenticates.
|
|
32
|
+
* 2. `x-api-key` header (or process `PILOT_STATUS_API_KEY` fallback) → API-key
|
|
33
|
+
* mode (back-compat).
|
|
34
|
+
* 3. Neither → 401 + `WWW-Authenticate` pointing at the protected-resource
|
|
35
|
+
* metadata, which starts the claude.ai OAuth flow.
|
|
36
|
+
*/
|
|
37
|
+
export declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, config: ResolvedConfig): Promise<void>;
|
|
38
|
+
/** Create (but do not start) the node:http server for the HTTP transport. */
|
|
39
|
+
export declare function createHttpServer(config: ResolvedConfig): Server;
|
|
40
|
+
/** Start the HTTP transport and resolve once it is listening. */
|
|
41
|
+
export declare function startHttpServer(config: ResolvedConfig): Promise<Server>;
|
|
42
|
+
//# sourceMappingURL=http-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../../src/lib/http-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,cAAc,EACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AA2CpE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEpE;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,cAAc,GAAG;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAOA;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAC9C,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,MAAM,OAAO,EAC7B,eAAe,EAAE,MAAM,GACxB,IAAI,CAuBN;AAwCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CAClC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA8C/D;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAWvE"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createServer, } from "node:http";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import { buildServer } from "./server.js";
|
|
4
|
+
import { SCOPES_SUPPORTED } from "./config.js";
|
|
5
|
+
const MCP_PATH = "/mcp";
|
|
6
|
+
/** RFC 9728 protected-resource metadata path. */
|
|
7
|
+
const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
8
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024; // 4 MiB
|
|
9
|
+
function headerValue(value) {
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value[0];
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function sendJson(res, status, body, extraHeaders) {
|
|
15
|
+
const payload = JSON.stringify(body);
|
|
16
|
+
res.writeHead(status, { "content-type": "application/json", ...extraHeaders });
|
|
17
|
+
res.end(payload);
|
|
18
|
+
}
|
|
19
|
+
/** Permissive CORS for browser-based MCP clients; safe for a public read API. */
|
|
20
|
+
function applyCors(res) {
|
|
21
|
+
res.setHeader("access-control-allow-origin", "*");
|
|
22
|
+
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
|
|
23
|
+
res.setHeader("access-control-allow-headers", "content-type, authorization, x-api-key, x-api-key-id, x-whatsapp-number-id, mcp-protocol-version, mcp-session-id, last-event-id");
|
|
24
|
+
// Expose the auth challenge so the OAuth dance can start from the browser.
|
|
25
|
+
res.setHeader("access-control-expose-headers", "WWW-Authenticate, mcp-session-id");
|
|
26
|
+
}
|
|
27
|
+
/** Extract the token from an `Authorization: Bearer <token>` header. */
|
|
28
|
+
function parseBearer(header) {
|
|
29
|
+
if (!header)
|
|
30
|
+
return undefined;
|
|
31
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
32
|
+
const token = match?.[1]?.trim();
|
|
33
|
+
return token ? token : undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The `WWW-Authenticate` challenge that points the client at this resource
|
|
37
|
+
* server's protected-resource metadata, kicking off OAuth discovery. The format
|
|
38
|
+
* is exactly `Bearer resource_metadata="<url>"`.
|
|
39
|
+
*/
|
|
40
|
+
export function wwwAuthenticateHeader(config) {
|
|
41
|
+
return `Bearer resource_metadata="${config.resourceUrl}${PROTECTED_RESOURCE_METADATA_PATH}"`;
|
|
42
|
+
}
|
|
43
|
+
/** RFC 9728 protected-resource metadata document for this resource server. */
|
|
44
|
+
export function protectedResourceMetadata(config) {
|
|
45
|
+
return {
|
|
46
|
+
resource: config.resourceUrl,
|
|
47
|
+
authorization_servers: [config.authServerUrl],
|
|
48
|
+
bearer_methods_supported: ["header"],
|
|
49
|
+
scopes_supported: [...SCOPES_SUPPORTED],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Wrap `res.writeHead` so that, if an upstream bearer-mode 401 was observed
|
|
54
|
+
* (`isUnauthorized()` returns true) by the time the response is committed, the
|
|
55
|
+
* status is rewritten to 401 with a `WWW-Authenticate` challenge — relaying the
|
|
56
|
+
* upstream re-auth requirement to the MCP client. No-op once headers are sent.
|
|
57
|
+
*/
|
|
58
|
+
export function applyBearerUnauthorizedInterceptor(res, isUnauthorized, wwwAuthenticate) {
|
|
59
|
+
const original = res.writeHead.bind(res);
|
|
60
|
+
res.writeHead = ((statusCode, arg2, arg3) => {
|
|
61
|
+
if (isUnauthorized()) {
|
|
62
|
+
const headers = arg2 && typeof arg2 === "object"
|
|
63
|
+
? { ...arg2 }
|
|
64
|
+
: arg3 && typeof arg3 === "object"
|
|
65
|
+
? { ...arg3 }
|
|
66
|
+
: {};
|
|
67
|
+
headers["WWW-Authenticate"] = wwwAuthenticate;
|
|
68
|
+
return original(401, headers);
|
|
69
|
+
}
|
|
70
|
+
return original(statusCode, arg2, arg3);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** JSON-RPC error envelope (no session id available in stateless mode). */
|
|
74
|
+
function rpcError(res, status, message) {
|
|
75
|
+
sendJson(res, status, {
|
|
76
|
+
jsonrpc: "2.0",
|
|
77
|
+
error: { code: -32000, message },
|
|
78
|
+
id: null,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function readJsonBody(req) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let size = 0;
|
|
85
|
+
req.on("data", (chunk) => {
|
|
86
|
+
size += chunk.length;
|
|
87
|
+
if (size > MAX_BODY_BYTES) {
|
|
88
|
+
reject(new Error("Request body too large"));
|
|
89
|
+
req.destroy();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
chunks.push(chunk);
|
|
93
|
+
});
|
|
94
|
+
req.on("end", () => {
|
|
95
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
96
|
+
if (!raw.length) {
|
|
97
|
+
resolve(undefined);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
resolve(JSON.parse(raw));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
reject(new Error("Invalid JSON body"));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
req.on("error", reject);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handle a single Streamable-HTTP request in *stateless* mode: a fresh server +
|
|
112
|
+
* transport per request, no session reuse (sessionIdGenerator: undefined).
|
|
113
|
+
*
|
|
114
|
+
* Authentication precedence:
|
|
115
|
+
* 1. `Authorization: Bearer <token>` → OAuth mode. The token is forwarded
|
|
116
|
+
* verbatim to `/v1`; this server does not verify it (dumb forwarder). If
|
|
117
|
+
* `/v1` rejects it with 401, the response is relayed as 401 + a
|
|
118
|
+
* `WWW-Authenticate` challenge so the client re-authenticates.
|
|
119
|
+
* 2. `x-api-key` header (or process `PILOT_STATUS_API_KEY` fallback) → API-key
|
|
120
|
+
* mode (back-compat).
|
|
121
|
+
* 3. Neither → 401 + `WWW-Authenticate` pointing at the protected-resource
|
|
122
|
+
* metadata, which starts the claude.ai OAuth flow.
|
|
123
|
+
*/
|
|
124
|
+
export async function handleMcpRequest(req, res, config) {
|
|
125
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
126
|
+
// Stateless: only POST carries JSON-RPC messages. GET (SSE) and DELETE
|
|
127
|
+
// (session teardown) are not supported without sessions.
|
|
128
|
+
if (method !== "POST") {
|
|
129
|
+
rpcError(res, 405, "Method not allowed (stateless server accepts POST /mcp).");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const bearer = parseBearer(headerValue(req.headers["authorization"]));
|
|
133
|
+
const apiKey = headerValue(req.headers["x-api-key"]) ?? config.apiKey;
|
|
134
|
+
const apiKeyId = headerValue(req.headers["x-api-key-id"]) ?? config.apiKeyId;
|
|
135
|
+
if (!bearer && !apiKey && !apiKeyId) {
|
|
136
|
+
// No credentials at all → challenge so claude.ai starts the OAuth dance.
|
|
137
|
+
sendJson(res, 401, {
|
|
138
|
+
error: "unauthorized",
|
|
139
|
+
error_description: "Authentication required. Provide an OAuth bearer token or an x-api-key header.",
|
|
140
|
+
resource_metadata: `${config.resourceUrl}${PROTECTED_RESOURCE_METADATA_PATH}`,
|
|
141
|
+
}, { "WWW-Authenticate": wwwAuthenticateHeader(config) });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let body;
|
|
145
|
+
try {
|
|
146
|
+
body = await readJsonBody(req);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
rpcError(res, 400, err instanceof Error ? err.message : "Invalid request body");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Track an upstream 401 seen during bearer-mode tool calls so we can rewrite
|
|
153
|
+
// the committed response into a re-auth challenge.
|
|
154
|
+
const authState = { unauthorized: false };
|
|
155
|
+
const server = bearer
|
|
156
|
+
? buildServer({
|
|
157
|
+
baseUrl: config.baseUrl,
|
|
158
|
+
bearer,
|
|
159
|
+
onBearerUnauthorized: () => {
|
|
160
|
+
authState.unauthorized = true;
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
: buildServer({ baseUrl: config.baseUrl, apiKey, apiKeyId });
|
|
164
|
+
if (bearer) {
|
|
165
|
+
applyBearerUnauthorizedInterceptor(res, () => authState.unauthorized, wwwAuthenticateHeader(config));
|
|
166
|
+
}
|
|
167
|
+
const transport = new StreamableHTTPServerTransport({
|
|
168
|
+
sessionIdGenerator: undefined,
|
|
169
|
+
});
|
|
170
|
+
res.on("close", () => {
|
|
171
|
+
void transport.close();
|
|
172
|
+
void server.close();
|
|
173
|
+
});
|
|
174
|
+
await server.connect(transport);
|
|
175
|
+
await transport.handleRequest(req, res, body);
|
|
176
|
+
}
|
|
177
|
+
/** Create (but do not start) the node:http server for the HTTP transport. */
|
|
178
|
+
export function createHttpServer(config) {
|
|
179
|
+
return createServer((req, res) => {
|
|
180
|
+
applyCors(res);
|
|
181
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
182
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
183
|
+
// CORS preflight.
|
|
184
|
+
if (method === "OPTIONS") {
|
|
185
|
+
res.writeHead(204);
|
|
186
|
+
res.end();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// OAuth 2.1 protected-resource metadata (RFC 9728) — discovery entrypoint
|
|
190
|
+
// for the claude.ai custom connector.
|
|
191
|
+
if (path === PROTECTED_RESOURCE_METADATA_PATH) {
|
|
192
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
193
|
+
sendJson(res, 405, { error: "Method not allowed" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
sendJson(res, 200, protectedResourceMetadata(config));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (path === "/health" || path === "/healthz") {
|
|
200
|
+
sendJson(res, 200, { status: "ok", transport: "http" });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (path !== MCP_PATH) {
|
|
204
|
+
sendJson(res, 404, { error: "Not found", path });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
handleMcpRequest(req, res, config).catch((err) => {
|
|
208
|
+
if (!res.headersSent) {
|
|
209
|
+
rpcError(res, 500, err instanceof Error ? err.message : "Internal server error");
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
res.end();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/** Start the HTTP transport and resolve once it is listening. */
|
|
218
|
+
export function startHttpServer(config) {
|
|
219
|
+
const httpServer = createHttpServer(config);
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
httpServer.listen(config.port, config.host, () => {
|
|
222
|
+
// stderr — stdout is reserved for the stdio protocol in the other mode.
|
|
223
|
+
console.error(`[mcp] Pilot Status MCP server (HTTP) listening on http://${config.host}:${config.port}${MCP_PATH}`);
|
|
224
|
+
resolve(httpServer);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=http-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-transport.js","sourceRoot":"","sources":["../../src/lib/http-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ApiClient } from "./api-client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural surface a tool-registry target must satisfy. The real
|
|
4
|
+
* MCP `McpServer` matches it; tests can pass a lightweight fake to assert
|
|
5
|
+
* registration without constructing the SDK server (and without importing it).
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolRegistrar {
|
|
8
|
+
tool(name: string, description: string, paramsSchema: Record<string, unknown>, cb: (args: Record<string, unknown>) => Promise<unknown>): unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generic per-call number selector. Number-scoped endpoints normally resolve the
|
|
12
|
+
* target number from a number-scoped API key. Under an OAuth/tenant token there
|
|
13
|
+
* is no number scope, so callers pass `whatsappNumberId`; we strip it from the
|
|
14
|
+
* tool args and attach it as the `x-whatsapp-number-id` request header instead
|
|
15
|
+
* (see {@link ApiClient.withNumber}). Tools that already declare the field in
|
|
16
|
+
* their own schema (e.g. `api_keys_create`, where it is a body field) keep it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const NUMBER_SELECTOR_KEY = "whatsappNumberId";
|
|
19
|
+
/** Serialize a thrown error into the text payload of an MCP tool error result. */
|
|
20
|
+
export declare function formatToolError(err: unknown): string;
|
|
21
|
+
/**
|
|
22
|
+
* Register every tool from the registry onto the given server, wiring each to
|
|
23
|
+
* the shared {@link ApiClient}. Tool results are returned as JSON text content;
|
|
24
|
+
* thrown {@link ApiError}s become `isError` results so the model sees the
|
|
25
|
+
* status/code instead of the connection dropping.
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerTools(server: ToolRegistrar, client: ApiClient): void;
|
|
28
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/lib/register.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC1B,IAAI,CACA,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GACxD,OAAO,CAAC;CACd;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAYtD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CASpD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CA0C5E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiError } from "./api-client.js";
|
|
3
|
+
import { tools } from "../tools/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Generic per-call number selector. Number-scoped endpoints normally resolve the
|
|
6
|
+
* target number from a number-scoped API key. Under an OAuth/tenant token there
|
|
7
|
+
* is no number scope, so callers pass `whatsappNumberId`; we strip it from the
|
|
8
|
+
* tool args and attach it as the `x-whatsapp-number-id` request header instead
|
|
9
|
+
* (see {@link ApiClient.withNumber}). Tools that already declare the field in
|
|
10
|
+
* their own schema (e.g. `api_keys_create`, where it is a body field) keep it.
|
|
11
|
+
*/
|
|
12
|
+
export const NUMBER_SELECTOR_KEY = "whatsappNumberId";
|
|
13
|
+
const numberSelectorSchema = z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Target WhatsApp number id for number-scoped endpoints. Required when authenticating " +
|
|
18
|
+
"with an OAuth/tenant token (sent as the x-whatsapp-number-id header); ignored when the " +
|
|
19
|
+
"API key is already scoped to a single number.");
|
|
20
|
+
/** Serialize a thrown error into the text payload of an MCP tool error result. */
|
|
21
|
+
export function formatToolError(err) {
|
|
22
|
+
if (err instanceof ApiError) {
|
|
23
|
+
return JSON.stringify(err.toJSON(), null, 2);
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) }, null, 2);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register every tool from the registry onto the given server, wiring each to
|
|
29
|
+
* the shared {@link ApiClient}. Tool results are returned as JSON text content;
|
|
30
|
+
* thrown {@link ApiError}s become `isError` results so the model sees the
|
|
31
|
+
* status/code instead of the connection dropping.
|
|
32
|
+
*/
|
|
33
|
+
export function registerTools(server, client) {
|
|
34
|
+
for (const tool of tools) {
|
|
35
|
+
// The tool owns the field (it is meaningful in the body) → leave args
|
|
36
|
+
// untouched. Otherwise inject an optional selector that we lift into the
|
|
37
|
+
// request header and strip from the handler args.
|
|
38
|
+
const ownsSelector = NUMBER_SELECTOR_KEY in tool.inputSchema;
|
|
39
|
+
const schema = ownsSelector
|
|
40
|
+
? tool.inputSchema
|
|
41
|
+
: { ...tool.inputSchema, [NUMBER_SELECTOR_KEY]: numberSelectorSchema };
|
|
42
|
+
server.tool(tool.name, tool.description, schema, async (args) => {
|
|
43
|
+
try {
|
|
44
|
+
const incoming = args ?? {};
|
|
45
|
+
const selector = incoming[NUMBER_SELECTOR_KEY];
|
|
46
|
+
const scopedClient = typeof selector === "string" && selector.length > 0
|
|
47
|
+
? client.withNumber(selector)
|
|
48
|
+
: client;
|
|
49
|
+
let handlerArgs = incoming;
|
|
50
|
+
if (!ownsSelector && NUMBER_SELECTOR_KEY in incoming) {
|
|
51
|
+
handlerArgs = { ...incoming };
|
|
52
|
+
delete handlerArgs[NUMBER_SELECTOR_KEY];
|
|
53
|
+
}
|
|
54
|
+
const data = await tool.handler(scopedClient, handlerArgs);
|
|
55
|
+
const text = typeof data === "string"
|
|
56
|
+
? data
|
|
57
|
+
: JSON.stringify(data, null, 2);
|
|
58
|
+
return { content: [{ type: "text", text }] };
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: formatToolError(err) }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/lib/register.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export { registerTools, formatToolError, type ToolRegistrar } from "./register.js";
|
|
3
|
+
export interface BuildServerOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
apiKeyId?: string;
|
|
7
|
+
/** OAuth 2.1 bearer token (forwarded to /v1; takes precedence over apiKey). */
|
|
8
|
+
bearer?: string;
|
|
9
|
+
/** Called when an upstream bearer-mode request returns HTTP 401. */
|
|
10
|
+
onBearerUnauthorized?: () => void;
|
|
11
|
+
/** Overridable fetch (tests / custom runtimes). */
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
/** Build a fully-wired MCP server instance for one connection / request. */
|
|
15
|
+
export declare function buildServer(options: BuildServerOptions): McpServer;
|
|
16
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/lib/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,mDAAmD;IACnD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC5B;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,CAelE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ApiClient } from "./api-client.js";
|
|
3
|
+
import { SERVER_NAME, SERVER_VERSION } from "./config.js";
|
|
4
|
+
import { registerTools } from "./register.js";
|
|
5
|
+
export { registerTools, formatToolError } from "./register.js";
|
|
6
|
+
/** Build a fully-wired MCP server instance for one connection / request. */
|
|
7
|
+
export function buildServer(options) {
|
|
8
|
+
const client = new ApiClient({
|
|
9
|
+
baseUrl: options.baseUrl,
|
|
10
|
+
apiKey: options.apiKey,
|
|
11
|
+
apiKeyId: options.apiKeyId,
|
|
12
|
+
bearer: options.bearer,
|
|
13
|
+
onBearerUnauthorized: options.onBearerUnauthorized,
|
|
14
|
+
fetchImpl: options.fetchImpl,
|
|
15
|
+
});
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: SERVER_NAME,
|
|
18
|
+
version: SERVER_VERSION,
|
|
19
|
+
});
|
|
20
|
+
registerTools(server, client);
|
|
21
|
+
return server;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/lib/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAsB,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAsB,MAAM,eAAe,CAAC;AAcnF,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,OAA2B;IACnD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;QAClD,SAAS,EAAE,OAAO,CAAC,SAAS;KAC/B,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QACzB,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;KAC1B,CAAC,CAAC;IACH,aAAa,CAAC,MAAkC,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/tools/analytics.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAuBjE,eAAO,MAAM,cAAc,EAAE,OAAO,EAAyB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool, pickQuery } from "./types.js";
|
|
3
|
+
const analyticsDashboard = defineTool({
|
|
4
|
+
name: "analytics_dashboard",
|
|
5
|
+
description: "Get message analytics (sent/delivered/read/failed series + totals) for the key's WhatsApp " +
|
|
6
|
+
"number via GET /v1/analytics/dashboard. Number-scoped API key required.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
period: z
|
|
9
|
+
.enum(["7d", "30d", "90d"])
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Reporting window (default 30d)"),
|
|
12
|
+
timezone: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("IANA timezone for day bucketing (default UTC)"),
|
|
16
|
+
},
|
|
17
|
+
handler: (client, args) => client.get("/v1/analytics/dashboard", {
|
|
18
|
+
query: pickQuery(args, ["period", "timezone"]),
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
export const analyticsTools = [analyticsDashboard];
|
|
22
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/tools/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAgB,MAAM,YAAY,CAAC;AAEjE,MAAM,kBAAkB,GAAG,UAAU,CAAC;IAClC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACP,4FAA4F;QAC5F,yEAAyE;IAC7E,WAAW,EAAE;QACT,MAAM,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;aAC1B,QAAQ,EAAE;aACV,QAAQ,CAAC,gCAAgC,CAAC;QAC/C,QAAQ,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,+CAA+C,CAAC;KACjE;IACD,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CACtB,MAAM,CAAC,GAAG,CAAC,yBAAyB,EAAE;QAClC,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;KACjD,CAAC;CACT,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAc,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/tools/api-keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAqCtD,eAAO,MAAM,WAAW,EAAE,OAAO,EAAiC,CAAC"}
|