@openship/protocol 0.0.1 → 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/README.md +3 -0
- package/dist/package-meta.json +2 -2
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/references/examples/invalid/discovery-mcp.json +18 -0
- package/dist/skill/references/examples/invalid/discovery.json +6 -1
- package/dist/skill/references/examples/valid/discovery.json +9 -2
- package/dist/skill/references/openship-mcp.md +53 -0
- package/dist/skill/references/openship-sources.md +5 -2
- package/dist/skill/references/openship.md +41 -5
- package/dist/skill/references/schemas/discovery.schema.json +22 -7
- package/package.json +1 -1
- package/src/index.d.ts +8 -1
- package/src/index.js +34 -10
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Canonical OpenShip 1.0 types, validators, source digest/diff helpers, selector matching, safe path/base64 utilities, browser discovery retrieval, and skill synchronization.
|
|
4
4
|
|
|
5
|
+
The package also exports the standard OpenShip Sources MCP tool/resource names and tool input types;
|
|
6
|
+
it deliberately does not depend on an MCP SDK.
|
|
7
|
+
|
|
5
8
|
```js
|
|
6
9
|
import { fetchOpenShip, validateSystems } from "@openship/protocol";
|
|
7
10
|
|
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
|
}
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -11,8 +11,8 @@ Read only the references needed for the task:
|
|
|
11
11
|
|
|
12
12
|
- For the protocol overview, discovery, shared conventions, or capability selection, read [references/openship.md](references/openship.md).
|
|
13
13
|
- For publishing, retrieving, or validating a source snapshot, read [references/openship-sources.md](references/openship-sources.md).
|
|
14
|
+
- For exposing or consuming Sources through MCP, also read [references/openship-mcp.md](references/openship-mcp.md).
|
|
14
15
|
- For proposing or serving candidate code versions, read both [references/openship-sources.md](references/openship-sources.md) and [references/openship-changes.md](references/openship-changes.md).
|
|
15
16
|
- For authoring or consuming a self-contained architecture and source payload, read both [references/openship-sources.md](references/openship-sources.md) and [references/openship-systems.md](references/openship-systems.md).
|
|
16
17
|
|
|
17
18
|
Machine-readable schemas and conformance examples are under [references/schemas](references/schemas) and [references/examples](references/examples). Treat the Markdown specifications as normative when a constraint cannot be expressed by JSON Schema.
|
|
18
|
-
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openship": "1.0",
|
|
3
|
+
"capability": "discovery",
|
|
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
|
+
},
|
|
10
|
+
"capabilities": {
|
|
11
|
+
"sources": {
|
|
12
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
13
|
+
"manifest": "https://example.com/openship/manifest.json",
|
|
14
|
+
"bundle": "https://example.com/openship/bundle.json",
|
|
15
|
+
"mcp": "/mcp"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -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,19 +2,26 @@
|
|
|
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",
|
|
16
|
+
"mcp": "https://mcp.example.com/mcp",
|
|
10
17
|
"file": "https://example.com/openship/file/{path}",
|
|
11
18
|
"archive": "https://example.com/openship/source.tar.gz"
|
|
12
19
|
},
|
|
13
20
|
"changes": {
|
|
21
|
+
"description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
|
|
14
22
|
"policy": "https://example.com/openship/policy.json",
|
|
15
23
|
"submit": "https://example.com/openship/changes",
|
|
16
24
|
"status": "https://example.com/openship/changes/{changeId}"
|
|
17
25
|
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
|
-
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# OpenShip Sources MCP binding
|
|
2
|
+
|
|
3
|
+
Status: Draft v1
|
|
4
|
+
Protocol version: `1.0`
|
|
5
|
+
|
|
6
|
+
This optional binding lets an MCP server expose one OpenShip Sources snapshot without replacing the normative HTTP discovery, Manifest, or Bundle representations. Read [openship.md](openship.md) and [openship-sources.md](openship-sources.md) first.
|
|
7
|
+
|
|
8
|
+
## Discovery
|
|
9
|
+
|
|
10
|
+
A producer MAY advertise an absolute HTTPS Streamable HTTP endpoint as `capabilities.sources.mcp`:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"capabilities": {
|
|
15
|
+
"sources": {
|
|
16
|
+
"description": "Retrieve and verify the exact source snapshot published by this deployment.",
|
|
17
|
+
"manifest": "https://example.com/openship/manifest.json",
|
|
18
|
+
"bundle": "https://example.com/openship/bundle.json",
|
|
19
|
+
"mcp": "https://mcp.example.com/mcp"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The MCP binding MUST expose the same current Sources snapshot as the advertised HTTP Manifest and Bundle. It MUST NOT require authentication. An MCP endpoint MAY expose unrelated authenticated tools alongside OpenShip, but authentication failures for those tools MUST NOT prevent OpenShip source reads.
|
|
26
|
+
|
|
27
|
+
## Tool
|
|
28
|
+
|
|
29
|
+
The server MUST register one tool named `openship` with this input union:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{ "operation": "manifest" }
|
|
33
|
+
{ "operation": "read", "path": "app/page.tsx" }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`manifest` returns the configured OpenShip origin and its complete validated Sources Manifest. `read` accepts one exact safe Manifest path and returns the snapshot digest, file metadata, declared encoding, and content. UTF-8 content is text; binary content is canonical base64. The tool MUST NOT resolve arbitrary filesystem or URL paths.
|
|
37
|
+
|
|
38
|
+
The binding does not define a whole-Bundle tool. A client retrieves only the files it needs and verifies their metadata against the returned Manifest.
|
|
39
|
+
|
|
40
|
+
## Resources
|
|
41
|
+
|
|
42
|
+
Resource-aware servers SHOULD also expose:
|
|
43
|
+
|
|
44
|
+
- `openship://sources/manifest` with the Manifest as `application/json` text.
|
|
45
|
+
- `openship://sources/file{?path}` as a resource template and enumerate its concrete Manifest files from `resources/list`.
|
|
46
|
+
|
|
47
|
+
UTF-8 files use MCP text contents. Base64 files use MCP blob contents with the Manifest media type. A concrete file URI percent-encodes the complete repository path in the `path` query parameter.
|
|
48
|
+
|
|
49
|
+
## Integrity and errors
|
|
50
|
+
|
|
51
|
+
The server MUST validate the Manifest and every Bundle byte before returning source content. A changed Manifest digest requires a newly validated Bundle; an invalid or incomplete replacement MUST NOT displace the last complete cache entry or be returned as current source. Unknown and unsafe paths fail without content.
|
|
52
|
+
|
|
53
|
+
Implementations MAY impose a decoded-size limit and SHOULD report machine-readable failures for invalid paths, missing files, unavailable origins, invalid snapshots, and snapshots exceeding that limit.
|
|
@@ -6,18 +6,22 @@ Read [openship.md](openship.md) first for discovery, transport, and caching rule
|
|
|
6
6
|
|
|
7
7
|
## Endpoints
|
|
8
8
|
|
|
9
|
-
Discovery advertises two required and
|
|
9
|
+
Discovery advertises two required and four optional URLs:
|
|
10
10
|
|
|
11
11
|
| Link | Required | Meaning |
|
|
12
12
|
|---|---:|---|
|
|
13
13
|
| `manifest` | Yes | Project identity and file metadata, without content. |
|
|
14
14
|
| `bundle` | Yes | Every file's encoded content in one JSON document. |
|
|
15
|
+
| `mcp` | No | A public MCP endpoint implementing the OpenShip Sources binding. |
|
|
15
16
|
| `file` | No | Raw content for one manifest path. |
|
|
16
17
|
| `archive` | No | The manifest file set as a compressed archive. |
|
|
17
18
|
| `instructions` | No | Plain-language project-specific retrieval guidance. |
|
|
18
19
|
|
|
19
20
|
All reads are public, CORS-readable GET requests.
|
|
20
21
|
|
|
22
|
+
See [openship-mcp.md](openship-mcp.md) when `mcp` is advertised. The HTTP Manifest and Bundle
|
|
23
|
+
remain normative and required.
|
|
24
|
+
|
|
21
25
|
## Manifest
|
|
22
26
|
|
|
23
27
|
```json
|
|
@@ -140,4 +144,3 @@ A consumer MUST finish validation before executing any retrieved code.
|
|
|
140
144
|
## Security
|
|
141
145
|
|
|
142
146
|
Serving Sources makes every included byte public. Producers MUST NOT publish secrets, environment values, signing keys, private deployment configuration, or data exports. File selection is the primary security boundary; filters and pattern scans are defense in depth.
|
|
143
|
-
|
|
@@ -56,21 +56,30 @@ 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",
|
|
70
|
+
"mcp": "https://mcp.example.com/mcp",
|
|
64
71
|
"file": "https://example.com/openship/file/{path}",
|
|
65
72
|
"archive": "https://example.com/openship/source.tar.gz",
|
|
66
73
|
"instructions": "https://example.com/openship/agent.txt"
|
|
67
74
|
},
|
|
68
75
|
"changes": {
|
|
76
|
+
"description": "Submit a patch against the published source digest and inspect an isolated candidate result.",
|
|
69
77
|
"policy": "https://example.com/openship/policy.json",
|
|
70
78
|
"submit": "https://example.com/openship/changes",
|
|
71
79
|
"status": "https://example.com/openship/changes/{changeId}"
|
|
72
80
|
},
|
|
73
81
|
"systems": {
|
|
82
|
+
"description": "Retrieve a self-contained description of this project’s source, architecture, infrastructure, and agent context.",
|
|
74
83
|
"document": "https://example.com/openship/system.json"
|
|
75
84
|
}
|
|
76
85
|
}
|
|
@@ -79,19 +88,46 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
|
|
|
79
88
|
|
|
80
89
|
Requirements:
|
|
81
90
|
|
|
82
|
-
- `project`, `capabilities`, and `capabilities.sources` are REQUIRED.
|
|
91
|
+
- `project`, `agent`, `capabilities`, and `capabilities.sources` are REQUIRED.
|
|
83
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.
|
|
84
96
|
- `sources.manifest` and `sources.bundle` are REQUIRED.
|
|
85
|
-
- `
|
|
97
|
+
- `sources.mcp` is OPTIONAL and, when present, advertises the OpenShip Sources MCP binding.
|
|
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.
|
|
86
100
|
- Changes MUST NOT be advertised without Sources.
|
|
87
101
|
- `file` and `status` are URI templates with exactly the named expansion.
|
|
88
102
|
- A project implementing Sources and Changes but not Systems simply omits `capabilities.systems`.
|
|
89
103
|
|
|
90
104
|
See [schemas/discovery.schema.json](schemas/discovery.schema.json).
|
|
91
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
|
+
|
|
92
126
|
## Access and transport
|
|
93
127
|
|
|
94
|
-
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.
|
|
95
131
|
|
|
96
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.
|
|
97
133
|
|
|
@@ -114,6 +150,7 @@ Changes status responses change over time and MUST use `Cache-Control: no-store`
|
|
|
114
150
|
## Capability documents
|
|
115
151
|
|
|
116
152
|
- Read [openship-sources.md](openship-sources.md) for source snapshots and integrity.
|
|
153
|
+
- Read [openship-mcp.md](openship-mcp.md) for the optional MCP binding for Sources.
|
|
117
154
|
- Read [openship-changes.md](openship-changes.md) for candidate changes and isolation.
|
|
118
155
|
- Read [openship-systems.md](openship-systems.md) for the self-contained systems model.
|
|
119
156
|
|
|
@@ -122,4 +159,3 @@ Changes status responses change over time and MUST use `Cache-Control: no-store`
|
|
|
122
159
|
A producer is conformant for a capability when its advertised documents pass the relevant schema and every cross-document invariant in that capability specification. Advertising one capability does not claim conformance for another.
|
|
123
160
|
|
|
124
161
|
A consumer SHOULD report a precise path and invariant when rejecting a payload. It MUST reject unsupported major versions rather than silently interpreting them as v1.
|
|
125
|
-
|
|
@@ -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,17 +16,29 @@
|
|
|
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" },
|
|
41
|
+
"mcp": { "$ref": "#/$defs/httpsUrl" },
|
|
30
42
|
"file": { "type": "string", "pattern": "^https://.+\\{path\\}.+$|^https://.+/\\{path\\}$" },
|
|
31
43
|
"archive": { "$ref": "#/$defs/httpsUrl" },
|
|
32
44
|
"instructions": { "$ref": "#/$defs/httpsUrl" }
|
|
@@ -35,8 +47,9 @@
|
|
|
35
47
|
},
|
|
36
48
|
"changes": {
|
|
37
49
|
"type": "object",
|
|
38
|
-
"required": ["policy", "submit", "status"],
|
|
50
|
+
"required": ["description", "policy", "submit", "status"],
|
|
39
51
|
"properties": {
|
|
52
|
+
"description": { "type": "string", "minLength": 1 },
|
|
40
53
|
"policy": { "$ref": "#/$defs/httpsUrl" },
|
|
41
54
|
"submit": { "$ref": "#/$defs/httpsUrl" },
|
|
42
55
|
"status": { "type": "string", "pattern": "^https://.+\\{changeId\\}.+$|^https://.+/\\{changeId\\}$" }
|
|
@@ -45,8 +58,11 @@
|
|
|
45
58
|
},
|
|
46
59
|
"systems": {
|
|
47
60
|
"type": "object",
|
|
48
|
-
"required": ["document"],
|
|
49
|
-
"properties": {
|
|
61
|
+
"required": ["description", "document"],
|
|
62
|
+
"properties": {
|
|
63
|
+
"description": { "type": "string", "minLength": 1 },
|
|
64
|
+
"document": { "$ref": "#/$defs/httpsUrl" }
|
|
65
|
+
},
|
|
50
66
|
"additionalProperties": true
|
|
51
67
|
}
|
|
52
68
|
},
|
|
@@ -58,4 +74,3 @@
|
|
|
58
74
|
},
|
|
59
75
|
"additionalProperties": true
|
|
60
76
|
}
|
|
61
|
-
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
export type OpenShipEncoding = "utf-8" | "base64";
|
|
2
2
|
export type OpenShipCapability = "discovery" | "sources" | "changes" | "systems";
|
|
3
|
+
export type OpenShipMcpOperation = "manifest" | "read";
|
|
4
|
+
export type OpenShipMcpInput = { operation: "manifest" } | { operation: "read"; path: string };
|
|
5
|
+
export const OPENSHIP_MCP_TOOL_NAME: "openship";
|
|
6
|
+
export const OPENSHIP_MCP_MANIFEST_RESOURCE_URI: "openship://sources/manifest";
|
|
7
|
+
export const OPENSHIP_MCP_FILE_RESOURCE_TEMPLATE: "openship://sources/file{?path}";
|
|
3
8
|
export interface SourceFileMetadata { path: string; size: number; sha256: string; encoding: OpenShipEncoding; mediaType: string; type: "file" | "symlink"; target?: string; [key: string]: unknown }
|
|
4
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 }
|
|
5
10
|
export interface SourcesBundle { openship: "1.0"; capability: "sources"; digest: string; files: Record<string, { encoding: OpenShipEncoding; content: string; [key: string]: unknown }>; [key: string]: unknown }
|
|
6
|
-
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 }
|
|
7
13
|
export type SystemsNodeKind = "Root" | "Host" | "Container" | "Process" | "Library";
|
|
8
14
|
export type SystemsNodeOwnership = "first_party" | "third_party";
|
|
9
15
|
export interface SystemsNodeMetadata { ownership: SystemsNodeOwnership; [key: string]: unknown }
|
|
@@ -22,6 +28,7 @@ export function compareUtf8(left: string, right: string): number;
|
|
|
22
28
|
export function matchOpenShipPattern(pattern: string, path: string): boolean;
|
|
23
29
|
export function computeSourcesDigest(files: SourceFileMetadata[]): string;
|
|
24
30
|
export function validateDiscovery(value: unknown): DiscoveryDocument;
|
|
31
|
+
export function validateSourcesManifest(value: unknown): SourcesManifest;
|
|
25
32
|
export function validateSources(manifest: unknown, bundle: unknown, options?: { maxDecodedBytes?: number }): VerifiedSources;
|
|
26
33
|
export function validateSystems(value: unknown, options?: { maxDecodedBytes?: number }): SystemsDocument;
|
|
27
34
|
export function validateChangesDocument(value: unknown): Record<string, unknown>;
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,10 @@ const digestPattern = /^sha256:[0-9a-f]{64}$/;
|
|
|
6
6
|
const hexPattern = /^[0-9a-f]{64}$/;
|
|
7
7
|
const idPattern = /^[A-Za-z0-9._:-]+$/;
|
|
8
8
|
|
|
9
|
+
export const OPENSHIP_MCP_TOOL_NAME = "openship";
|
|
10
|
+
export const OPENSHIP_MCP_MANIFEST_RESOURCE_URI = "openship://sources/manifest";
|
|
11
|
+
export const OPENSHIP_MCP_FILE_RESOURCE_TEMPLATE = "openship://sources/file{?path}";
|
|
12
|
+
|
|
9
13
|
export class OpenShipValidationError extends Error {
|
|
10
14
|
constructor(path, message, code = "invalid_openship") {
|
|
11
15
|
super(`${path}: ${message}`);
|
|
@@ -121,7 +125,7 @@ function decodeBundleEntry(entry, path) {
|
|
|
121
125
|
return bytes;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
function
|
|
128
|
+
export function validateSourcesManifest(value) {
|
|
125
129
|
const manifest = envelope(value, "sources");
|
|
126
130
|
if (!digestPattern.test(manifest.digest)) fail("$.digest", "must be a sha256 digest");
|
|
127
131
|
const project = object(manifest.project, "$.project");
|
|
@@ -151,7 +155,7 @@ function validateManifest(value) {
|
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
export function validateSources(manifestValue, bundleValue, options = {}) {
|
|
154
|
-
const manifest =
|
|
158
|
+
const manifest = validateSourcesManifest(manifestValue);
|
|
155
159
|
const bundle = envelope(bundleValue, "sources");
|
|
156
160
|
if (bundle.digest !== manifest.digest) fail("$.bundle.digest", "does not match the Manifest digest");
|
|
157
161
|
const bundleFiles = object(bundle.files, "$.bundle.files");
|
|
@@ -175,23 +179,43 @@ export function validateSources(manifestValue, bundleValue, options = {}) {
|
|
|
175
179
|
|
|
176
180
|
export function validateDiscovery(value) {
|
|
177
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
|
+
};
|
|
178
190
|
const project = object(discovery.project, "$.project");
|
|
179
191
|
string(project.name, "$.project.name");
|
|
180
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");
|
|
181
198
|
const capabilities = object(discovery.capabilities, "$.capabilities");
|
|
182
199
|
const sources = object(capabilities.sources, "$.capabilities.sources");
|
|
183
|
-
|
|
184
|
-
const url = string(value, path);
|
|
185
|
-
try { new URL(url); } catch { fail(path, "must be an absolute URL"); }
|
|
186
|
-
return url;
|
|
187
|
-
};
|
|
200
|
+
string(sources.description, "$.capabilities.sources.description");
|
|
188
201
|
for (const key of ["manifest", "bundle"]) {
|
|
189
|
-
|
|
190
|
-
|
|
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");
|
|
191
215
|
}
|
|
192
|
-
if (capabilities.systems) absoluteUrl(object(capabilities.systems, "$.capabilities.systems").document, "$.capabilities.systems.document");
|
|
193
216
|
if (capabilities.changes) {
|
|
194
217
|
const changes = object(capabilities.changes, "$.capabilities.changes");
|
|
218
|
+
string(changes.description, "$.capabilities.changes.description");
|
|
195
219
|
for (const key of ["policy", "submit", "status"]) absoluteUrl(changes[key], `$.capabilities.changes.${key}`);
|
|
196
220
|
if (!String(changes.status).includes("{changeId}")) fail("$.capabilities.changes.status", "must contain {changeId}");
|
|
197
221
|
}
|