mcpc-cli 0.1.0__tar.gz

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.
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcpc-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for the MCP Contract specification — validate, inspect, and manage contract bundles.
5
+ Author: Joe Fullerton
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://mcpcontracts.com
8
+ Project-URL: Repository, https://github.com/jmfullerton96/mcp-contract
9
+ Project-URL: Specification, https://github.com/jmfullerton96/mcp-contract/blob/main/SPEC.md
10
+ Keywords: mcp,ai,llm,composability,json-schema,workflow
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+
18
+ # MCP Contract
19
+
20
+ [![CI](https://github.com/jmfullerton96/mcp-contract/actions/workflows/ci.yml/badge.svg)](https://github.com/jmfullerton96/mcp-contract/actions/workflows/ci.yml)
21
+ [![Spec](https://img.shields.io/badge/spec-v0.1.0--draft-blue)](SPEC.md)
22
+ [![License](https://img.shields.io/badge/license-Apache--2.0-green)](LICENSE)
23
+ [![JSON Schema](https://img.shields.io/badge/JSON%20Schema-draft%202020--12-orange)](schema/mcp-contract.schema.json)
24
+ [![Website](https://img.shields.io/badge/web-mcpcontracts.com-purple)](https://mcpcontracts.com)
25
+
26
+ A specification for composable AI workflow bundles with typed contracts between layers.
27
+
28
+ ## The Problem
29
+
30
+ MCP servers are monoliths. If you want to use one server's reasoning prompts with another server's tools and a third server's UI, you fork all three. MCP Contract fixes this by separating bundles into independently authored, versioned, and composable layers.
31
+
32
+ ## The Layers
33
+
34
+ | Layer | Role | Analogy |
35
+ |---|---|---|
36
+ | **Prompts** | Reasoning logic | Source code |
37
+ | **Tools** | State + execution | Runtime libraries |
38
+ | **Apps** | Interactive views | UI framework |
39
+ | **Skills** | Platform adaptation | Compiler flags |
40
+ | **Compiler** | The LLM | gcc, clang, rustc |
41
+
42
+ Layers are wired together through **contracts** — typed `provides`/`consumes` declarations backed by JSON Schema. A prompt layer provides an output shape. A tool layer provides data schemas. An app layer consumes both.
43
+
44
+ ## Quick Start
45
+
46
+ ```
47
+ my-workflow/
48
+ ├── mcp-contract.json # Manifest
49
+ ├── prompts/ # Markdown reasoning
50
+ ├── tools/ # MCP server
51
+ ├── apps/ # Interactive UIs
52
+ ├── schemas/ # JSON Schema contracts
53
+ └── skills/ # Platform hints
54
+ ```
55
+
56
+ See [SPEC.md](SPEC.md) for the full specification.
57
+
58
+ ## Reference Implementation
59
+
60
+ [Synth Ops](https://github.com/jmfullerton96/synthops) — intelligent infrastructure operations built as an MCP Contract bundle.
61
+
62
+ ## Manifest Schema
63
+
64
+ The JSON Schema for validating `mcp-contract.json` manifests is at [`schema/mcp-contract.schema.json`](schema/mcp-contract.schema.json).
65
+
66
+ Reference it in your manifest:
67
+
68
+ ```json
69
+ {
70
+ "$schema": "https://mcpcontracts.com/schema/0.1.0.json",
71
+ "name": "my-workflow",
72
+ "version": "0.1.0",
73
+ "layers": { ... }
74
+ }
75
+ ```
76
+
77
+ ## Status
78
+
79
+ **v0.1.0-draft** — Spec, manifest schema, CLI (`mcpc validate`, `init`, `pack`, `test`), and one reference implementation.
80
+
81
+ ## License
82
+
83
+ Apache-2.0
@@ -0,0 +1,66 @@
1
+ # MCP Contract
2
+
3
+ [![CI](https://github.com/jmfullerton96/mcp-contract/actions/workflows/ci.yml/badge.svg)](https://github.com/jmfullerton96/mcp-contract/actions/workflows/ci.yml)
4
+ [![Spec](https://img.shields.io/badge/spec-v0.1.0--draft-blue)](SPEC.md)
5
+ [![License](https://img.shields.io/badge/license-Apache--2.0-green)](LICENSE)
6
+ [![JSON Schema](https://img.shields.io/badge/JSON%20Schema-draft%202020--12-orange)](schema/mcp-contract.schema.json)
7
+ [![Website](https://img.shields.io/badge/web-mcpcontracts.com-purple)](https://mcpcontracts.com)
8
+
9
+ A specification for composable AI workflow bundles with typed contracts between layers.
10
+
11
+ ## The Problem
12
+
13
+ MCP servers are monoliths. If you want to use one server's reasoning prompts with another server's tools and a third server's UI, you fork all three. MCP Contract fixes this by separating bundles into independently authored, versioned, and composable layers.
14
+
15
+ ## The Layers
16
+
17
+ | Layer | Role | Analogy |
18
+ |---|---|---|
19
+ | **Prompts** | Reasoning logic | Source code |
20
+ | **Tools** | State + execution | Runtime libraries |
21
+ | **Apps** | Interactive views | UI framework |
22
+ | **Skills** | Platform adaptation | Compiler flags |
23
+ | **Compiler** | The LLM | gcc, clang, rustc |
24
+
25
+ Layers are wired together through **contracts** — typed `provides`/`consumes` declarations backed by JSON Schema. A prompt layer provides an output shape. A tool layer provides data schemas. An app layer consumes both.
26
+
27
+ ## Quick Start
28
+
29
+ ```
30
+ my-workflow/
31
+ ├── mcp-contract.json # Manifest
32
+ ├── prompts/ # Markdown reasoning
33
+ ├── tools/ # MCP server
34
+ ├── apps/ # Interactive UIs
35
+ ├── schemas/ # JSON Schema contracts
36
+ └── skills/ # Platform hints
37
+ ```
38
+
39
+ See [SPEC.md](SPEC.md) for the full specification.
40
+
41
+ ## Reference Implementation
42
+
43
+ [Synth Ops](https://github.com/jmfullerton96/synthops) — intelligent infrastructure operations built as an MCP Contract bundle.
44
+
45
+ ## Manifest Schema
46
+
47
+ The JSON Schema for validating `mcp-contract.json` manifests is at [`schema/mcp-contract.schema.json`](schema/mcp-contract.schema.json).
48
+
49
+ Reference it in your manifest:
50
+
51
+ ```json
52
+ {
53
+ "$schema": "https://mcpcontracts.com/schema/0.1.0.json",
54
+ "name": "my-workflow",
55
+ "version": "0.1.0",
56
+ "layers": { ... }
57
+ }
58
+ ```
59
+
60
+ ## Status
61
+
62
+ **v0.1.0-draft** — Spec, manifest schema, CLI (`mcpc validate`, `init`, `pack`, `test`), and one reference implementation.
63
+
64
+ ## License
65
+
66
+ Apache-2.0
@@ -0,0 +1,3 @@
1
+ """mcpc — CLI for the MCP Contract specification."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as `python -m mcpc`."""
2
+
3
+ from mcpc.cli import main
4
+
5
+ main()
@@ -0,0 +1,172 @@
1
+ """mcpc CLI entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from mcpc import __version__
9
+ from mcpc.init import TEMPLATES, init_bundle
10
+ from mcpc.pack import pack_bundle
11
+ from mcpc.test import test_bundle
12
+ from mcpc.unpack import unpack_bundle
13
+ from mcpc.validate import validate_bundle
14
+
15
+
16
+ def main(argv: list[str] | None = None) -> None:
17
+ parser = argparse.ArgumentParser(
18
+ prog="mcpc",
19
+ description="CLI for the MCP Contract specification.",
20
+ )
21
+ parser.add_argument(
22
+ "--version", action="version", version=f"mcpc {__version__}"
23
+ )
24
+
25
+ subparsers = parser.add_subparsers(dest="command")
26
+
27
+ # -- validate --
28
+ validate_parser = subparsers.add_parser(
29
+ "validate",
30
+ help="Validate an MCP Contract bundle.",
31
+ description=(
32
+ "Validates the manifest against the schema, checks that all "
33
+ "provides/consumes contracts are satisfied, and verifies that "
34
+ "referenced files exist."
35
+ ),
36
+ )
37
+ validate_parser.add_argument(
38
+ "path",
39
+ nargs="?",
40
+ default=".",
41
+ help="Path to the bundle directory (default: current directory).",
42
+ )
43
+ validate_parser.add_argument(
44
+ "--quiet",
45
+ action="store_true",
46
+ help="Only print errors, suppress informational output.",
47
+ )
48
+
49
+ # -- init --
50
+ init_parser = subparsers.add_parser(
51
+ "init",
52
+ help="Scaffold a new MCP Contract bundle.",
53
+ description="Creates a new bundle directory with manifest and starter files.",
54
+ )
55
+ init_parser.add_argument(
56
+ "name",
57
+ help="Bundle name (lowercase, hyphens only).",
58
+ )
59
+ init_parser.add_argument(
60
+ "--path",
61
+ default=".",
62
+ help="Parent directory for the new bundle (default: current directory).",
63
+ )
64
+ init_parser.add_argument(
65
+ "--template",
66
+ choices=list(TEMPLATES.keys()),
67
+ default="full",
68
+ help="Bundle template (default: full).",
69
+ )
70
+
71
+ # -- pack --
72
+ pack_parser = subparsers.add_parser(
73
+ "pack",
74
+ help="Pack a bundle into a .mcpc archive.",
75
+ description=(
76
+ "Validates the bundle, then creates a .mcpc archive (zip) "
77
+ "containing all bundle files."
78
+ ),
79
+ )
80
+ pack_parser.add_argument(
81
+ "path",
82
+ nargs="?",
83
+ default=".",
84
+ help="Path to the bundle directory (default: current directory).",
85
+ )
86
+ pack_parser.add_argument(
87
+ "--output", "-o",
88
+ default=None,
89
+ help="Output file path (default: <name>-<version>.mcpc in parent directory).",
90
+ )
91
+ pack_parser.add_argument(
92
+ "--quiet",
93
+ action="store_true",
94
+ help="Only print errors, suppress informational output.",
95
+ )
96
+
97
+ # -- test --
98
+ test_parser = subparsers.add_parser(
99
+ "test",
100
+ help="Run structural tests on bundle layers.",
101
+ description=(
102
+ "Tests layer content quality: prompt frontmatter, schema "
103
+ "conventions, tool syntax, and app structure."
104
+ ),
105
+ )
106
+ test_parser.add_argument(
107
+ "path",
108
+ nargs="?",
109
+ default=".",
110
+ help="Path to the bundle directory (default: current directory).",
111
+ )
112
+ test_parser.add_argument(
113
+ "--layer",
114
+ choices=["prompts", "schemas", "tools", "apps"],
115
+ default=None,
116
+ help="Test only a specific layer.",
117
+ )
118
+ test_parser.add_argument(
119
+ "--quiet",
120
+ action="store_true",
121
+ help="Only print failures, suppress informational output.",
122
+ )
123
+
124
+ # -- unpack --
125
+ unpack_parser = subparsers.add_parser(
126
+ "unpack",
127
+ help="Extract a .mcpc archive into a bundle directory.",
128
+ description=(
129
+ "Extracts a .mcpc archive, verifies it contains a valid "
130
+ "manifest, and writes the bundle to a directory."
131
+ ),
132
+ )
133
+ unpack_parser.add_argument(
134
+ "archive",
135
+ help="Path to the .mcpc archive.",
136
+ )
137
+ unpack_parser.add_argument(
138
+ "--output", "-o",
139
+ default=None,
140
+ help="Output directory (default: archive stem in current directory).",
141
+ )
142
+ unpack_parser.add_argument(
143
+ "--quiet",
144
+ action="store_true",
145
+ help="Only print errors, suppress informational output.",
146
+ )
147
+
148
+ args = parser.parse_args(argv)
149
+
150
+ if args.command is None:
151
+ parser.print_help()
152
+ sys.exit(0)
153
+
154
+ if args.command == "validate":
155
+ ok = validate_bundle(args.path, quiet=args.quiet)
156
+ sys.exit(0 if ok else 1)
157
+
158
+ if args.command == "init":
159
+ ok = init_bundle(args.name, args.path, args.template)
160
+ sys.exit(0 if ok else 1)
161
+
162
+ if args.command == "pack":
163
+ ok = pack_bundle(args.path, output=args.output, quiet=args.quiet)
164
+ sys.exit(0 if ok else 1)
165
+
166
+ if args.command == "test":
167
+ ok = test_bundle(args.path, layer=args.layer, quiet=args.quiet)
168
+ sys.exit(0 if ok else 1)
169
+
170
+ if args.command == "unpack":
171
+ ok = unpack_bundle(args.archive, output=args.output, quiet=args.quiet)
172
+ sys.exit(0 if ok else 1)
@@ -0,0 +1,300 @@
1
+ """Scaffold a new MCP Contract bundle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ _BOLD = "\033[1m"
12
+ _GREEN = "\033[32m"
13
+ _CYAN = "\033[36m"
14
+ _RESET = "\033[0m"
15
+
16
+
17
+ def _supports_color() -> bool:
18
+ return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
19
+
20
+
21
+ def _fmt(code: str, text: str) -> str:
22
+ return f"{code}{text}{_RESET}" if _supports_color() else text
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Templates
27
+ # ---------------------------------------------------------------------------
28
+
29
+ TEMPLATES = {
30
+ "full": {
31
+ "description": "Complete bundle with all layers (prompts, tools, apps, skills)",
32
+ "layers": ["prompts", "tools", "apps", "skills", "schemas"],
33
+ },
34
+ "prompts-only": {
35
+ "description": "Minimal bundle with prompts layer only — no tools, apps, or skills",
36
+ "layers": ["prompts", "schemas"],
37
+ },
38
+ }
39
+
40
+
41
+ def _make_manifest(name: str, template: str) -> dict:
42
+ manifest: dict = {
43
+ "$schema": "https://mcpcontracts.com/schema/0.1.0.json",
44
+ "name": name,
45
+ "version": "0.1.0",
46
+ "description": "",
47
+ "author": {"name": ""},
48
+ "license": "Apache-2.0",
49
+ "layers": {},
50
+ }
51
+
52
+ layers = manifest["layers"]
53
+
54
+ if template in ("full", "prompts-only"):
55
+ layers["prompts"] = {
56
+ "entry": "prompts/main.md",
57
+ "provides": [
58
+ {
59
+ "name": f"{name}-output",
60
+ "schema": f"schemas/{name}-output.json",
61
+ "description": f"Output shape for {name} workflow.",
62
+ }
63
+ ],
64
+ }
65
+
66
+ if template == "full":
67
+ layers["tools"] = {
68
+ "entry": "tools/server.py",
69
+ "runtime": "python",
70
+ "provides": [
71
+ {
72
+ "name": f"{name}-data",
73
+ "schema": f"schemas/{name}-data.json",
74
+ "description": f"Data provided by {name} tool server.",
75
+ }
76
+ ],
77
+ }
78
+ layers["apps"] = {
79
+ "entry": "apps/main.html",
80
+ "consumes": [
81
+ {
82
+ "name": f"{name}-data",
83
+ "schema": f"schemas/{name}-data.json",
84
+ },
85
+ {
86
+ "name": f"{name}-output",
87
+ "schema": f"schemas/{name}-output.json",
88
+ },
89
+ ],
90
+ }
91
+ layers["skills"] = {
92
+ "targets": {
93
+ "claude": "skills/claude.md",
94
+ }
95
+ }
96
+ manifest["compiler_compatibility"] = ["claude"]
97
+ manifest["compose"] = {
98
+ "chain": ["prompts", "tools", "apps"],
99
+ "fallback": "prompts-only",
100
+ }
101
+ else:
102
+ manifest["compiler_compatibility"] = ["claude", "gpt", "gemini"]
103
+ manifest["compose"] = {
104
+ "chain": ["prompts"],
105
+ "fallback": "prompts-only",
106
+ }
107
+
108
+ return manifest
109
+
110
+
111
+ def _make_schema(name: str, title: str, description: str) -> dict:
112
+ return {
113
+ "$id": f"https://mcpcontracts.com/schemas/{name}/0.1.0",
114
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
115
+ "title": title,
116
+ "description": description,
117
+ "type": "object",
118
+ "required": [],
119
+ "properties": {},
120
+ }
121
+
122
+
123
+ def _make_prompt(bundle_name: str, provides_name: str) -> str:
124
+ return f"""---
125
+ name: {bundle_name}
126
+ version: 0.1.0
127
+ description: Primary methodology for {bundle_name}.
128
+ provides: {provides_name}
129
+ ---
130
+
131
+ # {bundle_name.replace('-', ' ').title()}
132
+
133
+ Describe your workflow's reasoning methodology here.
134
+
135
+ ## Methodology
136
+
137
+ 1. **Step one** — What to analyze first.
138
+ 2. **Step two** — How to structure the analysis.
139
+ 3. **Step three** — What output to produce.
140
+
141
+ ## Output Shape
142
+
143
+ The analysis produces structured output conforming to the
144
+ `{provides_name}` schema.
145
+ """
146
+
147
+
148
+ def _make_tool_stub(bundle_name: str) -> str:
149
+ return f'''"""
150
+ {bundle_name} MCP Tool Server (Stub)
151
+
152
+ Implement your MCP tools here. Each tool should:
153
+ - Declare typed input/output schemas in schemas/
154
+ - Not embed reasoning logic (that belongs in prompts/)
155
+ - Not assume a specific prompt or app layer
156
+ """
157
+ '''
158
+
159
+
160
+ def _make_app_stub(bundle_name: str) -> str:
161
+ return f"""<!--
162
+ {bundle_name} App (Stub)
163
+
164
+ Implement your interactive view here. The app should:
165
+ - Declare every schema it consumes in the manifest
166
+ - Not call external APIs directly (data flows through tools)
167
+ - Be renderable with mock data for independent testing
168
+ -->
169
+ <!DOCTYPE html>
170
+ <html lang="en">
171
+ <head>
172
+ <meta charset="UTF-8">
173
+ <title>{bundle_name.replace('-', ' ').title()}</title>
174
+ </head>
175
+ <body>
176
+ <h1>{bundle_name.replace('-', ' ').title()}</h1>
177
+ </body>
178
+ </html>
179
+ """
180
+
181
+
182
+ def _make_skill(bundle_name: str) -> str:
183
+ return f"""---
184
+ target: claude
185
+ version: 0.1.0
186
+ ---
187
+
188
+ # Claude Platform Skill
189
+
190
+ Platform-specific adaptation for running {bundle_name} on Claude.
191
+
192
+ ## Tool Registration
193
+
194
+ Register the tool server via MCP stdio transport in Claude Desktop config.
195
+
196
+ ## Context Window
197
+
198
+ Estimate your prompt chain token count here. Claude Opus supports 200k context.
199
+
200
+ ## Graceful Degradation
201
+
202
+ If the tool server is unavailable, the prompt layer should still produce
203
+ useful output from the LLM's training data.
204
+ """
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Public API
209
+ # ---------------------------------------------------------------------------
210
+
211
+ def init_bundle(name: str, path: str, template: str) -> bool:
212
+ """Scaffold a new MCP Contract bundle. Returns True on success."""
213
+ bundle_dir = Path(path).resolve() / name
214
+
215
+ if bundle_dir.exists():
216
+ print(f" error: {bundle_dir} already exists", file=sys.stderr)
217
+ return False
218
+
219
+ tmpl = TEMPLATES[template]
220
+ manifest = _make_manifest(name, template)
221
+
222
+ print(f"\n{_fmt(_BOLD, 'mcpc init')} {name} ({tmpl['description']})\n")
223
+
224
+ # Create directories
225
+ dirs = [bundle_dir / layer for layer in tmpl["layers"]]
226
+ for d in dirs:
227
+ d.mkdir(parents=True, exist_ok=True)
228
+
229
+ created: list[str] = []
230
+
231
+ # Write manifest
232
+ manifest_path = bundle_dir / "mcp-contract.json"
233
+ with open(manifest_path, "w") as f:
234
+ json.dump(manifest, f, indent=2)
235
+ f.write("\n")
236
+ created.append("mcp-contract.json")
237
+
238
+ # Write prompt
239
+ prompt_path = bundle_dir / "prompts" / "main.md"
240
+ with open(prompt_path, "w") as f:
241
+ f.write(_make_prompt(name, f"{name}-output"))
242
+ created.append("prompts/main.md")
243
+
244
+ # Write output schema
245
+ schema_path = bundle_dir / "schemas" / f"{name}-output.json"
246
+ with open(schema_path, "w") as f:
247
+ json.dump(
248
+ _make_schema(
249
+ f"{name}-output",
250
+ f"{name.replace('-', ' ').title()} Output",
251
+ f"Output shape for {name} workflow.",
252
+ ),
253
+ f,
254
+ indent=2,
255
+ )
256
+ f.write("\n")
257
+ created.append(f"schemas/{name}-output.json")
258
+
259
+ if template == "full":
260
+ # Data schema
261
+ data_schema_path = bundle_dir / "schemas" / f"{name}-data.json"
262
+ with open(data_schema_path, "w") as f:
263
+ json.dump(
264
+ _make_schema(
265
+ f"{name}-data",
266
+ f"{name.replace('-', ' ').title()} Data",
267
+ f"Data provided by {name} tool server.",
268
+ ),
269
+ f,
270
+ indent=2,
271
+ )
272
+ f.write("\n")
273
+ created.append(f"schemas/{name}-data.json")
274
+
275
+ # Tool stub
276
+ tool_path = bundle_dir / "tools" / "server.py"
277
+ with open(tool_path, "w") as f:
278
+ f.write(_make_tool_stub(name))
279
+ created.append("tools/server.py")
280
+
281
+ # App stub
282
+ app_path = bundle_dir / "apps" / "main.html"
283
+ with open(app_path, "w") as f:
284
+ f.write(_make_app_stub(name))
285
+ created.append("apps/main.html")
286
+
287
+ # Skill
288
+ skill_path = bundle_dir / "skills" / "claude.md"
289
+ with open(skill_path, "w") as f:
290
+ f.write(_make_skill(name))
291
+ created.append("skills/claude.md")
292
+
293
+ # Report
294
+ for f in created:
295
+ print(f" {_fmt(_GREEN, '+')} {f}")
296
+
297
+ print(f"\n{_fmt(_CYAN, ' info:')} created {len(created)} files in {bundle_dir}")
298
+ print(f"{_fmt(_CYAN, ' info:')} run {_fmt(_BOLD, f'mcpc validate {name}')} to check\n")
299
+
300
+ return True