astrocade-creator-mcp 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.
- astrocade_creator_mcp-0.1.0/PKG-INFO +58 -0
- astrocade_creator_mcp-0.1.0/README.md +38 -0
- astrocade_creator_mcp-0.1.0/pyproject.toml +41 -0
- astrocade_creator_mcp-0.1.0/setup.cfg +4 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/__init__.py +4 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/astrolib/lib-api-reference-animation.md +38 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/astrolib/lib-api-reference-base.md +170 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/astrolib/lib-api-reference-llm.md +52 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/astrolib_ref.py +19 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/auth/__init__.py +1 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/auth/cognito.py +71 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/auth/login_helper.py +236 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/backend_client.py +274 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/config.py +107 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/context.py +26 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/eslint.config.js +232 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/lib_docs.py +137 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/models.py +35 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/server.py +151 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/stdio_main.py +71 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/tools.py +422 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp/validation.py +526 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/PKG-INFO +58 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/SOURCES.txt +31 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/dependency_links.txt +1 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/entry_points.txt +2 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/requires.txt +14 -0
- astrocade_creator_mcp-0.1.0/src/astrocade_creator_mcp.egg-info/top_level.txt +1 -0
- astrocade_creator_mcp-0.1.0/tests/test_backend_client.py +102 -0
- astrocade_creator_mcp-0.1.0/tests/test_cognito.py +96 -0
- astrocade_creator_mcp-0.1.0/tests/test_config.py +16 -0
- astrocade_creator_mcp-0.1.0/tests/test_login_helper.py +115 -0
- astrocade_creator_mcp-0.1.0/tests/test_tools.py +393 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astrocade-creator-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Astrocade Creator MCP — pull and edit your Astrocade games locally over MCP (stdio).
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: mcp>=1.11.0
|
|
8
|
+
Requires-Dist: pydantic>=2
|
|
9
|
+
Requires-Dist: PyJWT[crypto]>=2.10
|
|
10
|
+
Requires-Dist: requests
|
|
11
|
+
Requires-Dist: aiohttp
|
|
12
|
+
Requires-Dist: html5lib
|
|
13
|
+
Requires-Dist: beautifulsoup4
|
|
14
|
+
Requires-Dist: esprima
|
|
15
|
+
Requires-Dist: tinycss2
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
19
|
+
Requires-Dist: mypy; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# Astrocade Creator MCP
|
|
22
|
+
|
|
23
|
+
A local (stdio) [MCP](https://modelcontextprotocol.io) server that lets a creator pull, edit,
|
|
24
|
+
push, and publish the games already on their Astrocade account — as ordinary local files.
|
|
25
|
+
|
|
26
|
+
## Install & run
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uvx astrocade-creator-mcp
|
|
30
|
+
# or
|
|
31
|
+
pip install astrocade-creator-mcp && astrocade-creator-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Targets **prod** by default — a creator sets nothing. To point at stage instead, set
|
|
35
|
+
`ASTROCADE_CREATOR_MCP_ENV=stage`.
|
|
36
|
+
|
|
37
|
+
### Sign-in
|
|
38
|
+
|
|
39
|
+
Nothing to configure. The **first tool call** opens a browser for Cognito (Google) login, caches
|
|
40
|
+
the tokens under `~/.astrocade`, and silently refreshes them afterward. Login is lazy (not at
|
|
41
|
+
startup) so the browser flow never blocks the MCP stdio handshake.
|
|
42
|
+
|
|
43
|
+
Optional — sign in ahead of time so the first call has no delay:
|
|
44
|
+
```bash
|
|
45
|
+
astrocade-creator-mcp login
|
|
46
|
+
```
|
|
47
|
+
Token cache dir: `~/.astrocade` (override with `ASTROCADE_TOKEN_CACHE_DIR`).
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
- `list_my_games` — your games (drafts included).
|
|
52
|
+
- `pull_game` / `push_game` — check a game out (files + metadata, as JSON), save edits back as a draft. `push_game` can also update metadata (`title`, `description`, `thumbnail_url`).
|
|
53
|
+
- `preview_game` — publish the draft and get a playable URL.
|
|
54
|
+
- `check_wish_status` — poll an AI "wish" that's in flight (wishes are submitted on astrocade.com; `push_game`/`pull_game` surface the request id when one is running).
|
|
55
|
+
- `list_lib_apis` / `get_lib_api` — browse the Astrocade `lib` API reference.
|
|
56
|
+
|
|
57
|
+
Optional JavaScript validation via ESLint runs on push/preview when `eslint` is on `PATH`;
|
|
58
|
+
it's skipped (non-fatal) otherwise.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Astrocade Creator MCP
|
|
2
|
+
|
|
3
|
+
A local (stdio) [MCP](https://modelcontextprotocol.io) server that lets a creator pull, edit,
|
|
4
|
+
push, and publish the games already on their Astrocade account — as ordinary local files.
|
|
5
|
+
|
|
6
|
+
## Install & run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
uvx astrocade-creator-mcp
|
|
10
|
+
# or
|
|
11
|
+
pip install astrocade-creator-mcp && astrocade-creator-mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Targets **prod** by default — a creator sets nothing. To point at stage instead, set
|
|
15
|
+
`ASTROCADE_CREATOR_MCP_ENV=stage`.
|
|
16
|
+
|
|
17
|
+
### Sign-in
|
|
18
|
+
|
|
19
|
+
Nothing to configure. The **first tool call** opens a browser for Cognito (Google) login, caches
|
|
20
|
+
the tokens under `~/.astrocade`, and silently refreshes them afterward. Login is lazy (not at
|
|
21
|
+
startup) so the browser flow never blocks the MCP stdio handshake.
|
|
22
|
+
|
|
23
|
+
Optional — sign in ahead of time so the first call has no delay:
|
|
24
|
+
```bash
|
|
25
|
+
astrocade-creator-mcp login
|
|
26
|
+
```
|
|
27
|
+
Token cache dir: `~/.astrocade` (override with `ASTROCADE_TOKEN_CACHE_DIR`).
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
- `list_my_games` — your games (drafts included).
|
|
32
|
+
- `pull_game` / `push_game` — check a game out (files + metadata, as JSON), save edits back as a draft. `push_game` can also update metadata (`title`, `description`, `thumbnail_url`).
|
|
33
|
+
- `preview_game` — publish the draft and get a playable URL.
|
|
34
|
+
- `check_wish_status` — poll an AI "wish" that's in flight (wishes are submitted on astrocade.com; `push_game`/`pull_game` surface the request id when one is running).
|
|
35
|
+
- `list_lib_apis` / `get_lib_api` — browse the Astrocade `lib` API reference.
|
|
36
|
+
|
|
37
|
+
Optional JavaScript validation via ESLint runs on push/preview when `eslint` is on `PATH`;
|
|
38
|
+
it's skipped (non-fatal) otherwise.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "astrocade-creator-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Astrocade Creator MCP — pull and edit your Astrocade games locally over MCP (stdio)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"mcp>=1.11.0",
|
|
13
|
+
"pydantic>=2",
|
|
14
|
+
"PyJWT[crypto]>=2.10",
|
|
15
|
+
"requests",
|
|
16
|
+
"aiohttp",
|
|
17
|
+
"html5lib",
|
|
18
|
+
"beautifulsoup4",
|
|
19
|
+
"esprima",
|
|
20
|
+
"tinycss2",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = ["pytest", "pytest-asyncio", "mypy"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
astrocade-creator-mcp = "astrocade_creator_mcp.stdio_main:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.package-data]
|
|
33
|
+
astrocade_creator_mcp = ["eslint.config.js", "astrolib/*.md"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
pythonpath = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.12"
|
|
41
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
## ANIMATION
|
|
2
|
+
|
|
3
|
+
### `AnimationPlayer` (Object)
|
|
4
|
+
|
|
5
|
+
- **update** `function(number): void` — Advances animation based on timestamp
|
|
6
|
+
- **draw** `function(CanvasRenderingContext2D, number, number, number, number): void` — Draws current frame
|
|
7
|
+
- **getCurrentFrame** `function(): {index: number, frame: {x: number, y: number, w: number, h: number}|undefined, total: number}` — Gets current frame info
|
|
8
|
+
- **reset** `function(): void` — Resets animation to first frame
|
|
9
|
+
|
|
10
|
+
Creates an animation player for sprite sheet animations.
|
|
11
|
+
The assetId should reference an asset with animation data.
|
|
12
|
+
- **assetId** (`string`): The animation asset ID
|
|
13
|
+
|
|
14
|
+
**Returns:** `AnimationPlayer|null` — The animation player, or null if not found
|
|
15
|
+
|
|
16
|
+
**Example:**
|
|
17
|
+
```js
|
|
18
|
+
const anim = lib.getAnimationPlayer('player_run');
|
|
19
|
+
function gameLoop(timestamp) {
|
|
20
|
+
anim.update(timestamp);
|
|
21
|
+
anim.draw(ctx, x, y, 64, 64);
|
|
22
|
+
requestAnimationFrame(gameLoop);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
lib.getAnimationPlayer = function(assetId) {};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Preloads an animation's sprite sheet for smoother playback.
|
|
31
|
+
Call during initialization to avoid loading delays.
|
|
32
|
+
- **assetId** (`string`): The animation asset ID
|
|
33
|
+
|
|
34
|
+
**Returns:** `Promise<void>`
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
lib.preloadAnimation = async function(assetId) {};
|
|
38
|
+
```
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
============================================================================
|
|
2
|
+
ASTROCADE LIBRARY (lib) - API REFERENCE
|
|
3
|
+
============================================================================
|
|
4
|
+
|
|
5
|
+
The lib object is automatically available in your game code via "lib"
|
|
6
|
+
This is READ-ONLY reference documentation - lib code is owned by Astrocade.
|
|
7
|
+
|
|
8
|
+
Your game's entry point:
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
function run(mode) {
|
|
12
|
+
// mode is 'edit' or 'play'
|
|
13
|
+
// window.gameConfig is already set up before run() is called
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If you remove this function the game will not run.
|
|
18
|
+
|
|
19
|
+
## TYPE DEFINITIONS
|
|
20
|
+
|
|
21
|
+
### `LeaderboardEntry` (Object)
|
|
22
|
+
|
|
23
|
+
- **username** `string` — The player's username
|
|
24
|
+
- **profilePicture** `string|null` — URL to profile picture, or null
|
|
25
|
+
- **score** `number` — The player's score
|
|
26
|
+
|
|
27
|
+
### `LeaderboardResponse` (Object)
|
|
28
|
+
|
|
29
|
+
- **entries** `LeaderboardEntry[]` — Array of leaderboard entries
|
|
30
|
+
- **userRank** `number|null` — Current user's rank, or null if not ranked
|
|
31
|
+
- **[success]** `boolean` — Whether the operation succeeded
|
|
32
|
+
|
|
33
|
+
### `UserGameStateResponse` (Object)
|
|
34
|
+
|
|
35
|
+
- **userId** `string` — The user's ID
|
|
36
|
+
- **gameId** `string` — The game's ID
|
|
37
|
+
- **state** `Object|null` — The saved state, or null if none exists
|
|
38
|
+
- **[success]** `boolean` — Whether the operation succeeded
|
|
39
|
+
|
|
40
|
+
## GLOBALS
|
|
41
|
+
|
|
42
|
+
The game's configuration state.
|
|
43
|
+
Automatically available before run() is called.
|
|
44
|
+
In edit mode, changes are tracked for undo/redo.
|
|
45
|
+
|
|
46
|
+
*Type:* `Object`
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
var gameConfig = window.gameConfig;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Current game mode. Use to conditionally enable editor features.
|
|
53
|
+
|
|
54
|
+
*Type:* `'edit' | 'play'`
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
var mode = window.mode;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## CORE FUNCTIONS
|
|
61
|
+
|
|
62
|
+
Retrieves an asset by its ID from the asset map.
|
|
63
|
+
- **id** (`string`): The asset ID
|
|
64
|
+
|
|
65
|
+
**Returns:** `Object|undefined` — The asset object with these properties, or undefined if not found:
|
|
66
|
+
- url: CDN URL for the asset
|
|
67
|
+
- type: "image", "audio", or "animation"
|
|
68
|
+
- aspect_ratio: [width, height] ratio (e.g., [1536, 1024]) - images only
|
|
69
|
+
- size_hint: suggested display size - images only
|
|
70
|
+
- view: perspective hint (e.g., "top-down", "side-view", "front-view") - images only
|
|
71
|
+
- outline: collision/selection shape - images only
|
|
72
|
+
- box: { type: "box", center: [x, y], size: [w, h] }
|
|
73
|
+
- circle: { type: "circle", center: [x, y], radius: r }
|
|
74
|
+
- polygon: { type: "polygon", points: [[x, y], ...] }
|
|
75
|
+
- loop: "true" or "false" - whether audio should repeat - audio only
|
|
76
|
+
- duration_secs: duration in seconds - audio only
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
lib.getAsset = function(id) {};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Debug logging. Messages are forwarded to the host environment console.
|
|
83
|
+
- **message** (`string`): The message to log
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
lib.log = function(message) {};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Displays a game-wide settings panel in the host UI.
|
|
90
|
+
Call once in run() to register persistent game parameters.
|
|
91
|
+
- **options.name** (`string`): Display name for the settings panel
|
|
92
|
+
- **options.params** (`Object`): Map of display names to parameter configs
|
|
93
|
+
Each param config has:
|
|
94
|
+
- key: path into gameConfig (e.g., 'settings.difficulty')
|
|
95
|
+
- type: 'slider' | 'dropdown' | 'color' | 'checkbox' | 'string' | 'number'
|
|
96
|
+
- min, max, step: for 'slider' type
|
|
97
|
+
- options: [{label, value}] for 'dropdown' type
|
|
98
|
+
- onChange(value): optional callback; if omitted, auto-updates gameConfig at key
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
lib.showGameParameters = function(options) {};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## LEADERBOARD
|
|
105
|
+
|
|
106
|
+
Submits the current player's score to the game's leaderboard.
|
|
107
|
+
Throws if game is not saved or user is not authenticated.
|
|
108
|
+
- **score** (`number`): The player's score
|
|
109
|
+
- **numEntries** (`number`, optional): Optional: number of top entries to return
|
|
110
|
+
|
|
111
|
+
**Returns:** `Promise<LeaderboardResponse>`
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
lib.addPlayerScoreToLeaderboard = async function(score, numEntries) {};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Retrieves the top N entries from the game's leaderboard.
|
|
118
|
+
- **numEntries** (`number`, optional): Optional: number of entries to retrieve
|
|
119
|
+
|
|
120
|
+
**Returns:** `Promise<LeaderboardResponse>`
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
lib.getTopNEntriesFromLeaderboard = async function(numEntries) {};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## USER GAME STATE (Save/Load)
|
|
127
|
+
|
|
128
|
+
Persists the user's game progress to the server.
|
|
129
|
+
State must be JSON-serializable (no functions, circular refs, etc).
|
|
130
|
+
Each user has one save slot per game; always pass the full latest state
|
|
131
|
+
(the slot is overwritten, not merged).
|
|
132
|
+
Call whenever meaningful state changes — level complete, item earned,
|
|
133
|
+
settings changed, or a short settle after the player stops interacting.
|
|
134
|
+
Keep state compact and bounded — store what's needed to resume, and never let
|
|
135
|
+
a saved array (inventory, history, gallery) grow without a cap.
|
|
136
|
+
- **state** (`Object`): The full game state to save
|
|
137
|
+
|
|
138
|
+
**Returns:** `Promise<UserGameStateResponse>`
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
lib.saveUserGameState = async function(state) {};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Retrieves the user's previously saved game state.
|
|
145
|
+
|
|
146
|
+
**Returns:** `Promise<UserGameStateResponse>`
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
lib.getUserGameState = async function() {};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Deletes the user's saved game state.
|
|
153
|
+
Use for "New Game" or reset functionality.
|
|
154
|
+
|
|
155
|
+
**Returns:** `Promise<{success: boolean}>`
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
lib.deleteUserGameState = async function() {};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## DEVICE DETECTION
|
|
162
|
+
|
|
163
|
+
Detects whether touch controls should be used.
|
|
164
|
+
Returns true on mobile/tablet devices (coarse pointer or no hover).
|
|
165
|
+
|
|
166
|
+
**Returns:** `boolean` — true if touch controls are appropriate
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
lib.shouldUseTouchControls = function() {};
|
|
170
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
## LLM (Chat Completions)
|
|
2
|
+
|
|
3
|
+
### `LlmMessage` (Object)
|
|
4
|
+
|
|
5
|
+
- **role** `'user' | 'assistant'` — Speaker for this turn
|
|
6
|
+
- **content** `string` — Message text
|
|
7
|
+
|
|
8
|
+
### `LlmConfig` (Object)
|
|
9
|
+
|
|
10
|
+
- **[temperature]** `number` — 0..2
|
|
11
|
+
- **[maxOutputTokens]** `number` — 1..4,096
|
|
12
|
+
|
|
13
|
+
Calls a chat-completion LLM with the given conversation. Only use when output
|
|
14
|
+
depends on runtime state that couldn't be authored upfront (free-form player
|
|
15
|
+
input, accumulated in-session choices). Fixed dialogue, flavor text, and
|
|
16
|
+
canned hints belong in code or gameConfig — don't fetch what you could author.
|
|
17
|
+
Don't use for game logic, scoring, physics, per-frame work, or anything
|
|
18
|
+
requiring determinism.
|
|
19
|
+
Async; can throw on rate limit, network failure, timeout, or all-empty
|
|
20
|
+
messages. Wrap in try/catch with a non-LLM fallback so the game keeps working
|
|
21
|
+
when the call fails. Empty/whitespace-only messages are silently dropped
|
|
22
|
+
before sending — safe to feed a running chat history without sanitizing.
|
|
23
|
+
Rate-limited server-side. Debounce user-triggered calls, cache repeated
|
|
24
|
+
prompts, never call per frame.
|
|
25
|
+
result.text is a string. For structured output, instruct the model in
|
|
26
|
+
systemPrompt to reply with JSON and parse defensively (JSON.parse inside
|
|
27
|
+
try/catch with a non-LLM fallback).
|
|
28
|
+
Caps: 100 messages, 50,000 chars/message, 200,000 chars aggregate, 10,000
|
|
29
|
+
chars systemPrompt.
|
|
30
|
+
- **options.messages** (`LlmMessage[]`): Conversation so far
|
|
31
|
+
- **options.systemPrompt** (`string`, optional): Persona / instructions
|
|
32
|
+
- **options.config** (`LlmConfig`, optional): Sampling controls
|
|
33
|
+
|
|
34
|
+
**Returns:** `Promise<{text: string}>`
|
|
35
|
+
|
|
36
|
+
**Example:**
|
|
37
|
+
```js
|
|
38
|
+
try {
|
|
39
|
+
const reply = await lib.llm({
|
|
40
|
+
messages: [{ role: 'user', content: 'Greet the player as a grumpy wizard.' }],
|
|
41
|
+
systemPrompt: 'You are a wizard NPC. Reply in one short sentence.',
|
|
42
|
+
config: { temperature: 0.8, maxOutputTokens: 60 },
|
|
43
|
+
});
|
|
44
|
+
showDialogue(reply.text);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
showDialogue('The wizard grumbles incoherently.');
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
lib.llm = async function(options) {};
|
|
52
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Loads the bundled astrolib API reference markdown for the lib-docs tools."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
_DIST_DIR = Path(__file__).parent / "astrolib"
|
|
6
|
+
|
|
7
|
+
ASTROLIB_API_REFERENCE_BASE: str = (_DIST_DIR / "lib-api-reference-base.md").read_text()
|
|
8
|
+
ASTROLIB_API_REFERENCE_ANIMATION: str = (_DIST_DIR / "lib-api-reference-animation.md").read_text()
|
|
9
|
+
ASTROLIB_API_REFERENCE_LLM: str = (_DIST_DIR / "lib-api-reference-llm.md").read_text()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_astrolib_api_reference(animation_enabled: bool = True, lib_llm_enabled: bool = True) -> str:
|
|
13
|
+
"""Get the astrolib API reference, optionally including animation and lib.llm docs."""
|
|
14
|
+
parts = [ASTROLIB_API_REFERENCE_BASE]
|
|
15
|
+
if animation_enabled:
|
|
16
|
+
parts.append(ASTROLIB_API_REFERENCE_ANIMATION)
|
|
17
|
+
if lib_llm_enabled:
|
|
18
|
+
parts.append(ASTROLIB_API_REFERENCE_LLM)
|
|
19
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cognito auth for the Creator MCP: shared JWT helpers and the stdio login flow."""
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Cognito JWT helpers used by the stdio login flow.
|
|
2
|
+
|
|
3
|
+
Verification mirrors the AstroBackend custom authorizer: RS256 against the pool's JWKS, issuer
|
|
4
|
+
match, token_use == "access", client_id match. Cognito access tokens carry no `aud`, so audience
|
|
5
|
+
verification is disabled — the client_id check is the equivalent binding.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import jwt
|
|
11
|
+
from jwt import PyJWKClient
|
|
12
|
+
|
|
13
|
+
from astrocade_creator_mcp.config import CognitoConfig
|
|
14
|
+
|
|
15
|
+
# One JWKS client per JWKS URL; PyJWKClient caches signing keys and refetches on a key miss.
|
|
16
|
+
_jwks_clients: dict[str, PyJWKClient] = {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _jwks_client(jwks_url: str) -> PyJWKClient:
|
|
20
|
+
client = _jwks_clients.get(jwks_url)
|
|
21
|
+
if client is None:
|
|
22
|
+
client = PyJWKClient(jwks_url)
|
|
23
|
+
_jwks_clients[jwks_url] = client
|
|
24
|
+
return client
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def verify_access_token(cfg: CognitoConfig, token: str) -> dict[str, Any] | None:
|
|
28
|
+
"""Verify a Cognito ACCESS token's signature and claims.
|
|
29
|
+
|
|
30
|
+
Returns the decoded claims on success, or None if the token is invalid, expired, from the
|
|
31
|
+
wrong pool/issuer, not an access token, or issued for a different client.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
signing_key = _jwks_client(cfg.jwks_url).get_signing_key_from_jwt(token)
|
|
35
|
+
claims: dict[str, Any] = jwt.decode(
|
|
36
|
+
token,
|
|
37
|
+
signing_key.key,
|
|
38
|
+
algorithms=["RS256"],
|
|
39
|
+
issuer=cfg.issuer_url,
|
|
40
|
+
options={"verify_aud": False},
|
|
41
|
+
)
|
|
42
|
+
except Exception:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
if claims.get("token_use") != "access":
|
|
46
|
+
return None
|
|
47
|
+
if claims.get("client_id") != cfg.client_id:
|
|
48
|
+
return None
|
|
49
|
+
return claims
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def decode_unverified_claims(token: str) -> dict[str, Any]:
|
|
53
|
+
"""Decode a JWT's claims WITHOUT signature verification.
|
|
54
|
+
|
|
55
|
+
Used to read the user id out of a token whose signature was already established: one we just
|
|
56
|
+
obtained from Cognito ourselves. The backend re-verifies the signature on every call regardless.
|
|
57
|
+
"""
|
|
58
|
+
claims: dict[str, Any] = jwt.decode(token, options={"verify_signature": False})
|
|
59
|
+
return claims
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def user_id_from_claims(claims: dict[str, Any]) -> str:
|
|
63
|
+
"""Resolve the Astrocade user id from token claims.
|
|
64
|
+
|
|
65
|
+
We only ever see Cognito ACCESS tokens (that's what the frontend forwards as the
|
|
66
|
+
Authorization header), which carry `username` but not `cognito:username`. The backend
|
|
67
|
+
resolves the same access token to the same id, so they match. `cognito:username` (present
|
|
68
|
+
on id tokens) and `sub` are fallbacks for robustness, not the expected path.
|
|
69
|
+
"""
|
|
70
|
+
user_id = claims.get("username") or claims.get("cognito:username") or claims.get("sub", "")
|
|
71
|
+
return str(user_id)
|