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 +129 -125
- package/package.json +38 -33
- package/scripts/windows-secure-store.ps1 +133 -133
- package/src/cli.mjs +182 -182
- package/src/codebuddy-setup.mjs +126 -126
- package/src/config.mjs +97 -97
- package/src/doctor.mjs +35 -35
- package/src/open-api-client.mjs +135 -135
- package/src/server-factory.mjs +15 -15
- package/src/server.mjs +14 -14
- package/src/tools.mjs +144 -144
- package/src/windows-secure-store.mjs +46 -46
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
|
-
##
|
|
110
|
-
|
|
111
|
-
`coach-partner-mcp` is
|
|
112
|
-
|
|
113
|
-
```powershell
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```powershell
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Governed Coach Partner MCP adapter and one-command CodeBuddy installer.",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
},
|
|
30
|
-
"
|
|
31
|
-
"@modelcontextprotocol/
|
|
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
|
+
}
|