@openship/protocol 0.0.2 → 0.0.3
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/dist/package-meta.json +2 -2
- package/dist/skill/references/examples/invalid/discovery-mcp.json +6 -0
- package/dist/skill/references/examples/invalid/discovery.json +6 -1
- package/dist/skill/references/examples/valid/discovery.json +8 -1
- package/dist/skill/references/openship-mcp.md +1 -0
- package/dist/skill/references/openship.md +38 -4
- package/dist/skill/references/schemas/discovery.schema.json +21 -6
- package/package.json +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +28 -9
package/dist/package-meta.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"openship": "1.0",
|
|
3
3
|
"package": "@openship/protocol",
|
|
4
|
-
"packageVersion": "0.0.
|
|
4
|
+
"packageVersion": "0.0.3",
|
|
5
5
|
"source": "https://github.com/openshipdev/openship/tree/main/skills/openship",
|
|
6
|
-
"sourceCommit": "
|
|
6
|
+
"sourceCommit": "dc28f02cd0608b2669c1f16135e4b04a59c96773"
|
|
7
7
|
}
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
"openship": "1.0",
|
|
3
3
|
"capability": "discovery",
|
|
4
4
|
"project": { "name": "Broken MCP", "description": "The MCP URL is not absolute HTTPS." },
|
|
5
|
+
"agent": {
|
|
6
|
+
"summary": "OpenShip exposes capabilities for a running project.",
|
|
7
|
+
"instructions": "Fetch and read agent.skill before using any capability.",
|
|
8
|
+
"skill": "https://example.com/openship/file/skills/openship/SKILL.md"
|
|
9
|
+
},
|
|
5
10
|
"capabilities": {
|
|
6
11
|
"sources": {
|
|
12
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
7
13
|
"manifest": "https://example.com/openship/manifest.json",
|
|
8
14
|
"bundle": "https://example.com/openship/bundle.json",
|
|
9
15
|
"mcp": "/mcp"
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
"openship": "1.0",
|
|
3
3
|
"capability": "discovery",
|
|
4
4
|
"project": { "name": "Broken", "description": "Sources is missing." },
|
|
5
|
+
"agent": {
|
|
6
|
+
"summary": "OpenShip exposes capabilities for a running project.",
|
|
7
|
+
"instructions": "Fetch and read agent.skill before using any capability.",
|
|
8
|
+
"skill": "https://example.com/openship/file/skills/openship/SKILL.md"
|
|
9
|
+
},
|
|
5
10
|
"capabilities": {
|
|
6
11
|
"changes": {
|
|
12
|
+
"description": "Submit a patch against a published source digest.",
|
|
7
13
|
"policy": "https://example.com/openship/policy.json",
|
|
8
14
|
"submit": "https://example.com/openship/changes",
|
|
9
15
|
"status": "https://example.com/openship/changes/{changeId}"
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
18
|
}
|
|
13
|
-
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
"openship": "1.0",
|
|
3
3
|
"capability": "discovery",
|
|
4
4
|
"project": { "name": "Example", "description": "An example OpenShip project." },
|
|
5
|
-
"
|
|
5
|
+
"agent": {
|
|
6
|
+
"summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
|
|
7
|
+
"instructions": "Fetch and read agent.skill before interpreting or using any advertised capability. Resolve relative links in the skill against the skill URL.",
|
|
8
|
+
"skill": "https://example.com/openship/file/skills/openship/SKILL.md"
|
|
9
|
+
},
|
|
10
|
+
"page": "https://example.com/openship",
|
|
6
11
|
"capabilities": {
|
|
7
12
|
"sources": {
|
|
13
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
8
14
|
"manifest": "https://example.com/openship/manifest.json",
|
|
9
15
|
"bundle": "https://example.com/openship/bundle.json",
|
|
10
16
|
"mcp": "https://mcp.example.com/mcp",
|
|
@@ -12,6 +18,7 @@
|
|
|
12
18
|
"archive": "https://example.com/openship/source.tar.gz"
|
|
13
19
|
},
|
|
14
20
|
"changes": {
|
|
21
|
+
"description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
|
|
15
22
|
"policy": "https://example.com/openship/policy.json",
|
|
16
23
|
"submit": "https://example.com/openship/changes",
|
|
17
24
|
"status": "https://example.com/openship/changes/{changeId}"
|
|
@@ -13,6 +13,7 @@ A producer MAY advertise an absolute HTTPS Streamable HTTP endpoint as `capabili
|
|
|
13
13
|
{
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"sources": {
|
|
16
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
16
17
|
"manifest": "https://example.com/openship/manifest.json",
|
|
17
18
|
"bundle": "https://example.com/openship/bundle.json",
|
|
18
19
|
"mcp": "https://mcp.example.com/mcp"
|
|
@@ -56,9 +56,15 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
|
|
|
56
56
|
"name": "Example",
|
|
57
57
|
"description": "A short description of the running project."
|
|
58
58
|
},
|
|
59
|
-
"
|
|
59
|
+
"agent": {
|
|
60
|
+
"summary": "OpenShip lets this running project publish verifiable source code and optionally accept isolated changes or describe its complete system.",
|
|
61
|
+
"instructions": "Fetch and read agent.skill before interpreting or using any advertised capability. Resolve relative links in the skill against the skill URL.",
|
|
62
|
+
"skill": "https://example.com/openship/file/skills/openship/SKILL.md"
|
|
63
|
+
},
|
|
64
|
+
"page": "https://example.com/openship",
|
|
60
65
|
"capabilities": {
|
|
61
66
|
"sources": {
|
|
67
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
62
68
|
"manifest": "https://example.com/openship/manifest.json",
|
|
63
69
|
"bundle": "https://example.com/openship/bundle.json",
|
|
64
70
|
"mcp": "https://mcp.example.com/mcp",
|
|
@@ -67,11 +73,13 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
|
|
|
67
73
|
"instructions": "https://example.com/openship/agent.txt"
|
|
68
74
|
},
|
|
69
75
|
"changes": {
|
|
76
|
+
"description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
|
|
70
77
|
"policy": "https://example.com/openship/policy.json",
|
|
71
78
|
"submit": "https://example.com/openship/changes",
|
|
72
79
|
"status": "https://example.com/openship/changes/{changeId}"
|
|
73
80
|
},
|
|
74
81
|
"systems": {
|
|
82
|
+
"description": "Retrieve a self-contained description of this project’s source, architecture, infrastructure, and agent context.",
|
|
75
83
|
"document": "https://example.com/openship/system.json"
|
|
76
84
|
}
|
|
77
85
|
}
|
|
@@ -80,20 +88,46 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
|
|
|
80
88
|
|
|
81
89
|
Requirements:
|
|
82
90
|
|
|
83
|
-
- `project`, `capabilities`, and `capabilities.sources` are REQUIRED.
|
|
91
|
+
- `project`, `agent`, `capabilities`, and `capabilities.sources` are REQUIRED.
|
|
84
92
|
- `project.name` and `project.description` are REQUIRED.
|
|
93
|
+
- `agent.summary`, `agent.instructions`, and `agent.skill` are REQUIRED. The summary MUST explain OpenShip in standalone plain language. The instructions MUST explicitly direct an unfamiliar agent to fetch and read `agent.skill` before interpreting or using the capability links.
|
|
94
|
+
- `agent.skill` MUST identify the OpenShip skill entry point. Relative links in that skill MUST be resolved against the skill URL.
|
|
95
|
+
- Every advertised capability MUST contain a standalone `description` explaining what the capability lets an agent do.
|
|
85
96
|
- `sources.manifest` and `sources.bundle` are REQUIRED.
|
|
86
97
|
- `sources.mcp` is OPTIONAL and, when present, advertises the OpenShip Sources MCP binding.
|
|
87
|
-
- `changes
|
|
98
|
+
- `changes` and `systems` are OPTIONAL and MUST be omitted when unavailable.
|
|
99
|
+
- `page` is OPTIONAL and, when present, MUST be an absolute HTTPS URL for a human- and agent-readable presentation of this OpenShip origin.
|
|
88
100
|
- Changes MUST NOT be advertised without Sources.
|
|
89
101
|
- `file` and `status` are URI templates with exactly the named expansion.
|
|
90
102
|
- A project implementing Sources and Changes but not Systems simply omits `capabilities.systems`.
|
|
91
103
|
|
|
92
104
|
See [schemas/discovery.schema.json](schemas/discovery.schema.json).
|
|
93
105
|
|
|
106
|
+
### Optional `/openship` presentation route
|
|
107
|
+
|
|
108
|
+
A website MAY serve `GET /openship` and advertise its absolute URL in discovery as `page`. This
|
|
109
|
+
route is a presentation aid for agents and humans. It does not replace
|
|
110
|
+
`/.well-known/openship.json`, and its absence does not affect conformance.
|
|
111
|
+
|
|
112
|
+
When implemented, the route MUST:
|
|
113
|
+
|
|
114
|
+
- identify itself as the project’s OpenShip page and explain OpenShip in plain language;
|
|
115
|
+
- show the project name and description and list every advertised capability with its meaning;
|
|
116
|
+
- tell an unfamiliar agent to start with discovery and read `agent.skill` before acting;
|
|
117
|
+
- link to discovery, the skill, and every advertised capability document or instruction URL;
|
|
118
|
+
- for Sources, show the current snapshot digest, file count, and byte count from the Manifest;
|
|
119
|
+
- for Changes, state whether submissions are currently accepted and make clear that a candidate result is not a production deployment; and
|
|
120
|
+
- distinguish explanatory page content from the authoritative JSON documents.
|
|
121
|
+
|
|
122
|
+
The route MAY use HTML or plain text. It MUST be publicly readable without authentication and
|
|
123
|
+
SHOULD keep its essential explanation and links available as semantic text rather than requiring
|
|
124
|
+
client-side interaction.
|
|
125
|
+
|
|
94
126
|
## Access and transport
|
|
95
127
|
|
|
96
|
-
Discovery, Sources, and Systems reads MUST NOT
|
|
128
|
+
Discovery, `agent.skill`, the skill’s referenced documents, Sources, and Systems reads MUST NOT
|
|
129
|
+
require cookies, credentials, custom headers, or query parameters and MUST allow cross-origin reads.
|
|
130
|
+
Changes writes MAY require authorization or payment disclosed by the Changes policy.
|
|
97
131
|
|
|
98
132
|
JSON responses MUST use UTF-8. Public reads SHOULD support transport compression. A producer MAY offer additional representations, but the advertised JSON representation remains normative.
|
|
99
133
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://openship.dev/schemas/discovery.schema.json",
|
|
4
4
|
"title": "OpenShip v1 discovery",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"required": ["openship", "capability", "project", "capabilities"],
|
|
6
|
+
"required": ["openship", "capability", "project", "agent", "capabilities"],
|
|
7
7
|
"properties": {
|
|
8
8
|
"openship": { "const": "1.0" },
|
|
9
9
|
"capability": { "const": "discovery" },
|
|
@@ -16,15 +16,26 @@
|
|
|
16
16
|
},
|
|
17
17
|
"additionalProperties": true
|
|
18
18
|
},
|
|
19
|
-
"
|
|
19
|
+
"agent": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["summary", "instructions", "skill"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
24
|
+
"instructions": { "type": "string", "minLength": 1 },
|
|
25
|
+
"skill": { "$ref": "#/$defs/httpsUrl" }
|
|
26
|
+
},
|
|
27
|
+
"additionalProperties": true
|
|
28
|
+
},
|
|
29
|
+
"page": { "$ref": "#/$defs/httpsUrl" },
|
|
20
30
|
"capabilities": {
|
|
21
31
|
"type": "object",
|
|
22
32
|
"required": ["sources"],
|
|
23
33
|
"properties": {
|
|
24
34
|
"sources": {
|
|
25
35
|
"type": "object",
|
|
26
|
-
"required": ["manifest", "bundle"],
|
|
36
|
+
"required": ["description", "manifest", "bundle"],
|
|
27
37
|
"properties": {
|
|
38
|
+
"description": { "type": "string", "minLength": 1 },
|
|
28
39
|
"manifest": { "$ref": "#/$defs/httpsUrl" },
|
|
29
40
|
"bundle": { "$ref": "#/$defs/httpsUrl" },
|
|
30
41
|
"mcp": { "$ref": "#/$defs/httpsUrl" },
|
|
@@ -36,8 +47,9 @@
|
|
|
36
47
|
},
|
|
37
48
|
"changes": {
|
|
38
49
|
"type": "object",
|
|
39
|
-
"required": ["policy", "submit", "status"],
|
|
50
|
+
"required": ["description", "policy", "submit", "status"],
|
|
40
51
|
"properties": {
|
|
52
|
+
"description": { "type": "string", "minLength": 1 },
|
|
41
53
|
"policy": { "$ref": "#/$defs/httpsUrl" },
|
|
42
54
|
"submit": { "$ref": "#/$defs/httpsUrl" },
|
|
43
55
|
"status": { "type": "string", "pattern": "^https://.+\\{changeId\\}.+$|^https://.+/\\{changeId\\}$" }
|
|
@@ -46,8 +58,11 @@
|
|
|
46
58
|
},
|
|
47
59
|
"systems": {
|
|
48
60
|
"type": "object",
|
|
49
|
-
"required": ["document"],
|
|
50
|
-
"properties": {
|
|
61
|
+
"required": ["description", "document"],
|
|
62
|
+
"properties": {
|
|
63
|
+
"description": { "type": "string", "minLength": 1 },
|
|
64
|
+
"document": { "$ref": "#/$defs/httpsUrl" }
|
|
65
|
+
},
|
|
51
66
|
"additionalProperties": true
|
|
52
67
|
}
|
|
53
68
|
},
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export const OPENSHIP_MCP_FILE_RESOURCE_TEMPLATE: "openship://sources/file{?path
|
|
|
8
8
|
export interface SourceFileMetadata { path: string; size: number; sha256: string; encoding: OpenShipEncoding; mediaType: string; type: "file" | "symlink"; target?: string; [key: string]: unknown }
|
|
9
9
|
export interface SourcesManifest { openship: "1.0"; capability: "sources"; digest: string; project: { name: string; description: string; [key: string]: unknown }; totals: { files: number; bytes: number; [key: string]: unknown }; files: SourceFileMetadata[]; [key: string]: unknown }
|
|
10
10
|
export interface SourcesBundle { openship: "1.0"; capability: "sources"; digest: string; files: Record<string, { encoding: OpenShipEncoding; content: string; [key: string]: unknown }>; [key: string]: unknown }
|
|
11
|
-
export interface
|
|
11
|
+
export interface DiscoveryAgent { summary: string; instructions: string; skill: string; [key: string]: unknown }
|
|
12
|
+
export interface DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: { name: string; description: string; [key: string]: unknown }; agent: DiscoveryAgent; page?: string; capabilities: { sources: { description: string; manifest: string; bundle: string; mcp?: string; [key: string]: unknown }; systems?: { description: string; document: string; [key: string]: unknown }; changes?: { description: string; policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
|
|
12
13
|
export type SystemsNodeKind = "Root" | "Host" | "Container" | "Process" | "Library";
|
|
13
14
|
export type SystemsNodeOwnership = "first_party" | "third_party";
|
|
14
15
|
export interface SystemsNodeMetadata { ownership: SystemsNodeOwnership; [key: string]: unknown }
|
package/src/index.js
CHANGED
|
@@ -179,24 +179,43 @@ export function validateSources(manifestValue, bundleValue, options = {}) {
|
|
|
179
179
|
|
|
180
180
|
export function validateDiscovery(value) {
|
|
181
181
|
const discovery = envelope(value, "discovery");
|
|
182
|
+
const absoluteUrl = (value, path) => {
|
|
183
|
+
const raw = string(value, path);
|
|
184
|
+
let url;
|
|
185
|
+
try { url = new URL(raw); } catch { fail(path, "must be an absolute HTTPS URL"); }
|
|
186
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
187
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) fail(path, "must be an absolute HTTPS URL outside local development");
|
|
188
|
+
return raw;
|
|
189
|
+
};
|
|
182
190
|
const project = object(discovery.project, "$.project");
|
|
183
191
|
string(project.name, "$.project.name");
|
|
184
192
|
string(project.description, "$.project.description");
|
|
193
|
+
const agent = object(discovery.agent, "$.agent");
|
|
194
|
+
string(agent.summary, "$.agent.summary");
|
|
195
|
+
string(agent.instructions, "$.agent.instructions");
|
|
196
|
+
absoluteUrl(agent.skill, "$.agent.skill");
|
|
197
|
+
if (discovery.page !== undefined) absoluteUrl(discovery.page, "$.page");
|
|
185
198
|
const capabilities = object(discovery.capabilities, "$.capabilities");
|
|
186
199
|
const sources = object(capabilities.sources, "$.capabilities.sources");
|
|
187
|
-
|
|
188
|
-
const url = string(value, path);
|
|
189
|
-
try { new URL(url); } catch { fail(path, "must be an absolute URL"); }
|
|
190
|
-
return url;
|
|
191
|
-
};
|
|
200
|
+
string(sources.description, "$.capabilities.sources.description");
|
|
192
201
|
for (const key of ["manifest", "bundle"]) {
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
absoluteUrl(sources[key], `$.capabilities.sources.${key}`);
|
|
203
|
+
}
|
|
204
|
+
for (const key of ["mcp", "archive", "instructions"]) {
|
|
205
|
+
if (sources[key] !== undefined) absoluteUrl(sources[key], `$.capabilities.sources.${key}`);
|
|
206
|
+
}
|
|
207
|
+
if (sources.file !== undefined) {
|
|
208
|
+
absoluteUrl(sources.file, "$.capabilities.sources.file");
|
|
209
|
+
if (!String(sources.file).includes("{path}")) fail("$.capabilities.sources.file", "must contain {path}");
|
|
210
|
+
}
|
|
211
|
+
if (capabilities.systems) {
|
|
212
|
+
const systems = object(capabilities.systems, "$.capabilities.systems");
|
|
213
|
+
string(systems.description, "$.capabilities.systems.description");
|
|
214
|
+
absoluteUrl(systems.document, "$.capabilities.systems.document");
|
|
195
215
|
}
|
|
196
|
-
if (sources.mcp !== undefined) absoluteUrl(sources.mcp, "$.capabilities.sources.mcp");
|
|
197
|
-
if (capabilities.systems) absoluteUrl(object(capabilities.systems, "$.capabilities.systems").document, "$.capabilities.systems.document");
|
|
198
216
|
if (capabilities.changes) {
|
|
199
217
|
const changes = object(capabilities.changes, "$.capabilities.changes");
|
|
218
|
+
string(changes.description, "$.capabilities.changes.description");
|
|
200
219
|
for (const key of ["policy", "submit", "status"]) absoluteUrl(changes[key], `$.capabilities.changes.${key}`);
|
|
201
220
|
if (!String(changes.status).includes("{changeId}")) fail("$.capabilities.changes.status", "must contain {changeId}");
|
|
202
221
|
}
|