coach-partner-mcp 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -1,125 +1,129 @@
1
- # Coach Partner MCP V1
2
-
3
- A governed MCP adapter over **Coach Partner Open API V1**.
4
-
5
- This package is an interface adapter, not a second business backend. It does not access CloudBase, repositories, Domain Commands, lesson ledgers or finance data directly. Every tool calls the existing `/v1` Open API and inherits its delegated Integration identity, tenant isolation, scopes, idempotency, audit and Domain rules.
6
-
7
- ## One-command CodeBuddy setup
8
-
9
- Target public command after npm release:
10
-
11
- ```powershell
12
- npx -y coach-partner-mcp setup --host codebuddy
13
- ```
14
-
15
- Default behavior:
16
-
17
- 1. installs Coach Partner MCP into **CodeBuddy user scope**, so it is available across CodeBuddy projects;
18
- 2. preserves existing CodeBuddy MCP servers, including CloudBase or other tools;
19
- 3. follows CodeBuddy user-scope config priority (`~/.codebuddy/.mcp.json` → `~/.codebuddy/mcp.json` → `~/.codebuddy.json`);
20
- 4. supports CodeBuddy JSONC files, including comments and trailing commas;
21
- 5. securely prompts once for the Coach Partner connection credential if Windows Credential Manager does not already contain it;
22
- 6. stores the credential in Windows Credential Manager, never in `.mcp.json`;
23
- 7. runs a read-only `/v1/me` connectivity check;
24
- 8. configures CodeBuddy to launch the version-pinned npm package through stdio;
25
- 9. forces the public CodeBuddy launcher into **read-only MCP mode**.
26
-
27
- Project-only install is also available:
28
-
29
- ```powershell
30
- npx -y coach-partner-mcp setup --host codebuddy --scope project --project C:\path\to\project
31
- ```
32
-
33
- After setup, reload CodeBuddy. The MCP server name is `coach-partner`.
34
-
35
- ### Authorization note
36
-
37
- One-command installation and service authorization are separate concerns. V1 expects the coach to receive a Coach Partner Open API connection credential from the governed onboarding process. The installer asks for it through a secure Windows prompt and stores it locally in Windows Credential Manager.
38
-
39
- The public install command must not contain a shared Production credential.
40
-
41
- ## CLI
42
-
43
- ```text
44
- coach-partner-mcp setup --host codebuddy
45
- coach-partner-mcp doctor
46
- coach-partner-mcp serve
47
- ```
48
-
49
- - `setup` configures CodeBuddy and securely stores/uses the connection credential.
50
- - `doctor` verifies the stored credential and Open API identity without exposing the credential.
51
- - `serve` starts the stdio MCP server used by CodeBuddy.
52
-
53
- ## Current boundary
54
-
55
- - Transport: local stdio.
56
- - SDK: `@modelcontextprotocol/server` 2.3.1.
57
- - Public CodeBuddy installer: Windows V1.
58
- - Default/public CodeBuddy tool surface: read-only.
59
- - Public `serve` command forcibly disables classroom writes and session actions.
60
- - Write-capable MCP exposure remains a separately governed path and is not enabled by the one-command installer.
61
- - Payment/refund/finance mutation is not exposed.
62
-
63
- ## Tools
64
-
65
- Read-only default (9):
66
-
67
- - `get_identity`
68
- - `list_classes`
69
- - `find_sessions`
70
- - `get_session`
71
- - `get_session_roster`
72
- - `get_learner`
73
- - `get_learner_measurements`
74
- - `get_lesson_account`
75
- - `list_metrics`
76
-
77
- The underlying MCP adapter still contains governed classroom-write tools for controlled internal configurations, but the public CodeBuddy `serve` launcher does not expose them.
78
-
79
- ## Security rules
80
-
81
- - non-loopback base URLs must use HTTPS;
82
- - base URL credentials/query/fragment/path are rejected;
83
- - the connection credential is never written to CodeBuddy MCP configuration;
84
- - the connection credential is never logged or returned by an MCP tool;
85
- - Windows installation stores the credential in Windows Credential Manager;
86
- - existing CodeBuddy MCP configuration is updated atomically and other MCP servers are preserved;
87
- - invalid config fails closed rather than overwriting user files;
88
- - Open API remains the final authorization authority;
89
- - public CodeBuddy setup is read-only even if unrelated environment variables request write exposure.
90
-
91
- ## Local development
92
-
93
- ```powershell
94
- cd mcp\coach-partner
95
- npm install
96
- npm run check
97
- npm run pack:check
98
- ```
99
-
100
- `npm run check` performs syntax validation plus unit, config, JSONC, MCP protocol, Open API client and real stdio child-process smoke tests.
101
-
102
- Before the npm package is publicly released, local dogfood can use:
103
-
104
- ```powershell
105
- npx -y file:. doctor
106
- npx -y file:. setup --host codebuddy --package-spec file:C:\absolute\path\to\mcp\coach-partner
107
- ```
108
-
109
- ## Distribution gate
110
-
111
- `coach-partner-mcp` is prepared as a public npm package, but publication is a separate release action. Verify package contents with:
112
-
113
- ```powershell
114
- npm pack --dry-run
115
- ```
116
-
117
- After npm authentication and publication, the stable external instruction becomes exactly:
118
-
119
- ```powershell
120
- npx -y coach-partner-mcp setup --host codebuddy
121
- ```
122
-
123
- ## Non-goals
124
-
125
- No raw API/path tool, direct database access, OAuth, remote MCP HTTP hosting, finance/refund/payment mutation, direct lesson-balance mutation, generic Tool Gateway or multi-agent platform is included in V1.
1
+ # Coach Partner MCP V1
2
+
3
+ A governed MCP adapter over **Coach Partner Open API V1**.
4
+
5
+ This package is an interface adapter, not a second business backend. It does not access CloudBase, repositories, Domain Commands, lesson ledgers or finance data directly. Every tool calls the existing `/v1` Open API and inherits its delegated Integration identity, tenant isolation, scopes, idempotency, audit and Domain rules.
6
+
7
+ ## One-command CodeBuddy setup
8
+
9
+ Target public command after npm release:
10
+
11
+ ```powershell
12
+ npx -y coach-partner-mcp setup --host codebuddy
13
+ ```
14
+
15
+ Default behavior:
16
+
17
+ 1. installs Coach Partner MCP into **CodeBuddy user scope**, so it is available across CodeBuddy projects;
18
+ 2. preserves existing CodeBuddy MCP servers, including CloudBase or other tools;
19
+ 3. follows CodeBuddy user-scope config priority (`~/.codebuddy/.mcp.json` → `~/.codebuddy/mcp.json` → `~/.codebuddy.json`);
20
+ 4. supports CodeBuddy JSONC files, including comments and trailing commas;
21
+ 5. securely prompts once for the Coach Partner connection credential if Windows Credential Manager does not already contain it;
22
+ 6. stores the credential in Windows Credential Manager, never in `.mcp.json`;
23
+ 7. runs a read-only `/v1/me` connectivity check;
24
+ 8. configures CodeBuddy to launch the version-pinned npm package through stdio;
25
+ 9. forces the public CodeBuddy launcher into **read-only MCP mode**.
26
+
27
+ Project-only install is also available:
28
+
29
+ ```powershell
30
+ npx -y coach-partner-mcp setup --host codebuddy --scope project --project C:\path\to\project
31
+ ```
32
+
33
+ After setup, reload CodeBuddy. The MCP server name is `coach-partner`.
34
+
35
+ ### Authorization note
36
+
37
+ One-command installation and service authorization are separate concerns. V1 expects the coach to receive a Coach Partner Open API connection credential from the governed onboarding process. The installer asks for it through a secure Windows prompt and stores it locally in Windows Credential Manager.
38
+
39
+ The public install command must not contain a shared Production credential.
40
+
41
+ ## CLI
42
+
43
+ ```text
44
+ coach-partner-mcp setup --host codebuddy
45
+ coach-partner-mcp doctor
46
+ coach-partner-mcp serve
47
+ ```
48
+
49
+ - `setup` configures CodeBuddy and securely stores/uses the connection credential.
50
+ - `doctor` verifies the stored credential and Open API identity without exposing the credential.
51
+ - `serve` starts the stdio MCP server used by CodeBuddy.
52
+
53
+ ## Current boundary
54
+
55
+ - Transport: local stdio.
56
+ - SDK: `@modelcontextprotocol/server` 2.3.1.
57
+ - Public CodeBuddy installer: Windows V1.
58
+ - Default/public CodeBuddy tool surface: read-only.
59
+ - Public `serve` command forcibly disables classroom writes and session actions.
60
+ - Write-capable MCP exposure remains a separately governed path and is not enabled by the one-command installer.
61
+ - Payment/refund/finance mutation is not exposed.
62
+
63
+ ## Tools
64
+
65
+ Read-only default (9):
66
+
67
+ - `get_identity`
68
+ - `list_classes`
69
+ - `find_sessions`
70
+ - `get_session`
71
+ - `get_session_roster`
72
+ - `get_learner`
73
+ - `get_learner_measurements`
74
+ - `get_lesson_account`
75
+ - `list_metrics`
76
+
77
+ The underlying MCP adapter still contains governed classroom-write tools for controlled internal configurations, but the public CodeBuddy `serve` launcher does not expose them.
78
+
79
+ ## Security rules
80
+
81
+ - non-loopback base URLs must use HTTPS;
82
+ - base URL credentials/query/fragment/path are rejected;
83
+ - the connection credential is never written to CodeBuddy MCP configuration;
84
+ - the connection credential is never logged or returned by an MCP tool;
85
+ - Windows installation stores the credential in Windows Credential Manager;
86
+ - existing CodeBuddy MCP configuration is updated atomically and other MCP servers are preserved;
87
+ - invalid config fails closed rather than overwriting user files;
88
+ - Open API remains the final authorization authority;
89
+ - public CodeBuddy setup is read-only even if unrelated environment variables request write exposure.
90
+
91
+ ## Local development
92
+
93
+ ```powershell
94
+ cd mcp\coach-partner
95
+ npm install
96
+ npm run check
97
+ npm run pack:check
98
+ ```
99
+
100
+ `npm run check` performs syntax validation plus unit, config, JSONC, MCP protocol, Open API client and real stdio child-process smoke tests.
101
+
102
+ Before the npm package is publicly released, local dogfood can use:
103
+
104
+ ```powershell
105
+ npx -y file:. doctor
106
+ npx -y file:. setup --host codebuddy --package-spec file:C:\absolute\path\to\mcp\coach-partner
107
+ ```
108
+
109
+ ## Public distribution and release
110
+
111
+ `coach-partner-mcp` is publicly distributed on npm. The stable external installation command is:
112
+
113
+ ```powershell
114
+ npx -y coach-partner-mcp setup --host codebuddy
115
+ ```
116
+
117
+ Release validation remains mandatory:
118
+
119
+ ```powershell
120
+ npm ci
121
+ npm run check
122
+ npm pack --dry-run
123
+ ```
124
+
125
+ Future releases use the dedicated GitHub Actions release lane and npm Trusted Publishing. CI receives short-lived OIDC authority to **stage** a package only; it does not store a long-lived npm publish token and does not directly make a candidate public. The release tag must be `coach-partner-mcp-v<package.json version>`. After GitHub Actions creates the npm staged candidate, the Founder verifies it and approves publication with npm Passkey/2FA. See `operations/runbooks/coach-partner-mcp-release.md`.
126
+
127
+ ## Non-goals
128
+
129
+ No raw API/path tool, direct database access, OAuth, remote MCP HTTP hosting, finance/refund/payment mutation, direct lesson-balance mutation, generic Tool Gateway or multi-agent platform is included in V1.
package/package.json CHANGED
@@ -1,33 +1,38 @@
1
- {
2
- "name": "coach-partner-mcp",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "description": "Governed Coach Partner MCP adapter and one-command CodeBuddy installer.",
6
- "license": "UNLICENSED",
7
- "bin": {
8
- "coach-partner-mcp": "src/cli.mjs"
9
- },
10
- "files": [
11
- "src/",
12
- "scripts/",
13
- "README.md"
14
- ],
15
- "engines": {
16
- "node": ">=20.19"
17
- },
18
- "scripts": {
19
- "start": "node src/cli.mjs serve",
20
- "test": "node --test",
21
- "check:syntax": "node --check src/config.mjs && node --check src/open-api-client.mjs && node --check src/tools.mjs && node --check src/server-factory.mjs && node --check src/server.mjs && node --check src/codebuddy-setup.mjs && node --check src/windows-secure-store.mjs && node --check src/doctor.mjs && node --check src/cli.mjs",
22
- "check": "npm run check:syntax && npm test",
23
- "pack:check": "npm pack --dry-run"
24
- },
25
- "dependencies": {
26
- "@modelcontextprotocol/server": "2.3.1",
27
- "jsonc-parser": "3.3.1",
28
- "zod": "4.6.7"
29
- },
30
- "devDependencies": {
31
- "@modelcontextprotocol/client": "2.3.1"
32
- }
33
- }
1
+ {
2
+ "name": "coach-partner-mcp",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "Governed Coach Partner MCP adapter and one-command CodeBuddy installer.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/nongjianweihao/coach-os-wechat.git",
9
+ "directory": "mcp/coach-partner"
10
+ },
11
+ "license": "UNLICENSED",
12
+ "bin": {
13
+ "coach-partner-mcp": "src/cli.mjs"
14
+ },
15
+ "files": [
16
+ "src/",
17
+ "scripts/",
18
+ "README.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20.19"
22
+ },
23
+ "scripts": {
24
+ "start": "node src/cli.mjs serve",
25
+ "test": "node --test",
26
+ "check:syntax": "node --check src/config.mjs && node --check src/open-api-client.mjs && node --check src/tools.mjs && node --check src/server-factory.mjs && node --check src/server.mjs && node --check src/codebuddy-setup.mjs && node --check src/windows-secure-store.mjs && node --check src/doctor.mjs && node --check src/cli.mjs",
27
+ "check": "npm run check:syntax && npm test",
28
+ "pack:check": "npm pack --dry-run"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/server": "2.3.1",
32
+ "jsonc-parser": "3.3.1",
33
+ "zod": "4.6.7"
34
+ },
35
+ "devDependencies": {
36
+ "@modelcontextprotocol/client": "2.3.1"
37
+ }
38
+ }
@@ -1,133 +1,133 @@
1
- [CmdletBinding()]
2
- param(
3
- [Parameter(Mandatory=$true)]
4
- [ValidateSet('Store','Status','Read')]
5
- [string]$Action,
6
- [Parameter(Mandatory=$true)]
7
- [string]$TargetName
8
- )
9
-
10
- Set-StrictMode -Version Latest
11
- $ErrorActionPreference = 'Stop'
12
-
13
- if (-not ('CoachPartnerMcpSecureStoreNative' -as [type])) {
14
- Add-Type -TypeDefinition @"
15
- using System;
16
- using System.Runtime.InteropServices;
17
-
18
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
19
- public struct CoachPartnerMcpNativeCredential {
20
- public UInt32 Flags;
21
- public UInt32 Type;
22
- public string TargetName;
23
- public string Comment;
24
- public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
25
- public UInt32 CredentialBlobSize;
26
- public IntPtr CredentialBlob;
27
- public UInt32 Persist;
28
- public UInt32 AttributeCount;
29
- public IntPtr Attributes;
30
- public string TargetAlias;
31
- public string UserName;
32
- }
33
-
34
- public static class CoachPartnerMcpSecureStoreNative {
35
- [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
36
- public static extern bool CredWrite(ref CoachPartnerMcpNativeCredential credential, UInt32 flags);
37
-
38
- [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
39
- public static extern bool CredRead(string target, UInt32 type, UInt32 reservedFlag, out IntPtr credentialPtr);
40
-
41
- [DllImport("advapi32.dll", SetLastError = true)]
42
- public static extern void CredFree(IntPtr buffer);
43
- }
44
- "@
45
- }
46
-
47
- $CRED_TYPE_GENERIC = [uint32]1
48
- $CRED_PERSIST_LOCAL_MACHINE = [uint32]2
49
-
50
- function Get-NativeCredential {
51
- $ptr = [IntPtr]::Zero
52
- if (-not [CoachPartnerMcpSecureStoreNative]::CredRead($TargetName, $CRED_TYPE_GENERIC, 0, [ref]$ptr)) {
53
- return $null
54
- }
55
- try {
56
- $credential = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][CoachPartnerMcpNativeCredential])
57
- if ($credential.CredentialBlobSize -eq 0 -or $credential.CredentialBlob -eq [IntPtr]::Zero) {
58
- return $null
59
- }
60
- $bytes = New-Object byte[] $credential.CredentialBlobSize
61
- [Runtime.InteropServices.Marshal]::Copy($credential.CredentialBlob, $bytes, 0, $bytes.Length)
62
- try {
63
- return [Text.Encoding]::Unicode.GetString($bytes)
64
- }
65
- finally {
66
- [Array]::Clear($bytes, 0, $bytes.Length)
67
- }
68
- }
69
- finally {
70
- if ($ptr -ne [IntPtr]::Zero) { [CoachPartnerMcpSecureStoreNative]::CredFree($ptr) }
71
- }
72
- }
73
-
74
- function Store-NativeCredential {
75
- $secure = Read-Host 'Coach Partner connection credential' -AsSecureString
76
- $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
77
- try {
78
- $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
79
- if ($plain -notmatch '^cp_v1_[a-f0-9]{32}\.[A-Za-z0-9_-]{32,128}$') {
80
- throw 'Credential format is invalid.'
81
- }
82
- $bytes = [Text.Encoding]::Unicode.GetBytes($plain)
83
- $blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)
84
- try {
85
- [Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)
86
- $credential = New-Object CoachPartnerMcpNativeCredential
87
- $credential.Flags = 0
88
- $credential.Type = $CRED_TYPE_GENERIC
89
- $credential.TargetName = $TargetName
90
- $credential.Comment = 'Coach Partner MCP connection credential. Managed by coach-partner-mcp setup.'
91
- $credential.CredentialBlobSize = [uint32]$bytes.Length
92
- $credential.CredentialBlob = $blob
93
- $credential.Persist = $CRED_PERSIST_LOCAL_MACHINE
94
- $credential.AttributeCount = 0
95
- $credential.Attributes = [IntPtr]::Zero
96
- $credential.TargetAlias = $null
97
- $credential.UserName = 'CoachPartnerMcp'
98
- if (-not [CoachPartnerMcpSecureStoreNative]::CredWrite([ref]$credential, 0)) {
99
- $code = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
100
- throw "Credential Manager write failed: $(([ComponentModel.Win32Exception]::new($code)).Message)"
101
- }
102
- }
103
- finally {
104
- if ($blob -ne [IntPtr]::Zero) {
105
- for ($i = 0; $i -lt $bytes.Length; $i++) { [Runtime.InteropServices.Marshal]::WriteByte($blob, $i, 0) }
106
- [Runtime.InteropServices.Marshal]::FreeHGlobal($blob)
107
- }
108
- [Array]::Clear($bytes, 0, $bytes.Length)
109
- $plain = $null
110
- }
111
- }
112
- finally {
113
- [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
114
- }
115
- }
116
-
117
- switch ($Action) {
118
- 'Status' {
119
- $value = Get-NativeCredential
120
- if ($null -eq $value) { 'missing' } else { 'configured' }
121
- $value = $null
122
- }
123
- 'Read' {
124
- $value = Get-NativeCredential
125
- if ($null -eq $value) { throw 'Coach Partner connection credential is not configured.' }
126
- [Console]::Out.Write($value)
127
- $value = $null
128
- }
129
- 'Store' {
130
- Store-NativeCredential
131
- 'configured'
132
- }
133
- }
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory=$true)]
4
+ [ValidateSet('Store','Status','Read')]
5
+ [string]$Action,
6
+ [Parameter(Mandatory=$true)]
7
+ [string]$TargetName
8
+ )
9
+
10
+ Set-StrictMode -Version Latest
11
+ $ErrorActionPreference = 'Stop'
12
+
13
+ if (-not ('CoachPartnerMcpSecureStoreNative' -as [type])) {
14
+ Add-Type -TypeDefinition @"
15
+ using System;
16
+ using System.Runtime.InteropServices;
17
+
18
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
19
+ public struct CoachPartnerMcpNativeCredential {
20
+ public UInt32 Flags;
21
+ public UInt32 Type;
22
+ public string TargetName;
23
+ public string Comment;
24
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
25
+ public UInt32 CredentialBlobSize;
26
+ public IntPtr CredentialBlob;
27
+ public UInt32 Persist;
28
+ public UInt32 AttributeCount;
29
+ public IntPtr Attributes;
30
+ public string TargetAlias;
31
+ public string UserName;
32
+ }
33
+
34
+ public static class CoachPartnerMcpSecureStoreNative {
35
+ [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
36
+ public static extern bool CredWrite(ref CoachPartnerMcpNativeCredential credential, UInt32 flags);
37
+
38
+ [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
39
+ public static extern bool CredRead(string target, UInt32 type, UInt32 reservedFlag, out IntPtr credentialPtr);
40
+
41
+ [DllImport("advapi32.dll", SetLastError = true)]
42
+ public static extern void CredFree(IntPtr buffer);
43
+ }
44
+ "@
45
+ }
46
+
47
+ $CRED_TYPE_GENERIC = [uint32]1
48
+ $CRED_PERSIST_LOCAL_MACHINE = [uint32]2
49
+
50
+ function Get-NativeCredential {
51
+ $ptr = [IntPtr]::Zero
52
+ if (-not [CoachPartnerMcpSecureStoreNative]::CredRead($TargetName, $CRED_TYPE_GENERIC, 0, [ref]$ptr)) {
53
+ return $null
54
+ }
55
+ try {
56
+ $credential = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][CoachPartnerMcpNativeCredential])
57
+ if ($credential.CredentialBlobSize -eq 0 -or $credential.CredentialBlob -eq [IntPtr]::Zero) {
58
+ return $null
59
+ }
60
+ $bytes = New-Object byte[] $credential.CredentialBlobSize
61
+ [Runtime.InteropServices.Marshal]::Copy($credential.CredentialBlob, $bytes, 0, $bytes.Length)
62
+ try {
63
+ return [Text.Encoding]::Unicode.GetString($bytes)
64
+ }
65
+ finally {
66
+ [Array]::Clear($bytes, 0, $bytes.Length)
67
+ }
68
+ }
69
+ finally {
70
+ if ($ptr -ne [IntPtr]::Zero) { [CoachPartnerMcpSecureStoreNative]::CredFree($ptr) }
71
+ }
72
+ }
73
+
74
+ function Store-NativeCredential {
75
+ $secure = Read-Host 'Coach Partner connection credential' -AsSecureString
76
+ $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
77
+ try {
78
+ $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
79
+ if ($plain -notmatch '^cp_v1_[a-f0-9]{32}\.[A-Za-z0-9_-]{32,128}$') {
80
+ throw 'Credential format is invalid.'
81
+ }
82
+ $bytes = [Text.Encoding]::Unicode.GetBytes($plain)
83
+ $blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)
84
+ try {
85
+ [Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)
86
+ $credential = New-Object CoachPartnerMcpNativeCredential
87
+ $credential.Flags = 0
88
+ $credential.Type = $CRED_TYPE_GENERIC
89
+ $credential.TargetName = $TargetName
90
+ $credential.Comment = 'Coach Partner MCP connection credential. Managed by coach-partner-mcp setup.'
91
+ $credential.CredentialBlobSize = [uint32]$bytes.Length
92
+ $credential.CredentialBlob = $blob
93
+ $credential.Persist = $CRED_PERSIST_LOCAL_MACHINE
94
+ $credential.AttributeCount = 0
95
+ $credential.Attributes = [IntPtr]::Zero
96
+ $credential.TargetAlias = $null
97
+ $credential.UserName = 'CoachPartnerMcp'
98
+ if (-not [CoachPartnerMcpSecureStoreNative]::CredWrite([ref]$credential, 0)) {
99
+ $code = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
100
+ throw "Credential Manager write failed: $(([ComponentModel.Win32Exception]::new($code)).Message)"
101
+ }
102
+ }
103
+ finally {
104
+ if ($blob -ne [IntPtr]::Zero) {
105
+ for ($i = 0; $i -lt $bytes.Length; $i++) { [Runtime.InteropServices.Marshal]::WriteByte($blob, $i, 0) }
106
+ [Runtime.InteropServices.Marshal]::FreeHGlobal($blob)
107
+ }
108
+ [Array]::Clear($bytes, 0, $bytes.Length)
109
+ $plain = $null
110
+ }
111
+ }
112
+ finally {
113
+ [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
114
+ }
115
+ }
116
+
117
+ switch ($Action) {
118
+ 'Status' {
119
+ $value = Get-NativeCredential
120
+ if ($null -eq $value) { 'missing' } else { 'configured' }
121
+ $value = $null
122
+ }
123
+ 'Read' {
124
+ $value = Get-NativeCredential
125
+ if ($null -eq $value) { throw 'Coach Partner connection credential is not configured.' }
126
+ [Console]::Out.Write($value)
127
+ $value = $null
128
+ }
129
+ 'Store' {
130
+ Store-NativeCredential
131
+ 'configured'
132
+ }
133
+ }