@openship/protocol 0.0.1 → 0.0.2

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 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
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "openship": "1.0",
3
3
  "package": "@openship/protocol",
4
- "packageVersion": "0.0.1",
4
+ "packageVersion": "0.0.2",
5
5
  "source": "https://github.com/openshipdev/openship/tree/main/skills/openship",
6
- "sourceCommit": "40e94cc1a5c7eb06787d22257df63785052c9124"
6
+ "sourceCommit": "9d6c35e78bae95a368220beea241a214aa0f24f3"
7
7
  }
@@ -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,12 @@
1
+ {
2
+ "openship": "1.0",
3
+ "capability": "discovery",
4
+ "project": { "name": "Broken MCP", "description": "The MCP URL is not absolute HTTPS." },
5
+ "capabilities": {
6
+ "sources": {
7
+ "manifest": "https://example.com/openship/manifest.json",
8
+ "bundle": "https://example.com/openship/bundle.json",
9
+ "mcp": "/mcp"
10
+ }
11
+ }
12
+ }
@@ -7,6 +7,7 @@
7
7
  "sources": {
8
8
  "manifest": "https://example.com/openship/manifest.json",
9
9
  "bundle": "https://example.com/openship/bundle.json",
10
+ "mcp": "https://mcp.example.com/mcp",
10
11
  "file": "https://example.com/openship/file/{path}",
11
12
  "archive": "https://example.com/openship/source.tar.gz"
12
13
  },
@@ -17,4 +18,3 @@
17
18
  }
18
19
  }
19
20
  }
20
-
@@ -0,0 +1,52 @@
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
+ "manifest": "https://example.com/openship/manifest.json",
17
+ "bundle": "https://example.com/openship/bundle.json",
18
+ "mcp": "https://mcp.example.com/mcp"
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ 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.
25
+
26
+ ## Tool
27
+
28
+ The server MUST register one tool named `openship` with this input union:
29
+
30
+ ```json
31
+ { "operation": "manifest" }
32
+ { "operation": "read", "path": "app/page.tsx" }
33
+ ```
34
+
35
+ `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.
36
+
37
+ 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.
38
+
39
+ ## Resources
40
+
41
+ Resource-aware servers SHOULD also expose:
42
+
43
+ - `openship://sources/manifest` with the Manifest as `application/json` text.
44
+ - `openship://sources/file{?path}` as a resource template and enumerate its concrete Manifest files from `resources/list`.
45
+
46
+ 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.
47
+
48
+ ## Integrity and errors
49
+
50
+ 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.
51
+
52
+ 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 three optional URLs:
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
-
@@ -61,6 +61,7 @@ The response MUST be unauthenticated JSON, MUST allow cross-origin reads with `A
61
61
  "sources": {
62
62
  "manifest": "https://example.com/openship/manifest.json",
63
63
  "bundle": "https://example.com/openship/bundle.json",
64
+ "mcp": "https://mcp.example.com/mcp",
64
65
  "file": "https://example.com/openship/file/{path}",
65
66
  "archive": "https://example.com/openship/source.tar.gz",
66
67
  "instructions": "https://example.com/openship/agent.txt"
@@ -82,6 +83,7 @@ Requirements:
82
83
  - `project`, `capabilities`, and `capabilities.sources` are REQUIRED.
83
84
  - `project.name` and `project.description` are REQUIRED.
84
85
  - `sources.manifest` and `sources.bundle` are REQUIRED.
86
+ - `sources.mcp` is OPTIONAL and, when present, advertises the OpenShip Sources MCP binding.
85
87
  - `changes`, `systems`, and `skill` are OPTIONAL and MUST be omitted when unavailable.
86
88
  - Changes MUST NOT be advertised without Sources.
87
89
  - `file` and `status` are URI templates with exactly the named expansion.
@@ -114,6 +116,7 @@ Changes status responses change over time and MUST use `Cache-Control: no-store`
114
116
  ## Capability documents
115
117
 
116
118
  - Read [openship-sources.md](openship-sources.md) for source snapshots and integrity.
119
+ - Read [openship-mcp.md](openship-mcp.md) for the optional MCP binding for Sources.
117
120
  - Read [openship-changes.md](openship-changes.md) for candidate changes and isolation.
118
121
  - Read [openship-systems.md](openship-systems.md) for the self-contained systems model.
119
122
 
@@ -122,4 +125,3 @@ Changes status responses change over time and MUST use `Cache-Control: no-store`
122
125
  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
126
 
124
127
  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
-
@@ -27,6 +27,7 @@
27
27
  "properties": {
28
28
  "manifest": { "$ref": "#/$defs/httpsUrl" },
29
29
  "bundle": { "$ref": "#/$defs/httpsUrl" },
30
+ "mcp": { "$ref": "#/$defs/httpsUrl" },
30
31
  "file": { "type": "string", "pattern": "^https://.+\\{path\\}.+$|^https://.+/\\{path\\}$" },
31
32
  "archive": { "$ref": "#/$defs/httpsUrl" },
32
33
  "instructions": { "$ref": "#/$defs/httpsUrl" }
@@ -58,4 +59,3 @@
58
59
  },
59
60
  "additionalProperties": true
60
61
  }
61
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openship/protocol",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Canonical OpenShip 1.0 types, validators, consumer helpers, schemas, and skill assets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.d.ts CHANGED
@@ -1,9 +1,14 @@
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 DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: { name: string; description: string; [key: string]: unknown }; capabilities: { sources: { manifest: string; bundle: string; [key: string]: unknown }; systems?: { document: string; [key: string]: unknown }; changes?: { policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
11
+ export interface DiscoveryDocument { openship: "1.0"; capability: "discovery"; project: { name: string; description: string; [key: string]: unknown }; capabilities: { sources: { manifest: string; bundle: string; mcp?: string; [key: string]: unknown }; systems?: { document: string; [key: string]: unknown }; changes?: { policy: string; submit: string; status: string; [key: string]: unknown }; [key: string]: unknown }; [key: string]: unknown }
7
12
  export type SystemsNodeKind = "Root" | "Host" | "Container" | "Process" | "Library";
8
13
  export type SystemsNodeOwnership = "first_party" | "third_party";
9
14
  export interface SystemsNodeMetadata { ownership: SystemsNodeOwnership; [key: string]: unknown }
@@ -22,6 +27,7 @@ export function compareUtf8(left: string, right: string): number;
22
27
  export function matchOpenShipPattern(pattern: string, path: string): boolean;
23
28
  export function computeSourcesDigest(files: SourceFileMetadata[]): string;
24
29
  export function validateDiscovery(value: unknown): DiscoveryDocument;
30
+ export function validateSourcesManifest(value: unknown): SourcesManifest;
25
31
  export function validateSources(manifest: unknown, bundle: unknown, options?: { maxDecodedBytes?: number }): VerifiedSources;
26
32
  export function validateSystems(value: unknown, options?: { maxDecodedBytes?: number }): SystemsDocument;
27
33
  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 validateManifest(value) {
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 = validateManifest(manifestValue);
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");
@@ -189,6 +193,7 @@ export function validateDiscovery(value) {
189
193
  const url = string(sources[key], `$.capabilities.sources.${key}`);
190
194
  try { new URL(url); } catch { fail(`$.capabilities.sources.${key}`, "must be an absolute URL"); }
191
195
  }
196
+ if (sources.mcp !== undefined) absoluteUrl(sources.mcp, "$.capabilities.sources.mcp");
192
197
  if (capabilities.systems) absoluteUrl(object(capabilities.systems, "$.capabilities.systems").document, "$.capabilities.systems.document");
193
198
  if (capabilities.changes) {
194
199
  const changes = object(capabilities.changes, "$.capabilities.changes");