@plingjs/surface-gateway 0.0.0-stage → 0.1.0-alpha.4

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/index.d.ts ADDED
@@ -0,0 +1,273 @@
1
+ export const BRIDGE_PROTOCOL: "pling.bridge/0.1";
2
+ export const SURFACE_PROTOCOL: "pling.surface/0.1";
3
+ export const PACKAGE_VERSION: string;
4
+
5
+ export interface SurfacePublicDemoConfig {
6
+ origins: string[];
7
+ scope: string[];
8
+ pageSessionTtlMs: number;
9
+ maxSessionsPerWindow: number;
10
+ windowMs: number;
11
+ maxActiveSessions: number;
12
+ userIdPrefix: string;
13
+ }
14
+
15
+ export interface SurfaceTenantConfig {
16
+ id: string;
17
+ displayName: string;
18
+ origins: string[];
19
+ maxPageSessionTtlMs: number;
20
+ maxAgentSessionTtlMs: number;
21
+ publicDemo: SurfacePublicDemoConfig | null;
22
+ }
23
+
24
+ export interface SurfaceConfig {
25
+ tenants: SurfaceTenantConfig[];
26
+ }
27
+
28
+ export interface SurfaceAuditEvent {
29
+ type: string;
30
+ at: string;
31
+ [key: string]: unknown;
32
+ }
33
+
34
+ export interface SurfacePageSocket {
35
+ readonly readyState: number;
36
+ send(data: string): void;
37
+ close(code?: number, reason?: string): void;
38
+ }
39
+
40
+ export interface SurfacePageSession {
41
+ id: string;
42
+ tenantId: string;
43
+ userId: string;
44
+ allowedScope: string[];
45
+ token: string;
46
+ createdAt: number;
47
+ expiresAt: number;
48
+ connectedAt: number | null;
49
+ socket: SurfacePageSocket | null;
50
+ activeAgentSessionId: string | null;
51
+ }
52
+
53
+ export interface SurfaceAgentSession {
54
+ id: string;
55
+ token: string;
56
+ tenantId: string;
57
+ pageSessionId: string;
58
+ agentId: string;
59
+ userId: string;
60
+ scope: string[];
61
+ createdAt: number;
62
+ expiresAt: number;
63
+ revokedAt: number | null;
64
+ }
65
+
66
+ export interface PublicPageSession {
67
+ pageSessionId: string;
68
+ token: string;
69
+ expiresAt: number;
70
+ tenantId: string;
71
+ surfaceUrl: string;
72
+ pairingUrl: string;
73
+ agentSessionsUrl: string;
74
+ mcpUrl: string;
75
+ scope: string[];
76
+ }
77
+
78
+ export interface SurfacePairing {
79
+ code: string;
80
+ tenantId: string;
81
+ tenantName: string;
82
+ mcpUrl: string;
83
+ expiresAt: number;
84
+ scope: string[];
85
+ }
86
+
87
+ export interface SurfaceAgentAccess {
88
+ agentSessionId: string;
89
+ token: string;
90
+ tenantId: string;
91
+ pageSessionId: string;
92
+ agentId: string;
93
+ scope: string[];
94
+ expiresAt: number;
95
+ mcpUrl: string;
96
+ }
97
+
98
+ export type SurfaceRequestState = "waiting" | "pending_approval" | "done" | "not_found";
99
+
100
+ export interface SurfaceProtocolFailure {
101
+ protocol: typeof SURFACE_PROTOCOL;
102
+ id: string;
103
+ ok: false;
104
+ error: { code: string; message: string };
105
+ }
106
+
107
+ export interface SurfaceProtocolSuccess<T = unknown> {
108
+ protocol: typeof SURFACE_PROTOCOL;
109
+ id: string;
110
+ ok: true;
111
+ result: T;
112
+ }
113
+
114
+ export type SurfaceProtocolResponse<T = unknown> = SurfaceProtocolSuccess<T> | SurfaceProtocolFailure;
115
+
116
+ export interface SurfaceApprovalView {
117
+ description: string;
118
+ args: Record<string, unknown>;
119
+ kind: string;
120
+ expiresAt: number;
121
+ flow?: Record<string, unknown>;
122
+ }
123
+
124
+ export interface SurfaceInvocationResult {
125
+ state: SurfaceRequestState;
126
+ requestId: string;
127
+ capability?: string;
128
+ approval?: SurfaceApprovalView;
129
+ response?: SurfaceProtocolResponse;
130
+ }
131
+
132
+ export interface SurfaceGatewayOptions {
133
+ config: SurfaceConfig;
134
+ publicBaseUrl: string;
135
+ now?: () => number;
136
+ requestTimeoutMs?: number;
137
+ approvalTimeoutMs?: number;
138
+ pageSessionTtlMs?: number;
139
+ pairingTtlMs?: number;
140
+ agentSessionTtlMs?: number;
141
+ resultTtlMs?: number;
142
+ revokedSessionRetentionMs?: number;
143
+ maxPairingClaimsPerWindow?: number;
144
+ pairingClaimWindowMs?: number;
145
+ onAudit?: (event: SurfaceAuditEvent) => void | Promise<void>;
146
+ }
147
+
148
+ export interface SurfaceGateway {
149
+ readonly version: string;
150
+ readonly config: SurfaceConfig;
151
+ createPageSession(input: { tenantId: string; userId: string; scope: string[]; ttlMs?: number }): PublicPageSession;
152
+ createPublicDemoPageSession(input: { tenantId: string; origin: string; clientKey?: string }): PublicPageSession;
153
+ authenticatePage(token: string | null | undefined, origin: string | null | undefined): SurfacePageSession;
154
+ attachPage(session: SurfacePageSession, socket: SurfacePageSocket, origin: string | null | undefined): void;
155
+ disconnectPage(session: SurfacePageSession, reason?: string, socket?: SurfacePageSocket | null): void;
156
+ createPairing(input: { pageToken: string; ttlMs?: number }): SurfacePairing;
157
+ claimPairing(input: { code: string; agentId: string; scope?: string[]; ttlMs?: number; clientKey?: string }): SurfaceAgentAccess;
158
+ authenticateAgent(token: string | null | undefined): SurfaceAgentSession;
159
+ revokeAgentSession(id: string, reason?: string): Record<string, unknown>;
160
+ revokePageSession(id: string, reason?: string): { pageSessionId: string; revoked: true };
161
+ describe(agentToken: string): Promise<SurfaceProtocolResponse>;
162
+ invoke(agentToken: string, capability: string, input?: Record<string, unknown>): Promise<SurfaceInvocationResult>;
163
+ status(agentToken: string, requestId: string): SurfaceInvocationResult;
164
+ notePageMessage(page: SurfacePageSession, message: unknown): void;
165
+ discover(tenantId: string): Record<string, unknown>;
166
+ listSessions(): {
167
+ tenants: Array<Pick<SurfaceTenantConfig, "id" | "displayName" | "origins">>;
168
+ pageSessions: Array<{
169
+ id: string;
170
+ tenantId: string;
171
+ userId: string;
172
+ connected: boolean;
173
+ scope: string[];
174
+ expiresAt: number;
175
+ activeAgentSessionId: string | null;
176
+ }>;
177
+ agentSessions: Array<{
178
+ id: string;
179
+ tenantId: string;
180
+ pageSessionId: string;
181
+ agentId: string;
182
+ userId: string;
183
+ scope: string[];
184
+ expiresAt: number;
185
+ revokedAt: number | null;
186
+ }>;
187
+ };
188
+ expire(): void;
189
+ shutdown(): void;
190
+ }
191
+
192
+ export interface SurfaceGatewaySubOptions {
193
+ now?: () => number;
194
+ requestTimeoutMs?: number;
195
+ approvalTimeoutMs?: number;
196
+ pageSessionTtlMs?: number;
197
+ pairingTtlMs?: number;
198
+ agentSessionTtlMs?: number;
199
+ resultTtlMs?: number;
200
+ revokedSessionRetentionMs?: number;
201
+ maxPairingClaimsPerWindow?: number;
202
+ pairingClaimWindowMs?: number;
203
+ onAudit?: (event: SurfaceAuditEvent) => void | Promise<void>;
204
+ }
205
+
206
+ export interface SurfaceServerOptions {
207
+ config: SurfaceConfig;
208
+ adminToken: string;
209
+ publicBaseUrl?: string;
210
+ host?: string;
211
+ port?: number;
212
+ demoTenantId?: string;
213
+ trustProxy?: boolean;
214
+ logger?: (message: string) => void;
215
+ gateway?: SurfaceGatewaySubOptions;
216
+ }
217
+
218
+ export interface SurfaceServer {
219
+ readonly gateway: SurfaceGateway | null;
220
+ readonly baseUrl: string | null;
221
+ start(): Promise<{ baseUrl: string; port: number }>;
222
+ stop(): Promise<void>;
223
+ }
224
+
225
+ export interface McpRequest {
226
+ jsonrpc: "2.0";
227
+ id?: string | number | null;
228
+ method: string;
229
+ params?: Record<string, unknown>;
230
+ }
231
+
232
+ export interface McpHandler {
233
+ handle(body: McpRequest, context?: { agentToken?: string }): Promise<Record<string, unknown> | null>;
234
+ }
235
+
236
+ export interface SurfaceMcpTool {
237
+ name: string;
238
+ description: string;
239
+ inputSchema: Record<string, unknown>;
240
+ }
241
+
242
+ export interface SurfaceCliRun {
243
+ server: SurfaceServer;
244
+ config: SurfaceConfig;
245
+ configPath: string;
246
+ baseUrl: string;
247
+ port: number;
248
+ stop(signal?: string): Promise<void>;
249
+ }
250
+
251
+ export class ConfigError extends Error {
252
+ readonly code: string;
253
+ }
254
+
255
+ export class GatewayError extends Error {
256
+ readonly code: string;
257
+ readonly status: number;
258
+ }
259
+
260
+ export function normalizeConfig(raw: unknown): SurfaceConfig;
261
+ export function loadConfig(file: string): Promise<SurfaceConfig>;
262
+ export function normalizeOrigin(value: string): string;
263
+ export function createSurfaceGateway(options: SurfaceGatewayOptions): SurfaceGateway;
264
+ export function createSurfaceServer(options: SurfaceServerOptions): SurfaceServer;
265
+ export function createMcpHandler(gateway: SurfaceGateway, options?: { version?: string }): McpHandler;
266
+ export function asGatewayError(error: unknown): GatewayError;
267
+ export function runSurfaceGatewayCli(options?: {
268
+ env?: Record<string, string | undefined>;
269
+ stderr?: { write(chunk: string): unknown };
270
+ }): Promise<SurfaceCliRun>;
271
+ export function cliHelp(): string;
272
+
273
+ export const TOOLS: SurfaceMcpTool[];
package/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ export { BRIDGE_PROTOCOL, SURFACE_PROTOCOL, createSurfaceGateway } from "./gateway.mjs";
2
+ export { createSurfaceServer } from "./server.mjs";
3
+ export { createMcpHandler, TOOLS } from "./mcp.mjs";
4
+ export { ConfigError, loadConfig, normalizeConfig, normalizeOrigin } from "./config.mjs";
5
+ export { GatewayError, asGatewayError } from "./errors.mjs";
6
+ export { cliHelp, runSurfaceGatewayCli } from "./cli.mjs";
7
+ export { PACKAGE_VERSION } from "./version.mjs";
package/mcp.mjs ADDED
@@ -0,0 +1,145 @@
1
+ import { GatewayError } from "./errors.mjs";
2
+ import { PACKAGE_VERSION } from "./version.mjs";
3
+
4
+ const TOOLS = [
5
+ {
6
+ name: "pling_surface_describe",
7
+ description:
8
+ "Read the currently paired business page: exposed capabilities, input schemas, records, regions, page state, and feedback. Call this before invoking an action.",
9
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
10
+ },
11
+ {
12
+ name: "pling_surface_invoke",
13
+ description:
14
+ "Invoke one capability returned by pling_surface_describe. If the result is pending_approval, the user must confirm in the business page. The Agent cannot approve.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ capability: { type: "string", description: "Stable capability id returned by describe" },
19
+ input: { type: "object", description: "Business input declared by the capability schema", additionalProperties: true },
20
+ },
21
+ required: ["capability"],
22
+ additionalProperties: false,
23
+ },
24
+ },
25
+ {
26
+ name: "pling_surface_approval_status",
27
+ description:
28
+ "Read the final status of an earlier Surface invocation. pending_approval means the user must finish confirmation in the business page.",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ requestId: { type: "string", description: "requestId returned by pling_surface_invoke" },
33
+ },
34
+ required: ["requestId"],
35
+ additionalProperties: false,
36
+ },
37
+ },
38
+ ];
39
+
40
+ export function createMcpHandler(gateway, options = {}) {
41
+ const version = typeof options.version === "string" && options.version.trim()
42
+ ? options.version.trim()
43
+ : PACKAGE_VERSION;
44
+ return {
45
+ async handle(body, context = {}) {
46
+ if (!body || typeof body !== "object" || body.jsonrpc !== "2.0" || typeof body.method !== "string") {
47
+ return { error: { code: -32600, message: "Invalid Request" } };
48
+ }
49
+ if (body.id === undefined || body.id === null) return null;
50
+ if (typeof body.id !== "string" && typeof body.id !== "number") {
51
+ return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid Request" } };
52
+ }
53
+
54
+ const id = body.id;
55
+ try {
56
+ if (body.params !== undefined && (!body.params || typeof body.params !== "object" || Array.isArray(body.params))) {
57
+ throw new GatewayError("INVALID_PARAMS", "params must be an object.", 400);
58
+ }
59
+ const result = await dispatch(gateway, body.method, body.params ?? {}, context, version);
60
+ return { jsonrpc: "2.0", id, result };
61
+ } catch (error) {
62
+ const code = error instanceof GatewayError
63
+ ? error.status === 401
64
+ ? -32001
65
+ : error.status === 404
66
+ ? -32601
67
+ : error.status === 400
68
+ ? -32602
69
+ : -32603
70
+ : -32603;
71
+ return {
72
+ jsonrpc: "2.0",
73
+ id,
74
+ error: { code, message: error instanceof Error ? error.message : String(error) },
75
+ };
76
+ }
77
+ },
78
+ };
79
+ }
80
+
81
+ async function dispatch(gateway, method, params, context, version) {
82
+ if (method === "initialize") {
83
+ return {
84
+ protocolVersion: typeof params.protocolVersion === "string" ? params.protocolVersion : "2025-03-26",
85
+ capabilities: { tools: { listChanged: false } },
86
+ serverInfo: { name: "pling-surface-gateway", version },
87
+ instructions:
88
+ "Pling Surface Gateway. The user must pair an open business page before tools work. Call pling_surface_describe first. " +
89
+ "If pling_surface_invoke returns pending_approval, ask the user to confirm in the open business page and then call pling_surface_approval_status. " +
90
+ "There is no approval tool; never claim an action was approved without a final response.",
91
+ };
92
+ }
93
+ if (method === "ping") return {};
94
+ if (method === "tools/list") return { tools: TOOLS };
95
+ if (method === "tools/call") return callTool(gateway, params, context);
96
+ throw new GatewayError("METHOD_NOT_FOUND", `Unsupported MCP method: ${method}`, 404);
97
+ }
98
+
99
+ async function callTool(gateway, params, context) {
100
+ const name = typeof params.name === "string" ? params.name : "";
101
+ const args = params.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments) ? params.arguments : {};
102
+ const agentToken = typeof context?.agentToken === "string" ? context.agentToken : "";
103
+ try {
104
+ if (name === "pling_surface_describe") {
105
+ const response = await gateway.describe(agentToken);
106
+ if (!response?.ok) return toolError(response?.error?.message ?? "describe failed");
107
+ return toolText(response.result);
108
+ }
109
+ if (name === "pling_surface_invoke") {
110
+ if (typeof args.capability !== "string" || !args.capability.trim()) return toolError("capability must be a non-empty string");
111
+ if (args.input !== undefined && (!args.input || typeof args.input !== "object" || Array.isArray(args.input))) {
112
+ return toolError("input must be an object");
113
+ }
114
+ const result = await gateway.invoke(agentToken, args.capability, args.input ?? {});
115
+ if (result.state === "pending_approval") {
116
+ return toolText(
117
+ `Waiting for user confirmation in the business page.\n` +
118
+ `requestId: ${result.requestId}\n` +
119
+ `capability: ${result.capability}\n\n` +
120
+ "Ask the user to complete confirmation in the open business page, then call pling_surface_approval_status. Do not claim approval.",
121
+ );
122
+ }
123
+ return toolText(result);
124
+ }
125
+ if (name === "pling_surface_approval_status") {
126
+ if (typeof args.requestId !== "string" || !args.requestId.trim()) return toolError("requestId must be a non-empty string");
127
+ return toolText(gateway.status(agentToken, args.requestId));
128
+ }
129
+ return toolError(`Unknown tool: ${name}`);
130
+ } catch (error) {
131
+ return toolError(error instanceof Error ? error.message : String(error));
132
+ }
133
+ }
134
+
135
+ function toolText(value) {
136
+ return {
137
+ content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }],
138
+ };
139
+ }
140
+
141
+ function toolError(message) {
142
+ return { content: [{ type: "text", text: message }], isError: true };
143
+ }
144
+
145
+ export { TOOLS };
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@plingjs/surface-gateway",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-alpha.4",
4
+ "description": "Reference Node gateway for Pling Agent Surface: page sessions, pairing, MCP, scopes, revocation, and audit.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/plingjs/pling.git",
9
+ "directory": "packages/surface-gateway"
10
+ },
11
+ "homepage": "https://plingjs.dev",
12
+ "bugs": "https://github.com/plingjs/pling/issues",
13
+ "type": "module",
14
+ "main": "./index.mjs",
15
+ "module": "./index.mjs",
16
+ "types": "./index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "import": "./index.mjs",
21
+ "default": "./index.mjs"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "bin": {
26
+ "pling-surface-gateway": "./bin/surface-gateway.mjs"
27
+ },
28
+ "dependencies": {
29
+ "ws": "^8.18.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=20.12"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }