Add AI development guidance for SDK maintainers. Includes directory
structure, key components, development workflow, proto code generation,
and instructions for adding new hooks and API methods.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Makefile to python-sdk/ with targets for common development operations:
- venv: Create virtual environment and install dependencies
- proto-gen: Generate Python gRPC code from proto files
- build: Build SDK package (wheel and sdist)
- test: Run pytest test suite
- lint: Run mypy type checking
- clean: Remove build artifacts
- help: Show available targets
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _to_command_response_proto helper to convert dict/wrapper/protobuf
to CommandResponse protobuf
- Plugin developers can now return plain dicts with response_type, text, etc.
- Also supports CommandResponse wrapper class and raw protobufs
- Export CommandResponse from mattermost_plugin package
This fixes the "ExecuteCommand returned unexpected type: <class 'dict'>"
error when Python plugin handlers return dict responses.
The API client was created but never connected, causing hook handlers
like OnActivate to fail when trying to use self.api methods:
"Client is not connected. Use 'with' statement or call connect() first."
Fix: Call api_client.connect() in serve_plugin() after creating the
client, before hooks can be invoked.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Go side:
- Log hooks returned by Implemented()
- Log each hook name -> ID mapping
- Log OnActivate implementation status
- Log OnActivate call flow
Python side:
- Log Implemented() return value
- Log OnActivate gRPC receipt and handler invocation
This is temporary debug logging to diagnose why OnActivate
isn't being called for Python plugins.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Export Command wrapper class from main package
- Update hello_python example to register /hello command in OnActivate
- Slash commands must be registered via api.register_command() to work
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create shell scripts for consistent test invocation across CI environments:
Python script (python-sdk/scripts/run_integration_tests.sh):
- Auto-detects virtual environment or uses system Python
- Installs package in editable mode if not present
- Runs test_integration_e2e.py with verbose output
- Supports passing extra pytest arguments
Go script (server/public/pluginapi/grpc/scripts/run_integration_tests.sh):
- Runs from the correct module directory
- Filters to TestPythonPlugin and TestIntegration tests
- Disables test caching for clean CI runs
- Supports passing extra go test arguments
Both scripts:
- Are executable (chmod +x)
- Print diagnostic information (working dir, version)
- Exit with appropriate error codes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update HTTPResponseWriter class to support streaming responses:
- Add flush() method for best-effort HTTP flush requests
- Track pending writes with flush markers for streaming mode
- Add get_pending_writes() and clear_pending_writes() methods
- Document streaming mode, header locking, and flush semantics
- Add MAX_CHUNK_SIZE constant (64KB, matching Go)
Update ServeHTTP servicer to stream responses:
- Send response chunks incrementally instead of buffering
- Include flush flag on each message when requested
- Check for cancellation between chunks
- Handle empty body case (init only)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add flush field to ServeHTTPResponse for best-effort HTTP flush support.
Update ServeHTTPResponse and ServeHTTPResponseInit with comprehensive
documentation of streaming invariants, message ordering, and status
code validation rules.
Changes:
- Add flush boolean field (field 4) to ServeHTTPResponse
- Document response streaming invariants (init-once, header locking)
- Document status code validation (100-999 range, 0 defaults to 200)
- Regenerate Go and Python protobuf code
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests for both Go and Python ServeHTTP implementations covering:
- Body chunking behavior (small, exact, multiple, large bodies)
- Context cancellation during body streaming
- Header conversion utilities
- HTTPRequest/HTTPResponseWriter helper classes
- Request body assembly from multiple chunks
- Error handling (500 for handler errors, 404 for missing handler)
Go tests:
- TestChunking_* for body chunking scenarios
- TestConvertHTTPHeaders for header conversion
- TestBuildRequestInit for request metadata building
- TestWriteResponseHeaders for response header writing
- TestContextCancellation_DuringBodyRead for cancellation
Python tests:
- TestHTTPRequest for request wrapper class
- TestHTTPResponseWriter for response writer class
- TestHeaderConversion for proto<->dict conversion
- TestServeHTTPServicer for gRPC servicer behavior
- TestChunkingBehavior for body assembly
- TestCancellation for request cancellation
Also:
- Fixed sendRequest to properly set body_complete flag on EOF
- Added ServeHTTP to HookName enum
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ServeHTTP bidirectional streaming implementation to the Python
hook servicer with HTTPRequest and HTTPResponseWriter helper classes.
Key features:
- Async generator for bidirectional streaming
- HTTPRequest wrapper with method, url, headers, body
- HTTPResponseWriter mimicking Go's http.ResponseWriter pattern
- Context cancellation detection during body streaming
- Header conversion utilities for proto <-> dict
Handler pattern:
- Receives: (plugin_context, response_writer, request)
- Similar to Go's http.Handler(w, r) pattern
- Response writer collects headers/status/body
Current limitations (deferred to 08-02):
- Request body fully buffered before handler invocation
- Response body fully buffered before streaming back
- No true streaming for large bodies yet
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add bidirectional streaming RPC for ServeHTTP hook to support
efficient HTTP request/response transfer between Go and Python.
Key changes:
- New hooks_http.proto with ServeHTTPRequest/Response messages
- HTTPHeader message for multi-value header support
- ServeHTTPRequestInit with full request metadata
- ServeHTTPResponseInit for status and headers
- Body chunks with completion flag for streaming
- Updated hooks.proto with ServeHTTP streaming RPC
- Regenerated Go and Python code
Design decisions:
- 64KB default chunk size per gRPC best practices
- First message carries metadata, subsequent messages carry body
- body_complete flag signals end of stream
- Headers as repeated HTTPHeader for multi-value support
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add test_hooks_command_config.py: ExecuteCommand and
ConfigurationWillBeSaved hook tests
- Add test_hooks_user_channel.py: User lifecycle, channel/team,
and SAML login hook tests
- Add test_hooks_notifications.py: Reaction, notification, and
preferences hook tests
- Add test_hooks_system.py: System, WebSocket, cluster, shared
channels, and support data hook tests
- All 65 new tests verify correct hook semantics including:
- Fire-and-forget vs return-value hooks
- Rejection string semantics for UserWillLogIn
- Modify/reject semantics for notifications
- Error handling for all hook types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add test_hooks_grpc_integration.py with in-process gRPC tests:
- Test Implemented RPC returns correct hook list
- Test lifecycle hooks (OnActivate, OnDeactivate, OnConfigurationChange)
- Test MessageWillBePosted allow/reject/modify scenarios
- Test notification hooks (MessageHasBeenPosted)
- Test activation failure propagation via response.error
- Test async handlers work through gRPC
- Fix lifecycle hooks to encode errors in response, not gRPC status
- Remove context parameter from runner.invoke() for lifecycle hooks
- Ensures activation failures are returned as AppError, not gRPC error
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test handshake line format matches go-plugin protocol
- Test server starts on ephemeral port
- Test health check responds with SERVING status
- Test RuntimeConfig loading from environment
- Add integration test for full plugin lifecycle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test sync and async handler execution
- Test timeout handling with HookTimeoutError
- Test exception wrapping in HookInvocationError
- Test gRPC status code conversion
- Test HookRunner class invocation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test hook decorator with enum, string, and inferred names
- Test Plugin base class hook discovery via __init_subclass__
- Test duplicate registration error
- Test implemented_hooks() returns sorted canonical names
- Test invoke_hook() and get_hook_handler() methods
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create server.py with PluginServer class for gRPC server lifecycle
- Register grpc.health.v1.Health service with plugin status SERVING
- Output go-plugin handshake line (1|1|tcp|127.0.0.1:PORT|grpc)
- Bind to ephemeral port on localhost for security
- Add serve_plugin() and run_plugin() entry points
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create runtime_config.py to load config from environment variables
- Support MATTERMOST_PLUGIN_ID, MATTERMOST_PLUGIN_API_TARGET
- Support MATTERMOST_PLUGIN_HOOK_TIMEOUT, MATTERMOST_PLUGIN_LOG_LEVEL
- Provide sensible defaults and configure_logging helper
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create hook_runner.py with run_hook_async function
- Support both sync and async handlers via asyncio.to_thread
- Enforce timeouts with asyncio.wait_for (default 30s)
- Convert exceptions to gRPC status codes
- Add HookRunner class for convenient invocation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create plugin.py with Plugin base class for plugin authors
- Use __init_subclass__ for automatic hook discovery at class definition
- Implement implemented_hooks(), has_hook(), invoke_hook() methods
- Provide api, logger, config properties for plugin instances
- Enforce single handler per hook with clear duplicate error
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create hooks.py with @hook decorator for registration
- Add HookName enum with all canonical hook names from hooks.proto
- Support multiple decorator forms: @hook(HookName.X), @hook("X"), @hook
- Preserve function metadata with functools.wraps
- Validate hook names and raise HookRegistrationError for invalid names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update PluginAPIClient to inherit from PostsMixin, FilesMixin,
and KVStoreMixin, providing 31 new typed methods for:
- Post CRUD, ephemeral posts, and reactions
- File metadata, content, and uploads
- Key-value store operations with atomic support
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add KVStoreMixin with 9 methods:
- kv_set, kv_get, kv_delete, kv_delete_all, kv_list
- kv_set_with_expiry
- kv_compare_and_set, kv_compare_and_delete, kv_set_with_options
Provides complete KV store functionality including atomic operations
and expiring keys for plugin data persistence.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add FilesMixin with 8 methods:
- get_file_info, get_file_infos, set_file_searchable_content
- get_file, get_file_link, read_file
- upload_file, copy_file_infos
Covers all file metadata, content retrieval, and upload operations
following the established mixin pattern.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add PostsMixin with 15 methods:
- create_post, get_post, update_post, delete_post
- send_ephemeral_post, update_ephemeral_post, delete_ephemeral_post
- get_post_thread, get_posts_since, get_posts_after, get_posts_before
- add_reaction, remove_reaction, get_reactions
All methods follow the established mixin pattern with proper error
handling and type annotations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add dataclasses for:
- Post: Mattermost message with full field support
- Reaction: User emoji reaction to a post
- PostList: Ordered list of posts with pagination
- FileInfo: File metadata with all attributes
- UploadSession: Resumable upload session
- PluginKVSetOptions: Options for KV set operations
All wrappers include from_proto() and to_proto() methods for
protobuf serialization.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add mixin imports and inheritance to PluginAPIClient so that the
User/Team/Channel API methods are actually available on the client.
This was missing from the previous mixin implementation commit.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Generate Python code from server/public/pluginapi/grpc/proto:
- Protocol buffer message definitions (*_pb2.py)
- gRPC service stubs (*_pb2_grpc.py)
- Type stubs for IDE completion (*_pb2.pyi, *_pb2_grpc.pyi)
- Imports fixed to use package-relative imports
Generated from 18 .proto files using grpcio-tools and mypy-protobuf.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add client classes for Mattermost Plugin API:
- PluginAPIClient: sync client with context manager support
- AsyncPluginAPIClient: async client with grpc.aio support
- Both implement get_server_version() as smoke test RPC
- Proper error handling converts gRPC errors to SDK exceptions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add generate_protos.py script that:
- Generates Python and gRPC code from .proto files
- Generates mypy type stubs via mypy-protobuf
- Fixes imports to use package-relative imports
- Outputs to src/mattermost_plugin/grpc/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create the foundational Python SDK package structure:
- pyproject.toml with project metadata and dependencies
- src/mattermost_plugin/ package with src layout
- Configured for Python >=3.9 with grpcio and protobuf deps
- Dev dependencies include grpcio-tools, mypy-protobuf, pytest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>