Prepare release machinery for v2 pre-releases (#2831)
This commit is contained in:
@@ -27,32 +27,81 @@ jobs:
|
||||
script: |
|
||||
const currentTag = process.env.CURRENT_TAG;
|
||||
|
||||
// Get all releases
|
||||
const { data: releases } = await github.rest.repos.listReleases({
|
||||
// Paginate: with two release lines publishing interleaved, the
|
||||
// previous release on this line can sit far down the list.
|
||||
const releases = await github.paginate(github.rest.repos.listReleases, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
// Find current release index
|
||||
const currentIndex = releases.findIndex(r => r.tag_name === currentTag);
|
||||
|
||||
if (currentIndex === -1) {
|
||||
if (!releases.some(r => r.tag_name === currentTag)) {
|
||||
console.log('Current release not found in list');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get previous release (next in the list since they're sorted by date desc)
|
||||
const previousRelease = releases[currentIndex + 1];
|
||||
const major = tag => (tag.match(/^v?(\d+)/) || [])[1];
|
||||
|
||||
if (!previousRelease) {
|
||||
console.log('No previous release found, this might be the first release');
|
||||
if (major(currentTag) === undefined) {
|
||||
console.log(`Cannot parse a major version from ${currentTag}; skipping comments`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Found previous release: ${previousRelease.tag_name}`);
|
||||
// The list is ordered by release creation date, which does not
|
||||
// reliably reflect tag topology (for example, a release published
|
||||
// from a long-lived draft keeps its draft creation date). Instead
|
||||
// of trusting list order, compare every same-major release and
|
||||
// pick the nearest ancestor of the current tag: the one the
|
||||
// smallest number of commits behind it. The major check runs
|
||||
// first so cross-line candidates cost no API calls; per_page=1
|
||||
// because only status/ahead_by are needed here (the commits are
|
||||
// fetched in the next step). For the first release of a new major
|
||||
// line there is no same-line predecessor, and we skip commenting
|
||||
// rather than compare across the entire new line's history.
|
||||
let best = null;
|
||||
for (const candidate of releases) {
|
||||
if (candidate.tag_name === currentTag || candidate.draft) continue;
|
||||
if (major(candidate.tag_name) !== major(currentTag)) continue;
|
||||
|
||||
return previousRelease.tag_name;
|
||||
let comparison;
|
||||
try {
|
||||
({ data: comparison } = await github.rest.repos.compareCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: candidate.tag_name,
|
||||
head: currentTag,
|
||||
per_page: 1
|
||||
}));
|
||||
} catch (error) {
|
||||
// Tolerate only candidates whose tag no longer resolves;
|
||||
// anything else (rate limits, server errors) must fail the
|
||||
// job rather than silently produce a wrong comparison base.
|
||||
if (error.status === 404) {
|
||||
console.log(`Skipping ${candidate.tag_name}: tag does not resolve`);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 'identical' covers a release re-cut on the same commit; it
|
||||
// yields an empty commit range downstream, hence no comments.
|
||||
if (comparison.status !== 'ahead' && comparison.status !== 'identical') {
|
||||
console.log(`Skipping ${candidate.tag_name}: not an ancestor of ${currentTag} (status: ${comparison.status})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (best === null || comparison.ahead_by < best.aheadBy) {
|
||||
best = { tagName: candidate.tag_name, aheadBy: comparison.ahead_by };
|
||||
}
|
||||
}
|
||||
|
||||
if (best === null) {
|
||||
console.log(`No previous release found for ${currentTag} on its major line (it may be the first); skipping comments`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Found previous release: ${best.tagName} (${best.aheadBy} commits behind ${currentTag})`);
|
||||
return best.tagName;
|
||||
|
||||
- name: Get merged PRs between releases
|
||||
id: get_prs
|
||||
@@ -72,15 +121,28 @@ jobs:
|
||||
|
||||
console.log(`Finding PRs between ${previousTag} and ${currentTag}`);
|
||||
|
||||
// Get commits between previous and current release
|
||||
const comparison = await github.rest.repos.compareCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: previousTag,
|
||||
head: currentTag
|
||||
});
|
||||
|
||||
const commits = comparison.data.commits;
|
||||
// Get commits between previous and current release. A single
|
||||
// compare response caps the commit list, so paginate — but bound
|
||||
// the total: a range this large means a mis-selected base, and
|
||||
// commenting on hundreds of PRs is worse than commenting on none.
|
||||
const MAX_COMMITS = 250;
|
||||
const commits = [];
|
||||
for (let page = 1; ; page++) {
|
||||
const { data: comparison } = await github.rest.repos.compareCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: previousTag,
|
||||
head: currentTag,
|
||||
per_page: 100,
|
||||
page
|
||||
});
|
||||
commits.push(...comparison.commits);
|
||||
if (commits.length > MAX_COMMITS) {
|
||||
console.log(`Range ${previousTag}...${currentTag} exceeds ${MAX_COMMITS} commits; skipping comments`);
|
||||
return [];
|
||||
}
|
||||
if (comparison.commits.length < 100) break;
|
||||
}
|
||||
console.log(`Found ${commits.length} commits`);
|
||||
|
||||
// Get PRs associated with each commit using GitHub API
|
||||
@@ -114,20 +176,28 @@ jobs:
|
||||
PR_NUMBERS_JSON: ${{ steps.get_prs.outputs.result }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
RELEASE_URL: ${{ github.event.release.html_url }}
|
||||
RELEASE_IS_PRERELEASE: ${{ github.event.release.prerelease }}
|
||||
with:
|
||||
script: |
|
||||
const prNumbers = JSON.parse(process.env.PR_NUMBERS_JSON);
|
||||
const releaseTag = process.env.RELEASE_TAG;
|
||||
const releaseUrl = process.env.RELEASE_URL;
|
||||
// Trust the tag as well as the flag, in case the release manager
|
||||
// forgets to tick the pre-release checkbox.
|
||||
const isPrerelease = process.env.RELEASE_IS_PRERELEASE === 'true' || /\d(a|b|rc)\d/.test(releaseTag);
|
||||
const releaseKind = isPrerelease ? 'pre-release' : 'release';
|
||||
|
||||
const comment = `This pull request is included in [${releaseTag}](${releaseUrl})`;
|
||||
const comment = `This pull request is included in ${releaseKind} [${releaseTag}](${releaseUrl})`;
|
||||
|
||||
let commentedCount = 0;
|
||||
|
||||
for (const prNumber of prNumbers) {
|
||||
try {
|
||||
// Check if we've already commented on this PR for this release
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
// Check if we've already commented on this PR for this
|
||||
// release. Paginate: comments are returned oldest-first, so
|
||||
// on a busy PR an earlier bot comment is exactly what would
|
||||
// fall off a single page.
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
@@ -135,7 +205,7 @@ jobs:
|
||||
});
|
||||
|
||||
const alreadyCommented = comments.some(c =>
|
||||
c.user.type === 'Bot' && c.body.includes(releaseTag)
|
||||
c.user.type === 'Bot' && c.body.includes(`[${releaseTag}]`)
|
||||
);
|
||||
|
||||
if (alreadyCommented) {
|
||||
|
||||
+37
-31
@@ -15,12 +15,13 @@
|
||||
|
||||
<!-- TODO(v2): Move this content back to README.md when v2 is released -->
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **This documents v2 of the SDK (currently in development, pre-alpha on `main`).**
|
||||
> **Important: this documents v2 of the SDK, which is in alpha.** Pre-releases are published to PyPI as `2.0.0aN`, and each alpha may contain breaking changes from the previous one.
|
||||
>
|
||||
> We anticipate a stable v2 release in Q1 2026. Until then, **v1.x remains the recommended version** for production use. v1.x will continue to receive bug fixes and security updates for at least 6 months after v2 ships to give people time to upgrade.
|
||||
> v2 is a major rework of the SDK, both to support the [2026-07-28 MCP specification release](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) and to fix long-standing architectural issues. See the [migration guide](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md) for what's changed. We're targeting a beta on 2026-06-30 and a stable v2 on 2026-07-27, alongside the spec release. Before stable, we plan to add a significant set of backwards compatibility shims so the final upgrade is much smaller than today's diff.
|
||||
>
|
||||
> For v1 documentation (the current stable release), see [`README.md`](README.md).
|
||||
> **v1.x is the only stable release line and remains recommended for production.** It is in maintenance mode and continues to receive critical bug fixes and security patches. Installers never select a pre-release unless you opt in (for example `pip install mcp==2.0.0aN`), so existing installs are unaffected. **If your package depends on `mcp`, add a `<2` upper bound to your version constraint (for example `mcp>=1.27,<2`) before the stable release lands.**
|
||||
>
|
||||
> Try the alpha and tell us what breaks: [#python-sdk-dev on the MCP Contributors Discord](https://discord.gg/6CSzBmMkjX). For v1 documentation, see [the v1.x README](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/README.md).
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
@@ -84,7 +85,7 @@
|
||||
[python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg
|
||||
[python-url]: https://www.python.org/downloads/
|
||||
[docs-badge]: https://img.shields.io/badge/docs-python--sdk-blue.svg
|
||||
[docs-url]: https://modelcontextprotocol.github.io/python-sdk/
|
||||
[docs-url]: https://py.sdk.modelcontextprotocol.io/v2/
|
||||
[protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg
|
||||
[protocol-url]: https://modelcontextprotocol.io
|
||||
[spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg
|
||||
@@ -115,15 +116,17 @@ If you haven't created a uv-managed project yet, create one:
|
||||
Then add MCP to your project dependencies:
|
||||
|
||||
```bash
|
||||
uv add "mcp[cli]"
|
||||
uv add "mcp[cli]==2.0.0a1"
|
||||
```
|
||||
|
||||
Alternatively, for projects using pip for dependencies:
|
||||
|
||||
```bash
|
||||
pip install "mcp[cli]"
|
||||
pip install "mcp[cli]==2.0.0a1"
|
||||
```
|
||||
|
||||
> While v2 is in pre-release, you must pin the version explicitly: unpinned installs resolve to the latest stable v1.x release, which these docs do not describe. Check the [release history](https://pypi.org/project/mcp/#history) for the newest pre-release. The same applies to ad-hoc commands: use `uv run --with "mcp==2.0.0a1"` rather than `uv run --with mcp`.
|
||||
|
||||
### Running the standalone MCP development tools
|
||||
|
||||
To run the mcp command with uv:
|
||||
@@ -188,7 +191,7 @@ _Full example: [examples/snippets/servers/mcpserver_quickstart.py](https://githu
|
||||
You can install this server in [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) and interact with it right away. First, run the server:
|
||||
|
||||
```bash
|
||||
uv run --with mcp examples/snippets/servers/mcpserver_quickstart.py
|
||||
uv run --with "mcp==2.0.0a1" examples/snippets/servers/mcpserver_quickstart.py
|
||||
```
|
||||
|
||||
Then add it to Claude Code:
|
||||
@@ -604,8 +607,8 @@ from mcp.server.mcpserver import MCPServer, Icon
|
||||
# Create an icon from a file path or URL
|
||||
icon = Icon(
|
||||
src="icon.png",
|
||||
mimeType="image/png",
|
||||
sizes="64x64"
|
||||
mime_type="image/png",
|
||||
sizes=["64x64"]
|
||||
)
|
||||
|
||||
# Add icons to server
|
||||
@@ -925,7 +928,8 @@ The `elicit()` method returns an `ElicitationResult` with:
|
||||
|
||||
- `action`: "accept", "decline", or "cancel"
|
||||
- `data`: The validated response (only when accepted)
|
||||
- `validation_error`: Any validation error message
|
||||
|
||||
If the client returns data that doesn't match the schema, `elicit()` raises a `pydantic.ValidationError`.
|
||||
|
||||
### Sampling
|
||||
|
||||
@@ -1052,7 +1056,7 @@ if __name__ == "__main__":
|
||||
_Full example: [examples/snippets/servers/oauth_server.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/oauth_server.py)_
|
||||
<!-- /snippet-source -->
|
||||
|
||||
For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](examples/servers/simple-auth/).
|
||||
For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth/).
|
||||
|
||||
**Architecture:**
|
||||
|
||||
@@ -1060,7 +1064,7 @@ For a complete example with separate Authorization Server and Resource Server im
|
||||
- **Resource Server (RS)**: Your MCP server that validates tokens and serves protected resources
|
||||
- **Client**: Discovers AS through RFC 9728, obtains tokens, and uses them with the MCP server
|
||||
|
||||
See [TokenVerifier](src/mcp/server/auth/provider.py) for more details on implementing token validation.
|
||||
See [TokenVerifier](https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/auth/provider.py) for more details on implementing token validation.
|
||||
|
||||
### MCPServer Properties
|
||||
|
||||
@@ -1098,7 +1102,7 @@ The session object accessible via `ctx.session` provides advanced control over c
|
||||
|
||||
- `ctx.session.client_params` - Client initialization parameters and declared capabilities
|
||||
- `await ctx.session.send_log_message(level, data, logger)` - Send log messages with full control
|
||||
- `await ctx.session.create_message(messages, max_tokens)` - Request LLM sampling/completion
|
||||
- `await ctx.session.create_message(messages, max_tokens=...)` - Request LLM sampling/completion (`max_tokens` is keyword-only)
|
||||
- `await ctx.session.send_progress_notification(token, progress, total, message)` - Direct progress updates
|
||||
- `await ctx.session.send_resource_updated(uri)` - Notify clients that a specific resource changed
|
||||
- `await ctx.session.send_resource_list_changed()` - Notify clients that the resource list changed
|
||||
@@ -1128,9 +1132,9 @@ The request context accessible via `ctx.request_context` contains request-specif
|
||||
- Database connections, configuration objects, shared services
|
||||
- Type-safe access to resources defined in your server's lifespan function
|
||||
- `ctx.request_context.meta` - Request metadata from the client including:
|
||||
- `progressToken` - Token for progress notifications
|
||||
- `progress_token` - Token for progress notifications
|
||||
- Other client-provided metadata
|
||||
- `ctx.request_context.request` - The original MCP request object for advanced processing
|
||||
- `ctx.request_context.request` - Data the transport attached to this message (for example the HTTP request object on HTTP transports; `None` on stdio)
|
||||
- `ctx.request_context.request_id` - Unique identifier for this request
|
||||
|
||||
```python
|
||||
@@ -1339,8 +1343,8 @@ _Full example: [examples/snippets/servers/streamable_starlette_mount.py](https:/
|
||||
|
||||
For low level server with Streamable HTTP implementations, see:
|
||||
|
||||
- Stateful server: [`examples/servers/simple-streamablehttp/`](examples/servers/simple-streamablehttp/)
|
||||
- Stateless server: [`examples/servers/simple-streamablehttp-stateless/`](examples/servers/simple-streamablehttp-stateless/)
|
||||
- Stateful server: [`examples/servers/simple-streamablehttp/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-streamablehttp/)
|
||||
- Stateless server: [`examples/servers/simple-streamablehttp-stateless/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-streamablehttp-stateless/)
|
||||
|
||||
The streamable HTTP transport supports:
|
||||
|
||||
@@ -2088,7 +2092,7 @@ _Full example: [examples/snippets/clients/pagination_client.py](https://github.c
|
||||
- **Backward compatible** - clients that don't support pagination will still work (they'll just get the first page)
|
||||
- **Flexible page sizes** - Each endpoint can define its own page size based on data characteristics
|
||||
|
||||
See the [simple-pagination example](examples/servers/simple-pagination) for a complete implementation.
|
||||
See the [simple-pagination example](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-pagination) for a complete implementation.
|
||||
|
||||
### Writing MCP Clients
|
||||
|
||||
@@ -2157,7 +2161,7 @@ async def run():
|
||||
# Read a resource (greeting resource from mcpserver_quickstart)
|
||||
resource_content = await session.read_resource("greeting://World")
|
||||
content_block = resource_content.contents[0]
|
||||
if isinstance(content_block, types.TextContent):
|
||||
if isinstance(content_block, types.TextResourceContents):
|
||||
print(f"Resource content: {content_block.text}")
|
||||
|
||||
# Call a tool (add tool from mcpserver_quickstart)
|
||||
@@ -2397,12 +2401,13 @@ if __name__ == "__main__":
|
||||
_Full example: [examples/snippets/clients/oauth_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py)_
|
||||
<!-- /snippet-source -->
|
||||
|
||||
For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).
|
||||
For a complete working example, see [`examples/clients/simple-auth-client/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/clients/simple-auth-client/).
|
||||
|
||||
### Parsing Tool Results
|
||||
|
||||
When calling tools through MCP, the `CallToolResult` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs.
|
||||
|
||||
<!-- snippet-source examples/snippets/clients/parsing_tool_results.py -->
|
||||
```python
|
||||
"""examples/snippets/clients/parsing_tool_results.py"""
|
||||
|
||||
@@ -2414,9 +2419,7 @@ from mcp.client.stdio import stdio_client
|
||||
|
||||
async def parse_tool_results():
|
||||
"""Demonstrates how to parse different types of content in CallToolResult."""
|
||||
server_params = StdioServerParameters(
|
||||
command="python", args=["path/to/mcp_server.py"]
|
||||
)
|
||||
server_params = StdioServerParameters(command="python", args=["path/to/mcp_server.py"])
|
||||
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
@@ -2430,9 +2433,9 @@ async def parse_tool_results():
|
||||
|
||||
# Example 2: Parsing structured content from JSON tools
|
||||
result = await session.call_tool("get_user", {"id": "123"})
|
||||
if hasattr(result, "structuredContent") and result.structuredContent:
|
||||
if hasattr(result, "structured_content") and result.structured_content:
|
||||
# Access structured data directly
|
||||
user_data = result.structuredContent
|
||||
user_data = result.structured_content
|
||||
print(f"User: {user_data.get('name')}, Age: {user_data.get('age')}")
|
||||
|
||||
# Example 3: Parsing embedded resources
|
||||
@@ -2442,18 +2445,18 @@ async def parse_tool_results():
|
||||
resource = content.resource
|
||||
if isinstance(resource, types.TextResourceContents):
|
||||
print(f"Config from {resource.uri}: {resource.text}")
|
||||
elif isinstance(resource, types.BlobResourceContents):
|
||||
else:
|
||||
print(f"Binary data from {resource.uri}")
|
||||
|
||||
# Example 4: Parsing image content
|
||||
result = await session.call_tool("generate_chart", {"data": [1, 2, 3]})
|
||||
for content in result.content:
|
||||
if isinstance(content, types.ImageContent):
|
||||
print(f"Image ({content.mimeType}): {len(content.data)} bytes")
|
||||
print(f"Image ({content.mime_type}): {len(content.data)} bytes")
|
||||
|
||||
# Example 5: Handling errors
|
||||
result = await session.call_tool("failing_tool", {})
|
||||
if result.isError:
|
||||
if result.is_error:
|
||||
print("Tool execution failed!")
|
||||
for content in result.content:
|
||||
if isinstance(content, types.TextContent):
|
||||
@@ -2468,6 +2471,9 @@ if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
_Full example: [examples/snippets/clients/parsing_tool_results.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/parsing_tool_results.py)_
|
||||
<!-- /snippet-source -->
|
||||
|
||||
### MCP Primitives
|
||||
|
||||
The MCP protocol defines three core primitives that servers can implement:
|
||||
@@ -2492,14 +2498,14 @@ MCP servers declare capabilities during initialization:
|
||||
|
||||
## Documentation
|
||||
|
||||
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
|
||||
- [API Reference](https://py.sdk.modelcontextprotocol.io/v2/api/mcp/)
|
||||
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
|
||||
- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest)
|
||||
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)
|
||||
|
||||
## Contributing
|
||||
|
||||
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](CONTRIBUTING.md) to get started.
|
||||
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](https://github.com/modelcontextprotocol/python-sdk/blob/main/CONTRIBUTING.md) to get started.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+43
-2
@@ -7,7 +7,48 @@
|
||||
|
||||
## Major or Minor Release
|
||||
|
||||
Create a GitHub release via UI with the tag being `vX.Y.Z` where `X.Y.Z` is the version,
|
||||
and the release title being the same. Then ask someone to review the release.
|
||||
Stable releases are cut from the `v1.x` branch. Create a GitHub release via UI
|
||||
with the tag being `vX.Y.Z` where `X.Y.Z` is the version and the release title
|
||||
being the same, and **set the tag's target to the `v1.x` branch** — the UI
|
||||
defaults to `main`, which is the v2 rework, and a v1 tag created there would
|
||||
publish the v2 codebase as a stable release. Then ask someone to review the
|
||||
release.
|
||||
|
||||
The package version will be set automatically from the tag.
|
||||
|
||||
## v2 Pre-releases
|
||||
|
||||
v2 pre-releases are cut from `main` with a PEP 440 pre-release tag: `v2.0.0aN`
|
||||
for alphas, later `bN`/`rcN` for betas and release candidates.
|
||||
|
||||
1. Check the full test matrix is green on the release commit. The matrix runs
|
||||
with `continue-on-error`, so a green workflow run does not mean the tests
|
||||
passed — check the individual jobs.
|
||||
2. Create the release as a pre-release, passing the exact commit verified in
|
||||
step 1 as `--target` (otherwise the tag is created from whatever `main`'s
|
||||
HEAD is by then). The tagged commit determines everything about the
|
||||
release — the workflows that run and the package metadata (readme,
|
||||
classifiers) that gets published — so it must contain the current release
|
||||
tooling, not just pass tests. `--target` is ignored if the tag already
|
||||
exists: when re-creating a release, delete the old tag first and
|
||||
double-check where the new tag points. The pre-release flag keeps GitHub's
|
||||
"Latest" badge and `/releases/latest` pointing at the stable v1.x line:
|
||||
|
||||
```shell
|
||||
gh release create v2.0.0aN --prerelease --title v2.0.0aN --target <commit-sha>
|
||||
```
|
||||
|
||||
3. Curate the release notes instead of relying on auto-generated ones: what
|
||||
changed since the previous pre-release, what is known-incomplete, the
|
||||
install line (`pip install mcp==2.0.0aN`), and a link to the migration
|
||||
guide. Use the absolute URL
|
||||
(`https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md`)
|
||||
because relative links don't resolve in GitHub release bodies.
|
||||
4. If a pre-release turns out to be broken, yank it on PyPI and cut the next
|
||||
one. Never delete a release from PyPI — version numbers cannot be reused.
|
||||
Yanking doesn't stop `==` pins from installing the broken version, so set
|
||||
the yank reason (and edit the GitHub release notes) to point at the
|
||||
replacement version.
|
||||
5. When the line moves to a new stage (first beta, first release candidate,
|
||||
stable), update the `Development Status` classifier in `pyproject.toml`
|
||||
before tagging — PyPI uploads are immutable.
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are released for the most recent stable (v1.x) release line.
|
||||
|
||||
v2 pre-releases (`2.0.0aN`, …) are development snapshots: fixes land only in
|
||||
the newest pre-release, and already-published pre-releases are not patched. If
|
||||
you are testing the v2 line, track the latest pre-release; for production use,
|
||||
stay on the latest stable release.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
If you discover a security vulnerability in this repository, please report it through
|
||||
|
||||
@@ -59,7 +59,7 @@ async def run():
|
||||
# Read a resource (greeting resource from mcpserver_quickstart)
|
||||
resource_content = await session.read_resource("greeting://World")
|
||||
content_block = resource_content.contents[0]
|
||||
if isinstance(content_block, types.TextContent):
|
||||
if isinstance(content_block, types.TextResourceContents):
|
||||
print(f"Resource content: {content_block.text}")
|
||||
|
||||
# Call a tool (add tool from mcpserver_quickstart)
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
name = "mcp"
|
||||
dynamic = ["version"]
|
||||
description = "Model Context Protocol SDK"
|
||||
readme = "README.md"
|
||||
readme = "README.v2.md"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
|
||||
maintainers = [
|
||||
@@ -11,10 +11,10 @@ maintainers = [
|
||||
{ name = "Max Isbey", email = "maxisbey@anthropic.com" },
|
||||
{ name = "Felix Weinberger", email = "fweinberger@anthropic.com" },
|
||||
]
|
||||
keywords = ["git", "mcp", "llm", "automation"]
|
||||
keywords = ["mcp", "llm", "automation"]
|
||||
license = { text = "MIT" }
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
|
||||
Reference in New Issue
Block a user