forgingstack-mandate-check 0.2.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,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: forgingstack-mandate-check
3
+ Version: 0.2.0
4
+ Summary: Before your agent acts, check its mandate. One call, one receipt.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # ForgingStack Mandate Check
9
+
10
+ Before your agent acts, check its mandate. One call, one receipt.
11
+
12
+ This is the **public client** for Mandate Check. The service itself —
13
+ the decision logic, the receipt chain, the key store, and billing —
14
+ runs on ForgingStack's private service and is **not** in this repo.
15
+ Copying this repo does not give you the service: you still need an
16
+ API key and credits, from signup below.
17
+
18
+ Live API: https://forgingstack-mandate-check.jkbattles.workers.dev
19
+
20
+ ## What it checks, in order
21
+
22
+ 1. Mandate agent matches the calling agent
23
+ 2. Mandate principal matches the principal the agent acts for
24
+ 3. Mandate expiry is parseable and in the future
25
+ 4. Action is in the mandate's allowed actions
26
+
27
+ Pass or fail, you get a reason and a hash-chained receipt.
28
+
29
+ What it does **not** do: decide if an action is wise, legal, or
30
+ safe. Mandate fit only.
31
+
32
+ ## Signup (v1)
33
+
34
+ ```bash
35
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/signup \
36
+ -H "Content-Type: application/json" \
37
+ -d '{"email":"you@example.com"}'
38
+ ```
39
+
40
+ Returns an API key **once** and 100 starter credits. Save the key
41
+ immediately — it is stored only as a hash and cannot be shown again.
42
+ Email verification is pending, and credit-pack billing is not live
43
+ yet, so do not expect a purchase flow in v1.
44
+
45
+ ## Agent sub-keys
46
+
47
+ A signed-up human creates a key for each agent, capped and revocable:
48
+
49
+ ```bash
50
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/keys \
51
+ -H "X-API-Key: YOUR_PRIMARY_KEY" -H "Content-Type: application/json" \
52
+ -d '{"agent_name":"my-agent","credits":20}'
53
+ ```
54
+
55
+ List with `GET /v1/keys`, revoke with `DELETE /v1/keys/{id}` —
56
+ revoking returns unused credits to the primary account.
57
+
58
+ ## Use it (MCP, stdio)
59
+
60
+ Set the key, then run the MCP server in this repo:
61
+
62
+ ```bash
63
+ export FORGINGSTACK_API_KEY="your-key-here"
64
+ python -m app.mcp_server
65
+ ```
66
+
67
+ Tools: `check_mandate`, `get_credits`, `get_receipt`.
68
+ See `SKILL.md` for when to use it and when not to.
69
+
70
+ ## Price
71
+
72
+ 1 credit per check call. A failed decision still costs 1 credit —
73
+ the check was performed. An idempotent replay (same
74
+ `Idempotency-Key`) is free and returns the original receipt.
75
+
76
+ ## Files in this repo
77
+
78
+ - `app/mcp_server.py` — MCP wrapper client (stdio, stdlib only)
79
+ - `SKILL.md` — when to use it, how to read pass/fail
80
+ - `agent-card.json` — A2A-style discovery card
81
+ - `server.json` — MCP Registry metadata (draft until PyPI publish)
82
+ - `pyproject.toml` — package metadata for the MCP client
@@ -0,0 +1,75 @@
1
+ # ForgingStack Mandate Check
2
+
3
+ Before your agent acts, check its mandate. One call, one receipt.
4
+
5
+ This is the **public client** for Mandate Check. The service itself —
6
+ the decision logic, the receipt chain, the key store, and billing —
7
+ runs on ForgingStack's private service and is **not** in this repo.
8
+ Copying this repo does not give you the service: you still need an
9
+ API key and credits, from signup below.
10
+
11
+ Live API: https://forgingstack-mandate-check.jkbattles.workers.dev
12
+
13
+ ## What it checks, in order
14
+
15
+ 1. Mandate agent matches the calling agent
16
+ 2. Mandate principal matches the principal the agent acts for
17
+ 3. Mandate expiry is parseable and in the future
18
+ 4. Action is in the mandate's allowed actions
19
+
20
+ Pass or fail, you get a reason and a hash-chained receipt.
21
+
22
+ What it does **not** do: decide if an action is wise, legal, or
23
+ safe. Mandate fit only.
24
+
25
+ ## Signup (v1)
26
+
27
+ ```bash
28
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/signup \
29
+ -H "Content-Type: application/json" \
30
+ -d '{"email":"you@example.com"}'
31
+ ```
32
+
33
+ Returns an API key **once** and 100 starter credits. Save the key
34
+ immediately — it is stored only as a hash and cannot be shown again.
35
+ Email verification is pending, and credit-pack billing is not live
36
+ yet, so do not expect a purchase flow in v1.
37
+
38
+ ## Agent sub-keys
39
+
40
+ A signed-up human creates a key for each agent, capped and revocable:
41
+
42
+ ```bash
43
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/keys \
44
+ -H "X-API-Key: YOUR_PRIMARY_KEY" -H "Content-Type: application/json" \
45
+ -d '{"agent_name":"my-agent","credits":20}'
46
+ ```
47
+
48
+ List with `GET /v1/keys`, revoke with `DELETE /v1/keys/{id}` —
49
+ revoking returns unused credits to the primary account.
50
+
51
+ ## Use it (MCP, stdio)
52
+
53
+ Set the key, then run the MCP server in this repo:
54
+
55
+ ```bash
56
+ export FORGINGSTACK_API_KEY="your-key-here"
57
+ python -m app.mcp_server
58
+ ```
59
+
60
+ Tools: `check_mandate`, `get_credits`, `get_receipt`.
61
+ See `SKILL.md` for when to use it and when not to.
62
+
63
+ ## Price
64
+
65
+ 1 credit per check call. A failed decision still costs 1 credit —
66
+ the check was performed. An idempotent replay (same
67
+ `Idempotency-Key`) is free and returns the original receipt.
68
+
69
+ ## Files in this repo
70
+
71
+ - `app/mcp_server.py` — MCP wrapper client (stdio, stdlib only)
72
+ - `SKILL.md` — when to use it, how to read pass/fail
73
+ - `agent-card.json` — A2A-style discovery card
74
+ - `server.json` — MCP Registry metadata (draft until PyPI publish)
75
+ - `pyproject.toml` — package metadata for the MCP client
File without changes
@@ -0,0 +1,173 @@
1
+ """MCP wrapper client for ForgingStack Mandate Check, v0.2.0.
2
+
3
+ An agent-native service must be usable by agents, not just by humans
4
+ with curl. MCP is the standard interface agents already speak, so the
5
+ wrapper exposes the same live REST API as MCP tools, with no duplicated
6
+ decision logic: every tool call goes through the REST API, so metering
7
+ and receipts stay in one place. The service itself is private; this
8
+ file is only a client.
9
+
10
+ Protocol: JSON-RPC 2.0 over stdio, no new dependencies (stdlib only).
11
+ Supported methods: initialize, ping, tools/list, tools/call.
12
+ Notifications (no id, e.g. notifications/initialized) get no response.
13
+
14
+ Environment:
15
+ FORGINGSTACK_API_BASE default https://forgingstack-mandate-check.jkbattles.workers.dev
16
+ FORGINGSTACK_API_KEY required; sign up via POST /v1/signup (key shown once)
17
+
18
+ Run: python -m app.mcp_server
19
+ """
20
+
21
+ import json
22
+ import os
23
+ import sys
24
+ import urllib.error
25
+ import urllib.request
26
+
27
+ BASE = os.environ.get(
28
+ "FORGINGSTACK_API_BASE",
29
+ "https://forgingstack-mandate-check.jkbattles.workers.dev",
30
+ )
31
+ API_KEY = os.environ.get("FORGINGSTACK_API_KEY", "")
32
+
33
+ TOOLS = [
34
+ {
35
+ "name": "check_mandate",
36
+ "description": "Check an agent action against its mandate. Returns a "
37
+ "pass/fail decision and a receipt. Costs 1 credit per call; "
38
+ "an idempotency key replay is free.",
39
+ "inputSchema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "agent_id": {"type": "string"},
43
+ "principal": {"type": "string"},
44
+ "action": {"type": "string"},
45
+ "mandate": {
46
+ "type": "object",
47
+ "properties": {
48
+ "agent_id": {"type": "string"},
49
+ "principal": {"type": "string"},
50
+ "allowed_actions": {
51
+ "type": "array", "items": {"type": "string"}
52
+ },
53
+ "expires_at": {"type": "string"},
54
+ "scope": {"type": "string"},
55
+ },
56
+ "required": [
57
+ "agent_id", "principal", "allowed_actions", "expires_at"
58
+ ],
59
+ },
60
+ "idempotency_key": {"type": "string"},
61
+ },
62
+ "required": ["agent_id", "principal", "action", "mandate"],
63
+ },
64
+ },
65
+ {
66
+ "name": "get_credits",
67
+ "description": "Remaining credit balance for the configured API key.",
68
+ "inputSchema": {"type": "object", "properties": {}},
69
+ },
70
+ {
71
+ "name": "get_receipt",
72
+ "description": "Fetch a past mandate-check receipt by id.",
73
+ "inputSchema": {
74
+ "type": "object",
75
+ "properties": {"receipt_id": {"type": "integer"}},
76
+ "required": ["receipt_id"],
77
+ },
78
+ },
79
+ ]
80
+
81
+
82
+ def _http(method, path, body=None, idempotency_key=None):
83
+ req = urllib.request.Request(
84
+ BASE + path,
85
+ data=json.dumps(body).encode() if body is not None else None,
86
+ method=method,
87
+ )
88
+ req.add_header("X-API-Key", API_KEY)
89
+ req.add_header("Content-Type", "application/json")
90
+ # A User-Agent is required: the default Python urllib agent is
91
+ # rejected (Cloudflare 1010) through some egress paths.
92
+ req.add_header("User-Agent", "ForgingStack-Mandate-Check/0.2.0")
93
+ if idempotency_key:
94
+ req.add_header("Idempotency-Key", idempotency_key)
95
+ try:
96
+ with urllib.request.urlopen(req, timeout=30) as resp:
97
+ return resp.status, json.loads(resp.read().decode() or "{}")
98
+ except urllib.error.HTTPError as e:
99
+ try:
100
+ return e.code, json.loads(e.read().decode() or "{}")
101
+ except Exception:
102
+ return e.code, {"detail": "api error"}
103
+
104
+
105
+ def _text(payload, is_error=False):
106
+ return {
107
+ "content": [{"type": "text", "text": json.dumps(payload)}],
108
+ "isError": is_error,
109
+ }
110
+
111
+
112
+ def handle(msg):
113
+ method = msg.get("method")
114
+ params = msg.get("params") or {}
115
+ if method == "initialize":
116
+ return {
117
+ "protocolVersion": "2024-11-05",
118
+ "capabilities": {"tools": {}},
119
+ "serverInfo": {"name": "forgingstack-mandate-check", "version": "0.2.0"},
120
+ }
121
+ if method == "ping":
122
+ return {}
123
+ if method == "tools/list":
124
+ return {"tools": TOOLS}
125
+ if method == "tools/call":
126
+ name = params.get("name")
127
+ args = params.get("arguments") or {}
128
+ if name == "check_mandate":
129
+ body = {k: args[k] for k in
130
+ ("agent_id", "principal", "action", "mandate")}
131
+ status, data = _http(
132
+ "POST", "/v1/check-mandate", body,
133
+ args.get("idempotency_key"),
134
+ )
135
+ return _text(data, is_error=status >= 400)
136
+ if name == "get_credits":
137
+ status, data = _http("GET", "/v1/credits")
138
+ return _text(data, is_error=status >= 400)
139
+ if name == "get_receipt":
140
+ status, data = _http(
141
+ "GET", f"/v1/receipts/{args['receipt_id']}"
142
+ )
143
+ return _text(data, is_error=status >= 400)
144
+ raise ValueError(f"unknown tool: {name}")
145
+ raise ValueError(f"unknown method: {method}")
146
+
147
+
148
+ def main():
149
+ for line in sys.stdin:
150
+ line = line.strip()
151
+ if not line:
152
+ continue
153
+ try:
154
+ msg = json.loads(line)
155
+ except json.JSONDecodeError:
156
+ continue
157
+ # Notifications carry no id and get no response.
158
+ if "id" not in msg:
159
+ continue
160
+ try:
161
+ result = handle(msg)
162
+ out = {"jsonrpc": "2.0", "id": msg["id"], "result": result}
163
+ except Exception as exc: # noqa: BLE001 - MCP error surface
164
+ out = {
165
+ "jsonrpc": "2.0", "id": msg["id"],
166
+ "error": {"code": -32603, "message": str(exc)},
167
+ }
168
+ sys.stdout.write(json.dumps(out) + "\n")
169
+ sys.stdout.flush()
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: forgingstack-mandate-check
3
+ Version: 0.2.0
4
+ Summary: Before your agent acts, check its mandate. One call, one receipt.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # ForgingStack Mandate Check
9
+
10
+ Before your agent acts, check its mandate. One call, one receipt.
11
+
12
+ This is the **public client** for Mandate Check. The service itself —
13
+ the decision logic, the receipt chain, the key store, and billing —
14
+ runs on ForgingStack's private service and is **not** in this repo.
15
+ Copying this repo does not give you the service: you still need an
16
+ API key and credits, from signup below.
17
+
18
+ Live API: https://forgingstack-mandate-check.jkbattles.workers.dev
19
+
20
+ ## What it checks, in order
21
+
22
+ 1. Mandate agent matches the calling agent
23
+ 2. Mandate principal matches the principal the agent acts for
24
+ 3. Mandate expiry is parseable and in the future
25
+ 4. Action is in the mandate's allowed actions
26
+
27
+ Pass or fail, you get a reason and a hash-chained receipt.
28
+
29
+ What it does **not** do: decide if an action is wise, legal, or
30
+ safe. Mandate fit only.
31
+
32
+ ## Signup (v1)
33
+
34
+ ```bash
35
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/signup \
36
+ -H "Content-Type: application/json" \
37
+ -d '{"email":"you@example.com"}'
38
+ ```
39
+
40
+ Returns an API key **once** and 100 starter credits. Save the key
41
+ immediately — it is stored only as a hash and cannot be shown again.
42
+ Email verification is pending, and credit-pack billing is not live
43
+ yet, so do not expect a purchase flow in v1.
44
+
45
+ ## Agent sub-keys
46
+
47
+ A signed-up human creates a key for each agent, capped and revocable:
48
+
49
+ ```bash
50
+ curl -X POST https://forgingstack-mandate-check.jkbattles.workers.dev/v1/keys \
51
+ -H "X-API-Key: YOUR_PRIMARY_KEY" -H "Content-Type: application/json" \
52
+ -d '{"agent_name":"my-agent","credits":20}'
53
+ ```
54
+
55
+ List with `GET /v1/keys`, revoke with `DELETE /v1/keys/{id}` —
56
+ revoking returns unused credits to the primary account.
57
+
58
+ ## Use it (MCP, stdio)
59
+
60
+ Set the key, then run the MCP server in this repo:
61
+
62
+ ```bash
63
+ export FORGINGSTACK_API_KEY="your-key-here"
64
+ python -m app.mcp_server
65
+ ```
66
+
67
+ Tools: `check_mandate`, `get_credits`, `get_receipt`.
68
+ See `SKILL.md` for when to use it and when not to.
69
+
70
+ ## Price
71
+
72
+ 1 credit per check call. A failed decision still costs 1 credit —
73
+ the check was performed. An idempotent replay (same
74
+ `Idempotency-Key`) is free and returns the original receipt.
75
+
76
+ ## Files in this repo
77
+
78
+ - `app/mcp_server.py` — MCP wrapper client (stdio, stdlib only)
79
+ - `SKILL.md` — when to use it, how to read pass/fail
80
+ - `agent-card.json` — A2A-style discovery card
81
+ - `server.json` — MCP Registry metadata (draft until PyPI publish)
82
+ - `pyproject.toml` — package metadata for the MCP client
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ app/__init__.py
4
+ app/mcp_server.py
5
+ forgingstack_mandate_check.egg-info/PKG-INFO
6
+ forgingstack_mandate_check.egg-info/SOURCES.txt
7
+ forgingstack_mandate_check.egg-info/dependency_links.txt
8
+ forgingstack_mandate_check.egg-info/entry_points.txt
9
+ forgingstack_mandate_check.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ forgingstack-mandate-check = app.mcp_server:main
@@ -0,0 +1,24 @@
1
+ # DRAFT packaging for the MCP server only (stdlib, no FastAPI dependency).
2
+ # Do not publish to PyPI until: (1) the GitHub repo exists and is public,
3
+ # (2) the live API endpoint exists (the MCP server calls it over HTTP),
4
+ # (3) the maintainer approves the PyPI name and licence.
5
+ # The REST API itself (app/main.py) is a separate service, not this package.
6
+
7
+ [build-system]
8
+ requires = ["setuptools>=61"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "forgingstack-mandate-check"
13
+ version = "0.2.0"
14
+ description = "Before your agent acts, check its mandate. One call, one receipt."
15
+ readme = "README.md"
16
+ requires-python = ">=3.10"
17
+ dependencies = []
18
+
19
+
20
+ [project.scripts]
21
+ forgingstack-mandate-check = "app.mcp_server:main"
22
+
23
+ [tool.setuptools.packages.find]
24
+ include = ["app*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+