easyeda-mcp-pro 0.17.0 → 0.18.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 +82 -13
- package/dist/config/version.d.ts +1 -1
- package/dist/config/version.js +1 -1
- package/dist/remote/approval-policy.d.ts +33 -0
- package/dist/remote/approval-policy.js +41 -0
- package/dist/remote/approval-policy.js.map +1 -0
- package/dist/remote/index.d.ts +5 -0
- package/dist/remote/index.js +6 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/observability.d.ts +27 -0
- package/dist/remote/observability.js +37 -0
- package/dist/remote/observability.js.map +1 -0
- package/dist/remote/protocol.d.ts +321 -0
- package/dist/remote/protocol.js +104 -0
- package/dist/remote/protocol.js.map +1 -0
- package/dist/remote/scope.d.ts +16 -0
- package/dist/remote/scope.js +25 -0
- package/dist/remote/scope.js.map +1 -0
- package/dist/remote/session-router.d.ts +54 -0
- package/dist/remote/session-router.js +138 -0
- package/dist/remote/session-router.js.map +1 -0
- package/easyeda-bridge-extension.eext +0 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# easyeda-mcp-pro
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[](https://www.bestpractices.dev/projects/13406)
|
|
9
|
-
[](https://deepwiki.com/oaslananka/easyeda-mcp-pro)
|
|
5
|
+
<p>
|
|
6
|
+
Production-grade MCP server for EasyEDA Pro: safe PCB design inspection, BOM sourcing, manufacturing export, and AI-assisted hardware review.
|
|
7
|
+
</p>
|
|
10
8
|
|
|
11
|
-
<p
|
|
12
|
-
<a href="https://
|
|
13
|
-
<img src="https://
|
|
9
|
+
<p>
|
|
10
|
+
<a href="https://github.com/oaslananka/easyeda-mcp-pro/actions/workflows/ci.yml">
|
|
11
|
+
<img src="https://github.com/oaslananka/easyeda-mcp-pro/actions/workflows/ci.yml/badge.svg" alt="CI status" />
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/easyeda-mcp-pro">
|
|
14
|
+
<img src="https://img.shields.io/npm/v/easyeda-mcp-pro.svg?logo=npm" alt="npm version" />
|
|
15
|
+
</a>
|
|
16
|
+
<a href="https://www.npmjs.com/package/easyeda-mcp-pro">
|
|
17
|
+
<img src="https://img.shields.io/npm/dt/easyeda-mcp-pro?logo=npm&label=total%20downloads" alt="npm total downloads" />
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://www.npmjs.com/package/easyeda-mcp-pro">
|
|
20
|
+
<img src="https://img.shields.io/node/v/easyeda-mcp-pro" alt="supported Node.js version" />
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
23
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" />
|
|
24
|
+
</a>
|
|
25
|
+
<a href="https://www.bestpractices.dev/projects/13406">
|
|
26
|
+
<img src="https://www.bestpractices.dev/projects/13406/badge" alt="OpenSSF Best Practices" />
|
|
27
|
+
</a>
|
|
28
|
+
<a href="https://github.com/oaslananka/easyeda-mcp-pro/security/policy">
|
|
29
|
+
<img src="https://img.shields.io/badge/security-policy-blue" alt="Security policy" />
|
|
30
|
+
</a>
|
|
31
|
+
<a href="https://deepwiki.com/oaslananka/easyeda-mcp-pro">
|
|
32
|
+
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" />
|
|
14
33
|
</a>
|
|
15
34
|
</p>
|
|
16
35
|
|
|
36
|
+
<p>
|
|
37
|
+
<strong>Compliance docs:</strong>
|
|
38
|
+
<a href="THIRD_PARTY_NOTICES.md">Third-Party Notices</a>
|
|
39
|
+
·
|
|
40
|
+
<a href="docs/vendor-terms.md">Vendor Terms and Unsupported Workflows</a>
|
|
41
|
+
·
|
|
42
|
+
<a href="docs/OPENSSF_BEST_PRACTICES.md">OpenSSF Best Practices</a>
|
|
43
|
+
·
|
|
44
|
+
<a href="docs/REMOTE_MCP_MODES.md">Remote MCP Modes</a>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
|
|
17
49
|
---
|
|
18
50
|
|
|
19
51
|
## Quick Start
|
|
@@ -46,7 +78,7 @@ For advanced configurations, manual instructions, and specific clients, see [Ins
|
|
|
46
78
|
|
|
47
79
|
## Overview
|
|
48
80
|
|
|
49
|
-
easyeda-mcp-pro is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that bridges AI assistants with hardware design workflows in EasyEDA Pro. It exposes
|
|
81
|
+
easyeda-mcp-pro is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that bridges AI assistants with hardware design workflows in EasyEDA Pro. It exposes up to 60 profile-gated MCP tools for schematic inspection and editing, controlled EasyEDA Pro API calls, BOM management, design rule checks, PCB board analysis, fabrication exports, diagnostics, and supplier integration.
|
|
50
82
|
|
|
51
83
|
The server connects to EasyEDA Pro via a WebSocket bridge extension, enabling real-time access to open project data. It integrates with JLCPCB, LCSC, Mouser, and DigiKey for BOM sourcing and pricing.
|
|
52
84
|
|
|
@@ -456,6 +488,24 @@ When `OAUTH_ENABLED=true`, every request to `/mcp` must include an `Authorizatio
|
|
|
456
488
|
|
|
457
489
|
The server enforces startup safety checks: **non-loopback `HTTP_HOST` without OAuth is rejected**, and `HTTP_AUTH_DISABLED=true` is rejected outside non-production loopback deployments.
|
|
458
490
|
|
|
491
|
+
### Docker defaults
|
|
492
|
+
|
|
493
|
+
The Docker image starts in HTTP mode with `HTTP_HOST=127.0.0.1` so the default container boot path is safe and passes the same startup safety checks as local HTTP mode. For an externally reachable container, override the bind address and configure OAuth plus an explicit origin allowlist:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
docker run --rm \
|
|
497
|
+
-e HTTP_HOST=0.0.0.0 \
|
|
498
|
+
-e ALLOWED_ORIGINS=https://your-client.example.com \
|
|
499
|
+
-e OAUTH_ENABLED=true \
|
|
500
|
+
-e OAUTH_ISSUER=https://issuer.example.com/ \
|
|
501
|
+
-e OAUTH_JWKS_URI=https://issuer.example.com/.well-known/jwks.json \
|
|
502
|
+
-e OAUTH_AUDIENCE=easyeda-mcp-pro \
|
|
503
|
+
-p 127.0.0.1:3000:3000 \
|
|
504
|
+
ghcr.io/oaslananka/easyeda-mcp-pro:latest
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Do not expose non-loopback HTTP without OAuth. Use a reverse proxy or platform gateway for TLS termination and external access.
|
|
508
|
+
|
|
459
509
|
#### HTTP Security Features
|
|
460
510
|
|
|
461
511
|
- **Rate limiting**: Per-IP sliding window (configurable via `HTTP_RATE_LIMIT_MAX`), returns `429 Too Many Requests` with retry-after header
|
|
@@ -468,7 +518,14 @@ See `.env.example` for the complete list of configuration variables.
|
|
|
468
518
|
|
|
469
519
|
## MCP Tools
|
|
470
520
|
|
|
471
|
-
The server currently registers
|
|
521
|
+
The server currently registers up to 60 profile-gated tools. Tools are filtered by the active `TOOL_PROFILE`:
|
|
522
|
+
|
|
523
|
+
- `core`: 42 tools
|
|
524
|
+
- `pro`: 47 tools
|
|
525
|
+
- `full`: 56 tools
|
|
526
|
+
- `dev`: 60 tools
|
|
527
|
+
|
|
528
|
+
`core` exposes the standard workflow tools, `pro` adds manufacturing exports, `full` adds controlled documented EasyEDA API calls, and `dev` adds runtime probes for debugging.
|
|
472
529
|
|
|
473
530
|
Capability scopes add a second authorization layer when `TOOL_SCOPES` is set. Leave it empty for the default local all-capabilities mode, or restrict it with comma/space separated scopes such as `diagnostics:read`, `schematic:read`, `schematic:write`, `bom:read`, `bom:source`, `checks:read`, `pcb:read`, `pcb:write`, `export:write`, `api:read`, `api:write`, and `bridge:execute`.
|
|
474
531
|
|
|
@@ -568,7 +625,7 @@ The schematic write APIs use EasyEDA Pro extension APIs that EasyEDA currently m
|
|
|
568
625
|
│ (via Plugin) │ Protocol │ └───────────────┘ │
|
|
569
626
|
└─────────────────┘ │ ┌───────────────┐ │
|
|
570
627
|
│ │ ToolRegistry │ │
|
|
571
|
-
│ │
|
|
628
|
+
│ │ (up to 60 tools) │ │
|
|
572
629
|
│ └───────────────┘ │
|
|
573
630
|
│ ┌───────────────┐ │
|
|
574
631
|
│ │ Storage │──┼──► SQLite
|
|
@@ -712,6 +769,18 @@ This repository uses automated workflows to manage dependencies and releases:
|
|
|
712
769
|
|
|
713
770
|
---
|
|
714
771
|
|
|
772
|
+
## Support the project
|
|
773
|
+
|
|
774
|
+
If this project helps you save time while working with EasyEDA Pro, BOM workflows, or MCP integrations, you can support ongoing development here:
|
|
775
|
+
|
|
776
|
+
<p align="left">
|
|
777
|
+
<a href="https://www.buymeacoffee.com/oaslananka">
|
|
778
|
+
<img src="https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=%E2%98%95&slug=oaslananka&button_colour=FFDD00&font_colour=000000&font_family=Inter&outline_colour=000000&coffee_colour=ffffff" alt="Buy me a coffee" />
|
|
779
|
+
</a>
|
|
780
|
+
</p>
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
715
784
|
## License
|
|
716
785
|
|
|
717
786
|
[MIT](LICENSE)
|
package/dist/config/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SERVER_VERSION = "0.
|
|
1
|
+
export declare const SERVER_VERSION = "0.18.0";
|
package/dist/config/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const SERVER_VERSION = '0.
|
|
1
|
+
export const SERVER_VERSION = '0.18.0'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ActiveProject, RemoteRiskLevel } from './protocol.js';
|
|
2
|
+
export interface ApprovalRequestInput {
|
|
3
|
+
approvalId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
toolName: string;
|
|
7
|
+
riskLevel: RemoteRiskLevel;
|
|
8
|
+
inputHash: string;
|
|
9
|
+
actionSummary: string;
|
|
10
|
+
activeProject?: ActiveProject;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
}
|
|
13
|
+
export type ApprovalDecision = 'approved' | 'rejected' | 'timeout';
|
|
14
|
+
export interface ApprovalRecord extends ApprovalRequestInput {
|
|
15
|
+
decision?: ApprovalDecision;
|
|
16
|
+
decidedAt?: Date;
|
|
17
|
+
}
|
|
18
|
+
export declare function requiresApproval(riskLevel: RemoteRiskLevel): boolean;
|
|
19
|
+
export declare function isDestructiveRisk(riskLevel: RemoteRiskLevel): boolean;
|
|
20
|
+
export declare class ApprovalStore {
|
|
21
|
+
private readonly approvals;
|
|
22
|
+
request(input: ApprovalRequestInput): ApprovalRecord;
|
|
23
|
+
resolve(approvalId: string, decision: ApprovalDecision, now?: Date): ApprovalRecord | undefined;
|
|
24
|
+
consumeApproved(input: {
|
|
25
|
+
approvalId: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
toolName: string;
|
|
29
|
+
inputHash: string;
|
|
30
|
+
now?: Date;
|
|
31
|
+
}): boolean;
|
|
32
|
+
get(approvalId: string): ApprovalRecord | undefined;
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function requiresApproval(riskLevel) {
|
|
2
|
+
return riskLevel !== 'read';
|
|
3
|
+
}
|
|
4
|
+
export function isDestructiveRisk(riskLevel) {
|
|
5
|
+
return riskLevel === 'destructive';
|
|
6
|
+
}
|
|
7
|
+
export class ApprovalStore {
|
|
8
|
+
approvals = new Map();
|
|
9
|
+
request(input) {
|
|
10
|
+
const record = { ...input };
|
|
11
|
+
this.approvals.set(input.approvalId, record);
|
|
12
|
+
return record;
|
|
13
|
+
}
|
|
14
|
+
resolve(approvalId, decision, now = new Date()) {
|
|
15
|
+
const record = this.approvals.get(approvalId);
|
|
16
|
+
if (!record)
|
|
17
|
+
return undefined;
|
|
18
|
+
record.decision = now.getTime() > record.expiresAt.getTime() ? 'timeout' : decision;
|
|
19
|
+
record.decidedAt = now;
|
|
20
|
+
return record;
|
|
21
|
+
}
|
|
22
|
+
consumeApproved(input) {
|
|
23
|
+
const record = this.approvals.get(input.approvalId);
|
|
24
|
+
if (!record)
|
|
25
|
+
return false;
|
|
26
|
+
const now = input.now ?? new Date();
|
|
27
|
+
const matches = record.userId === input.userId &&
|
|
28
|
+
record.sessionId === input.sessionId &&
|
|
29
|
+
record.toolName === input.toolName &&
|
|
30
|
+
record.inputHash === input.inputHash &&
|
|
31
|
+
record.decision === 'approved' &&
|
|
32
|
+
record.expiresAt.getTime() > now.getTime();
|
|
33
|
+
if (matches)
|
|
34
|
+
this.approvals.delete(input.approvalId);
|
|
35
|
+
return matches;
|
|
36
|
+
}
|
|
37
|
+
get(approvalId) {
|
|
38
|
+
return this.approvals.get(approvalId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=approval-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../../src/remote/approval-policy.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,gBAAgB,CAAC,SAA0B;IACzD,OAAO,SAAS,KAAK,MAAM,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAA0B;IAC1D,OAAO,SAAS,KAAK,aAAa,CAAC;AACrC,CAAC;AAED,MAAM,OAAO,aAAa;IACP,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE/D,OAAO,CAAC,KAA2B;QACjC,MAAM,MAAM,GAAmB,EAAE,GAAG,KAAK,EAAE,CAAC;QAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,CACL,UAAkB,EAClB,QAA0B,EAC1B,GAAG,GAAG,IAAI,IAAI,EAAE;QAEhB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,MAAM,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpF,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,eAAe,CAAC,KAOf;QACC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;QACpC,MAAM,OAAO,GACX,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAC9B,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;YACpC,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;YAClC,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;YACpC,MAAM,CAAC,QAAQ,KAAK,UAAU;YAC9B,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7C,IAAI,OAAO;YAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,GAAG,CAAC,UAAkB;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/remote/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RemoteDeploymentMode, RemoteRiskLevel } from './protocol.js';
|
|
2
|
+
export type RemoteAuditEventName = 'remote.session.registered' | 'remote.session.paired' | 'remote.session.disconnected' | 'remote.tool.requested' | 'remote.tool.dispatched' | 'remote.tool.completed' | 'remote.tool.failed' | 'remote.approval.requested' | 'remote.approval.resolved' | 'remote.identity.rejected';
|
|
3
|
+
export interface RemoteAuditEvent {
|
|
4
|
+
event: RemoteAuditEventName;
|
|
5
|
+
observedAt: string;
|
|
6
|
+
mode: RemoteDeploymentMode;
|
|
7
|
+
userId?: string;
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
connectionId?: string;
|
|
10
|
+
toolName?: string;
|
|
11
|
+
riskLevel?: RemoteRiskLevel;
|
|
12
|
+
inputHash?: string;
|
|
13
|
+
status?: 'ok' | 'error' | 'rejected' | 'timeout';
|
|
14
|
+
durationMs?: number;
|
|
15
|
+
errorCode?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function redactRemotePayload(value: unknown): unknown;
|
|
18
|
+
export declare class RemoteAuditLog {
|
|
19
|
+
private readonly maxEvents;
|
|
20
|
+
private events;
|
|
21
|
+
constructor(maxEvents?: number);
|
|
22
|
+
record(event: Omit<RemoteAuditEvent, 'observedAt'> & {
|
|
23
|
+
observedAt?: string;
|
|
24
|
+
}): RemoteAuditEvent;
|
|
25
|
+
recent(limit?: number): RemoteAuditEvent[];
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const REDACTED = '[redacted]';
|
|
2
|
+
const SECRET_KEY_PATTERN = /(token|secret|password|credential|apiKey|api_key|pairingCode)/i;
|
|
3
|
+
export function redactRemotePayload(value) {
|
|
4
|
+
if (Array.isArray(value))
|
|
5
|
+
return value.map((item) => redactRemotePayload(item));
|
|
6
|
+
if (!value || typeof value !== 'object')
|
|
7
|
+
return value;
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const [key, child] of Object.entries(value)) {
|
|
10
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactRemotePayload(child);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
export class RemoteAuditLog {
|
|
15
|
+
maxEvents;
|
|
16
|
+
events = [];
|
|
17
|
+
constructor(maxEvents = 200) {
|
|
18
|
+
this.maxEvents = maxEvents;
|
|
19
|
+
}
|
|
20
|
+
record(event) {
|
|
21
|
+
const stored = {
|
|
22
|
+
...event,
|
|
23
|
+
observedAt: event.observedAt ?? new Date().toISOString(),
|
|
24
|
+
};
|
|
25
|
+
this.events.push(stored);
|
|
26
|
+
if (this.events.length > this.maxEvents)
|
|
27
|
+
this.events = this.events.slice(-this.maxEvents);
|
|
28
|
+
return stored;
|
|
29
|
+
}
|
|
30
|
+
recent(limit = 25) {
|
|
31
|
+
return this.events.slice(-limit);
|
|
32
|
+
}
|
|
33
|
+
clear() {
|
|
34
|
+
this.events = [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=observability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.js","sourceRoot":"","sources":["../../src/remote/observability.ts"],"names":[],"mappings":"AA6BA,MAAM,QAAQ,GAAG,YAAY,CAAC;AAC9B,MAAM,kBAAkB,GAAG,gEAAgE,CAAC;AAE5F,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IAChF,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,OAAO,cAAc;IAGI;IAFrB,MAAM,GAAuB,EAAE,CAAC;IAExC,YAA6B,YAAY,GAAG;QAAf,cAAS,GAAT,SAAS,CAAM;IAAG,CAAC;IAEhD,MAAM,CAAC,KAAqE;QAC1E,MAAM,MAAM,GAAqB;YAC/B,GAAG,KAAK;YACR,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACzD,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1F,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,KAAK,GAAG,EAAE;QACf,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const REMOTE_RELAY_PROTOCOL_VERSION: "2026-07-remote-relay-v1";
|
|
3
|
+
export declare const RemoteDeploymentModeSchema: z.ZodEnum<{
|
|
4
|
+
local: "local";
|
|
5
|
+
hosted: "hosted";
|
|
6
|
+
self_hosted: "self_hosted";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const RemoteRiskLevelSchema: z.ZodEnum<{
|
|
9
|
+
export: "export";
|
|
10
|
+
write: "write";
|
|
11
|
+
read: "read";
|
|
12
|
+
destructive: "destructive";
|
|
13
|
+
}>;
|
|
14
|
+
export declare const RemoteScopeSchema: z.ZodEnum<{
|
|
15
|
+
"easyeda.read": "easyeda.read";
|
|
16
|
+
"easyeda.write": "easyeda.write";
|
|
17
|
+
"easyeda.export": "easyeda.export";
|
|
18
|
+
"easyeda.project_admin": "easyeda.project_admin";
|
|
19
|
+
}>;
|
|
20
|
+
export type RemoteDeploymentMode = z.infer<typeof RemoteDeploymentModeSchema>;
|
|
21
|
+
export type RemoteRiskLevel = z.infer<typeof RemoteRiskLevelSchema>;
|
|
22
|
+
export type RemoteScope = z.infer<typeof RemoteScopeSchema>;
|
|
23
|
+
export declare const ActiveProjectSchema: z.ZodObject<{
|
|
24
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
25
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
26
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
27
|
+
unknown: "unknown";
|
|
28
|
+
schematic: "schematic";
|
|
29
|
+
pcb: "pcb";
|
|
30
|
+
}>>;
|
|
31
|
+
url: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export type ActiveProject = z.infer<typeof ActiveProjectSchema>;
|
|
34
|
+
export declare const RegisterSessionMessageSchema: z.ZodObject<{
|
|
35
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
36
|
+
messageId: z.ZodString;
|
|
37
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
38
|
+
timestamp: z.ZodString;
|
|
39
|
+
type: z.ZodLiteral<"register_session">;
|
|
40
|
+
extensionVersion: z.ZodString;
|
|
41
|
+
mode: z.ZodEnum<{
|
|
42
|
+
hosted: "hosted";
|
|
43
|
+
self_hosted: "self_hosted";
|
|
44
|
+
}>;
|
|
45
|
+
activeProject: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
47
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
48
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
49
|
+
unknown: "unknown";
|
|
50
|
+
schematic: "schematic";
|
|
51
|
+
pcb: "pcb";
|
|
52
|
+
}>>;
|
|
53
|
+
url: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>>;
|
|
55
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export declare const SessionRegisteredMessageSchema: z.ZodObject<{
|
|
58
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
59
|
+
messageId: z.ZodString;
|
|
60
|
+
timestamp: z.ZodString;
|
|
61
|
+
type: z.ZodLiteral<"session_registered">;
|
|
62
|
+
sessionId: z.ZodString;
|
|
63
|
+
paired: z.ZodBoolean;
|
|
64
|
+
expiresAt: z.ZodString;
|
|
65
|
+
}, z.core.$strip>;
|
|
66
|
+
export declare const HeartbeatMessageSchema: z.ZodObject<{
|
|
67
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
68
|
+
messageId: z.ZodString;
|
|
69
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
70
|
+
timestamp: z.ZodString;
|
|
71
|
+
type: z.ZodLiteral<"heartbeat">;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
export declare const ToolRequestMessageSchema: z.ZodObject<{
|
|
74
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
75
|
+
messageId: z.ZodString;
|
|
76
|
+
timestamp: z.ZodString;
|
|
77
|
+
type: z.ZodLiteral<"tool_request">;
|
|
78
|
+
sessionId: z.ZodString;
|
|
79
|
+
toolName: z.ZodString;
|
|
80
|
+
riskLevel: z.ZodEnum<{
|
|
81
|
+
export: "export";
|
|
82
|
+
write: "write";
|
|
83
|
+
read: "read";
|
|
84
|
+
destructive: "destructive";
|
|
85
|
+
}>;
|
|
86
|
+
requiresApproval: z.ZodBoolean;
|
|
87
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
88
|
+
inputHash: z.ZodString;
|
|
89
|
+
activeProjectHint: z.ZodOptional<z.ZodObject<{
|
|
90
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
91
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
92
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
93
|
+
unknown: "unknown";
|
|
94
|
+
schematic: "schematic";
|
|
95
|
+
pcb: "pcb";
|
|
96
|
+
}>>;
|
|
97
|
+
url: z.ZodOptional<z.ZodString>;
|
|
98
|
+
}, z.core.$strip>>;
|
|
99
|
+
deadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
}, z.core.$strip>;
|
|
101
|
+
export declare const ToolResponseMessageSchema: z.ZodObject<{
|
|
102
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
103
|
+
messageId: z.ZodString;
|
|
104
|
+
timestamp: z.ZodString;
|
|
105
|
+
type: z.ZodLiteral<"tool_response">;
|
|
106
|
+
sessionId: z.ZodString;
|
|
107
|
+
requestMessageId: z.ZodString;
|
|
108
|
+
ok: z.ZodBoolean;
|
|
109
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
110
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
111
|
+
code: z.ZodString;
|
|
112
|
+
message: z.ZodString;
|
|
113
|
+
suggestion: z.ZodOptional<z.ZodString>;
|
|
114
|
+
}, z.core.$strip>>;
|
|
115
|
+
durationMs: z.ZodNumber;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
export declare const ApprovalRequestMessageSchema: z.ZodObject<{
|
|
118
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
119
|
+
messageId: z.ZodString;
|
|
120
|
+
timestamp: z.ZodString;
|
|
121
|
+
type: z.ZodLiteral<"approval_request">;
|
|
122
|
+
sessionId: z.ZodString;
|
|
123
|
+
approvalId: z.ZodString;
|
|
124
|
+
toolName: z.ZodString;
|
|
125
|
+
riskLevel: z.ZodEnum<{
|
|
126
|
+
export: "export";
|
|
127
|
+
write: "write";
|
|
128
|
+
read: "read";
|
|
129
|
+
destructive: "destructive";
|
|
130
|
+
}>;
|
|
131
|
+
actionSummary: z.ZodString;
|
|
132
|
+
inputHash: z.ZodString;
|
|
133
|
+
activeProject: z.ZodOptional<z.ZodObject<{
|
|
134
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
135
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
136
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
137
|
+
unknown: "unknown";
|
|
138
|
+
schematic: "schematic";
|
|
139
|
+
pcb: "pcb";
|
|
140
|
+
}>>;
|
|
141
|
+
url: z.ZodOptional<z.ZodString>;
|
|
142
|
+
}, z.core.$strip>>;
|
|
143
|
+
expiresAt: z.ZodString;
|
|
144
|
+
}, z.core.$strip>;
|
|
145
|
+
export declare const ApprovalResultMessageSchema: z.ZodObject<{
|
|
146
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
147
|
+
messageId: z.ZodString;
|
|
148
|
+
timestamp: z.ZodString;
|
|
149
|
+
type: z.ZodLiteral<"approval_result">;
|
|
150
|
+
sessionId: z.ZodString;
|
|
151
|
+
approvalId: z.ZodString;
|
|
152
|
+
result: z.ZodEnum<{
|
|
153
|
+
timeout: "timeout";
|
|
154
|
+
rejected: "rejected";
|
|
155
|
+
approved: "approved";
|
|
156
|
+
}>;
|
|
157
|
+
}, z.core.$strip>;
|
|
158
|
+
export declare const SessionClosedMessageSchema: z.ZodObject<{
|
|
159
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
160
|
+
messageId: z.ZodString;
|
|
161
|
+
timestamp: z.ZodString;
|
|
162
|
+
type: z.ZodLiteral<"session_closed">;
|
|
163
|
+
sessionId: z.ZodString;
|
|
164
|
+
reason: z.ZodEnum<{
|
|
165
|
+
disconnected: "disconnected";
|
|
166
|
+
user_disabled: "user_disabled";
|
|
167
|
+
expired: "expired";
|
|
168
|
+
replaced: "replaced";
|
|
169
|
+
}>;
|
|
170
|
+
}, z.core.$strip>;
|
|
171
|
+
export declare const RelayErrorMessageSchema: z.ZodObject<{
|
|
172
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
173
|
+
messageId: z.ZodString;
|
|
174
|
+
timestamp: z.ZodString;
|
|
175
|
+
type: z.ZodLiteral<"error">;
|
|
176
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
177
|
+
code: z.ZodString;
|
|
178
|
+
message: z.ZodString;
|
|
179
|
+
}, z.core.$strip>;
|
|
180
|
+
export declare const RelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
181
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
182
|
+
messageId: z.ZodString;
|
|
183
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
184
|
+
timestamp: z.ZodString;
|
|
185
|
+
type: z.ZodLiteral<"register_session">;
|
|
186
|
+
extensionVersion: z.ZodString;
|
|
187
|
+
mode: z.ZodEnum<{
|
|
188
|
+
hosted: "hosted";
|
|
189
|
+
self_hosted: "self_hosted";
|
|
190
|
+
}>;
|
|
191
|
+
activeProject: z.ZodOptional<z.ZodObject<{
|
|
192
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
193
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
194
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
195
|
+
unknown: "unknown";
|
|
196
|
+
schematic: "schematic";
|
|
197
|
+
pcb: "pcb";
|
|
198
|
+
}>>;
|
|
199
|
+
url: z.ZodOptional<z.ZodString>;
|
|
200
|
+
}, z.core.$strip>>;
|
|
201
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
202
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
203
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
204
|
+
messageId: z.ZodString;
|
|
205
|
+
timestamp: z.ZodString;
|
|
206
|
+
type: z.ZodLiteral<"session_registered">;
|
|
207
|
+
sessionId: z.ZodString;
|
|
208
|
+
paired: z.ZodBoolean;
|
|
209
|
+
expiresAt: z.ZodString;
|
|
210
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
211
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
212
|
+
messageId: z.ZodString;
|
|
213
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
214
|
+
timestamp: z.ZodString;
|
|
215
|
+
type: z.ZodLiteral<"heartbeat">;
|
|
216
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
217
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
218
|
+
messageId: z.ZodString;
|
|
219
|
+
timestamp: z.ZodString;
|
|
220
|
+
type: z.ZodLiteral<"tool_request">;
|
|
221
|
+
sessionId: z.ZodString;
|
|
222
|
+
toolName: z.ZodString;
|
|
223
|
+
riskLevel: z.ZodEnum<{
|
|
224
|
+
export: "export";
|
|
225
|
+
write: "write";
|
|
226
|
+
read: "read";
|
|
227
|
+
destructive: "destructive";
|
|
228
|
+
}>;
|
|
229
|
+
requiresApproval: z.ZodBoolean;
|
|
230
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
231
|
+
inputHash: z.ZodString;
|
|
232
|
+
activeProjectHint: z.ZodOptional<z.ZodObject<{
|
|
233
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
234
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
235
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
236
|
+
unknown: "unknown";
|
|
237
|
+
schematic: "schematic";
|
|
238
|
+
pcb: "pcb";
|
|
239
|
+
}>>;
|
|
240
|
+
url: z.ZodOptional<z.ZodString>;
|
|
241
|
+
}, z.core.$strip>>;
|
|
242
|
+
deadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
243
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
244
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
245
|
+
messageId: z.ZodString;
|
|
246
|
+
timestamp: z.ZodString;
|
|
247
|
+
type: z.ZodLiteral<"tool_response">;
|
|
248
|
+
sessionId: z.ZodString;
|
|
249
|
+
requestMessageId: z.ZodString;
|
|
250
|
+
ok: z.ZodBoolean;
|
|
251
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
252
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
253
|
+
code: z.ZodString;
|
|
254
|
+
message: z.ZodString;
|
|
255
|
+
suggestion: z.ZodOptional<z.ZodString>;
|
|
256
|
+
}, z.core.$strip>>;
|
|
257
|
+
durationMs: z.ZodNumber;
|
|
258
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
259
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
260
|
+
messageId: z.ZodString;
|
|
261
|
+
timestamp: z.ZodString;
|
|
262
|
+
type: z.ZodLiteral<"approval_request">;
|
|
263
|
+
sessionId: z.ZodString;
|
|
264
|
+
approvalId: z.ZodString;
|
|
265
|
+
toolName: z.ZodString;
|
|
266
|
+
riskLevel: z.ZodEnum<{
|
|
267
|
+
export: "export";
|
|
268
|
+
write: "write";
|
|
269
|
+
read: "read";
|
|
270
|
+
destructive: "destructive";
|
|
271
|
+
}>;
|
|
272
|
+
actionSummary: z.ZodString;
|
|
273
|
+
inputHash: z.ZodString;
|
|
274
|
+
activeProject: z.ZodOptional<z.ZodObject<{
|
|
275
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
276
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
277
|
+
documentType: z.ZodDefault<z.ZodEnum<{
|
|
278
|
+
unknown: "unknown";
|
|
279
|
+
schematic: "schematic";
|
|
280
|
+
pcb: "pcb";
|
|
281
|
+
}>>;
|
|
282
|
+
url: z.ZodOptional<z.ZodString>;
|
|
283
|
+
}, z.core.$strip>>;
|
|
284
|
+
expiresAt: z.ZodString;
|
|
285
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
286
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
287
|
+
messageId: z.ZodString;
|
|
288
|
+
timestamp: z.ZodString;
|
|
289
|
+
type: z.ZodLiteral<"approval_result">;
|
|
290
|
+
sessionId: z.ZodString;
|
|
291
|
+
approvalId: z.ZodString;
|
|
292
|
+
result: z.ZodEnum<{
|
|
293
|
+
timeout: "timeout";
|
|
294
|
+
rejected: "rejected";
|
|
295
|
+
approved: "approved";
|
|
296
|
+
}>;
|
|
297
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
298
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
299
|
+
messageId: z.ZodString;
|
|
300
|
+
timestamp: z.ZodString;
|
|
301
|
+
type: z.ZodLiteral<"session_closed">;
|
|
302
|
+
sessionId: z.ZodString;
|
|
303
|
+
reason: z.ZodEnum<{
|
|
304
|
+
disconnected: "disconnected";
|
|
305
|
+
user_disabled: "user_disabled";
|
|
306
|
+
expired: "expired";
|
|
307
|
+
replaced: "replaced";
|
|
308
|
+
}>;
|
|
309
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
310
|
+
protocolVersion: z.ZodLiteral<"2026-07-remote-relay-v1">;
|
|
311
|
+
messageId: z.ZodString;
|
|
312
|
+
timestamp: z.ZodString;
|
|
313
|
+
type: z.ZodLiteral<"error">;
|
|
314
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
315
|
+
code: z.ZodString;
|
|
316
|
+
message: z.ZodString;
|
|
317
|
+
}, z.core.$strip>], "type">;
|
|
318
|
+
export type RelayMessage = z.infer<typeof RelayMessageSchema>;
|
|
319
|
+
export type RegisterSessionMessage = z.infer<typeof RegisterSessionMessageSchema>;
|
|
320
|
+
export type ToolRequestMessage = z.infer<typeof ToolRequestMessageSchema>;
|
|
321
|
+
export type ApprovalRequestMessage = z.infer<typeof ApprovalRequestMessageSchema>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const REMOTE_RELAY_PROTOCOL_VERSION = '2026-07-remote-relay-v1';
|
|
3
|
+
export const RemoteDeploymentModeSchema = z.enum(['local', 'hosted', 'self_hosted']);
|
|
4
|
+
export const RemoteRiskLevelSchema = z.enum(['read', 'write', 'export', 'destructive']);
|
|
5
|
+
export const RemoteScopeSchema = z.enum([
|
|
6
|
+
'easyeda.read',
|
|
7
|
+
'easyeda.write',
|
|
8
|
+
'easyeda.export',
|
|
9
|
+
'easyeda.project_admin',
|
|
10
|
+
]);
|
|
11
|
+
export const ActiveProjectSchema = z.object({
|
|
12
|
+
projectId: z.string().min(1).optional(),
|
|
13
|
+
projectName: z.string().min(1).optional(),
|
|
14
|
+
documentType: z.enum(['schematic', 'pcb', 'unknown']).default('unknown'),
|
|
15
|
+
url: z.string().url().optional(),
|
|
16
|
+
});
|
|
17
|
+
const BaseRelayMessageSchema = z.object({
|
|
18
|
+
protocolVersion: z.literal(REMOTE_RELAY_PROTOCOL_VERSION),
|
|
19
|
+
messageId: z.string().min(1),
|
|
20
|
+
sessionId: z.string().min(1).optional(),
|
|
21
|
+
timestamp: z.string().datetime(),
|
|
22
|
+
});
|
|
23
|
+
export const RegisterSessionMessageSchema = BaseRelayMessageSchema.extend({
|
|
24
|
+
type: z.literal('register_session'),
|
|
25
|
+
extensionVersion: z.string().min(1),
|
|
26
|
+
mode: RemoteDeploymentModeSchema.exclude(['local']),
|
|
27
|
+
activeProject: ActiveProjectSchema.optional(),
|
|
28
|
+
capabilities: z.array(z.string().min(1)).default([]),
|
|
29
|
+
});
|
|
30
|
+
export const SessionRegisteredMessageSchema = BaseRelayMessageSchema.extend({
|
|
31
|
+
type: z.literal('session_registered'),
|
|
32
|
+
sessionId: z.string().min(1),
|
|
33
|
+
paired: z.boolean(),
|
|
34
|
+
expiresAt: z.string().datetime(),
|
|
35
|
+
});
|
|
36
|
+
export const HeartbeatMessageSchema = BaseRelayMessageSchema.extend({
|
|
37
|
+
type: z.literal('heartbeat'),
|
|
38
|
+
});
|
|
39
|
+
export const ToolRequestMessageSchema = BaseRelayMessageSchema.extend({
|
|
40
|
+
type: z.literal('tool_request'),
|
|
41
|
+
sessionId: z.string().min(1),
|
|
42
|
+
toolName: z.string().min(1),
|
|
43
|
+
riskLevel: RemoteRiskLevelSchema,
|
|
44
|
+
requiresApproval: z.boolean(),
|
|
45
|
+
input: z.unknown().optional(),
|
|
46
|
+
inputHash: z.string().min(1),
|
|
47
|
+
activeProjectHint: ActiveProjectSchema.optional(),
|
|
48
|
+
deadlineMs: z.number().int().positive().optional(),
|
|
49
|
+
});
|
|
50
|
+
export const ToolResponseMessageSchema = BaseRelayMessageSchema.extend({
|
|
51
|
+
type: z.literal('tool_response'),
|
|
52
|
+
sessionId: z.string().min(1),
|
|
53
|
+
requestMessageId: z.string().min(1),
|
|
54
|
+
ok: z.boolean(),
|
|
55
|
+
result: z.unknown().optional(),
|
|
56
|
+
error: z
|
|
57
|
+
.object({
|
|
58
|
+
code: z.string().min(1),
|
|
59
|
+
message: z.string().min(1),
|
|
60
|
+
suggestion: z.string().min(1).optional(),
|
|
61
|
+
})
|
|
62
|
+
.optional(),
|
|
63
|
+
durationMs: z.number().nonnegative(),
|
|
64
|
+
});
|
|
65
|
+
export const ApprovalRequestMessageSchema = BaseRelayMessageSchema.extend({
|
|
66
|
+
type: z.literal('approval_request'),
|
|
67
|
+
sessionId: z.string().min(1),
|
|
68
|
+
approvalId: z.string().min(1),
|
|
69
|
+
toolName: z.string().min(1),
|
|
70
|
+
riskLevel: RemoteRiskLevelSchema,
|
|
71
|
+
actionSummary: z.string().min(1),
|
|
72
|
+
inputHash: z.string().min(1),
|
|
73
|
+
activeProject: ActiveProjectSchema.optional(),
|
|
74
|
+
expiresAt: z.string().datetime(),
|
|
75
|
+
});
|
|
76
|
+
export const ApprovalResultMessageSchema = BaseRelayMessageSchema.extend({
|
|
77
|
+
type: z.literal('approval_result'),
|
|
78
|
+
sessionId: z.string().min(1),
|
|
79
|
+
approvalId: z.string().min(1),
|
|
80
|
+
result: z.enum(['approved', 'rejected', 'timeout']),
|
|
81
|
+
});
|
|
82
|
+
export const SessionClosedMessageSchema = BaseRelayMessageSchema.extend({
|
|
83
|
+
type: z.literal('session_closed'),
|
|
84
|
+
sessionId: z.string().min(1),
|
|
85
|
+
reason: z.enum(['user_disabled', 'expired', 'disconnected', 'replaced']),
|
|
86
|
+
});
|
|
87
|
+
export const RelayErrorMessageSchema = BaseRelayMessageSchema.extend({
|
|
88
|
+
type: z.literal('error'),
|
|
89
|
+
sessionId: z.string().min(1).optional(),
|
|
90
|
+
code: z.string().min(1),
|
|
91
|
+
message: z.string().min(1),
|
|
92
|
+
});
|
|
93
|
+
export const RelayMessageSchema = z.discriminatedUnion('type', [
|
|
94
|
+
RegisterSessionMessageSchema,
|
|
95
|
+
SessionRegisteredMessageSchema,
|
|
96
|
+
HeartbeatMessageSchema,
|
|
97
|
+
ToolRequestMessageSchema,
|
|
98
|
+
ToolResponseMessageSchema,
|
|
99
|
+
ApprovalRequestMessageSchema,
|
|
100
|
+
ApprovalResultMessageSchema,
|
|
101
|
+
SessionClosedMessageSchema,
|
|
102
|
+
RelayErrorMessageSchema,
|
|
103
|
+
]);
|
|
104
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/remote/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RemoteRiskLevel, RemoteScope } from './protocol.js';
|
|
2
|
+
export interface RemoteIdentity {
|
|
3
|
+
userId: string;
|
|
4
|
+
scopes: readonly RemoteScope[];
|
|
5
|
+
expiresAt?: Date;
|
|
6
|
+
}
|
|
7
|
+
export type ScopeCheckResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
code: 'IDENTITY_MISSING' | 'IDENTITY_EXPIRED' | 'SCOPE_MISSING';
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function requiredScopeForRisk(riskLevel: RemoteRiskLevel): RemoteScope;
|
|
15
|
+
export declare function hasRemoteScope(identity: RemoteIdentity, scope: RemoteScope): boolean;
|
|
16
|
+
export declare function checkRemoteScope(identity: RemoteIdentity | undefined, riskLevel: RemoteRiskLevel, now?: Date): ScopeCheckResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function requiredScopeForRisk(riskLevel) {
|
|
2
|
+
if (riskLevel === 'read')
|
|
3
|
+
return 'easyeda.read';
|
|
4
|
+
if (riskLevel === 'write')
|
|
5
|
+
return 'easyeda.write';
|
|
6
|
+
if (riskLevel === 'export')
|
|
7
|
+
return 'easyeda.export';
|
|
8
|
+
return 'easyeda.project_admin';
|
|
9
|
+
}
|
|
10
|
+
export function hasRemoteScope(identity, scope) {
|
|
11
|
+
return identity.scopes.includes(scope) || identity.scopes.includes('easyeda.project_admin');
|
|
12
|
+
}
|
|
13
|
+
export function checkRemoteScope(identity, riskLevel, now = new Date()) {
|
|
14
|
+
if (!identity)
|
|
15
|
+
return { ok: false, code: 'IDENTITY_MISSING', message: 'Remote identity is required.' };
|
|
16
|
+
if (identity.expiresAt && identity.expiresAt.getTime() <= now.getTime()) {
|
|
17
|
+
return { ok: false, code: 'IDENTITY_EXPIRED', message: 'Remote identity has expired.' };
|
|
18
|
+
}
|
|
19
|
+
const required = requiredScopeForRisk(riskLevel);
|
|
20
|
+
if (!hasRemoteScope(identity, required)) {
|
|
21
|
+
return { ok: false, code: 'SCOPE_MISSING', message: `Remote tool requires ${required}.` };
|
|
22
|
+
}
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/remote/scope.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,oBAAoB,CAAC,SAA0B;IAC7D,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,cAAc,CAAC;IAChD,IAAI,SAAS,KAAK,OAAO;QAAE,OAAO,eAAe,CAAC;IAClD,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC;IACpD,OAAO,uBAAuB,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAwB,EAAE,KAAkB;IACzE,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,QAAoC,EACpC,SAA0B,EAC1B,GAAG,GAAG,IAAI,IAAI,EAAE;IAEhB,IAAI,CAAC,QAAQ;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;IAC1F,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACxE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;IAC1F,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACjD,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,wBAAwB,QAAQ,GAAG,EAAE,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ActiveProject, RemoteDeploymentMode, RemoteRiskLevel } from './protocol.js';
|
|
2
|
+
export interface ExtensionSession {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
connectionId: string;
|
|
5
|
+
mode: Exclude<RemoteDeploymentMode, 'local'>;
|
|
6
|
+
extensionVersion: string;
|
|
7
|
+
userId?: string;
|
|
8
|
+
activeProject?: ActiveProject;
|
|
9
|
+
connected: boolean;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
lastSeenAt: Date;
|
|
12
|
+
expiresAt: Date;
|
|
13
|
+
}
|
|
14
|
+
export type SessionRouteResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
session: ExtensionSession;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
code: 'SESSION_UNPAIRED' | 'SESSION_DISCONNECTED' | 'SESSION_EXPIRED' | 'SESSION_AMBIGUOUS' | 'PROJECT_INACTIVE';
|
|
20
|
+
message: string;
|
|
21
|
+
};
|
|
22
|
+
export declare class RemoteSessionRouter {
|
|
23
|
+
private readonly now;
|
|
24
|
+
private readonly makeId;
|
|
25
|
+
private readonly sessions;
|
|
26
|
+
private readonly pairings;
|
|
27
|
+
constructor(now?: () => Date, makeId?: () => string);
|
|
28
|
+
registerSession(input: {
|
|
29
|
+
connectionId: string;
|
|
30
|
+
mode: Exclude<RemoteDeploymentMode, 'local'>;
|
|
31
|
+
extensionVersion: string;
|
|
32
|
+
activeProject?: ActiveProject;
|
|
33
|
+
ttlMs?: number;
|
|
34
|
+
}): ExtensionSession;
|
|
35
|
+
createPairingCode(input: {
|
|
36
|
+
userId: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
ttlMs?: number;
|
|
39
|
+
}): string;
|
|
40
|
+
completePairing(input: {
|
|
41
|
+
code: string;
|
|
42
|
+
userId: string;
|
|
43
|
+
sessionId: string;
|
|
44
|
+
}): boolean;
|
|
45
|
+
heartbeat(sessionId: string): boolean;
|
|
46
|
+
updateActiveProject(sessionId: string, activeProject?: ActiveProject): boolean;
|
|
47
|
+
disconnect(sessionId: string): boolean;
|
|
48
|
+
resolve(input: {
|
|
49
|
+
userId: string;
|
|
50
|
+
riskLevel: RemoteRiskLevel;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
}): SessionRouteResult;
|
|
53
|
+
getSession(sessionId: string): ExtensionSession | undefined;
|
|
54
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export class RemoteSessionRouter {
|
|
3
|
+
now;
|
|
4
|
+
makeId;
|
|
5
|
+
sessions = new Map();
|
|
6
|
+
pairings = new Map();
|
|
7
|
+
constructor(now = () => new Date(), makeId = () => randomUUID()) {
|
|
8
|
+
this.now = now;
|
|
9
|
+
this.makeId = makeId;
|
|
10
|
+
}
|
|
11
|
+
registerSession(input) {
|
|
12
|
+
const now = this.now();
|
|
13
|
+
const session = {
|
|
14
|
+
sessionId: `sess_${this.makeId()}`,
|
|
15
|
+
connectionId: input.connectionId,
|
|
16
|
+
mode: input.mode,
|
|
17
|
+
extensionVersion: input.extensionVersion,
|
|
18
|
+
activeProject: input.activeProject,
|
|
19
|
+
connected: true,
|
|
20
|
+
createdAt: now,
|
|
21
|
+
lastSeenAt: now,
|
|
22
|
+
expiresAt: new Date(now.getTime() + (input.ttlMs ?? 2 * 60 * 60 * 1000)),
|
|
23
|
+
};
|
|
24
|
+
this.sessions.set(session.sessionId, session);
|
|
25
|
+
return session;
|
|
26
|
+
}
|
|
27
|
+
createPairingCode(input) {
|
|
28
|
+
const now = this.now();
|
|
29
|
+
const code = `EDA-${this.makeId().replace(/-/g, '').slice(0, 8).toUpperCase()}`;
|
|
30
|
+
this.pairings.set(code, {
|
|
31
|
+
code,
|
|
32
|
+
userId: input.userId,
|
|
33
|
+
sessionId: input.sessionId,
|
|
34
|
+
createdAt: now,
|
|
35
|
+
expiresAt: new Date(now.getTime() + (input.ttlMs ?? 10 * 60 * 1000)),
|
|
36
|
+
});
|
|
37
|
+
return code;
|
|
38
|
+
}
|
|
39
|
+
completePairing(input) {
|
|
40
|
+
const pairing = this.pairings.get(input.code);
|
|
41
|
+
const session = this.sessions.get(input.sessionId);
|
|
42
|
+
const now = this.now();
|
|
43
|
+
if (!pairing || !session)
|
|
44
|
+
return false;
|
|
45
|
+
if (pairing.usedAt)
|
|
46
|
+
return false;
|
|
47
|
+
if (pairing.userId !== input.userId)
|
|
48
|
+
return false;
|
|
49
|
+
if (pairing.sessionId && pairing.sessionId !== input.sessionId)
|
|
50
|
+
return false;
|
|
51
|
+
if (pairing.expiresAt.getTime() <= now.getTime())
|
|
52
|
+
return false;
|
|
53
|
+
if (!session.connected || session.expiresAt.getTime() <= now.getTime())
|
|
54
|
+
return false;
|
|
55
|
+
pairing.usedAt = now;
|
|
56
|
+
session.userId = input.userId;
|
|
57
|
+
session.lastSeenAt = now;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
heartbeat(sessionId) {
|
|
61
|
+
const session = this.sessions.get(sessionId);
|
|
62
|
+
if (!session || !session.connected)
|
|
63
|
+
return false;
|
|
64
|
+
session.lastSeenAt = this.now();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
updateActiveProject(sessionId, activeProject) {
|
|
68
|
+
const session = this.sessions.get(sessionId);
|
|
69
|
+
if (!session)
|
|
70
|
+
return false;
|
|
71
|
+
session.activeProject = activeProject;
|
|
72
|
+
session.lastSeenAt = this.now();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
disconnect(sessionId) {
|
|
76
|
+
const session = this.sessions.get(sessionId);
|
|
77
|
+
if (!session)
|
|
78
|
+
return false;
|
|
79
|
+
session.connected = false;
|
|
80
|
+
session.lastSeenAt = this.now();
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
resolve(input) {
|
|
84
|
+
const now = this.now();
|
|
85
|
+
const candidates = [...this.sessions.values()].filter((session) => session.userId === input.userId &&
|
|
86
|
+
(!input.sessionId || session.sessionId === input.sessionId));
|
|
87
|
+
if (candidates.length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
code: 'SESSION_UNPAIRED',
|
|
91
|
+
message: 'No paired EasyEDA extension session.',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const active = candidates.filter((session) => session.connected);
|
|
95
|
+
if (active.length === 0) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
code: 'SESSION_DISCONNECTED',
|
|
99
|
+
message: 'Paired EasyEDA extension is disconnected.',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const unexpired = active.filter((session) => session.expiresAt.getTime() > now.getTime());
|
|
103
|
+
if (unexpired.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
code: 'SESSION_EXPIRED',
|
|
107
|
+
message: 'Paired EasyEDA extension session expired.',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (!input.sessionId && unexpired.length > 1) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
code: 'SESSION_AMBIGUOUS',
|
|
114
|
+
message: 'Multiple active EasyEDA sessions require explicit selection.',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const session = unexpired[0];
|
|
118
|
+
if (!session) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
code: 'SESSION_UNPAIRED',
|
|
122
|
+
message: 'No paired EasyEDA extension session.',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (input.riskLevel !== 'read' && !session.activeProject) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
code: 'PROJECT_INACTIVE',
|
|
129
|
+
message: 'No active EasyEDA project is visible.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, session };
|
|
133
|
+
}
|
|
134
|
+
getSession(sessionId) {
|
|
135
|
+
return this.sessions.get(sessionId);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=session-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-router.js","sourceRoot":"","sources":["../../src/remote/session-router.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easyeda-mcp-pro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"mcpName": "io.github.oaslananka/easyeda-mcp-pro",
|
|
5
5
|
"description": "MCP server for EasyEDA Pro: PCB inspection, BOM, exports, and hardware review.",
|
|
6
6
|
"keywords": [
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
"check:metadata": "node scripts/check-metadata.mjs",
|
|
73
73
|
"inventory:capture": "tsx scripts/capture-runtime-inventory.mts",
|
|
74
74
|
"inventory:diff": "tsx scripts/diff-runtime-inventory.mts",
|
|
75
|
-
"eval:golden": "tsx scripts/run-evals.mts"
|
|
75
|
+
"eval:golden": "tsx scripts/run-evals.mts",
|
|
76
|
+
"eval:golden:update": "tsx scripts/run-evals.mts --update-baseline"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
79
|
"@modelcontextprotocol/sdk": "^1.29.0",
|