mainsequence 9.0.22__tar.gz → 9.0.23.dev66__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.
Files changed (111) hide show
  1. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/PKG-INFO +2 -2
  2. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/AGENTS.md +2 -1
  3. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +59 -5
  4. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +10 -6
  5. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +2 -1
  6. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/cli.py +24 -27
  7. mainsequence-9.0.23.dev66/mainsequence/server/_mcp_skills_upstream/__init__.py +60 -0
  8. mainsequence-9.0.23.dev66/mainsequence/server/_mcp_skills_upstream/server.py +172 -0
  9. mainsequence-9.0.23.dev66/mainsequence/server/_mcp_skills_upstream/shared.py +297 -0
  10. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/server/fastapi.py +3 -1
  11. mainsequence-9.0.23.dev66/mainsequence/server/mcp_skills.py +408 -0
  12. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/PKG-INFO +2 -2
  13. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/SOURCES.txt +5 -0
  14. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/requires.txt +1 -1
  15. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/pyproject.toml +18 -5
  16. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_fastapi_mcp.py +9 -9
  17. mainsequence-9.0.23.dev66/tests/test_mcp_skills.py +573 -0
  18. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/LICENSE +0 -0
  19. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/README.md +0 -0
  20. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  21. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
  22. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  23. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
  24. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  25. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  26. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/__init__.py +0 -0
  27. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/__main__.py +0 -0
  28. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/_request_identity.py +0 -0
  29. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/_transport.py +0 -0
  30. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/bootstrap.py +0 -0
  31. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/__init__.py +0 -0
  32. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/api.py +0 -0
  33. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/browser_auth.py +0 -0
  34. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/code_repository_status.py +0 -0
  35. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/config.py +0 -0
  36. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/docker_utils.py +0 -0
  37. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/doctor.py +0 -0
  38. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/local_ops.py +0 -0
  39. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/model_filters.py +0 -0
  40. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/pydantic_cli.py +0 -0
  41. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/sdk_utils.py +0 -0
  42. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/ssh_utils.py +0 -0
  43. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/cli/ui.py +0 -0
  44. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/__init__.py +0 -0
  45. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/agent_runtime_models.py +0 -0
  46. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/base.py +0 -0
  47. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/client.py +0 -0
  48. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/compute_validation.py +0 -0
  49. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/exceptions.py +0 -0
  50. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/github_issues.py +0 -0
  51. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/inference.py +0 -0
  52. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/models_foundry.py +0 -0
  53. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/models_helpers.py +0 -0
  54. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/models_user.py +0 -0
  55. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/observability.py +0 -0
  56. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/utils.py +0 -0
  57. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/client/value_sets.py +0 -0
  58. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/code_repository_context.py +0 -0
  59. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/code_repository_skills.py +0 -0
  60. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/defaults.py +0 -0
  61. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/instrumentation/__init__.py +0 -0
  62. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/instrumentation/utils.py +0 -0
  63. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/logconf.py +0 -0
  64. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/repository_identity_security.py +0 -0
  65. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/runtime_credential_exchange.py +0 -0
  66. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/runtime_flags.py +0 -0
  67. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/scaffold_skills.py +0 -0
  68. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/server/__init__.py +0 -0
  69. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence/server/caller_assertions.py +0 -0
  70. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/dependency_links.txt +0 -0
  71. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/entry_points.txt +0 -0
  72. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/mainsequence.egg-info/top_level.txt +0 -0
  73. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/setup.cfg +0 -0
  74. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_auth_bootstrap.py +0 -0
  75. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_auth_precedence.py +0 -0
  76. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_caller_assertions.py +0 -0
  77. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_canonical_api_route_contract.py +0 -0
  78. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_cli_auth_contract.py +0 -0
  79. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_cli_browser_auth.py +0 -0
  80. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_cli_request_budget.py +0 -0
  81. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_cli_secure_storage.py +0 -0
  82. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_client.py +0 -0
  83. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_code_repository_branch_contract.py +0 -0
  84. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_code_repository_documentation_contract.py +0 -0
  85. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_code_repository_runtime_context.py +0 -0
  86. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_code_repository_skills.py +0 -0
  87. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_credential_repr.py +0 -0
  88. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_dependency_extras.py +0 -0
  89. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_dev_release_version.py +0 -0
  90. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_environment_resource_context.py +0 -0
  91. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_environment_response_contracts.py +0 -0
  92. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_fastapi_request_identity.py +0 -0
  93. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_github_issues_client.py +0 -0
  94. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_http_retry_budget.py +0 -0
  95. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_independent_context.py +0 -0
  96. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_inference_client.py +0 -0
  97. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_instrumentation.py +0 -0
  98. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_job_client.py +0 -0
  99. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_local_ops.py +0 -0
  100. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_logconf.py +0 -0
  101. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_models_user_request_bound_auth.py +0 -0
  102. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_notification_uid_contract.py +0 -0
  103. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_observability_client.py +0 -0
  104. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_runtime_credential_exchange.py +0 -0
  105. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_scaffold_skills.py +0 -0
  106. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_secret_client_model.py +0 -0
  107. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_source_ownership.py +0 -0
  108. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_ssh_utils.py +0 -0
  109. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_tolerant_response_reading.py +0 -0
  110. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_tolerant_value_sets.py +0 -0
  111. {mainsequence-9.0.22 → mainsequence-9.0.23.dev66}/tests/test_user_application_facts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.22
3
+ Version: 9.0.23.dev66
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ Requires-Dist: structlog
32
32
  Requires-Dist: typer
33
33
  Requires-Dist: urllib3>=2.7.0
34
34
  Provides-Extra: mcp
35
- Requires-Dist: mcp<2,>=1.28; extra == "mcp"
35
+ Requires-Dist: mcp<3,>=2.3; extra == "mcp"
36
36
  Dynamic: license-file
37
37
 
38
38
  <p align="center">
@@ -77,7 +77,8 @@ bundle. Platform skills are installed separately under
77
77
  `mainsequence.client.metatables`, or calls the retired `mainsequence` table
78
78
  and migration commands:
79
79
  `.agents/skills/mainsequence/maintenance/metatables_transition/SKILL.md`
80
- - FastAPI APIs serving the Command Center frontend:
80
+ - FastAPI APIs serving the Command Center frontend, and MCP tools and skills
81
+ served from a FastAPI release:
81
82
  `.agents/skills/mainsequence/application_surfaces/api_surfaces/SKILL.md`
82
83
  - Jobs, schedules, images, resources, releases, and Artifacts:
83
84
  `.agents/skills/mainsequence/platform_operations/orchestration_and_releases/SKILL.md`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mainsequence-command-center-fastapi
3
- description: Build, contract-test, release, and verify a FastAPI code repository resource serving the Command Center frontend, including applications with public OAuth callback or webhook routes. Use when implementing a Command Center wire contract, declaring exact public ingress, or validating the deployed API.
3
+ description: Build, contract-test, release, and verify a FastAPI code repository resource serving the Command Center frontend, including applications with public OAuth callback or webhook routes. Use when implementing a Command Center wire contract, declaring exact public ingress, serving MCP tools or publishing MCP skills from the app (including setting the folder of skills it publishes), or validating the deployed API.
4
4
  ---
5
5
 
6
6
  # Command Center FastAPI Release Lifecycle
@@ -236,10 +236,11 @@ See also
236
236
 
237
237
  ### MCP tools
238
238
 
239
- To serve MCP tools from the same application, add the `mcp` extra and call
240
- `install_mcp(app, mcp.streamable_http_app(), lifespan=mcp.session_manager.run)`
241
- after `install_request_identity(app)`, with a stateless FastMCP server that
242
- returns JSON. Tools authorize with `User.get_logged_user()` and
239
+ To serve MCP tools from the same application, add the `mcp` extra (the
240
+ official MCP Python SDK 2.x) and call
241
+ `install_mcp(app, mcp.streamable_http_app(stateless_http=True, json_response=True, transport_security=...), lifespan=mcp.session_manager.run)`
242
+ after `install_request_identity(app)`, with an `MCPServer`; 1.x's `FastMCP`
243
+ is not supported by the extra. Tools authorize with `User.get_logged_user()` and
243
244
  `User.get_requester()` exactly like REST handlers; admission to the release
244
245
  does not authorize every tool. Each tool decides whether it needs a person: a
245
246
  tool that needs one fails with its own error when the getter returns `None`,
@@ -251,6 +252,59 @@ threads it uses through `asyncio.to_thread` or anyio's `to_thread.run_sync`,
251
252
  and ends with the call. See
252
253
  [Serving MCP](https://mainsequence-sdk.github.io/mainsequence-sdk/knowledge/fastapi/#serving-mcp).
253
254
 
255
+ ### MCP skills
256
+
257
+ The MCP server publishes skills to its clients only from the folder set with
258
+ `skills_extension`. Setting it is required: without it, the server exposes no
259
+ skills and declares no Skills extension, whatever the repository holds.
260
+
261
+ 1. Ask the user which folder holds the skills to publish. Its name and place
262
+ are the user's choice; the SDK neither requires nor suggests one. The
263
+ examples below use `.agents/skills/mcp_only`. Only that folder is published;
264
+ the coding agents' other skill folders are not.
265
+ 2. In that folder, a folder with a `SKILL.md` is a skill, and a folder without
266
+ one groups skills; the group's path prefixes the URIs of the skills in it.
267
+ Each `SKILL.md` has frontmatter with `name`, equal to its folder's name, and
268
+ `description`. Supporting files sit beside it, in any subfolders:
269
+
270
+ ```text
271
+ .agents/skills/mcp_only/ # the folder set below (an example)
272
+ billing/ # a group: no SKILL.md
273
+ refund-review/
274
+ SKILL.md # skill://billing/refund-review/SKILL.md
275
+ references/policy.md
276
+ chargebacks/
277
+ SKILL.md # skill://billing/chargebacks/SKILL.md
278
+ daily-close/
279
+ SKILL.md # skill://daily-close/SKILL.md
280
+ ```
281
+
282
+ Skill and group folder names are 1 to 64 characters of `a-z`, `0-9` and
283
+ single hyphens. A skill inside another skill is refused.
284
+ 3. Set the folder when constructing the server. A relative path is
285
+ repository-relative: it is looked up from the module that calls
286
+ `skills_extension` upwards, so it works from any module of the repository
287
+ and any working directory. Point at the folder that holds the skills, never
288
+ at `.agents/skills` itself:
289
+
290
+ ```python
291
+ from mcp.server.mcpserver import MCPServer
292
+
293
+ from mainsequence.server.mcp_skills import skills_extension
294
+
295
+ mcp = MCPServer("refunds", extensions=[skills_extension(".agents/skills/mcp_only")])
296
+ ```
297
+
298
+ 4. Commit the folder and start the app. Every skill in it is published; add or
299
+ remove a skill folder and redeploy to change the list. An invalid skill
300
+ fails startup with the file and the rule; quote frontmatter values such as
301
+ `yes`, dates and `0755`.
302
+
303
+ Clients list the skills with `skills/list` and read them with `skills/get`
304
+ and `resources/read` on the release's `/mcp`, behind the same authentication
305
+ as tools. Do not register your own `skill://` resources. See
306
+ [Skills](https://mainsequence-sdk.github.io/mainsequence-sdk/knowledge/fastapi/#skills).
307
+
254
308
  ### Public provider callbacks and webhooks
255
309
 
256
310
  An external OAuth redirect or webhook cannot supply a Main Sequence Bearer token.
@@ -162,18 +162,22 @@ mainsequence code-repository update-sdk --path . --dry-run
162
162
  mainsequence code-repository update-sdk --path .
163
163
  ```
164
164
 
165
- `update-sdk` updates the lock and local environment. It does not publish the
166
- working tree or authorize a managed-skill or `AGENTS.md` refresh. Report any
167
- resulting pin mismatch and run those updates only when the user explicitly
168
- requests them.
165
+ `update-sdk` updates the lock and local environment, then runs the newly
166
+ installed SDK's skill copier in a fresh process against the same repository.
167
+ It replaces SDK-owned skills, removes retired ones, and updates their sentinel.
168
+ It does not refresh platform skills or `AGENTS.md`, or publish the working tree.
169
+ If copying fails, the command fails but the SDK remains upgraded; report that
170
+ partial result and use the printed refresh command to retry after addressing
171
+ the failure. On success, reload this maintenance skill before continuing.
169
172
 
170
173
  Do not automatically commit or push an SDK update unless the user also asked
171
174
  to publish the CodeRepository changes.
172
175
 
173
176
  ## Refresh Managed Skills And Instructions
174
177
 
175
- Run either managed-scaffold command only when the user explicitly requests that
176
- specific update. When both are requested, refresh the SDK-owned skills first,
178
+ Outside the skill refresh included in `update-sdk`, run either managed-scaffold
179
+ command only when the user explicitly requests that specific update. When both
180
+ are requested, refresh the SDK-owned skills first,
177
181
  then update the managed Main Sequence block in
178
182
  `AGENTS.md`:
179
183
 
@@ -49,7 +49,8 @@ Do not let this skill become a domain manual.
49
49
 
50
50
  - MetaTables, table updates, and domain data discovery: use the installed
51
51
  `metatables` package skills and documentation
52
- - FastAPI APIs serving the Command Center frontend:
52
+ - FastAPI APIs serving the Command Center frontend, and MCP tools and skills
53
+ served from a FastAPI release:
53
54
  `.agents/skills/mainsequence/application_surfaces/api_surfaces/SKILL.md`
54
55
  - CodeRepository audits, blocker analysis, and upstream SDK assessment:
55
56
  `.agents/skills/mainsequence/maintenance/bug_auditor/SKILL.md`
@@ -7633,7 +7633,10 @@ def code_repository_update_sdk(
7633
7633
  dry_run: bool = typer.Option(False, "--dry-run", help="Print steps but do not execute"),
7634
7634
  ):
7635
7635
  """
7636
- Upgrade the CodeRepository SDK dependency (`mainsequence`) using `uv`.
7636
+ Upgrade the CodeRepository SDK dependency and refresh its SDK-owned skills.
7637
+
7638
+ After installing with `uv`, run the updated SDK's skill copier in a fresh
7639
+ process. Platform skills and AGENTS.md are not changed.
7637
7640
 
7638
7641
  Parameters
7639
7642
  ----------
@@ -7655,10 +7658,20 @@ def code_repository_update_sdk(
7655
7658
  code_repository_dir = _resolve_code_repository_dir(code_repository_id, path)
7656
7659
  ensure_venv(code_repository_dir)
7657
7660
 
7661
+ refresh_args = [
7662
+ "run",
7663
+ "--no-sync",
7664
+ "mainsequence",
7665
+ "code-repository",
7666
+ "update-agent-skills",
7667
+ "--path",
7668
+ str(code_repository_dir),
7669
+ ]
7658
7670
  steps = [
7659
7671
  "resolve uv executable",
7660
7672
  "uv lock --upgrade-package mainsequence",
7661
7673
  "uv sync",
7674
+ f"uv {shlex.join(refresh_args)}",
7662
7675
  ]
7663
7676
  print_table("Update SDK plan", ["Step"], [[s] for s in steps])
7664
7677
 
@@ -7671,34 +7684,18 @@ def code_repository_update_sdk(
7671
7684
  run_uv(uv, ["lock", "--upgrade-package", "mainsequence"], cwd=code_repository_dir)
7672
7685
  run_uv(uv, ["sync"], cwd=code_repository_dir)
7673
7686
 
7674
- success("SDK update complete.")
7675
- _warn_if_sdk_skills_stale(code_repository_dir)
7676
-
7677
-
7678
- def _warn_if_sdk_skills_stale(code_repository_dir: pathlib.Path) -> None:
7687
+ # The current process still has the pre-upgrade CLI loaded.
7679
7688
  try:
7680
- version = _code_repository_installed_package_version(code_repository_dir, "mainsequence")
7681
- sentinel = code_repository_dir / ".agents" / "skills" / "mainsequence" / "PINNED_FROM.txt"
7682
- values = (
7683
- dict(
7684
- line.split("=", 1)
7685
- for line in sentinel.read_text(encoding="utf-8").splitlines()
7686
- if "=" in line
7687
- )
7688
- if sentinel.is_file()
7689
- else {}
7690
- )
7691
- except (OSError, ValueError, typer.Exit):
7692
- warn("Could not check the vendored SDK skill pin. Run update-agent-skills to refresh it.")
7693
- return
7694
- pinned = values.get("pinned_version", values.get("sdk_version"))
7695
- if pinned != version:
7696
- warn(
7697
- f"Vendored SDK skills are stale or missing (pinned: {pinned or 'missing'}; installed: {version}). "
7698
- "No scaffold files were changed. Refresh with "
7699
- "`uv run --offline mainsequence code-repository update-agent-skills --path .` "
7700
- "and `uv run --offline mainsequence code-repository update AGENTS.md --path .`."
7689
+ run_uv(uv, refresh_args, cwd=code_repository_dir)
7690
+ except (OSError, RuntimeError) as exc:
7691
+ error(
7692
+ f"SDK upgraded, but SDK skill refresh failed: {exc}\n"
7693
+ "The SDK upgrade has not been rolled back. "
7694
+ f"Retry from {code_repository_dir} with: uv {shlex.join(refresh_args)}"
7701
7695
  )
7696
+ raise typer.Exit(1) from exc
7697
+
7698
+ success("SDK update and SDK skill refresh complete.")
7702
7699
 
7703
7700
 
7704
7701
  @code_repository.command("update")
@@ -0,0 +1,60 @@
1
+ """The official MCP Skills extension, copied until an ``mcp`` release ships it (ADR 0039).
2
+
3
+ ``server.py`` and ``shared.py`` are ``src/mcp/server/skills.py`` and
4
+ ``src/mcp/shared/skills.py`` of the official MCP Python SDK,
5
+ https://github.com/modelcontextprotocol/python-sdk, at commit
6
+ ``9a754b0aabb873d9707d52dc165b569fb442ec8c`` ("Add `Skills` extension", #3485),
7
+ the server side of the ``io.modelcontextprotocol/skills`` extension. They run on
8
+ the released ``mcp`` 2.3.0.
9
+
10
+ The only change is one import in ``server.py``, which reads ``shared.py`` from
11
+ this package instead of ``mcp.shared.skills``. Nothing else is edited, fixed or
12
+ extended; take an upstream fix by copying both files again from a later commit.
13
+ ``UPSTREAM`` records each file's SHA-256 as upstream published it, and a test
14
+ checks that the copies still match. Delete this package once an ``mcp`` release
15
+ contains the extension.
16
+
17
+ The copied files are under the license of the MCP Python SDK:
18
+
19
+ MIT License
20
+
21
+ Copyright (c) 2024 Anthropic, PBC
22
+
23
+ Permission is hereby granted, free of charge, to any person obtaining a copy
24
+ of this software and associated documentation files (the "Software"), to deal
25
+ in the Software without restriction, including without limitation the rights
26
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
+ copies of the Software, and to permit persons to whom the Software is
28
+ furnished to do so, subject to the following conditions:
29
+
30
+ The above copyright notice and this permission notice shall be included in all
31
+ copies or substantial portions of the Software.
32
+
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39
+ SOFTWARE.
40
+ """
41
+
42
+ COMMIT = "9a754b0aabb873d9707d52dc165b569fb442ec8c"
43
+
44
+ # Copied file: (upstream path, SHA-256 of the upstream file).
45
+ UPSTREAM = {
46
+ "server.py": (
47
+ "src/mcp/server/skills.py",
48
+ "4a7e9b1db05d693b07c45a4db4ef85ef54713708a96154841255aa3c8d8a5334",
49
+ ),
50
+ "shared.py": (
51
+ "src/mcp/shared/skills.py",
52
+ "2669d7f4fa0ba328bdde70589d410f58274f5e8aff9d01e31e9b500d7a45ccfb",
53
+ ),
54
+ }
55
+
56
+ # The one line changed in server.py: (upstream, copy).
57
+ CHANGED_IMPORT = (
58
+ "from mcp.shared.skills import (",
59
+ "from mainsequence.server._mcp_skills_upstream.shared import (",
60
+ )
@@ -0,0 +1,172 @@
1
+ """The Skills extension (`io.modelcontextprotocol/skills`, SEP-2640).
2
+
3
+ SEP-2640 defines a convention for serving Agent Skills over MCP using the
4
+ Resources primitive: a skill is a directory of files, conventionally exposed
5
+ under the `skill://` scheme, and enumerated and fetched through two required
6
+ methods (`skills/list`, `skills/get`) plus one optional one
7
+ (`resources/directory/read`). See
8
+ https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640.
9
+
10
+ This module provides the protocol-level plumbing only: request/response
11
+ handling, SEP-2640 conformance validation, and capability advertisement. It
12
+ does not discover, read, or hash skills from a filesystem — a server author
13
+ supplies handlers that answer `skills/list`/`skills/get`/`resources/directory/read`
14
+ however their catalog is stored, and serves the underlying `skill://` file
15
+ content through the server's ordinary resource-registration APIs
16
+ (`MCPServer.add_resource`, or an `@mcp.resource(...)` template).
17
+
18
+ async def list_skills(ctx, params):
19
+ return ListSkillsResult(skills=[...])
20
+
21
+ async def get_skill(ctx, params):
22
+ if params.uri != "skill://git-workflow/SKILL.md":
23
+ raise MCPError(code=INVALID_PARAMS, message="unknown skill")
24
+ return GetSkillResult(skill=...)
25
+
26
+ mcp = MCPServer("catalog", extensions=[Skills(list_skills=list_skills, get_skill=get_skill)])
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from collections.abc import Awaitable, Callable, Sequence
32
+ from typing import Any
33
+
34
+ from mcp_types import CacheableResult
35
+ from mcp_types.jsonrpc import INTERNAL_ERROR, INVALID_PARAMS
36
+ from mcp_types.version import MODERN_PROTOCOL_VERSIONS
37
+ from pydantic import ValidationError
38
+
39
+ from mcp.server.context import HandlerResult, ServerRequestContext
40
+ from mcp.server.extension import Extension, MethodBinding
41
+ from mcp.shared.exceptions import MCPError
42
+ from mainsequence.server._mcp_skills_upstream.shared import (
43
+ EXTENSION_ID,
44
+ METHOD_GET,
45
+ METHOD_LIST,
46
+ METHOD_READ_DIRECTORY,
47
+ GetSkillParams,
48
+ GetSkillResult,
49
+ ListSkillsParams,
50
+ ListSkillsResult,
51
+ ReadDirectoryParams,
52
+ ReadDirectoryResult,
53
+ parse_directory_uri,
54
+ skill_name_from_uri,
55
+ validate_directory_result,
56
+ )
57
+
58
+ __all__ = ["Skills"]
59
+
60
+ ListSkillsHandler = Callable[[ServerRequestContext[Any, Any], ListSkillsParams], Awaitable[ListSkillsResult]]
61
+ GetSkillHandler = Callable[[ServerRequestContext[Any, Any], GetSkillParams], Awaitable[GetSkillResult]]
62
+ ReadDirectoryHandler = Callable[[ServerRequestContext[Any, Any], ReadDirectoryParams], Awaitable[ReadDirectoryResult]]
63
+
64
+
65
+ class Skills(Extension):
66
+ """The Skills extension: serve `skills/list`, `skills/get`, and directory reads.
67
+
68
+ `list_skills` and `get_skill` are required; a server MUST answer both per
69
+ SEP-2640, whether or not a skill appears in the listing. `read_directory`
70
+ is optional — supplying it advertises the `directoryRead` capability
71
+ setting and serves `resources/directory/read`; omitting it advertises
72
+ neither. Handlers run per request, so a catalog that changes over time
73
+ (or is too large to enumerate) can return a partial or empty listing.
74
+
75
+ Handler error contract: raise `MCPError` to return a specific error to the
76
+ caller (e.g. `INVALID_PARAMS` from `get_skill` for a URI it doesn't serve).
77
+ A result that isn't SEP-2640 conformant is caught here and reported as
78
+ `INTERNAL_ERROR` — a server fault, not the caller's bad params.
79
+ """
80
+
81
+ identifier = EXTENSION_ID
82
+
83
+ def __init__(
84
+ self,
85
+ *,
86
+ list_skills: ListSkillsHandler,
87
+ get_skill: GetSkillHandler,
88
+ read_directory: ReadDirectoryHandler | None = None,
89
+ ) -> None:
90
+ self._list_skills = list_skills
91
+ self._get_skill = get_skill
92
+ self._read_directory = read_directory
93
+
94
+ def settings(self) -> dict[str, Any]:
95
+ return {"directoryRead": True} if self._read_directory is not None else {}
96
+
97
+ def methods(self) -> Sequence[MethodBinding]:
98
+ bindings = [
99
+ MethodBinding(METHOD_LIST, ListSkillsParams, self._handle_list),
100
+ MethodBinding(METHOD_GET, GetSkillParams, self._handle_get),
101
+ ]
102
+ if self._read_directory is not None:
103
+ bindings.append(MethodBinding(METHOD_READ_DIRECTORY, ReadDirectoryParams, self._handle_read_directory))
104
+ return bindings
105
+
106
+ async def _handle_list(self, ctx: ServerRequestContext[Any, Any], params: ListSkillsParams) -> HandlerResult:
107
+ # `ListSkillsResult`/`Skill` self-validate on construction, so a handler that builds a
108
+ # non-conformant listing raises `ValidationError` here — a server fault, surfaced as an
109
+ # Internal error rather than the framework's default Invalid params for a bad body. The
110
+ # models stay mutable, so re-validate the outbound payload too: a handler that mutates a
111
+ # skill after building the result can't ship a non-conformant listing past this point.
112
+ try:
113
+ result = await self._list_skills(ctx, params)
114
+ ListSkillsResult.model_validate(result.model_dump())
115
+ except ValidationError:
116
+ raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None
117
+ return _finalize_cacheable(result, ctx.protocol_version)
118
+
119
+ async def _handle_get(self, ctx: ServerRequestContext[Any, Any], params: GetSkillParams) -> HandlerResult:
120
+ _require_skill_md_uri(params.uri)
121
+ # `Skill` self-validates on construction, and the re-validation guards post-construction
122
+ # mutation, both as in `_handle_list`.
123
+ try:
124
+ result = await self._get_skill(ctx, params)
125
+ GetSkillResult.model_validate(result.model_dump())
126
+ except ValidationError:
127
+ raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None
128
+ if result.skill.uri != params.uri:
129
+ raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result")
130
+ return _finalize_cacheable(result, ctx.protocol_version)
131
+
132
+ async def _handle_read_directory(
133
+ self, ctx: ServerRequestContext[Any, Any], params: ReadDirectoryParams
134
+ ) -> HandlerResult:
135
+ assert self._read_directory is not None
136
+ _require_directory_uri(params.uri)
137
+ result = await self._read_directory(ctx, params)
138
+ try:
139
+ validate_directory_result(params.uri, result)
140
+ except ValueError:
141
+ raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None
142
+ return result
143
+
144
+
145
+ def _require_skill_md_uri(uri: str) -> None:
146
+ try:
147
+ skill_name_from_uri(uri)
148
+ except ValueError as exc:
149
+ raise MCPError(code=INVALID_PARAMS, message=str(exc)) from exc
150
+
151
+
152
+ def _require_directory_uri(uri: str) -> None:
153
+ try:
154
+ parse_directory_uri(uri)
155
+ except ValueError as exc:
156
+ raise MCPError(code=INVALID_PARAMS, message=str(exc)) from exc
157
+
158
+
159
+ def _finalize_cacheable(result: CacheableResult, protocol_version: str) -> HandlerResult:
160
+ """Gate SEP-2549's `ttlMs`/`cacheScope` to protocol version 2026-07-28+.
161
+
162
+ `skills/list` and `skills/get` are extension methods, so — unlike a core spec
163
+ method — the runner's per-version surface sieve never runs on their results;
164
+ nothing else strips these fields for a legacy connection. `CacheableResult`
165
+ defaults to `cache_scope="private"`, keeping user-specific entries out of shared caches.
166
+ """
167
+ if protocol_version in MODERN_PROTOCOL_VERSIONS:
168
+ return result
169
+ dumped = result.model_dump(by_alias=True, mode="json", exclude_none=True)
170
+ dumped.pop("ttlMs", None)
171
+ dumped.pop("cacheScope", None)
172
+ return dumped